From 9dbf4e2acd91c2eda31412f5010bbee99de756b3 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Tue, 18 Jun 2024 19:29:17 +0200 Subject: [PATCH 01/18] mv hooksConfig to poolConfig --- pkg/vault/contracts/VaultStorage.sol | 4 +- pkg/vault/contracts/lib/HooksConfigLib.sol | 422 +-------------- pkg/vault/contracts/lib/PoolConfigLib.sol | 577 ++++++++++++++++++--- 3 files changed, 494 insertions(+), 509 deletions(-) diff --git a/pkg/vault/contracts/VaultStorage.sol b/pkg/vault/contracts/VaultStorage.sol index 46a9c51d6..5cb4940db 100644 --- a/pkg/vault/contracts/VaultStorage.sol +++ b/pkg/vault/contracts/VaultStorage.sol @@ -67,8 +67,8 @@ contract VaultStorage { // Registry of pool configs. mapping(address => PoolConfigBits) internal _poolConfigBits; - // Registry of pool hooks. - mapping(address => HooksConfigBits) internal _hooksConfigBits; + // Registry of pool hooks contracts. + mapping(address => IHooks) internal _hooksContracts; // Pool -> (token -> PackedTokenBalance): structure containing the current raw and "last live" scaled balances. // Last live balances are used for yield fee computation, and since these have rates applied, they are stored diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol index 938f9b448..6222cb84e 100644 --- a/pkg/vault/contracts/lib/HooksConfigLib.sol +++ b/pkg/vault/contracts/lib/HooksConfigLib.sol @@ -30,425 +30,5 @@ library HooksConfigLib { uint8 public constant AFTER_REMOVE_LIQUIDITY_OFFSET = BEFORE_REMOVE_LIQUIDITY_OFFSET + 1; uint8 public constant HOOKS_CONTRACT_OFFSET = AFTER_REMOVE_LIQUIDITY_OFFSET + 1; - function shouldCallBeforeInitialize(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_INITIALIZE_OFFSET); - } - - function setShouldCallBeforeInitialize(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_INITIALIZE_OFFSET)); - } - - function shouldCallAfterInitialize(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_INITIALIZE_OFFSET); - } - - function setShouldCallAfterInitialize(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_INITIALIZE_OFFSET)); - } - - function shouldCallComputeDynamicSwapFee(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(DYNAMIC_SWAP_FEE_OFFSET); - } - - function setShouldCallComputeDynamicSwapFee( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, DYNAMIC_SWAP_FEE_OFFSET)); - } - - function shouldCallBeforeSwap(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_SWAP_OFFSET); - } - - function setShouldCallBeforeSwap(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_SWAP_OFFSET)); - } - - function shouldCallAfterSwap(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_SWAP_OFFSET); - } - - function setShouldCallAfterSwap(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_SWAP_OFFSET)); - } - - function shouldCallBeforeAddLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_ADD_LIQUIDITY_OFFSET); - } - - function setShouldCallBeforeAddLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_ADD_LIQUIDITY_OFFSET)); - } - - function shouldCallAfterAddLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_ADD_LIQUIDITY_OFFSET); - } - - function setShouldCallAfterAddLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_ADD_LIQUIDITY_OFFSET)); - } - - function shouldCallBeforeRemoveLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_REMOVE_LIQUIDITY_OFFSET); - } - - function setShouldCallBeforeRemoveLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_REMOVE_LIQUIDITY_OFFSET)); - } - - function shouldCallAfterRemoveLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_REMOVE_LIQUIDITY_OFFSET); - } - - function setShouldCallAfterRemoveLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_REMOVE_LIQUIDITY_OFFSET)); - } - - function getHooksContract(HooksConfigBits config) internal pure returns (address) { - return HooksConfigBits.unwrap(config).decodeAddress(HOOKS_CONTRACT_OFFSET); - } - - function setHooksContract(HooksConfigBits config, address value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertAddress(value, HOOKS_CONTRACT_OFFSET)); - } - - function toHooksConfig(HooksConfigBits config) internal pure returns (HooksConfig memory) { - return - HooksConfig({ - shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), - shouldCallAfterInitialize: config.shouldCallAfterInitialize(), - shouldCallBeforeAddLiquidity: config.shouldCallBeforeAddLiquidity(), - shouldCallAfterAddLiquidity: config.shouldCallAfterAddLiquidity(), - shouldCallBeforeRemoveLiquidity: config.shouldCallBeforeRemoveLiquidity(), - shouldCallAfterRemoveLiquidity: config.shouldCallAfterRemoveLiquidity(), - shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), - shouldCallBeforeSwap: config.shouldCallBeforeSwap(), - shouldCallAfterSwap: config.shouldCallAfterSwap(), - hooksContract: config.getHooksContract() - }); - } - - /** - * @dev Check if dynamic swap fee hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param swapParams The swap parameters used to calculate the fee - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - * @return swapFeePercentage the calculated swap fee percentage. 0 if hook is disabled - */ - function onComputeDynamicSwapFee( - HooksConfigBits config, - IBasePool.PoolSwapParams memory swapParams, - uint256 staticSwapFeePercentage - ) internal view returns (bool, uint256) { - if (config.shouldCallComputeDynamicSwapFee() == false) { - return (false, 0); - } - - (bool success, uint256 swapFeePercentage) = IHooks(config.getHooksContract()).onComputeDynamicSwapFee( - swapParams, - staticSwapFeePercentage - ); - - if (success == false) { - revert IVaultErrors.DynamicSwapFeeHookFailed(); - } - return (success, swapFeePercentage); - } - - /** - * @dev Check if before swap hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param swapParams The swap parameters used in the hook - * @param pool Pool address - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function onBeforeSwap( - HooksConfigBits config, - IBasePool.PoolSwapParams memory swapParams, - address pool - ) internal returns (bool) { - if (config.shouldCallBeforeSwap() == false) { - // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) - return false; - } - - if (IHooks(config.getHooksContract()).onBeforeSwap(swapParams, pool) == false) { - // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. - revert IVaultErrors.BeforeSwapHookFailed(); - } - return true; - } - - /** - * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param amountCalculatedScaled18 Token amount calculated by the swap - * @param amountCalculatedRaw Token amount calculated by the swap - * @param params The swap parameters - * @param state Temporary state used in swap operations - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountCalculatedRaw New amount calculated, modified by the hook - */ - function onAfterSwap( - HooksConfigBits config, - uint256 amountCalculatedScaled18, - uint256 amountCalculatedRaw, - address router, - SwapParams memory params, - SwapState memory state, - PoolData memory poolData - ) internal returns (uint256 hookAdjustedAmountCalculatedRaw) { - if (config.shouldCallAfterSwap() == false) { - // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not - // change amountCalculatedRaw (no deltas) - return amountCalculatedRaw; - } - - // Adjust balances for the AfterSwap hook. - (uint256 amountInScaled18, uint256 amountOutScaled18) = params.kind == SwapKind.EXACT_IN - ? (state.amountGivenScaled18, amountCalculatedScaled18) - : (amountCalculatedScaled18, state.amountGivenScaled18); - - bool success; - (success, hookAdjustedAmountCalculatedRaw) = IHooks(config.getHooksContract()).onAfterSwap( - IHooks.AfterSwapParams({ - kind: params.kind, - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, - amountInScaled18: amountInScaled18, - amountOutScaled18: amountOutScaled18, - tokenInBalanceScaled18: poolData.balancesLiveScaled18[state.indexIn], - tokenOutBalanceScaled18: poolData.balancesLiveScaled18[state.indexOut], - amountCalculatedScaled18: amountCalculatedScaled18, - amountCalculatedRaw: amountCalculatedRaw, - router: router, - pool: params.pool, - userData: params.userData - }) - ); - - if (success == false) { - // Hook contract implements onAfterSwap, but it has failed, so reverts the transaction. - revert IVaultErrors.AfterSwapHookFailed(); - } - - if ( - (params.kind == SwapKind.EXACT_IN && hookAdjustedAmountCalculatedRaw < params.limitRaw) || - (params.kind == SwapKind.EXACT_OUT && hookAdjustedAmountCalculatedRaw > params.limitRaw) - ) { - revert IVaultErrors.SwapLimit(hookAdjustedAmountCalculatedRaw, params.limitRaw); - } - } - - /** - * @dev Check if before add liquidity hook should be called and call it. Throws an error if the hook contract fails - * to execute the hook. - * - * @param config The encoded hooks configuration - * @param maxAmountsInScaled18 An array with maximum amounts for each input token of the add liquidity operation - * @param params The add liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function onBeforeAddLiquidity( - HooksConfigBits config, - uint256[] memory maxAmountsInScaled18, - address router, - AddLiquidityParams memory params, - PoolData memory poolData - ) internal returns (bool) { - if (config.shouldCallBeforeAddLiquidity() == false) { - return false; - } - - if ( - IHooks(config.getHooksContract()).onBeforeAddLiquidity( - router, - params.kind, - maxAmountsInScaled18, - params.minBptAmountOut, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.BeforeAddLiquidityHookFailed(); - } - return true; - } - - /** - * @dev Check if after add liquidity hook should be called and call it. Throws an error if the hook contract fails - * to execute the hook. - * - * @param config The encoded hooks configuration - * @param amountsInScaled18 Scaled amounts of tokens added in token registration order - * @param bptAmountOut The BPT amount a user will receive after add liquidity operation succeeds - * @param params The add liquidity parameters - * @param poolData Struct containing balance and token information of the pool - */ - function onAfterAddLiquidity( - HooksConfigBits config, - uint256[] memory amountsInScaled18, - uint256 bptAmountOut, - address router, - AddLiquidityParams memory params, - PoolData memory poolData - ) internal { - if (config.shouldCallAfterAddLiquidity() == false) { - return; - } - - if ( - IHooks(config.getHooksContract()).onAfterAddLiquidity( - router, - amountsInScaled18, - bptAmountOut, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.AfterAddLiquidityHookFailed(); - } - } - - /** - * @dev Check if before remove liquidity hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param minAmountsOutScaled18 An array with minimum amounts for each output token of the remove liquidity - * operation - * @param params The remove liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function onBeforeRemoveLiquidity( - HooksConfigBits config, - uint256[] memory minAmountsOutScaled18, - address router, - RemoveLiquidityParams memory params, - PoolData memory poolData - ) internal returns (bool) { - if (config.shouldCallBeforeRemoveLiquidity() == false) { - return false; - } - - if ( - IHooks(config.getHooksContract()).onBeforeRemoveLiquidity( - router, - params.kind, - params.maxBptAmountIn, - minAmountsOutScaled18, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.BeforeRemoveLiquidityHookFailed(); - } - return true; - } - - /** - * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param amountsOutScaled18 Amount of tokens to receive in token registration order - * @param bptAmountIn The BPT amount a user will need burn to remove the liquidity of the pool - * @param params The remove liquidity parameters - * @param poolData Struct containing balance and token information of the pool - */ - function onAfterRemoveLiquidity( - HooksConfigBits config, - uint256[] memory amountsOutScaled18, - uint256 bptAmountIn, - address router, - RemoveLiquidityParams memory params, - PoolData memory poolData - ) internal { - if (config.shouldCallAfterRemoveLiquidity() == false) { - return; - } - - if ( - IHooks(config.getHooksContract()).onAfterRemoveLiquidity( - router, - bptAmountIn, - amountsOutScaled18, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.AfterRemoveLiquidityHookFailed(); - } - } - - /** - * @dev Check if before initialization hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param exactAmountsInScaled18 An array with the initial liquidity of the pool - * @param userData Additional (optional) data required for adding initial liquidity - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function onBeforeInitialize( - HooksConfigBits config, - uint256[] memory exactAmountsInScaled18, - bytes memory userData - ) internal returns (bool) { - if (config.shouldCallBeforeInitialize() == false) { - return false; - } - - if (IHooks(config.getHooksContract()).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { - revert IVaultErrors.BeforeInitializeHookFailed(); - } - return true; - } - - /** - * @dev Check if after initialization hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param exactAmountsInScaled18 An array with the initial liquidity of the pool - * @param bptAmountOut The BPT amount a user will receive after initialization operation succeeds - * @param userData Additional (optional) data required for adding initial liquidity - */ - function onAfterInitialize( - HooksConfigBits config, - uint256[] memory exactAmountsInScaled18, - uint256 bptAmountOut, - bytes memory userData - ) internal { - if (config.shouldCallAfterInitialize() == false) { - return; - } - - if ( - IHooks(config.getHooksContract()).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false - ) { - revert IVaultErrors.AfterInitializeHookFailed(); - } - } + } diff --git a/pkg/vault/contracts/lib/PoolConfigLib.sol b/pkg/vault/contracts/lib/PoolConfigLib.sol index 186971056..ed82ef6da 100644 --- a/pkg/vault/contracts/lib/PoolConfigLib.sol +++ b/pkg/vault/contracts/lib/PoolConfigLib.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.24; +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"; @@ -20,12 +22,24 @@ library PoolConfigLib { uint8 public constant POOL_PAUSED_OFFSET = POOL_INITIALIZED_OFFSET + 1; uint8 public constant POOL_RECOVERY_MODE_OFFSET = POOL_PAUSED_OFFSET + 1; - // Supported liquidity API bit offsets + // Bit offsets for liquidity operations uint8 public constant UNBALANCED_LIQUIDITY_OFFSET = POOL_RECOVERY_MODE_OFFSET + 1; uint8 public constant ADD_LIQUIDITY_CUSTOM_OFFSET = UNBALANCED_LIQUIDITY_OFFSET + 1; uint8 public constant REMOVE_LIQUIDITY_CUSTOM_OFFSET = ADD_LIQUIDITY_CUSTOM_OFFSET + 1; - uint8 public constant STATIC_SWAP_FEE_OFFSET = REMOVE_LIQUIDITY_CUSTOM_OFFSET + 1; + // Bit offsets for hooks config + uint8 public constant BEFORE_INITIALIZE_OFFSET = REMOVE_LIQUIDITY_CUSTOM_OFFSET + 1; + uint8 public constant AFTER_INITIALIZE_OFFSET = BEFORE_INITIALIZE_OFFSET + 1; + uint8 public constant DYNAMIC_SWAP_FEE_OFFSET = AFTER_INITIALIZE_OFFSET + 1; + uint8 public constant BEFORE_SWAP_OFFSET = DYNAMIC_SWAP_FEE_OFFSET + 1; + uint8 public constant AFTER_SWAP_OFFSET = BEFORE_SWAP_OFFSET + 1; + uint8 public constant BEFORE_ADD_LIQUIDITY_OFFSET = AFTER_SWAP_OFFSET + 1; + uint8 public constant AFTER_ADD_LIQUIDITY_OFFSET = BEFORE_ADD_LIQUIDITY_OFFSET + 1; + uint8 public constant BEFORE_REMOVE_LIQUIDITY_OFFSET = AFTER_ADD_LIQUIDITY_OFFSET + 1; + uint8 public constant AFTER_REMOVE_LIQUIDITY_OFFSET = BEFORE_REMOVE_LIQUIDITY_OFFSET + 1; + + // Bit offsets for uint values + uint8 public constant STATIC_SWAP_FEE_OFFSET = AFTER_REMOVE_LIQUIDITY_OFFSET + 1; uint256 public constant AGGREGATE_SWAP_FEE_OFFSET = STATIC_SWAP_FEE_OFFSET + FEE_BITLENGTH; uint256 public constant AGGREGATE_YIELD_FEE_OFFSET = AGGREGATE_SWAP_FEE_OFFSET + FEE_BITLENGTH; uint256 public constant DECIMAL_SCALING_FACTORS_OFFSET = AGGREGATE_YIELD_FEE_OFFSET + FEE_BITLENGTH; @@ -39,6 +53,7 @@ library PoolConfigLib { uint8 private constant _TIMESTAMP_BITLENGTH = 32; + // #region Bit offsets for pool config function isPoolRegistered(PoolConfigBits config) internal pure returns (bool) { return PoolConfigBits.unwrap(config).decodeBool(POOL_REGISTERED_OFFSET); } @@ -55,6 +70,14 @@ library PoolConfigLib { return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_INITIALIZED_OFFSET)); } + function isPoolPaused(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(POOL_PAUSED_OFFSET); + } + + function setPoolPaused(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_PAUSED_OFFSET)); + } + function isPoolInRecoveryMode(PoolConfigBits config) internal pure returns (bool) { return PoolConfigBits.unwrap(config).decodeBool(POOL_RECOVERY_MODE_OFFSET); } @@ -62,15 +85,458 @@ library PoolConfigLib { function setPoolInRecoveryMode(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_RECOVERY_MODE_OFFSET)); } + // #endregion - function isPoolPaused(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_PAUSED_OFFSET); + // #region Bit offsets for liquidity operations + function supportsUnbalancedLiquidity(PoolConfigBits config) internal pure returns (bool) { + // NOTE: The unbalanced liquidity flag is default-on (false means it is supported). + // This function returns the inverted value. + return !PoolConfigBits.unwrap(config).decodeBool(UNBALANCED_LIQUIDITY_OFFSET); } - function setPoolPaused(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_PAUSED_OFFSET)); + function requireUnbalancedLiquidityEnabled(PoolConfigBits config) internal pure { + if (config.supportsUnbalancedLiquidity() == false) { + revert IVaultErrors.DoesNotSupportUnbalancedLiquidity(); + } + } + + function setDisableUnbalancedLiquidity( + PoolConfigBits config, + bool disableUnbalancedLiquidity + ) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(disableUnbalancedLiquidity, UNBALANCED_LIQUIDITY_OFFSET) + ); + } + + function supportsAddLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(ADD_LIQUIDITY_CUSTOM_OFFSET); + } + + function requireAddCustomLiquidityEnabled(PoolConfigBits config) internal pure { + if (config.supportsAddLiquidityCustom() == false) { + revert IVaultErrors.DoesNotSupportAddLiquidityCustom(); + } + } + + function setAddLiquidityCustom( + PoolConfigBits config, + bool enableAddLiquidityCustom + ) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(enableAddLiquidityCustom, ADD_LIQUIDITY_CUSTOM_OFFSET) + ); + } + + function supportsRemoveLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(REMOVE_LIQUIDITY_CUSTOM_OFFSET); + } + + function requireRemoveCustomLiquidityEnabled(PoolConfigBits config) internal pure { + if (config.supportsRemoveLiquidityCustom() == false) { + revert IVaultErrors.DoesNotSupportRemoveLiquidityCustom(); + } + } + + function setRemoveLiquidityCustom( + PoolConfigBits config, + bool enableRemoveLiquidityCustom + ) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(enableRemoveLiquidityCustom, REMOVE_LIQUIDITY_CUSTOM_OFFSET) + ); + } + // #endregion + + // #region Bit offsets for hooks config + function shouldCallBeforeInitialize(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(BEFORE_INITIALIZE_OFFSET); + } + + function setShouldCallBeforeInitialize(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_INITIALIZE_OFFSET)); + } + + function shouldCallAfterInitialize(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(AFTER_INITIALIZE_OFFSET); + } + + function setShouldCallAfterInitialize(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_INITIALIZE_OFFSET)); + } + + function shouldCallComputeDynamicSwapFee(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(DYNAMIC_SWAP_FEE_OFFSET); + } + + function setShouldCallComputeDynamicSwapFee( + PoolConfigBits config, + bool value + ) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, DYNAMIC_SWAP_FEE_OFFSET)); + } + + function shouldCallBeforeSwap(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(BEFORE_SWAP_OFFSET); + } + + function setShouldCallBeforeSwap(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_SWAP_OFFSET)); + } + + function shouldCallAfterSwap(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(AFTER_SWAP_OFFSET); + } + + function setShouldCallAfterSwap(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_SWAP_OFFSET)); + } + + function shouldCallBeforeAddLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(BEFORE_ADD_LIQUIDITY_OFFSET); + } + + function setShouldCallBeforeAddLiquidity(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_ADD_LIQUIDITY_OFFSET)); + } + + function shouldCallAfterAddLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(AFTER_ADD_LIQUIDITY_OFFSET); + } + + function setShouldCallAfterAddLiquidity(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_ADD_LIQUIDITY_OFFSET)); + } + + function shouldCallBeforeRemoveLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(BEFORE_REMOVE_LIQUIDITY_OFFSET); + } + + function setShouldCallBeforeRemoveLiquidity( + PoolConfigBits config, + bool value + ) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_REMOVE_LIQUIDITY_OFFSET)); + } + + function shouldCallAfterRemoveLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(AFTER_REMOVE_LIQUIDITY_OFFSET); + } + + function setShouldCallAfterRemoveLiquidity( + PoolConfigBits config, + bool value + ) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_REMOVE_LIQUIDITY_OFFSET)); + } + + function toHooksConfig(PoolConfigBits config, address hooksContract) internal pure returns (HooksConfig memory) { + return + HooksConfig({ + shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), + shouldCallAfterInitialize: config.shouldCallAfterInitialize(), + shouldCallBeforeAddLiquidity: config.shouldCallBeforeAddLiquidity(), + shouldCallAfterAddLiquidity: config.shouldCallAfterAddLiquidity(), + shouldCallBeforeRemoveLiquidity: config.shouldCallBeforeRemoveLiquidity(), + shouldCallAfterRemoveLiquidity: config.shouldCallAfterRemoveLiquidity(), + shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), + shouldCallBeforeSwap: config.shouldCallBeforeSwap(), + shouldCallAfterSwap: config.shouldCallAfterSwap(), + hooksContract: hooksContract + }); + } + + /** + * @dev Check if dynamic swap fee hook should be called and call it. Throws an error if the hook contract fails to + * execute the hook. + * + * @param config The encoded hooks configuration + * @param swapParams The swap parameters used to calculate the fee + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + * @return swapFeePercentage the calculated swap fee percentage. 0 if hook is disabled + */ + function onComputeDynamicSwapFee( + PoolConfigBits config, + IBasePool.PoolSwapParams memory swapParams, + uint256 staticSwapFeePercentage, + HooksContractBox storage hooksContractBox + ) internal view returns (bool, uint256) { + if (config.shouldCallComputeDynamicSwapFee() == false) { + return (false, 0); + } + + (bool success, uint256 swapFeePercentage) = IHooks(config.getHooksContract()).onComputeDynamicSwapFee( + swapParams, + staticSwapFeePercentage + ); + + if (success == false) { + revert IVaultErrors.DynamicSwapFeeHookFailed(); + } + return (success, swapFeePercentage); + } + + /** + * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to + * execute the hook. + * + * @param config The encoded hooks configuration + * @param amountCalculatedScaled18 Token amount calculated by the swap + * @param amountCalculatedRaw Token amount calculated by the swap + * @param params The swap parameters + * @param state Temporary state used in swap operations + * @param poolData Struct containing balance and token information of the pool + * @return hookAdjustedAmountCalculatedRaw New amount calculated, modified by the hook + */ + function onAfterSwap( + PoolConfigBits config, + uint256 amountCalculatedScaled18, + uint256 amountCalculatedRaw, + address router, + SwapParams memory params, + SwapState memory state, + PoolData memory poolData + ) internal returns (uint256 hookAdjustedAmountCalculatedRaw) { + if (config.shouldCallAfterSwap() == false) { + // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not + // change amountCalculatedRaw (no deltas) + return amountCalculatedRaw; + } + + // Adjust balances for the AfterSwap hook. + (uint256 amountInScaled18, uint256 amountOutScaled18) = params.kind == SwapKind.EXACT_IN + ? (state.amountGivenScaled18, amountCalculatedScaled18) + : (amountCalculatedScaled18, state.amountGivenScaled18); + + bool success; + (success, hookAdjustedAmountCalculatedRaw) = IHooks(config.getHooksContract()).onAfterSwap( + IHooks.AfterSwapParams({ + kind: params.kind, + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountInScaled18: amountInScaled18, + amountOutScaled18: amountOutScaled18, + tokenInBalanceScaled18: poolData.balancesLiveScaled18[state.indexIn], + tokenOutBalanceScaled18: poolData.balancesLiveScaled18[state.indexOut], + amountCalculatedScaled18: amountCalculatedScaled18, + amountCalculatedRaw: amountCalculatedRaw, + router: router, + pool: params.pool, + userData: params.userData + }) + ); + + if (success == false) { + // Hook contract implements onAfterSwap, but it has failed, so reverts the transaction. + revert IVaultErrors.AfterSwapHookFailed(); + } + + if ( + (params.kind == SwapKind.EXACT_IN && hookAdjustedAmountCalculatedRaw < params.limitRaw) || + (params.kind == SwapKind.EXACT_OUT && hookAdjustedAmountCalculatedRaw > params.limitRaw) + ) { + revert IVaultErrors.SwapLimit(hookAdjustedAmountCalculatedRaw, params.limitRaw); + } + } + + /** + * @dev Check if before add liquidity hook should be called and call it. Throws an error if the hook contract fails + * to execute the hook. + * + * @param config The encoded hooks configuration + * @param maxAmountsInScaled18 An array with maximum amounts for each input token of the add liquidity operation + * @param params The add liquidity parameters + * @param poolData Struct containing balance and token information of the pool + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function onBeforeAddLiquidity( + PoolConfigBits config, + uint256[] memory maxAmountsInScaled18, + address router, + AddLiquidityParams memory params, + PoolData memory poolData + ) internal returns (bool) { + if (config.shouldCallBeforeAddLiquidity() == false) { + return false; + } + + if ( + IHooks(config.getHooksContract()).onBeforeAddLiquidity( + router, + params.kind, + maxAmountsInScaled18, + params.minBptAmountOut, + poolData.balancesLiveScaled18, + params.userData + ) == false + ) { + revert IVaultErrors.BeforeAddLiquidityHookFailed(); + } + return true; + } + + /** + * @dev Check if after add liquidity hook should be called and call it. Throws an error if the hook contract fails + * to execute the hook. + * + * @param config The encoded hooks configuration + * @param amountsInScaled18 Scaled amounts of tokens added in token registration order + * @param bptAmountOut The BPT amount a user will receive after add liquidity operation succeeds + * @param params The add liquidity parameters + * @param poolData Struct containing balance and token information of the pool + */ + function onAfterAddLiquidity( + PoolConfigBits config, + uint256[] memory amountsInScaled18, + uint256 bptAmountOut, + address router, + AddLiquidityParams memory params, + PoolData memory poolData + ) internal { + if (config.shouldCallAfterAddLiquidity() == false) { + return; + } + + if ( + IHooks(config.getHooksContract()).onAfterAddLiquidity( + router, + amountsInScaled18, + bptAmountOut, + poolData.balancesLiveScaled18, + params.userData + ) == false + ) { + revert IVaultErrors.AfterAddLiquidityHookFailed(); + } + } + + /** + * @dev Check if before remove liquidity hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param minAmountsOutScaled18 An array with minimum amounts for each output token of the remove liquidity + * operation + * @param params The remove liquidity parameters + * @param poolData Struct containing balance and token information of the pool + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function onBeforeRemoveLiquidity( + PoolConfigBits config, + uint256[] memory minAmountsOutScaled18, + address router, + RemoveLiquidityParams memory params, + PoolData memory poolData + ) internal returns (bool) { + if (config.shouldCallBeforeRemoveLiquidity() == false) { + return false; + } + + if ( + IHooks(config.getHooksContract()).onBeforeRemoveLiquidity( + router, + params.kind, + params.maxBptAmountIn, + minAmountsOutScaled18, + poolData.balancesLiveScaled18, + params.userData + ) == false + ) { + revert IVaultErrors.BeforeRemoveLiquidityHookFailed(); + } + return true; + } + + /** + * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param amountsOutScaled18 Amount of tokens to receive in token registration order + * @param bptAmountIn The BPT amount a user will need burn to remove the liquidity of the pool + * @param params The remove liquidity parameters + * @param poolData Struct containing balance and token information of the pool + */ + function onAfterRemoveLiquidity( + PoolConfigBits config, + uint256[] memory amountsOutScaled18, + uint256 bptAmountIn, + address router, + RemoveLiquidityParams memory params, + PoolData memory poolData + ) internal { + if (config.shouldCallAfterRemoveLiquidity() == false) { + return; + } + + if ( + IHooks(config.getHooksContract()).onAfterRemoveLiquidity( + router, + bptAmountIn, + amountsOutScaled18, + poolData.balancesLiveScaled18, + params.userData + ) == false + ) { + revert IVaultErrors.AfterRemoveLiquidityHookFailed(); + } + } + + /** + * @dev Check if before initialization hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param exactAmountsInScaled18 An array with the initial liquidity of the pool + * @param userData Additional (optional) data required for adding initial liquidity + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function onBeforeInitialize( + PoolConfigBits config, + uint256[] memory exactAmountsInScaled18, + bytes memory userData + ) internal returns (bool) { + if (config.shouldCallBeforeInitialize() == false) { + return false; + } + + if (IHooks(config.getHooksContract()).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { + revert IVaultErrors.BeforeInitializeHookFailed(); + } + return true; + } + + /** + * @dev Check if after initialization hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param exactAmountsInScaled18 An array with the initial liquidity of the pool + * @param bptAmountOut The BPT amount a user will receive after initialization operation succeeds + * @param userData Additional (optional) data required for adding initial liquidity + */ + function onAfterInitialize( + PoolConfigBits config, + uint256[] memory exactAmountsInScaled18, + uint256 bptAmountOut, + bytes memory userData + ) internal { + if (config.shouldCallAfterInitialize() == false) { + return; + } + + if ( + IHooks(config.getHooksContract()).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false + ) { + revert IVaultErrors.AfterInitializeHookFailed(); + } } + // #endregion + // #region Bit offsets for uint values function getStaticSwapFeePercentage(PoolConfigBits config) internal pure returns (uint256) { return PoolConfigBits.unwrap(config).decodeUint(STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; } @@ -133,6 +599,24 @@ library PoolConfigLib { ); } + function getDecimalScalingFactors( + PoolConfigBits config, + uint256 numTokens + ) internal pure returns (uint256[] memory) { + uint256[] memory scalingFactors = new uint256[](numTokens); + + bytes32 tokenDecimalDiffs = bytes32(uint256(config.getTokenDecimalDiffs())); + + for (uint256 i = 0; i < numTokens; ++i) { + uint256 decimalDiff = tokenDecimalDiffs.decodeUint(i * _DECIMAL_DIFF_BITLENGTH, _DECIMAL_DIFF_BITLENGTH); + + // This is equivalent to `10**(18+decimalsDifference)` but this form optimizes for 18 decimal tokens. + scalingFactors[i] = FixedPoint.ONE * 10 ** decimalDiff; + } + + return scalingFactors; + } + function setTokenDecimalDiffs(PoolConfigBits config, uint24 value) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap( @@ -154,68 +638,7 @@ library PoolConfigLib { PoolConfigBits.unwrap(config).insertUint(value, PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH) ); } - - function supportsUnbalancedLiquidity(PoolConfigBits config) internal pure returns (bool) { - // NOTE: The unbalanced liquidity flag is default-on (false means it is supported). - // This function returns the inverted value. - return !PoolConfigBits.unwrap(config).decodeBool(UNBALANCED_LIQUIDITY_OFFSET); - } - - function setDisableUnbalancedLiquidity( - PoolConfigBits config, - bool disableUnbalancedLiquidity - ) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(disableUnbalancedLiquidity, UNBALANCED_LIQUIDITY_OFFSET) - ); - } - - function requireUnbalancedLiquidityEnabled(PoolConfigBits config) internal pure { - if (config.supportsUnbalancedLiquidity() == false) { - revert IVaultErrors.DoesNotSupportUnbalancedLiquidity(); - } - } - - function supportsAddLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(ADD_LIQUIDITY_CUSTOM_OFFSET); - } - - function setAddLiquidityCustom( - PoolConfigBits config, - bool enableAddLiquidityCustom - ) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(enableAddLiquidityCustom, ADD_LIQUIDITY_CUSTOM_OFFSET) - ); - } - - function requireAddCustomLiquidityEnabled(PoolConfigBits config) internal pure { - if (config.supportsAddLiquidityCustom() == false) { - revert IVaultErrors.DoesNotSupportAddLiquidityCustom(); - } - } - - function supportsRemoveLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(REMOVE_LIQUIDITY_CUSTOM_OFFSET); - } - - function setRemoveLiquidityCustom( - PoolConfigBits config, - bool enableRemoveLiquidityCustom - ) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(enableRemoveLiquidityCustom, REMOVE_LIQUIDITY_CUSTOM_OFFSET) - ); - } - - function requireRemoveCustomLiquidityEnabled(PoolConfigBits config) internal pure { - if (config.supportsRemoveLiquidityCustom() == false) { - revert IVaultErrors.DoesNotSupportRemoveLiquidityCustom(); - } - } + // #endregion // Convert from an array of decimal differences, to the encoded 24 bit value (only uses bottom 20 bits). function toTokenDecimalDiffs(uint8[] memory tokenDecimalDiffs) internal pure returns (uint24) { @@ -227,22 +650,4 @@ library PoolConfigLib { return uint24(uint256(value)); } - - function getDecimalScalingFactors( - PoolConfigBits config, - uint256 numTokens - ) internal pure returns (uint256[] memory) { - uint256[] memory scalingFactors = new uint256[](numTokens); - - bytes32 tokenDecimalDiffs = bytes32(uint256(config.getTokenDecimalDiffs())); - - for (uint256 i = 0; i < numTokens; ++i) { - uint256 decimalDiff = tokenDecimalDiffs.decodeUint(i * _DECIMAL_DIFF_BITLENGTH, _DECIMAL_DIFF_BITLENGTH); - - // This is equivalent to `10**(18+decimalsDifference)` but this form optimizes for 18 decimal tokens. - scalingFactors[i] = FixedPoint.ONE * 10 ** decimalDiff; - } - - return scalingFactors; - } } From b3ae548b7d5c06f9d52fb1f5b467507bca6579cd Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Tue, 18 Jun 2024 20:12:35 +0200 Subject: [PATCH 02/18] add simple version of merging --- .../contracts/test/IVaultMainMock.sol | 6 +- pkg/interfaces/contracts/vault/VaultTypes.sol | 1 + ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- pkg/vault/contracts/Vault.sol | 58 ++- pkg/vault/contracts/VaultExtension.sol | 88 ++-- pkg/vault/contracts/lib/PoolConfigLib.sol | 72 ++- pkg/vault/contracts/test/VaultMock.sol | 34 +- pkg/vault/test/foundry/Hooks.t.sol | 22 +- .../test/foundry/HooksAlteringBalances.t.sol | 6 +- .../test/foundry/HooksAlteringRates.t.sol | 8 +- pkg/vault/test/foundry/Initializer.t.sol | 4 +- pkg/vault/test/foundry/VaultLiquidity.t.sol | 4 +- pkg/vault/test/foundry/VaultSwap.t.sol | 2 +- .../test/foundry/unit/HooksConfigLib.t.sol | 442 +++++++++--------- 17 files changed, 413 insertions(+), 342 deletions(-) diff --git a/pkg/interfaces/contracts/test/IVaultMainMock.sol b/pkg/interfaces/contracts/test/IVaultMainMock.sol index 0316236eb..fa9a5b248 100644 --- a/pkg/interfaces/contracts/test/IVaultMainMock.sol +++ b/pkg/interfaces/contracts/test/IVaultMainMock.sol @@ -13,8 +13,6 @@ interface IVaultMainMock { function mintERC20(address token, address to, uint256 amount) external; - function setHooksConfig(address pool, HooksConfig calldata config) external; - function manualRegisterPool(address pool, IERC20[] memory tokens) external; function manualRegisterPoolWithSwapFee(address pool, IERC20[] memory tokens, uint256 swapFeePercentage) external; @@ -44,7 +42,9 @@ interface IVaultMainMock { function manualSetPoolTokenConfig(address, IERC20[] memory, TokenConfig[] memory) external; - function manualSetPoolConfig(address, PoolConfig memory) external; + function manualSetPoolConfig(address pool, PoolConfig memory config) external; + + function manualSetHooksConfig(address pool, HooksConfig memory config) external; function manualSetPoolConfigBits(address pool, PoolConfigBits config) external; diff --git a/pkg/interfaces/contracts/vault/VaultTypes.sol b/pkg/interfaces/contracts/vault/VaultTypes.sol index 5f8e2e95e..4d3fc64f0 100644 --- a/pkg/interfaces/contracts/vault/VaultTypes.sol +++ b/pkg/interfaces/contracts/vault/VaultTypes.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; + import { IRateProvider } from "./IRateProvider.sol"; struct LiquidityManagement { diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots index 56189c51a..d0539e72c 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -178.0k \ No newline at end of file +178.2k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots index a40b2dab4..f61f50c4c 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -161.5k \ No newline at end of file +161.7k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots index c55b98f4a..b4e9dea21 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -212.5k \ No newline at end of file +212.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots index 34e80eef2..5c6672db0 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -178.9k \ No newline at end of file +179.0k \ No newline at end of file diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index 5bf8be65c..61fd09bd2 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -171,7 +171,6 @@ contract Vault is IVaultMain, VaultCommon, Proxy { returns (uint256 amountCalculated, uint256 amountIn, uint256 amountOut) { _ensureUnpaused(params.pool); - HooksConfigBits hooksConfig = _hooksConfigBits[params.pool]; if (params.amountGivenRaw == 0) { revert AmountGivenZero(); @@ -194,7 +193,9 @@ contract Vault is IVaultMain, VaultCommon, Proxy { IBasePool.PoolSwapParams memory swapParams = _buildPoolSwapParams(params, state, poolData); - if (hooksConfig.onBeforeSwap(swapParams, params.pool)) { + IHooks hooksContract = _hooksContracts[params.pool]; + + if (poolData.poolConfigBits.onBeforeSwap(swapParams, params.pool, hooksContract)) { // The call to `onBeforeSwap` could potentially update token rates and balances. // We update `poolData.tokenRates`, `poolData.rawBalances` and `poolData.balancesLiveScaled18` // to ensure the `onSwap` and `onComputeDynamicSwapFee` are called with the current values. @@ -211,9 +212,10 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // At this point, the static swap fee percentage is loaded in the swap state as the default, // to be used unless the pool has a dynamic swap fee. It is also passed into the hook, to support common cases // where the dynamic fee computation logic uses it. - (bool dynamicSwapFeeCalculated, uint256 dynamicSwapFee) = hooksConfig.onComputeDynamicSwapFee( + (bool dynamicSwapFeeCalculated, uint256 dynamicSwapFee) = poolData.poolConfigBits.onComputeDynamicSwapFee( swapParams, - state.swapFeePercentage + state.swapFeePercentage, + hooksContract ); if (dynamicSwapFeeCalculated) { state.swapFeePercentage = dynamicSwapFee; @@ -228,13 +230,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // If the hook contract does not exist or does not implement onAfterSwap, HooksConfigLib returns the original // amountCalculated. Otherwise, the new amount calculated is 'amountCalculated + delta'. If the underlying // hook fails, or limits are violated, `onAfterSwap` will revert. - amountCalculated = hooksConfig.onAfterSwap( + amountCalculated = poolData.poolConfigBits.onAfterSwap( amountCalculatedScaled18, amountCalculated, msg.sender, params, state, - poolData + poolData, + hooksContract ); if (params.kind == SwapKind.EXACT_IN) { @@ -496,7 +499,6 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // bptOut = supply * (ratio - 1), so lower ratio = less bptOut, favoring the pool. _ensureUnpaused(params.pool); - HooksConfigBits hooksConfig = _hooksConfigBits[params.pool]; // `_loadPoolDataUpdatingBalancesAndYieldFees` is non-reentrant, as it updates storage as well // as filling in poolData in memory. Since the add liquidity hooks are reentrant and could do anything, @@ -516,7 +518,16 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - if (hooksConfig.onBeforeAddLiquidity(maxAmountsInScaled18, msg.sender, params, poolData)) { + IHooks hooksContract = _hooksContracts[params.pool]; + if ( + poolData.poolConfigBits.onBeforeAddLiquidity( + maxAmountsInScaled18, + msg.sender, + params, + poolData, + hooksContract + ) + ) { // The hook might alter the balances, so we need to read them again to ensure that the data is // fresh moving forward. // We also need to upscale (adding liquidity, so round up) again. @@ -542,7 +553,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { maxAmountsInScaled18 ); - hooksConfig.onAfterAddLiquidity(amountsInScaled18, bptAmountOut, msg.sender, params, poolData); + poolData.poolConfigBits.onAfterAddLiquidity( + amountsInScaled18, + bptAmountOut, + msg.sender, + params, + poolData, + hooksContract + ); } /// @dev Avoid "stack too deep" - without polluting the Add/RemoveLiquidity params interface. @@ -707,8 +725,6 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // bptIn = supply * (1 - ratio), so lower ratio = more bptIn, favoring the pool. _ensureUnpaused(params.pool); - HooksConfigBits hooksConfig = _hooksConfigBits[params.pool]; - // `_loadPoolDataUpdatingBalancesAndYieldFees` is non-reentrant, as it updates storage as well // as filling in poolData in memory. Since the swap hooks are reentrant and could do anything, including // change these balances, we cannot defer settlement until `_removeLiquidity`. @@ -726,7 +742,16 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - if (hooksConfig.onBeforeRemoveLiquidity(minAmountsOutScaled18, msg.sender, params, poolData) == true) { + IHooks hooksContract = _hooksContracts[params.pool]; + if ( + poolData.poolConfigBits.onBeforeRemoveLiquidity( + minAmountsOutScaled18, + msg.sender, + params, + poolData, + hooksContract + ) == true + ) { // The hook might alter the balances, so we need to read them again to ensure that the data is // fresh moving forward. // We also need to upscale (removing liquidity, so round down) again. @@ -750,7 +775,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { minAmountsOutScaled18 ); - hooksConfig.onAfterRemoveLiquidity(amountsOutScaled18, bptAmountIn, msg.sender, params, poolData); + poolData.poolConfigBits.onAfterRemoveLiquidity( + amountsOutScaled18, + bptAmountIn, + msg.sender, + params, + poolData, + hooksContract + ); } /** diff --git a/pkg/vault/contracts/VaultExtension.sol b/pkg/vault/contracts/VaultExtension.sol index f6bb7f597..e2474a890 100644 --- a/pkg/vault/contracts/VaultExtension.sol +++ b/pkg/vault/contracts/VaultExtension.sol @@ -179,39 +179,6 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { revert PoolAlreadyRegistered(pool); } - HooksConfigBits hooksConfig = _hooksConfigBits[pool]; - - if (params.poolHooksContract != address(0)) { - // If a hook address was passed, make sure that hook trusts the pool factory - if ( - IHooks(params.poolHooksContract).onRegister( - msg.sender, - pool, - params.tokenConfig, - params.liquidityManagement - ) == false - ) { - revert HookRegistrationFailed(params.poolHooksContract, pool, msg.sender); - } - - // Gets the default HooksConfig from the hook contract and saves in the vault state - // Storing into hooksConfig first avoids stack-too-deep - IHooks.HookFlags memory hookFlags = IHooks(params.poolHooksContract).getHookFlags(); - - hooksConfig = hooksConfig.setShouldCallBeforeInitialize(hookFlags.shouldCallBeforeInitialize); - hooksConfig = hooksConfig.setShouldCallAfterInitialize(hookFlags.shouldCallAfterInitialize); - hooksConfig = hooksConfig.setShouldCallComputeDynamicSwapFee(hookFlags.shouldCallComputeDynamicSwapFee); - hooksConfig = hooksConfig.setShouldCallBeforeSwap(hookFlags.shouldCallBeforeSwap); - hooksConfig = hooksConfig.setShouldCallAfterSwap(hookFlags.shouldCallAfterSwap); - hooksConfig = hooksConfig.setShouldCallBeforeAddLiquidity(hookFlags.shouldCallBeforeAddLiquidity); - hooksConfig = hooksConfig.setShouldCallAfterAddLiquidity(hookFlags.shouldCallAfterAddLiquidity); - hooksConfig = hooksConfig.setShouldCallBeforeRemoveLiquidity(hookFlags.shouldCallBeforeRemoveLiquidity); - hooksConfig = hooksConfig.setShouldCallAfterRemoveLiquidity(hookFlags.shouldCallAfterRemoveLiquidity); - hooksConfig = hooksConfig.setHooksContract(params.poolHooksContract); - - _hooksConfigBits[pool] = hooksConfig; - } - uint256 numTokens = params.tokenConfig.length; if (numTokens < _MIN_TOKENS) { revert MinTokens(); @@ -277,14 +244,14 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { // Make pool role assignments. A zero address means default to the authorizer. _assignPoolRoles(pool, params.roleAccounts); + PoolConfigBits poolConfigBits; + // Store config and mark the pool as registered { // Initialize the pool-specific protocol fee values to the current global defaults. (uint256 aggregateSwapFeePercentage, uint256 aggregateYieldFeePercentage) = _protocolFeeController .registerPool(pool, params.roleAccounts.poolCreator, params.protocolFeeExempt); - PoolConfigBits poolConfigBits = _poolConfigBits[pool]; - poolConfigBits = poolConfigBits.setPoolRegistered(true); poolConfigBits = poolConfigBits.setDisableUnbalancedLiquidity( params.liquidityManagement.disableUnbalancedLiquidity @@ -298,7 +265,42 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { poolConfigBits = poolConfigBits.setAggregateSwapFeePercentage(aggregateSwapFeePercentage); poolConfigBits = poolConfigBits.setAggregateYieldFeePercentage(aggregateYieldFeePercentage); + if (params.poolHooksContract != address(0)) { + // If a hook address was passed, make sure that hook trusts the pool factory + if ( + IHooks(params.poolHooksContract).onRegister( + msg.sender, + pool, + params.tokenConfig, + params.liquidityManagement + ) == false + ) { + revert HookRegistrationFailed(params.poolHooksContract, pool, msg.sender); + } + + // Gets the default HooksConfig from the hook contract and saves in the vault state + // Storing into poolConfigBits first avoids stack-too-deep + IHooks.HookFlags memory hookFlags = IHooks(params.poolHooksContract).getHookFlags(); + + poolConfigBits = poolConfigBits.setShouldCallBeforeInitialize(hookFlags.shouldCallBeforeInitialize); + poolConfigBits = poolConfigBits.setShouldCallAfterInitialize(hookFlags.shouldCallAfterInitialize); + poolConfigBits = poolConfigBits.setShouldCallComputeDynamicSwapFee( + hookFlags.shouldCallComputeDynamicSwapFee + ); + poolConfigBits = poolConfigBits.setShouldCallBeforeSwap(hookFlags.shouldCallBeforeSwap); + poolConfigBits = poolConfigBits.setShouldCallAfterSwap(hookFlags.shouldCallAfterSwap); + poolConfigBits = poolConfigBits.setShouldCallBeforeAddLiquidity(hookFlags.shouldCallBeforeAddLiquidity); + poolConfigBits = poolConfigBits.setShouldCallAfterAddLiquidity(hookFlags.shouldCallAfterAddLiquidity); + poolConfigBits = poolConfigBits.setShouldCallBeforeRemoveLiquidity( + hookFlags.shouldCallBeforeRemoveLiquidity + ); + poolConfigBits = poolConfigBits.setShouldCallAfterRemoveLiquidity( + hookFlags.shouldCallAfterRemoveLiquidity + ); + } + _poolConfigBits[pool] = poolConfigBits; + _hooksContracts[pool] = IHooks(params.poolHooksContract); } _setStaticSwapFeePercentage(pool, params.swapFeePercentage); @@ -311,7 +313,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { params.swapFeePercentage, params.pauseWindowEndTime, params.roleAccounts, - hooksConfig.toHooksConfig(), + poolConfigBits.toHooksConfig(IHooks(params.poolHooksContract)), params.liquidityManagement ); } @@ -351,7 +353,6 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { bytes memory userData ) external onlyWhenUnlocked withRegisteredPool(pool) onlyVaultDelegateCall returns (uint256 bptAmountOut) { _ensureUnpaused(pool); - HooksConfigBits hooksConfig = _hooksConfigBits[pool]; // Balances are zero until after initialize is callled, so there is no need to charge pending yield fee here. PoolData memory poolData = _loadPoolData(pool, Rounding.ROUND_DOWN); @@ -370,7 +371,9 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { poolData.tokenRates ); - if (hooksConfig.onBeforeInitialize(exactAmountsInScaled18, userData) == true) { + IHooks hooksContract = _hooksContracts[pool]; + + if (poolData.poolConfigBits.onBeforeInitialize(exactAmountsInScaled18, userData, hooksContract) == true) { // The before hook is reentrant, and could have changed token rates. // Updating balances here is unnecessary since they're 0, but we do not special case before init // for the sake of bytecode size. @@ -385,7 +388,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { bptAmountOut = _initialize(pool, to, poolData, tokens, exactAmountsIn, exactAmountsInScaled18, minBptAmountOut); - hooksConfig.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData); + poolData.poolConfigBits.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData, hooksContract); } function _initialize( @@ -487,7 +490,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { function getHooksConfig( address pool ) external view withRegisteredPool(pool) onlyVaultDelegateCall returns (HooksConfig memory) { - return _hooksConfigBits[pool].toHooksConfig(); + return _poolConfigBits[pool].toHooksConfig(_hooksContracts[pool]); } /// @inheritdoc IVaultExtension @@ -521,9 +524,10 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { IBasePool.PoolSwapParams memory swapParams ) external view withRegisteredPool(pool) onlyVaultDelegateCall returns (bool success, uint256 dynamicSwapFee) { return - _hooksConfigBits[pool].onComputeDynamicSwapFee( + _poolConfigBits[pool].onComputeDynamicSwapFee( swapParams, - _poolConfigBits[pool].getStaticSwapFeePercentage() + _poolConfigBits[pool].getStaticSwapFeePercentage(), + _hooksContracts[pool] ); } diff --git a/pkg/vault/contracts/lib/PoolConfigLib.sol b/pkg/vault/contracts/lib/PoolConfigLib.sol index ed82ef6da..26c2e47dd 100644 --- a/pkg/vault/contracts/lib/PoolConfigLib.sol +++ b/pkg/vault/contracts/lib/PoolConfigLib.sol @@ -233,7 +233,7 @@ library PoolConfigLib { return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_REMOVE_LIQUIDITY_OFFSET)); } - function toHooksConfig(PoolConfigBits config, address hooksContract) internal pure returns (HooksConfig memory) { + function toHooksConfig(PoolConfigBits config, IHooks hooksContract) internal pure returns (HooksConfig memory) { return HooksConfig({ shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), @@ -245,7 +245,7 @@ library PoolConfigLib { shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), shouldCallBeforeSwap: config.shouldCallBeforeSwap(), shouldCallAfterSwap: config.shouldCallAfterSwap(), - hooksContract: hooksContract + hooksContract: address(hooksContract) }); } @@ -262,13 +262,13 @@ library PoolConfigLib { PoolConfigBits config, IBasePool.PoolSwapParams memory swapParams, uint256 staticSwapFeePercentage, - HooksContractBox storage hooksContractBox + IHooks hooksContract ) internal view returns (bool, uint256) { if (config.shouldCallComputeDynamicSwapFee() == false) { return (false, 0); } - (bool success, uint256 swapFeePercentage) = IHooks(config.getHooksContract()).onComputeDynamicSwapFee( + (bool success, uint256 swapFeePercentage) = hooksContract.onComputeDynamicSwapFee( swapParams, staticSwapFeePercentage ); @@ -279,6 +279,33 @@ library PoolConfigLib { return (success, swapFeePercentage); } + /** + * @dev Check if before swap hook should be called and call it. Throws an error if the hook contract fails to + * execute the hook. + * + * @param config The encoded hooks configuration + * @param swapParams The swap parameters used in the hook + * @param pool Pool address + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function onBeforeSwap( + PoolConfigBits config, + IBasePool.PoolSwapParams memory swapParams, + address pool, + IHooks hooksContract + ) internal returns (bool) { + if (config.shouldCallBeforeSwap() == false) { + // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) + return false; + } + + if (hooksContract.onBeforeSwap(swapParams, pool) == false) { + // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. + revert IVaultErrors.BeforeSwapHookFailed(); + } + return true; + } + /** * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to * execute the hook. @@ -298,7 +325,8 @@ library PoolConfigLib { address router, SwapParams memory params, SwapState memory state, - PoolData memory poolData + PoolData memory poolData, + IHooks hooksContract ) internal returns (uint256 hookAdjustedAmountCalculatedRaw) { if (config.shouldCallAfterSwap() == false) { // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not @@ -312,7 +340,7 @@ library PoolConfigLib { : (amountCalculatedScaled18, state.amountGivenScaled18); bool success; - (success, hookAdjustedAmountCalculatedRaw) = IHooks(config.getHooksContract()).onAfterSwap( + (success, hookAdjustedAmountCalculatedRaw) = hooksContract.onAfterSwap( IHooks.AfterSwapParams({ kind: params.kind, tokenIn: params.tokenIn, @@ -357,14 +385,15 @@ library PoolConfigLib { uint256[] memory maxAmountsInScaled18, address router, AddLiquidityParams memory params, - PoolData memory poolData + PoolData memory poolData, + IHooks hooksContract ) internal returns (bool) { if (config.shouldCallBeforeAddLiquidity() == false) { return false; } if ( - IHooks(config.getHooksContract()).onBeforeAddLiquidity( + hooksContract.onBeforeAddLiquidity( router, params.kind, maxAmountsInScaled18, @@ -394,14 +423,15 @@ library PoolConfigLib { uint256 bptAmountOut, address router, AddLiquidityParams memory params, - PoolData memory poolData + PoolData memory poolData, + IHooks hooksContract ) internal { if (config.shouldCallAfterAddLiquidity() == false) { return; } if ( - IHooks(config.getHooksContract()).onAfterAddLiquidity( + hooksContract.onAfterAddLiquidity( router, amountsInScaled18, bptAmountOut, @@ -429,14 +459,15 @@ library PoolConfigLib { uint256[] memory minAmountsOutScaled18, address router, RemoveLiquidityParams memory params, - PoolData memory poolData + PoolData memory poolData, + IHooks hooksContract ) internal returns (bool) { if (config.shouldCallBeforeRemoveLiquidity() == false) { return false; } if ( - IHooks(config.getHooksContract()).onBeforeRemoveLiquidity( + hooksContract.onBeforeRemoveLiquidity( router, params.kind, params.maxBptAmountIn, @@ -466,14 +497,15 @@ library PoolConfigLib { uint256 bptAmountIn, address router, RemoveLiquidityParams memory params, - PoolData memory poolData + PoolData memory poolData, + IHooks hooksContract ) internal { if (config.shouldCallAfterRemoveLiquidity() == false) { return; } if ( - IHooks(config.getHooksContract()).onAfterRemoveLiquidity( + hooksContract.onAfterRemoveLiquidity( router, bptAmountIn, amountsOutScaled18, @@ -497,13 +529,14 @@ library PoolConfigLib { function onBeforeInitialize( PoolConfigBits config, uint256[] memory exactAmountsInScaled18, - bytes memory userData + bytes memory userData, + IHooks hooksContract ) internal returns (bool) { if (config.shouldCallBeforeInitialize() == false) { return false; } - if (IHooks(config.getHooksContract()).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { + if (hooksContract.onBeforeInitialize(exactAmountsInScaled18, userData) == false) { revert IVaultErrors.BeforeInitializeHookFailed(); } return true; @@ -522,15 +555,14 @@ library PoolConfigLib { PoolConfigBits config, uint256[] memory exactAmountsInScaled18, uint256 bptAmountOut, - bytes memory userData + bytes memory userData, + IHooks hooksContract ) internal { if (config.shouldCallAfterInitialize() == false) { return; } - if ( - IHooks(config.getHooksContract()).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false - ) { + if (hooksContract.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false) { revert IVaultErrors.AfterInitializeHookFailed(); } } diff --git a/pkg/vault/contracts/test/VaultMock.sol b/pkg/vault/contracts/test/VaultMock.sol index b4dc4d9f9..b66ab26a3 100644 --- a/pkg/vault/contracts/test/VaultMock.sol +++ b/pkg/vault/contracts/test/VaultMock.sol @@ -78,23 +78,6 @@ contract VaultMock is IVaultMainMock, Vault { _mint(token, to, amount); } - function setHooksConfig(address pool, HooksConfig calldata config) external { - HooksConfigBits hooksConfig = _hooksConfigBits[pool]; - - hooksConfig = hooksConfig.setShouldCallBeforeInitialize(config.shouldCallBeforeInitialize); - hooksConfig = hooksConfig.setShouldCallAfterInitialize(config.shouldCallAfterInitialize); - hooksConfig = hooksConfig.setShouldCallComputeDynamicSwapFee(config.shouldCallComputeDynamicSwapFee); - hooksConfig = hooksConfig.setShouldCallBeforeSwap(config.shouldCallBeforeSwap); - hooksConfig = hooksConfig.setShouldCallAfterSwap(config.shouldCallAfterSwap); - hooksConfig = hooksConfig.setShouldCallBeforeAddLiquidity(config.shouldCallBeforeAddLiquidity); - hooksConfig = hooksConfig.setShouldCallAfterAddLiquidity(config.shouldCallAfterAddLiquidity); - hooksConfig = hooksConfig.setShouldCallBeforeRemoveLiquidity(config.shouldCallBeforeRemoveLiquidity); - hooksConfig = hooksConfig.setShouldCallAfterRemoveLiquidity(config.shouldCallAfterRemoveLiquidity); - hooksConfig = hooksConfig.setHooksContract(config.hooksContract); - - _hooksConfigBits[pool] = hooksConfig; - } - // Used for testing pool registration, which is ordinarily done in the pool factory. // The Mock pool has an argument for whether or not to register on deployment. To call register pool // separately, deploy it with the registration flag false, then call this function. @@ -223,6 +206,23 @@ contract VaultMock is IVaultMainMock, Vault { _poolConfigBits[pool] = poolConfigBits; } + function manualSetHooksConfig(address pool, HooksConfig memory hooksConfig) public { + PoolConfigBits poolConfigBits = _poolConfigBits[pool]; + + poolConfigBits = poolConfigBits.setShouldCallBeforeInitialize(hooksConfig.shouldCallBeforeInitialize); + poolConfigBits = poolConfigBits.setShouldCallAfterInitialize(hooksConfig.shouldCallAfterInitialize); + poolConfigBits = poolConfigBits.setShouldCallComputeDynamicSwapFee(hooksConfig.shouldCallComputeDynamicSwapFee); + poolConfigBits = poolConfigBits.setShouldCallBeforeSwap(hooksConfig.shouldCallBeforeSwap); + poolConfigBits = poolConfigBits.setShouldCallAfterSwap(hooksConfig.shouldCallAfterSwap); + poolConfigBits = poolConfigBits.setShouldCallBeforeAddLiquidity(hooksConfig.shouldCallBeforeAddLiquidity); + poolConfigBits = poolConfigBits.setShouldCallAfterAddLiquidity(hooksConfig.shouldCallAfterAddLiquidity); + poolConfigBits = poolConfigBits.setShouldCallBeforeRemoveLiquidity(hooksConfig.shouldCallBeforeRemoveLiquidity); + poolConfigBits = poolConfigBits.setShouldCallAfterRemoveLiquidity(hooksConfig.shouldCallAfterRemoveLiquidity); + + _poolConfigBits[pool] = poolConfigBits; + _hooksContracts[pool] = IHooks(hooksConfig.hooksContract); + } + function manualSetPoolConfigBits(address pool, PoolConfigBits config) public { _poolConfigBits[pool] = config; } diff --git a/pkg/vault/test/foundry/Hooks.t.sol b/pkg/vault/test/foundry/Hooks.t.sol index f41d1d6a2..ebc2d4b80 100644 --- a/pkg/vault/test/foundry/Hooks.t.sol +++ b/pkg/vault/test/foundry/Hooks.t.sol @@ -124,7 +124,7 @@ contract HooksTest is BaseVaultTest { function testOnComputeDynamicSwapFeeHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallComputeDynamicSwapFee = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); vm.prank(bob); vm.expectCall( @@ -151,7 +151,7 @@ contract HooksTest is BaseVaultTest { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallComputeDynamicSwapFee = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); authorizer.grantRole(vault.getActionId(IVaultAdmin.setStaticSwapFeePercentage.selector), alice); vm.prank(alice); @@ -180,7 +180,7 @@ contract HooksTest is BaseVaultTest { function testOnComputeDynamicSwapFeeHookRevert() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallComputeDynamicSwapFee = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); // should fail PoolHooksMock(poolHooksContract).setFailOnComputeDynamicSwapFeeHook(true); @@ -194,7 +194,7 @@ contract HooksTest is BaseVaultTest { function testOnBeforeSwapHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallBeforeSwap = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); vm.prank(bob); vm.expectCall( @@ -219,7 +219,7 @@ contract HooksTest is BaseVaultTest { function testOnBeforeSwapHookRevert() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallBeforeSwap = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); // should fail PoolHooksMock(poolHooksContract).setFailOnBeforeSwapHook(true); @@ -233,7 +233,7 @@ contract HooksTest is BaseVaultTest { function testOnAfterSwapHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallAfterSwap = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); setSwapFeePercentage(swapFeePercentage); vault.manualSetAggregateSwapFeePercentage(pool, _getAggregateFeePercentage(protocolSwapFeePercentage, 0)); @@ -271,7 +271,7 @@ contract HooksTest is BaseVaultTest { function testOnAfterSwapHookRevert() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallAfterSwap = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); // should fail PoolHooksMock(poolHooksContract).setFailOnAfterSwapHook(true); @@ -299,7 +299,7 @@ contract HooksTest is BaseVaultTest { function testOnBeforeAddLiquidityHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallBeforeAddLiquidity = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); vm.prank(bob); vm.expectCall( @@ -350,7 +350,7 @@ contract HooksTest is BaseVaultTest { function testOnBeforeRemoveLiquidityHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallBeforeRemoveLiquidity = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); vm.prank(alice); router.addLiquidityUnbalanced( @@ -402,7 +402,7 @@ contract HooksTest is BaseVaultTest { function testOnAfterAddLiquidityHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallAfterAddLiquidity = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); vm.prank(bob); vm.expectCall( @@ -452,7 +452,7 @@ contract HooksTest is BaseVaultTest { function testOnAfterRemoveLiquidityHook() public { HooksConfig memory hooksConfig = vault.getHooksConfig(pool); hooksConfig.shouldCallAfterRemoveLiquidity = true; - vault.setHooksConfig(pool, hooksConfig); + vault.manualSetHooksConfig(pool, hooksConfig); vm.prank(alice); router.addLiquidityUnbalanced( diff --git a/pkg/vault/test/foundry/HooksAlteringBalances.t.sol b/pkg/vault/test/foundry/HooksAlteringBalances.t.sol index ce84ce70c..1c350f8f3 100644 --- a/pkg/vault/test/foundry/HooksAlteringBalances.t.sol +++ b/pkg/vault/test/foundry/HooksAlteringBalances.t.sol @@ -32,7 +32,7 @@ contract HooksAlteringBalancesTest is BaseVaultTest { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeSwap = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); // Sets the pool address in the hook, so we can change pool balances inside the hook PoolHooksMock(poolHooksContract).setPool(pool); @@ -103,7 +103,7 @@ contract HooksAlteringBalancesTest is BaseVaultTest { function testOnBeforeAddLiquidityHookAltersBalances() public { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeAddLiquidity = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); uint256[] memory originalBalances = [poolInitAmount, poolInitAmount].toMemoryArray(); // newBalances are raw and scaled18, because rate is 1 and decimals are 18 @@ -146,7 +146,7 @@ contract HooksAlteringBalancesTest is BaseVaultTest { function testOnBeforeRemoveLiquidityHookAlterBalance() public { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeRemoveLiquidity = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); uint256[] memory amountsOut = [defaultAmount, defaultAmount].toMemoryArray(); diff --git a/pkg/vault/test/foundry/HooksAlteringRates.t.sol b/pkg/vault/test/foundry/HooksAlteringRates.t.sol index dc5e9b27e..b78f6a961 100644 --- a/pkg/vault/test/foundry/HooksAlteringRates.t.sol +++ b/pkg/vault/test/foundry/HooksAlteringRates.t.sol @@ -30,7 +30,7 @@ contract HooksAlteringRatesTest is BaseVaultTest { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeSwap = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); (daiIdx, usdcIdx) = getSortedIndexes(address(dai), address(usdc)); } @@ -102,7 +102,7 @@ contract HooksAlteringRatesTest is BaseVaultTest { HooksConfig memory config = vault.getHooksConfig(address(newPool)); config.shouldCallBeforeInitialize = true; config.shouldCallAfterInitialize = true; - vault.setHooksConfig(address(newPool), config); + vault.manualSetHooksConfig(address(newPool), config); // Change rate of first token PoolHooksMock(poolHooksContract).setChangeTokenRateOnBeforeInitializeHook(true, rateProvider, 0.5e18); @@ -136,7 +136,7 @@ contract HooksAlteringRatesTest is BaseVaultTest { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeAddLiquidity = true; config.shouldCallAfterAddLiquidity = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); // Change rate of first token PoolHooksMock(poolHooksContract).setChangeTokenRateOnBeforeAddLiquidityHook(true, rateProvider, 0.5e18); @@ -180,7 +180,7 @@ contract HooksAlteringRatesTest is BaseVaultTest { function testOnBeforeRemoveLiquidityHookAlterRate() public { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeRemoveLiquidity = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); // Change rate of first token PoolHooksMock(poolHooksContract).setChangeTokenRateOnBeforeRemoveLiquidityHook(true, rateProvider, 0.5e18); diff --git a/pkg/vault/test/foundry/Initializer.t.sol b/pkg/vault/test/foundry/Initializer.t.sol index fdbb05394..42ac03f10 100644 --- a/pkg/vault/test/foundry/Initializer.t.sol +++ b/pkg/vault/test/foundry/Initializer.t.sol @@ -32,7 +32,7 @@ contract InitializerTest is BaseVaultTest { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeInitialize = true; config.shouldCallAfterInitialize = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); standardPoolTokens = InputHelpers.sortTokens([address(dai), address(usdc)].toMemoryArray().asIERC20()); } @@ -43,7 +43,7 @@ contract InitializerTest is BaseVaultTest { HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeInitialize = false; config.shouldCallAfterInitialize = false; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); PoolHooksMock(poolHooksContract).setFailOnBeforeInitializeHook(true); PoolHooksMock(poolHooksContract).setFailOnAfterInitializeHook(true); diff --git a/pkg/vault/test/foundry/VaultLiquidity.t.sol b/pkg/vault/test/foundry/VaultLiquidity.t.sol index 2b4d8fe95..6fbdd1eb8 100644 --- a/pkg/vault/test/foundry/VaultLiquidity.t.sol +++ b/pkg/vault/test/foundry/VaultLiquidity.t.sol @@ -6,7 +6,7 @@ import "forge-std/Test.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; -import { PoolConfig } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { PoolConfig, HooksConfig } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/ArrayHelpers.sol"; @@ -260,6 +260,7 @@ contract VaultLiquidityTest is BaseVaultTest { // Disable unbalanced liquidity PoolConfig memory poolConfigBits = vault.getPoolConfig(pool); poolConfigBits.liquidityManagement.disableUnbalancedLiquidity = true; + vault.manualSetPoolConfig(pool, poolConfigBits); vm.expectRevert(IVaultErrors.DoesNotSupportUnbalancedLiquidity.selector); @@ -289,6 +290,7 @@ contract VaultLiquidityTest is BaseVaultTest { // Disable remove custom liquidity PoolConfig memory poolConfigBits = vault.getPoolConfig(pool); poolConfigBits.liquidityManagement.enableRemoveLiquidityCustom = false; + vault.manualSetPoolConfig(pool, poolConfigBits); vm.expectRevert(IVaultErrors.DoesNotSupportRemoveLiquidityCustom.selector); diff --git a/pkg/vault/test/foundry/VaultSwap.t.sol b/pkg/vault/test/foundry/VaultSwap.t.sol index cd9e4d920..45c8740d0 100644 --- a/pkg/vault/test/foundry/VaultSwap.t.sol +++ b/pkg/vault/test/foundry/VaultSwap.t.sol @@ -411,7 +411,7 @@ contract VaultSwapTest is BaseVaultTest { // Enable before swap HooksConfig memory config = vault.getHooksConfig(pool); config.shouldCallBeforeSwap = true; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); // Enable reentrancy hook PoolHooksMock(poolHooksContract).setSwapReentrancyHookActive(true); diff --git a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol index c602a92f9..2ddac1704 100644 --- a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol @@ -1,221 +1,221 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.24; - -import "forge-std/Test.sol"; - -import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; -import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; -import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; -import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; - -import { HooksConfigLib, HooksConfigBits } from "@balancer-labs/v3-vault/contracts/lib/HooksConfigLib.sol"; - -import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/ArrayHelpers.sol"; - -import { BaseBitsConfigTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseBitsConfigTest.sol"; - -contract HooksConfigLibTest is BaseBitsConfigTest { - using WordCodec for bytes32; - using ArrayHelpers for *; - - uint256 public constant ADDRESS_BITLENGTH = 160; - - uint256 public staticSwapFeePercentage = 5e18; - address public hooksContract = address(0x05); - - function testOffsets() public { - _checkBitsUsedOnce(HooksConfigLib.BEFORE_INITIALIZE_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.AFTER_INITIALIZE_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.DYNAMIC_SWAP_FEE_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.BEFORE_SWAP_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.AFTER_SWAP_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.AFTER_ADD_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(HooksConfigLib.HOOKS_CONTRACT_OFFSET, ADDRESS_BITLENGTH); - } - - function testZeroConfigBytes() public { - HooksConfigBits config; - - assertFalse(config.shouldCallBeforeInitialize(), "shouldCallBeforeInitialize should be false"); - assertFalse(config.shouldCallAfterInitialize(), "shouldCallAfterInitialize should be false"); - assertFalse(config.shouldCallComputeDynamicSwapFee(), "shouldCallComputeDynamicSwapFee should be false"); - assertFalse(config.shouldCallBeforeSwap(), "shouldCallBeforeSwap should be false"); - assertFalse(config.shouldCallAfterSwap(), "shouldCallAfterSwap should be false"); - assertFalse(config.shouldCallBeforeAddLiquidity(), "shouldCallBeforeAddLiquidity should be false"); - assertFalse(config.shouldCallAfterAddLiquidity(), "shouldCallAfterAddLiquidity should be false"); - assertFalse(config.shouldCallBeforeRemoveLiquidity(), "shouldCallBeforeRemoveLiquidity should be false"); - assertFalse(config.shouldCallAfterRemoveLiquidity(), "shouldCallAfterRemoveLiquidity should be false"); - assertEq(config.getHooksContract(), address(0), "getHooksContract should be address(0)"); - } - - // #region test setters and getters - function testShouldCallBeforeInitialize() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_INITIALIZE_OFFSET) - ); - assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (getter)"); - } - - function testSetShouldCallBeforeInitialize() public { - HooksConfigBits config; - config = config.setShouldCallBeforeInitialize(true); - assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); - } - - function testShouldCallAfterInitialize() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_INITIALIZE_OFFSET) - ); - assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (getter)"); - } - - function testSetShouldCallAfterInitialize() public { - HooksConfigBits config; - config = config.setShouldCallAfterInitialize(true); - assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (setter)"); - } - - function testShouldCallComputeDynamicSwapFee() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.DYNAMIC_SWAP_FEE_OFFSET) - ); - assertEq( - config.shouldCallComputeDynamicSwapFee(), - true, - "shouldCallComputeDynamicSwapFee should be true (getter)" - ); - } - - function testSetShouldCallComputeDynamicSwapFee() public { - HooksConfigBits config; - config = config.setShouldCallComputeDynamicSwapFee(true); - assertEq( - config.shouldCallComputeDynamicSwapFee(), - true, - "shouldCallComputeDynamicSwapFee should be true (setter)" - ); - } - - function testShouldCallBeforeSwap() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_SWAP_OFFSET) - ); - assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (getter)"); - } - - function testSetShouldCallBeforeSwap() public { - HooksConfigBits config; - config = config.setShouldCallBeforeSwap(true); - assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (setter)"); - } - - function testShouldCallAfterSwap() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_SWAP_OFFSET) - ); - assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (getter)"); - } - - function testSetShouldCallAfterSwap() public { - HooksConfigBits config; - config = config.setShouldCallAfterSwap(true); - assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (setter)"); - } - - function testShouldCallBeforeAddLiquidity() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET) - ); - assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (getter)"); - } - - function testSetShouldCallBeforeAddLiquidity() public { - HooksConfigBits config; - config = config.setShouldCallBeforeAddLiquidity(true); - assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (setter)"); - } - - function testShouldCallAfterAddLiquidity() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_ADD_LIQUIDITY_OFFSET) - ); - assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (getter)"); - } - - function testSetShouldCallAfterAddLiquidity() public { - HooksConfigBits config; - config = config.setShouldCallAfterAddLiquidity(true); - assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (setter)"); - } - - function testShouldCallBeforeRemoveLiquidity() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET) - ); - assertEq( - config.shouldCallBeforeRemoveLiquidity(), - true, - "shouldCallBeforeRemoveLiquidity should be true (getter)" - ); - } - - function testSetShouldCallBeforeRemoveLiquidity() public { - HooksConfigBits config; - config = config.setShouldCallBeforeRemoveLiquidity(true); - assertEq( - config.shouldCallBeforeRemoveLiquidity(), - true, - "shouldCallBeforeRemoveLiquidity should be true (setter)" - ); - } - - function testShouldCallAfterRemoveLiquidity() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET) - ); - assertEq( - config.shouldCallAfterRemoveLiquidity(), - true, - "shouldCallAfterRemoveLiquidity should be true (getter)" - ); - } - - function testSetShouldCallAfterRemoveLiquidity() public { - HooksConfigBits config; - config = config.setShouldCallAfterRemoveLiquidity(true); - assertEq( - config.shouldCallAfterRemoveLiquidity(), - true, - "shouldCallAfterRemoveLiquidity should be true (setter)" - ); - } - - function testGetHooksContract() public { - HooksConfigBits config; - config = HooksConfigBits.wrap( - HooksConfigBits.unwrap(config).insertAddress(hooksContract, HooksConfigLib.HOOKS_CONTRACT_OFFSET) - ); - assertEq(config.getHooksContract(), hooksContract, "getHooksContract should be hooksContract (getter)"); - } - - function testSetHooksContract() public { - HooksConfigBits config; - config = config.setHooksContract(hooksContract); - assertEq(config.getHooksContract(), hooksContract, "getHooksContract should be hooksContract (setter)"); - } - // #endregion -} +// // SPDX-License-Identifier: GPL-3.0-or-later + +// pragma solidity ^0.8.24; + +// import "forge-std/Test.sol"; + +// import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; +// import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; +// import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; +// import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; + +// import { HooksConfigLib, HooksConfigBits } from "@balancer-labs/v3-vault/contracts/lib/HooksConfigLib.sol"; + +// import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; +// import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/ArrayHelpers.sol"; + +// import { BaseBitsConfigTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseBitsConfigTest.sol"; + +// contract HooksConfigLibTest is BaseBitsConfigTest { +// using WordCodec for bytes32; +// using ArrayHelpers for *; + +// uint256 public constant ADDRESS_BITLENGTH = 160; + +// uint256 public staticSwapFeePercentage = 5e18; +// address public hooksContract = address(0x05); + +// function testOffsets() public { +// _checkBitsUsedOnce(HooksConfigLib.BEFORE_INITIALIZE_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.AFTER_INITIALIZE_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.DYNAMIC_SWAP_FEE_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.BEFORE_SWAP_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.AFTER_SWAP_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.AFTER_ADD_LIQUIDITY_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET); +// _checkBitsUsedOnce(HooksConfigLib.HOOKS_CONTRACT_OFFSET, ADDRESS_BITLENGTH); +// } + +// function testZeroConfigBytes() public { +// HooksConfigBits config; + +// assertFalse(config.shouldCallBeforeInitialize(), "shouldCallBeforeInitialize should be false"); +// assertFalse(config.shouldCallAfterInitialize(), "shouldCallAfterInitialize should be false"); +// assertFalse(config.shouldCallComputeDynamicSwapFee(), "shouldCallComputeDynamicSwapFee should be false"); +// assertFalse(config.shouldCallBeforeSwap(), "shouldCallBeforeSwap should be false"); +// assertFalse(config.shouldCallAfterSwap(), "shouldCallAfterSwap should be false"); +// assertFalse(config.shouldCallBeforeAddLiquidity(), "shouldCallBeforeAddLiquidity should be false"); +// assertFalse(config.shouldCallAfterAddLiquidity(), "shouldCallAfterAddLiquidity should be false"); +// assertFalse(config.shouldCallBeforeRemoveLiquidity(), "shouldCallBeforeRemoveLiquidity should be false"); +// assertFalse(config.shouldCallAfterRemoveLiquidity(), "shouldCallAfterRemoveLiquidity should be false"); +// assertEq(config.getHooksContract(), address(0), "getHooksContract should be address(0)"); +// } + +// // #region test setters and getters +// function testShouldCallBeforeInitialize() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_INITIALIZE_OFFSET) +// ); +// assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (getter)"); +// } + +// function testSetShouldCallBeforeInitialize() public { +// HooksConfigBits config; +// config = config.setShouldCallBeforeInitialize(true); +// assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); +// } + +// function testShouldCallAfterInitialize() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_INITIALIZE_OFFSET) +// ); +// assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (getter)"); +// } + +// function testSetShouldCallAfterInitialize() public { +// HooksConfigBits config; +// config = config.setShouldCallAfterInitialize(true); +// assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (setter)"); +// } + +// function testShouldCallComputeDynamicSwapFee() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.DYNAMIC_SWAP_FEE_OFFSET) +// ); +// assertEq( +// config.shouldCallComputeDynamicSwapFee(), +// true, +// "shouldCallComputeDynamicSwapFee should be true (getter)" +// ); +// } + +// function testSetShouldCallComputeDynamicSwapFee() public { +// HooksConfigBits config; +// config = config.setShouldCallComputeDynamicSwapFee(true); +// assertEq( +// config.shouldCallComputeDynamicSwapFee(), +// true, +// "shouldCallComputeDynamicSwapFee should be true (setter)" +// ); +// } + +// function testShouldCallBeforeSwap() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_SWAP_OFFSET) +// ); +// assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (getter)"); +// } + +// function testSetShouldCallBeforeSwap() public { +// HooksConfigBits config; +// config = config.setShouldCallBeforeSwap(true); +// assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (setter)"); +// } + +// function testShouldCallAfterSwap() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_SWAP_OFFSET) +// ); +// assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (getter)"); +// } + +// function testSetShouldCallAfterSwap() public { +// HooksConfigBits config; +// config = config.setShouldCallAfterSwap(true); +// assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (setter)"); +// } + +// function testShouldCallBeforeAddLiquidity() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET) +// ); +// assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (getter)"); +// } + +// function testSetShouldCallBeforeAddLiquidity() public { +// HooksConfigBits config; +// config = config.setShouldCallBeforeAddLiquidity(true); +// assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (setter)"); +// } + +// function testShouldCallAfterAddLiquidity() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_ADD_LIQUIDITY_OFFSET) +// ); +// assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (getter)"); +// } + +// function testSetShouldCallAfterAddLiquidity() public { +// HooksConfigBits config; +// config = config.setShouldCallAfterAddLiquidity(true); +// assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (setter)"); +// } + +// function testShouldCallBeforeRemoveLiquidity() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET) +// ); +// assertEq( +// config.shouldCallBeforeRemoveLiquidity(), +// true, +// "shouldCallBeforeRemoveLiquidity should be true (getter)" +// ); +// } + +// function testSetShouldCallBeforeRemoveLiquidity() public { +// HooksConfigBits config; +// config = config.setShouldCallBeforeRemoveLiquidity(true); +// assertEq( +// config.shouldCallBeforeRemoveLiquidity(), +// true, +// "shouldCallBeforeRemoveLiquidity should be true (setter)" +// ); +// } + +// function testShouldCallAfterRemoveLiquidity() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET) +// ); +// assertEq( +// config.shouldCallAfterRemoveLiquidity(), +// true, +// "shouldCallAfterRemoveLiquidity should be true (getter)" +// ); +// } + +// function testSetShouldCallAfterRemoveLiquidity() public { +// HooksConfigBits config; +// config = config.setShouldCallAfterRemoveLiquidity(true); +// assertEq( +// config.shouldCallAfterRemoveLiquidity(), +// true, +// "shouldCallAfterRemoveLiquidity should be true (setter)" +// ); +// } + +// function testGetHooksContract() public { +// HooksConfigBits config; +// config = HooksConfigBits.wrap( +// HooksConfigBits.unwrap(config).insertAddress(hooksContract, HooksConfigLib.HOOKS_CONTRACT_OFFSET) +// ); +// assertEq(config.getHooksContract(), hooksContract, "getHooksContract should be hooksContract (getter)"); +// } + +// function testSetHooksContract() public { +// HooksConfigBits config; +// config = config.setHooksContract(hooksContract); +// assertEq(config.getHooksContract(), hooksContract, "getHooksContract should be hooksContract (setter)"); +// } +// // #endregion +// } From b585f927ed0cabaea43be2448679329a9809122e Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Tue, 18 Jun 2024 22:02:19 +0200 Subject: [PATCH 03/18] add optimization for load hooksContract --- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- .../contracts/helpers/Cache.sol | 41 ++++++++++++++++ pkg/vault/contracts/Vault.sol | 25 +++++----- pkg/vault/contracts/VaultExtension.sol | 16 +++--- pkg/vault/contracts/VaultStorage.sol | 3 +- pkg/vault/contracts/lib/HooksConfigLib.sol | 34 ------------- pkg/vault/contracts/lib/PoolConfigLib.sol | 49 +++++++++++-------- pkg/vault/contracts/test/VaultMock.sol | 3 +- 11 files changed, 97 insertions(+), 82 deletions(-) create mode 100644 pkg/solidity-utils/contracts/helpers/Cache.sol delete mode 100644 pkg/vault/contracts/lib/HooksConfigLib.sol diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots index d0539e72c..281d84625 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -178.2k \ No newline at end of file +176.2k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots index f61f50c4c..7e84900be 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -161.7k \ No newline at end of file +159.7k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots index b4e9dea21..4a646bf0d 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -212.6k \ No newline at end of file +210.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots index 5c6672db0..f946bdf47 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -179.0k \ No newline at end of file +177.0k \ No newline at end of file diff --git a/pkg/solidity-utils/contracts/helpers/Cache.sol b/pkg/solidity-utils/contracts/helpers/Cache.sol new file mode 100644 index 000000000..d9e461b5d --- /dev/null +++ b/pkg/solidity-utils/contracts/helpers/Cache.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; + +library Cache { + struct AddressCache { + address value; + bytes32 slot; + } + + function initAddressCache( + StorageSlot.AddressSlot storage addressSlot + ) internal pure returns (AddressCache memory cache) { + bytes32 slot; + + assembly { + slot := addressSlot.slot + } + + cache.slot = slot; + } + + function getValue(AddressCache memory cache) internal view returns (address) { + if (cache.value == address(0)) { + address _value; + bytes32 slot = cache.slot; + + assembly { + _value := sload(slot) + } + + cache.value = _value; + } + + return cache.value; + } +} diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index 61fd09bd2..9ef1acc50 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -31,10 +31,10 @@ import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/Fixe import { BasePoolMath } from "@balancer-labs/v3-solidity-utils/contracts/math/BasePoolMath.sol"; import { EnumerableMap } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/EnumerableMap.sol"; import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; +import { Cache } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Cache.sol"; import { VaultStateLib, VaultStateBits, VaultStateBits } from "./lib/VaultStateLib.sol"; import { PoolConfigLib } from "./lib/PoolConfigLib.sol"; -import { HooksConfigLib, HooksConfigBits } from "./lib/HooksConfigLib.sol"; import { PackedTokenBalance } from "./lib/PackedTokenBalance.sol"; import { PoolDataLib } from "./lib/PoolDataLib.sol"; import { BufferPackedTokenBalance } from "./lib/BufferPackedBalance.sol"; @@ -53,6 +53,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { using BufferPackedTokenBalance for bytes32; using TransientStorageHelpers for *; using StorageSlot for *; + using Cache for Cache.AddressCache; using PoolDataLib for PoolData; constructor(IVaultExtension vaultExtension, IAuthorizer authorizer, IProtocolFeeController protocolFeeController) { @@ -193,9 +194,9 @@ contract Vault is IVaultMain, VaultCommon, Proxy { IBasePool.PoolSwapParams memory swapParams = _buildPoolSwapParams(params, state, poolData); - IHooks hooksContract = _hooksContracts[params.pool]; + Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); - if (poolData.poolConfigBits.onBeforeSwap(swapParams, params.pool, hooksContract)) { + if (poolData.poolConfigBits.onBeforeSwap(swapParams, params.pool, hooksContractCache)) { // The call to `onBeforeSwap` could potentially update token rates and balances. // We update `poolData.tokenRates`, `poolData.rawBalances` and `poolData.balancesLiveScaled18` // to ensure the `onSwap` and `onComputeDynamicSwapFee` are called with the current values. @@ -215,7 +216,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { (bool dynamicSwapFeeCalculated, uint256 dynamicSwapFee) = poolData.poolConfigBits.onComputeDynamicSwapFee( swapParams, state.swapFeePercentage, - hooksContract + hooksContractCache ); if (dynamicSwapFeeCalculated) { state.swapFeePercentage = dynamicSwapFee; @@ -227,7 +228,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { uint256 amountCalculatedScaled18; (amountCalculated, amountCalculatedScaled18, amountIn, amountOut) = _swap(params, state, poolData, swapParams); - // If the hook contract does not exist or does not implement onAfterSwap, HooksConfigLib returns the original + // If the hook contract does not exist or does not implement onAfterSwap, PoolConfigLib returns the original // amountCalculated. Otherwise, the new amount calculated is 'amountCalculated + delta'. If the underlying // hook fails, or limits are violated, `onAfterSwap` will revert. amountCalculated = poolData.poolConfigBits.onAfterSwap( @@ -237,7 +238,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { params, state, poolData, - hooksContract + hooksContractCache ); if (params.kind == SwapKind.EXACT_IN) { @@ -518,14 +519,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - IHooks hooksContract = _hooksContracts[params.pool]; + Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); if ( poolData.poolConfigBits.onBeforeAddLiquidity( maxAmountsInScaled18, msg.sender, params, poolData, - hooksContract + hooksContractCache ) ) { // The hook might alter the balances, so we need to read them again to ensure that the data is @@ -559,7 +560,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { msg.sender, params, poolData, - hooksContract + hooksContractCache ); } @@ -742,14 +743,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - IHooks hooksContract = _hooksContracts[params.pool]; + Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); if ( poolData.poolConfigBits.onBeforeRemoveLiquidity( minAmountsOutScaled18, msg.sender, params, poolData, - hooksContract + hooksContractCache ) == true ) { // The hook might alter the balances, so we need to read them again to ensure that the data is @@ -781,7 +782,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { msg.sender, params, poolData, - hooksContract + hooksContractCache ); } diff --git a/pkg/vault/contracts/VaultExtension.sol b/pkg/vault/contracts/VaultExtension.sol index e2474a890..144616bc8 100644 --- a/pkg/vault/contracts/VaultExtension.sol +++ b/pkg/vault/contracts/VaultExtension.sol @@ -34,10 +34,10 @@ import { ReentrancyGuardTransient } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/ReentrancyGuardTransient.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; +import { Cache } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Cache.sol"; import { VaultStateBits, VaultStateLib } from "./lib/VaultStateLib.sol"; import { PoolConfigLib } from "./lib/PoolConfigLib.sol"; -import { HooksConfigLib, HooksConfigBits } from "./lib/HooksConfigLib.sol"; import { VaultExtensionsLib } from "./lib/VaultExtensionsLib.sol"; import { VaultCommon } from "./VaultCommon.sol"; import { PackedTokenBalance } from "./lib/PackedTokenBalance.sol"; @@ -300,7 +300,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { } _poolConfigBits[pool] = poolConfigBits; - _hooksContracts[pool] = IHooks(params.poolHooksContract); + _hooksContracts[pool].value = params.poolHooksContract; } _setStaticSwapFeePercentage(pool, params.swapFeePercentage); @@ -313,7 +313,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { params.swapFeePercentage, params.pauseWindowEndTime, params.roleAccounts, - poolConfigBits.toHooksConfig(IHooks(params.poolHooksContract)), + poolConfigBits.toHooksConfig(params.poolHooksContract), params.liquidityManagement ); } @@ -371,9 +371,9 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { poolData.tokenRates ); - IHooks hooksContract = _hooksContracts[pool]; + Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[pool]); - if (poolData.poolConfigBits.onBeforeInitialize(exactAmountsInScaled18, userData, hooksContract) == true) { + if (poolData.poolConfigBits.onBeforeInitialize(exactAmountsInScaled18, userData, hooksContractCache) == true) { // The before hook is reentrant, and could have changed token rates. // Updating balances here is unnecessary since they're 0, but we do not special case before init // for the sake of bytecode size. @@ -388,7 +388,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { bptAmountOut = _initialize(pool, to, poolData, tokens, exactAmountsIn, exactAmountsInScaled18, minBptAmountOut); - poolData.poolConfigBits.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData, hooksContract); + poolData.poolConfigBits.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData, hooksContractCache); } function _initialize( @@ -490,7 +490,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { function getHooksConfig( address pool ) external view withRegisteredPool(pool) onlyVaultDelegateCall returns (HooksConfig memory) { - return _poolConfigBits[pool].toHooksConfig(_hooksContracts[pool]); + return _poolConfigBits[pool].toHooksConfig(_hooksContracts[pool].value); } /// @inheritdoc IVaultExtension @@ -527,7 +527,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { _poolConfigBits[pool].onComputeDynamicSwapFee( swapParams, _poolConfigBits[pool].getStaticSwapFeePercentage(), - _hooksContracts[pool] + Cache.initAddressCache(_hooksContracts[pool]) ); } diff --git a/pkg/vault/contracts/VaultStorage.sol b/pkg/vault/contracts/VaultStorage.sol index 5cb4940db..d8abf61b4 100644 --- a/pkg/vault/contracts/VaultStorage.sol +++ b/pkg/vault/contracts/VaultStorage.sol @@ -23,7 +23,6 @@ import { import { VaultStateBits } from "./lib/VaultStateLib.sol"; import { PoolConfigBits } from "./lib/PoolConfigLib.sol"; -import { HooksConfigBits } from "./lib/HooksConfigLib.sol"; import { PackedTokenBalance } from "./lib/PackedTokenBalance.sol"; // solhint-disable max-states-count @@ -68,7 +67,7 @@ contract VaultStorage { mapping(address => PoolConfigBits) internal _poolConfigBits; // Registry of pool hooks contracts. - mapping(address => IHooks) internal _hooksContracts; + mapping(address => StorageSlot.AddressSlot) internal _hooksContracts; // Pool -> (token -> PackedTokenBalance): structure containing the current raw and "last live" scaled balances. // Last live balances are used for yield fee computation, and since these have rates applied, they are stored diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol deleted file mode 100644 index 6222cb84e..000000000 --- a/pkg/vault/contracts/lib/HooksConfigLib.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.24; - -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 { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; - -// @notice Config type to store entire configuration of the hooks. -type HooksConfigBits is bytes32; - -using HooksConfigLib for HooksConfigBits global; - -library HooksConfigLib { - using WordCodec for bytes32; - - // Bit offsets for pool config - uint8 public constant BEFORE_INITIALIZE_OFFSET = 0; - uint8 public constant AFTER_INITIALIZE_OFFSET = BEFORE_INITIALIZE_OFFSET + 1; - uint8 public constant DYNAMIC_SWAP_FEE_OFFSET = AFTER_INITIALIZE_OFFSET + 1; - uint8 public constant BEFORE_SWAP_OFFSET = DYNAMIC_SWAP_FEE_OFFSET + 1; - uint8 public constant AFTER_SWAP_OFFSET = BEFORE_SWAP_OFFSET + 1; - uint8 public constant BEFORE_ADD_LIQUIDITY_OFFSET = AFTER_SWAP_OFFSET + 1; - uint8 public constant AFTER_ADD_LIQUIDITY_OFFSET = BEFORE_ADD_LIQUIDITY_OFFSET + 1; - uint8 public constant BEFORE_REMOVE_LIQUIDITY_OFFSET = AFTER_ADD_LIQUIDITY_OFFSET + 1; - uint8 public constant AFTER_REMOVE_LIQUIDITY_OFFSET = BEFORE_REMOVE_LIQUIDITY_OFFSET + 1; - uint8 public constant HOOKS_CONTRACT_OFFSET = AFTER_REMOVE_LIQUIDITY_OFFSET + 1; - - -} diff --git a/pkg/vault/contracts/lib/PoolConfigLib.sol b/pkg/vault/contracts/lib/PoolConfigLib.sol index 26c2e47dd..4f4a024f9 100644 --- a/pkg/vault/contracts/lib/PoolConfigLib.sol +++ b/pkg/vault/contracts/lib/PoolConfigLib.sol @@ -9,10 +9,12 @@ import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; +import { Cache } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Cache.sol"; library PoolConfigLib { using WordCodec for bytes32; using PoolConfigLib for PoolConfigBits; + using Cache for Cache.AddressCache; error InvalidSize(uint256 currentValue, uint256 expectedSize); @@ -85,6 +87,7 @@ library PoolConfigLib { function setPoolInRecoveryMode(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_RECOVERY_MODE_OFFSET)); } + // #endregion // #region Bit offsets for liquidity operations @@ -149,6 +152,7 @@ library PoolConfigLib { PoolConfigBits.unwrap(config).insertBool(enableRemoveLiquidityCustom, REMOVE_LIQUIDITY_CUSTOM_OFFSET) ); } + // #endregion // #region Bit offsets for hooks config @@ -233,7 +237,7 @@ library PoolConfigLib { return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_REMOVE_LIQUIDITY_OFFSET)); } - function toHooksConfig(PoolConfigBits config, IHooks hooksContract) internal pure returns (HooksConfig memory) { + function toHooksConfig(PoolConfigBits config, address hooksContract) internal pure returns (HooksConfig memory) { return HooksConfig({ shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), @@ -245,7 +249,7 @@ library PoolConfigLib { shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), shouldCallBeforeSwap: config.shouldCallBeforeSwap(), shouldCallAfterSwap: config.shouldCallAfterSwap(), - hooksContract: address(hooksContract) + hooksContract: hooksContract }); } @@ -262,13 +266,13 @@ library PoolConfigLib { PoolConfigBits config, IBasePool.PoolSwapParams memory swapParams, uint256 staticSwapFeePercentage, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal view returns (bool, uint256) { if (config.shouldCallComputeDynamicSwapFee() == false) { return (false, 0); } - (bool success, uint256 swapFeePercentage) = hooksContract.onComputeDynamicSwapFee( + (bool success, uint256 swapFeePercentage) = IHooks(hooksContractCache.getValue()).onComputeDynamicSwapFee( swapParams, staticSwapFeePercentage ); @@ -292,14 +296,14 @@ library PoolConfigLib { PoolConfigBits config, IBasePool.PoolSwapParams memory swapParams, address pool, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal returns (bool) { if (config.shouldCallBeforeSwap() == false) { // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) return false; } - if (hooksContract.onBeforeSwap(swapParams, pool) == false) { + if (IHooks(hooksContractCache.getValue()).onBeforeSwap(swapParams, pool) == false) { // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. revert IVaultErrors.BeforeSwapHookFailed(); } @@ -326,7 +330,7 @@ library PoolConfigLib { SwapParams memory params, SwapState memory state, PoolData memory poolData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal returns (uint256 hookAdjustedAmountCalculatedRaw) { if (config.shouldCallAfterSwap() == false) { // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not @@ -340,7 +344,7 @@ library PoolConfigLib { : (amountCalculatedScaled18, state.amountGivenScaled18); bool success; - (success, hookAdjustedAmountCalculatedRaw) = hooksContract.onAfterSwap( + (success, hookAdjustedAmountCalculatedRaw) = IHooks(hooksContractCache.getValue()).onAfterSwap( IHooks.AfterSwapParams({ kind: params.kind, tokenIn: params.tokenIn, @@ -386,14 +390,14 @@ library PoolConfigLib { address router, AddLiquidityParams memory params, PoolData memory poolData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal returns (bool) { if (config.shouldCallBeforeAddLiquidity() == false) { return false; } if ( - hooksContract.onBeforeAddLiquidity( + IHooks(hooksContractCache.getValue()).onBeforeAddLiquidity( router, params.kind, maxAmountsInScaled18, @@ -424,14 +428,14 @@ library PoolConfigLib { address router, AddLiquidityParams memory params, PoolData memory poolData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal { if (config.shouldCallAfterAddLiquidity() == false) { return; } if ( - hooksContract.onAfterAddLiquidity( + IHooks(hooksContractCache.getValue()).onAfterAddLiquidity( router, amountsInScaled18, bptAmountOut, @@ -460,14 +464,14 @@ library PoolConfigLib { address router, RemoveLiquidityParams memory params, PoolData memory poolData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal returns (bool) { if (config.shouldCallBeforeRemoveLiquidity() == false) { return false; } if ( - hooksContract.onBeforeRemoveLiquidity( + IHooks(hooksContractCache.getValue()).onBeforeRemoveLiquidity( router, params.kind, params.maxBptAmountIn, @@ -498,14 +502,14 @@ library PoolConfigLib { address router, RemoveLiquidityParams memory params, PoolData memory poolData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal { if (config.shouldCallAfterRemoveLiquidity() == false) { return; } if ( - hooksContract.onAfterRemoveLiquidity( + IHooks(hooksContractCache.getValue()).onAfterRemoveLiquidity( router, bptAmountIn, amountsOutScaled18, @@ -530,13 +534,13 @@ library PoolConfigLib { PoolConfigBits config, uint256[] memory exactAmountsInScaled18, bytes memory userData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal returns (bool) { if (config.shouldCallBeforeInitialize() == false) { return false; } - if (hooksContract.onBeforeInitialize(exactAmountsInScaled18, userData) == false) { + if (IHooks(hooksContractCache.getValue()).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { revert IVaultErrors.BeforeInitializeHookFailed(); } return true; @@ -556,16 +560,20 @@ library PoolConfigLib { uint256[] memory exactAmountsInScaled18, uint256 bptAmountOut, bytes memory userData, - IHooks hooksContract + Cache.AddressCache memory hooksContractCache ) internal { if (config.shouldCallAfterInitialize() == false) { return; } - if (hooksContract.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false) { + if ( + IHooks(hooksContractCache.getValue()).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == + false + ) { revert IVaultErrors.AfterInitializeHookFailed(); } } + // #endregion // #region Bit offsets for uint values @@ -670,6 +678,7 @@ library PoolConfigLib { PoolConfigBits.unwrap(config).insertUint(value, PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH) ); } + // #endregion // Convert from an array of decimal differences, to the encoded 24 bit value (only uses bottom 20 bits). diff --git a/pkg/vault/contracts/test/VaultMock.sol b/pkg/vault/contracts/test/VaultMock.sol index b66ab26a3..4a8755cb4 100644 --- a/pkg/vault/contracts/test/VaultMock.sol +++ b/pkg/vault/contracts/test/VaultMock.sol @@ -27,7 +27,6 @@ import { InputHelpersMock } from "@balancer-labs/v3-solidity-utils/contracts/tes import { VaultStateLib, VaultStateBits, VaultStateBits } from "../lib/VaultStateLib.sol"; import { PoolConfigBits, PoolConfigLib } from "../lib/PoolConfigLib.sol"; -import { HooksConfigLib, HooksConfigBits } from "../lib/HooksConfigLib.sol"; import { PoolFactoryMock } from "./PoolFactoryMock.sol"; import { Vault } from "../Vault.sol"; import { VaultExtension } from "../VaultExtension.sol"; @@ -220,7 +219,7 @@ contract VaultMock is IVaultMainMock, Vault { poolConfigBits = poolConfigBits.setShouldCallAfterRemoveLiquidity(hooksConfig.shouldCallAfterRemoveLiquidity); _poolConfigBits[pool] = poolConfigBits; - _hooksContracts[pool] = IHooks(hooksConfig.hooksContract); + _hooksContracts[pool] = StorageSlot.AddressSlot({ value: hooksConfig.hooksContract }); } function manualSetPoolConfigBits(address pool, PoolConfigBits config) public { From 2c3c9e18b88e27e17d1a4c7db1a7f7aa5d2d74ab Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 15:16:16 +0200 Subject: [PATCH 04/18] add tests for PoolConfigLib --- pkg/vault/contracts/lib/PoolConfigLib.sol | 4 +- .../test/foundry/unit/HooksConfigLib.t.sol | 221 ------ .../test/foundry/unit/PoolConfigLib.t.sol | 629 +++++++++++++----- 3 files changed, 472 insertions(+), 382 deletions(-) delete mode 100644 pkg/vault/test/foundry/unit/HooksConfigLib.t.sol diff --git a/pkg/vault/contracts/lib/PoolConfigLib.sol b/pkg/vault/contracts/lib/PoolConfigLib.sol index 4f4a024f9..45d14309a 100644 --- a/pkg/vault/contracts/lib/PoolConfigLib.sol +++ b/pkg/vault/contracts/lib/PoolConfigLib.sol @@ -18,7 +18,7 @@ library PoolConfigLib { error InvalidSize(uint256 currentValue, uint256 expectedSize); - // Bit offsets for pool config + // Bit offsets for main pool config settings uint8 public constant POOL_REGISTERED_OFFSET = 0; uint8 public constant POOL_INITIALIZED_OFFSET = POOL_REGISTERED_OFFSET + 1; uint8 public constant POOL_PAUSED_OFFSET = POOL_INITIALIZED_OFFSET + 1; @@ -55,7 +55,7 @@ library PoolConfigLib { uint8 private constant _TIMESTAMP_BITLENGTH = 32; - // #region Bit offsets for pool config + // #region Bit offsets for main pool config settings function isPoolRegistered(PoolConfigBits config) internal pure returns (bool) { return PoolConfigBits.unwrap(config).decodeBool(POOL_REGISTERED_OFFSET); } diff --git a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol deleted file mode 100644 index 2ddac1704..000000000 --- a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol +++ /dev/null @@ -1,221 +0,0 @@ -// // SPDX-License-Identifier: GPL-3.0-or-later - -// pragma solidity ^0.8.24; - -// import "forge-std/Test.sol"; - -// import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; -// import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; -// import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; -// import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; - -// import { HooksConfigLib, HooksConfigBits } from "@balancer-labs/v3-vault/contracts/lib/HooksConfigLib.sol"; - -// import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -// import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/ArrayHelpers.sol"; - -// import { BaseBitsConfigTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseBitsConfigTest.sol"; - -// contract HooksConfigLibTest is BaseBitsConfigTest { -// using WordCodec for bytes32; -// using ArrayHelpers for *; - -// uint256 public constant ADDRESS_BITLENGTH = 160; - -// uint256 public staticSwapFeePercentage = 5e18; -// address public hooksContract = address(0x05); - -// function testOffsets() public { -// _checkBitsUsedOnce(HooksConfigLib.BEFORE_INITIALIZE_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.AFTER_INITIALIZE_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.DYNAMIC_SWAP_FEE_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.BEFORE_SWAP_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.AFTER_SWAP_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.AFTER_ADD_LIQUIDITY_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET); -// _checkBitsUsedOnce(HooksConfigLib.HOOKS_CONTRACT_OFFSET, ADDRESS_BITLENGTH); -// } - -// function testZeroConfigBytes() public { -// HooksConfigBits config; - -// assertFalse(config.shouldCallBeforeInitialize(), "shouldCallBeforeInitialize should be false"); -// assertFalse(config.shouldCallAfterInitialize(), "shouldCallAfterInitialize should be false"); -// assertFalse(config.shouldCallComputeDynamicSwapFee(), "shouldCallComputeDynamicSwapFee should be false"); -// assertFalse(config.shouldCallBeforeSwap(), "shouldCallBeforeSwap should be false"); -// assertFalse(config.shouldCallAfterSwap(), "shouldCallAfterSwap should be false"); -// assertFalse(config.shouldCallBeforeAddLiquidity(), "shouldCallBeforeAddLiquidity should be false"); -// assertFalse(config.shouldCallAfterAddLiquidity(), "shouldCallAfterAddLiquidity should be false"); -// assertFalse(config.shouldCallBeforeRemoveLiquidity(), "shouldCallBeforeRemoveLiquidity should be false"); -// assertFalse(config.shouldCallAfterRemoveLiquidity(), "shouldCallAfterRemoveLiquidity should be false"); -// assertEq(config.getHooksContract(), address(0), "getHooksContract should be address(0)"); -// } - -// // #region test setters and getters -// function testShouldCallBeforeInitialize() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_INITIALIZE_OFFSET) -// ); -// assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (getter)"); -// } - -// function testSetShouldCallBeforeInitialize() public { -// HooksConfigBits config; -// config = config.setShouldCallBeforeInitialize(true); -// assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); -// } - -// function testShouldCallAfterInitialize() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_INITIALIZE_OFFSET) -// ); -// assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (getter)"); -// } - -// function testSetShouldCallAfterInitialize() public { -// HooksConfigBits config; -// config = config.setShouldCallAfterInitialize(true); -// assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (setter)"); -// } - -// function testShouldCallComputeDynamicSwapFee() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.DYNAMIC_SWAP_FEE_OFFSET) -// ); -// assertEq( -// config.shouldCallComputeDynamicSwapFee(), -// true, -// "shouldCallComputeDynamicSwapFee should be true (getter)" -// ); -// } - -// function testSetShouldCallComputeDynamicSwapFee() public { -// HooksConfigBits config; -// config = config.setShouldCallComputeDynamicSwapFee(true); -// assertEq( -// config.shouldCallComputeDynamicSwapFee(), -// true, -// "shouldCallComputeDynamicSwapFee should be true (setter)" -// ); -// } - -// function testShouldCallBeforeSwap() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_SWAP_OFFSET) -// ); -// assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (getter)"); -// } - -// function testSetShouldCallBeforeSwap() public { -// HooksConfigBits config; -// config = config.setShouldCallBeforeSwap(true); -// assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (setter)"); -// } - -// function testShouldCallAfterSwap() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_SWAP_OFFSET) -// ); -// assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (getter)"); -// } - -// function testSetShouldCallAfterSwap() public { -// HooksConfigBits config; -// config = config.setShouldCallAfterSwap(true); -// assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (setter)"); -// } - -// function testShouldCallBeforeAddLiquidity() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET) -// ); -// assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (getter)"); -// } - -// function testSetShouldCallBeforeAddLiquidity() public { -// HooksConfigBits config; -// config = config.setShouldCallBeforeAddLiquidity(true); -// assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (setter)"); -// } - -// function testShouldCallAfterAddLiquidity() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_ADD_LIQUIDITY_OFFSET) -// ); -// assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (getter)"); -// } - -// function testSetShouldCallAfterAddLiquidity() public { -// HooksConfigBits config; -// config = config.setShouldCallAfterAddLiquidity(true); -// assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (setter)"); -// } - -// function testShouldCallBeforeRemoveLiquidity() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET) -// ); -// assertEq( -// config.shouldCallBeforeRemoveLiquidity(), -// true, -// "shouldCallBeforeRemoveLiquidity should be true (getter)" -// ); -// } - -// function testSetShouldCallBeforeRemoveLiquidity() public { -// HooksConfigBits config; -// config = config.setShouldCallBeforeRemoveLiquidity(true); -// assertEq( -// config.shouldCallBeforeRemoveLiquidity(), -// true, -// "shouldCallBeforeRemoveLiquidity should be true (setter)" -// ); -// } - -// function testShouldCallAfterRemoveLiquidity() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertBool(true, HooksConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET) -// ); -// assertEq( -// config.shouldCallAfterRemoveLiquidity(), -// true, -// "shouldCallAfterRemoveLiquidity should be true (getter)" -// ); -// } - -// function testSetShouldCallAfterRemoveLiquidity() public { -// HooksConfigBits config; -// config = config.setShouldCallAfterRemoveLiquidity(true); -// assertEq( -// config.shouldCallAfterRemoveLiquidity(), -// true, -// "shouldCallAfterRemoveLiquidity should be true (setter)" -// ); -// } - -// function testGetHooksContract() public { -// HooksConfigBits config; -// config = HooksConfigBits.wrap( -// HooksConfigBits.unwrap(config).insertAddress(hooksContract, HooksConfigLib.HOOKS_CONTRACT_OFFSET) -// ); -// assertEq(config.getHooksContract(), hooksContract, "getHooksContract should be hooksContract (getter)"); -// } - -// function testSetHooksContract() public { -// HooksConfigBits config; -// config = config.setHooksContract(hooksContract); -// assertEq(config.getHooksContract(), hooksContract, "getHooksContract should be hooksContract (setter)"); -// } -// // #endregion -// } diff --git a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol index 186971056..f073d4228 100644 --- a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol @@ -5,33 +5,15 @@ pragma solidity ^0.8.24; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { PoolConfigLib } from "@balancer-labs/v3-vault/contracts/lib/PoolConfigLib.sol"; import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; -library PoolConfigLib { +import { BaseBitsConfigTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseBitsConfigTest.sol"; + +contract PoolConfigLibTest is BaseBitsConfigTest { using WordCodec for bytes32; using PoolConfigLib for PoolConfigBits; - error InvalidSize(uint256 currentValue, uint256 expectedSize); - - // Bit offsets for pool config - uint8 public constant POOL_REGISTERED_OFFSET = 0; - uint8 public constant POOL_INITIALIZED_OFFSET = POOL_REGISTERED_OFFSET + 1; - uint8 public constant POOL_PAUSED_OFFSET = POOL_INITIALIZED_OFFSET + 1; - uint8 public constant POOL_RECOVERY_MODE_OFFSET = POOL_PAUSED_OFFSET + 1; - - // Supported liquidity API bit offsets - uint8 public constant UNBALANCED_LIQUIDITY_OFFSET = POOL_RECOVERY_MODE_OFFSET + 1; - uint8 public constant ADD_LIQUIDITY_CUSTOM_OFFSET = UNBALANCED_LIQUIDITY_OFFSET + 1; - uint8 public constant REMOVE_LIQUIDITY_CUSTOM_OFFSET = ADD_LIQUIDITY_CUSTOM_OFFSET + 1; - - uint8 public constant STATIC_SWAP_FEE_OFFSET = REMOVE_LIQUIDITY_CUSTOM_OFFSET + 1; - uint256 public constant AGGREGATE_SWAP_FEE_OFFSET = STATIC_SWAP_FEE_OFFSET + FEE_BITLENGTH; - uint256 public constant AGGREGATE_YIELD_FEE_OFFSET = AGGREGATE_SWAP_FEE_OFFSET + FEE_BITLENGTH; - uint256 public constant DECIMAL_SCALING_FACTORS_OFFSET = AGGREGATE_YIELD_FEE_OFFSET + FEE_BITLENGTH; - uint256 public constant PAUSE_WINDOW_END_TIME_OFFSET = - DECIMAL_SCALING_FACTORS_OFFSET + _TOKEN_DECIMAL_DIFFS_BITLENGTH; - // Uses a uint24 (3 bytes): least significant 20 bits to store the values, and a 4-bit pad. // This maximum token count is also hard-coded in the Vault. uint8 private constant _TOKEN_DECIMAL_DIFFS_BITLENGTH = 24; @@ -39,210 +21,539 @@ library PoolConfigLib { uint8 private constant _TIMESTAMP_BITLENGTH = 32; - function isPoolRegistered(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_REGISTERED_OFFSET); + uint256 private constant _MAX_UINT32_VALUE = type(uint32).max; + uint256 private constant _MAX_UINT24_VALUE = type(uint24).max; + + function testOffsets() public { + _checkBitsUsedOnce(PoolConfigLib.POOL_REGISTERED_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.POOL_INITIALIZED_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.POOL_PAUSED_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.POOL_RECOVERY_MODE_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.BEFORE_INITIALIZE_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.AFTER_INITIALIZE_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.DYNAMIC_SWAP_FEE_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.BEFORE_SWAP_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.AFTER_SWAP_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.AFTER_ADD_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH); + _checkBitsUsedOnce(PoolConfigLib.AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH); + _checkBitsUsedOnce(PoolConfigLib.AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH); + _checkBitsUsedOnce(PoolConfigLib.DECIMAL_SCALING_FACTORS_OFFSET, _TOKEN_DECIMAL_DIFFS_BITLENGTH); + _checkBitsUsedOnce(PoolConfigLib.PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH); + } + + function testZeroConfigBytes() public { + PoolConfigBits config; + + assertEq(config.isPoolRegistered(), false, "isPoolRegistered mismatch (zero config)"); + assertEq(config.isPoolInitialized(), false, "isPoolInitialized mismatch (zero config)"); + assertEq(config.isPoolPaused(), false, "isPoolPaused mismatch (zero config)"); + assertEq(config.isPoolInRecoveryMode(), false, "isPoolInRecoveryMode mismatch (zero config)"); + assertEq(config.supportsUnbalancedLiquidity(), true, "supportsUnbalancedLiquidity mismatch (zero config)"); + assertEq(config.supportsAddLiquidityCustom(), false, "supportsAddLiquidityCustom mismatch (zero config)"); + assertEq(config.supportsRemoveLiquidityCustom(), false, "supportsRemoveLiquidityCustom mismatch (zero config)"); + assertEq(config.shouldCallBeforeInitialize(), false, "shouldCallBeforeInitialize mismatch (zero config)"); + assertEq(config.shouldCallAfterInitialize(), false, "shouldCallAfterInitialize mismatch (zero config)"); + assertEq( + config.shouldCallComputeDynamicSwapFee(), + false, + "shouldCallComputeDynamicSwapFee mismatch (zero config)" + ); + assertEq(config.shouldCallBeforeSwap(), false, "shouldCallBeforeSwap mismatch (zero config)"); + assertEq(config.shouldCallAfterSwap(), false, "shouldCallAfterSwap mismatch (zero config)"); + assertEq(config.shouldCallBeforeAddLiquidity(), false, "shouldCallBeforeAddLiquidity mismatch (zero config)"); + assertEq(config.shouldCallAfterAddLiquidity(), false, "shouldCallAfterAddLiquidity mismatch (zero config)"); + assertEq( + config.shouldCallBeforeRemoveLiquidity(), + false, + "shouldCallBeforeRemoveLiquidity mismatch (zero config)" + ); + assertEq( + config.shouldCallAfterRemoveLiquidity(), + false, + "shouldCallAfterRemoveLiquidity mismatch (zero config)" + ); + assertEq(config.getStaticSwapFeePercentage(), 0, "getStaticSwapFeePercentage mismatch (zero config)"); + assertEq(config.getAggregateSwapFeePercentage(), 0, "getAggregateSwapFeePercentage mismatch (zero config)"); + assertEq(config.getAggregateYieldFeePercentage(), 0, "getAggregateYieldFeePercentage mismatch (zero config)"); + assertEq(config.getTokenDecimalDiffs(), 0, "getTokenDecimalDiffs mismatch (zero config)"); + assertEq(config.getPauseWindowEndTime(), 0, "getPauseWindowEndTime mismatch (zero config)"); + } + + // #region Tests for main pool config settings + function testIsPoolRegistered() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_REGISTERED_OFFSET) + ); + assertTrue(configBits.isPoolRegistered(), "isPoolRegistered is false"); + } + + function testSetPoolRegistered() public { + PoolConfigBits configBits; + configBits.setPoolRegistered(true); + assertTrue(configBits.isPoolRegistered(), "isPoolRegistered is false"); + } + + function testIsPoolInitialized() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_INITIALIZED_OFFSET) + ); + assertTrue(configBits.isPoolInitialized(), "isPoolInitialized is false"); + } + + function testSetPoolInitialized() public { + PoolConfigBits configBits; + configBits.setPoolInitialized(true); + assertTrue(configBits.isPoolInitialized(), "isPoolInitialized is false"); + } + + function testIsPoolPaused() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_PAUSED_OFFSET) + ); + assertTrue(configBits.isPoolPaused(), "isPoolPaused is false"); + } + + function testSetPoolPaused() public { + PoolConfigBits configBits; + configBits.setPoolPaused(true); + assertTrue(configBits.isPoolPaused(), "isPoolPaused is false"); + } + + function testIsPoolInRecoveryMode() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_RECOVERY_MODE_OFFSET) + ); + assertTrue(configBits.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); + } + + function testSetPoolInRecoveryMode() public { + PoolConfigBits configBits; + configBits.setPoolInRecoveryMode(true); + assertTrue(configBits.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); + } + // #endregion + + // #region Tests for liquidity operations + function testSupportsUnbalancedLiquidity() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET) + ); + // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value + assertFalse(configBits.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); } - function setPoolRegistered(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_REGISTERED_OFFSET)); + function testSetDisableUnbalancedLiquidity() public { + PoolConfigBits configBits; + configBits.setDisableUnbalancedLiquidity(true); + // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value + assertFalse(configBits.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); } - function isPoolInitialized(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_INITIALIZED_OFFSET); + function testRequireUnbalancedLiquidityEnabled() public pure { + PoolConfigBits config; + + // It's enabled by default + config.requireUnbalancedLiquidityEnabled(); } - function setPoolInitialized(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_INITIALIZED_OFFSET)); + function testRequireUnbalancedLiquidityRevertIfIsDisabled() public { + PoolConfigBits config; + config.setDisableUnbalancedLiquidity(true); + + vm.expectRevert(IVaultErrors.DoesNotSupportUnbalancedLiquidity.selector); + config.requireUnbalancedLiquidityEnabled(); } - function isPoolInRecoveryMode(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_RECOVERY_MODE_OFFSET); + function testSupportsAddLiquidityCustom() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET) + ); + assertTrue(configBits.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); } - function setPoolInRecoveryMode(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_RECOVERY_MODE_OFFSET)); + function testSetAddLiquidityCustom() public { + PoolConfigBits configBits; + configBits.setAddLiquidityCustom(true); + assertTrue(configBits.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); } - function isPoolPaused(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_PAUSED_OFFSET); + function testRequireAddCustomLiquidityEnabled() public pure { + PoolConfigBits config; + config.setAddLiquidityCustom(true); + + config.requireAddCustomLiquidityEnabled(); } - function setPoolPaused(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_PAUSED_OFFSET)); + function testRequireAddCustomLiquidityRevertIfIsDisabled() public { + PoolConfigBits config; + + vm.expectRevert(IVaultErrors.DoesNotSupportAddLiquidityCustom.selector); + config.requireAddCustomLiquidityEnabled(); } - function getStaticSwapFeePercentage(PoolConfigBits config) internal pure returns (uint256) { - return PoolConfigBits.unwrap(config).decodeUint(STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; + function testSupportsRemoveLiquidityCustom() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET) + ); + assertTrue(configBits.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); } - function setStaticSwapFeePercentage(PoolConfigBits config, uint256 value) internal pure returns (PoolConfigBits) { - value /= FEE_SCALING_FACTOR; + function testSetRemoveLiquidityCustom() public { + PoolConfigBits configBits; + configBits.setRemoveLiquidityCustom(true); + assertTrue(configBits.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); + } - if (value > MAX_FEE_VALUE) { - revert InvalidSize(value, FEE_BITLENGTH); - } + function testRequireRemoveCustomLiquidityEnabled() public pure { + PoolConfigBits config; + config.setRemoveLiquidityCustom(true); - return - PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertUint(value, STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH)); + config.requireRemoveCustomLiquidityEnabled(); } - function getAggregateSwapFeePercentage(PoolConfigBits config) internal pure returns (uint256) { - return PoolConfigBits.unwrap(config).decodeUint(AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; + function testRequireRemoveCustomLiquidityReveryIfIsDisabled() public { + PoolConfigBits config; + + vm.expectRevert(IVaultErrors.DoesNotSupportRemoveLiquidityCustom.selector); + config.requireRemoveCustomLiquidityEnabled(); } - function setAggregateSwapFeePercentage( - PoolConfigBits config, - uint256 value - ) internal pure returns (PoolConfigBits) { - value /= FEE_SCALING_FACTOR; + // #endregion - if (value > MAX_FEE_VALUE) { - revert InvalidSize(value, FEE_BITLENGTH); - } + // #region Tests for hooks config + function testShouldCallBeforeInitialize() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_INITIALIZE_OFFSET) + ); + assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (getter)"); + } - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint(value, AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH) - ); + function testSetShouldCallBeforeInitialize() public { + PoolConfigBits config; + config = config.setShouldCallBeforeInitialize(true); + assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); } - function getAggregateYieldFeePercentage(PoolConfigBits config) internal pure returns (uint256) { - return PoolConfigBits.unwrap(config).decodeUint(AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; + function testShouldCallAfterInitialize() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_INITIALIZE_OFFSET) + ); + assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (getter)"); } - function setAggregateYieldFeePercentage( - PoolConfigBits config, - uint256 value - ) internal pure returns (PoolConfigBits) { - value /= FEE_SCALING_FACTOR; + function testSetShouldCallAfterInitialize() public { + PoolConfigBits config; + config = config.setShouldCallAfterInitialize(true); + assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (setter)"); + } - if (value > MAX_FEE_VALUE) { - revert InvalidSize(value, FEE_BITLENGTH); - } + function testShouldCallComputeDynamicSwapFee() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.DYNAMIC_SWAP_FEE_OFFSET) + ); + assertEq( + config.shouldCallComputeDynamicSwapFee(), + true, + "shouldCallComputeDynamicSwapFee should be true (getter)" + ); + } - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint(value, AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH) - ); + function testSetShouldCallComputeDynamicSwapFee() public { + PoolConfigBits config; + config = config.setShouldCallComputeDynamicSwapFee(true); + assertEq( + config.shouldCallComputeDynamicSwapFee(), + true, + "shouldCallComputeDynamicSwapFee should be true (setter)" + ); } - function getTokenDecimalDiffs(PoolConfigBits config) internal pure returns (uint24) { - return - uint24( - PoolConfigBits.unwrap(config).decodeUint(DECIMAL_SCALING_FACTORS_OFFSET, _TOKEN_DECIMAL_DIFFS_BITLENGTH) - ); + function testShouldCallBeforeSwap() public { + PoolConfigBits config; + config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_SWAP_OFFSET)); + assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (getter)"); } - function setTokenDecimalDiffs(PoolConfigBits config, uint24 value) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint( - value, - DECIMAL_SCALING_FACTORS_OFFSET, - _TOKEN_DECIMAL_DIFFS_BITLENGTH - ) - ); + function testSetShouldCallBeforeSwap() public { + PoolConfigBits config; + config = config.setShouldCallBeforeSwap(true); + assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (setter)"); } - function getPauseWindowEndTime(PoolConfigBits config) internal pure returns (uint32) { - return uint32(PoolConfigBits.unwrap(config).decodeUint(PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH)); + function testShouldCallAfterSwap() public { + PoolConfigBits config; + config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_SWAP_OFFSET)); + assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (getter)"); } - function setPauseWindowEndTime(PoolConfigBits config, uint32 value) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint(value, PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH) - ); + function testSetShouldCallAfterSwap() public { + PoolConfigBits config; + config = config.setShouldCallAfterSwap(true); + assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (setter)"); } - function supportsUnbalancedLiquidity(PoolConfigBits config) internal pure returns (bool) { - // NOTE: The unbalanced liquidity flag is default-on (false means it is supported). - // This function returns the inverted value. - return !PoolConfigBits.unwrap(config).decodeBool(UNBALANCED_LIQUIDITY_OFFSET); + function testShouldCallBeforeAddLiquidity() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET) + ); + assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (getter)"); } - function setDisableUnbalancedLiquidity( - PoolConfigBits config, - bool disableUnbalancedLiquidity - ) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(disableUnbalancedLiquidity, UNBALANCED_LIQUIDITY_OFFSET) - ); + function testSetShouldCallBeforeAddLiquidity() public { + PoolConfigBits config; + config = config.setShouldCallBeforeAddLiquidity(true); + assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (setter)"); } - function requireUnbalancedLiquidityEnabled(PoolConfigBits config) internal pure { - if (config.supportsUnbalancedLiquidity() == false) { - revert IVaultErrors.DoesNotSupportUnbalancedLiquidity(); - } + function testShouldCallAfterAddLiquidity() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_ADD_LIQUIDITY_OFFSET) + ); + assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (getter)"); } - function supportsAddLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(ADD_LIQUIDITY_CUSTOM_OFFSET); + function testSetShouldCallAfterAddLiquidity() public { + PoolConfigBits config; + config = config.setShouldCallAfterAddLiquidity(true); + assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (setter)"); } - function setAddLiquidityCustom( - PoolConfigBits config, - bool enableAddLiquidityCustom - ) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(enableAddLiquidityCustom, ADD_LIQUIDITY_CUSTOM_OFFSET) - ); + function testShouldCallBeforeRemoveLiquidity() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET) + ); + assertEq( + config.shouldCallBeforeRemoveLiquidity(), + true, + "shouldCallBeforeRemoveLiquidity should be true (getter)" + ); } - function requireAddCustomLiquidityEnabled(PoolConfigBits config) internal pure { - if (config.supportsAddLiquidityCustom() == false) { - revert IVaultErrors.DoesNotSupportAddLiquidityCustom(); - } + function testSetShouldCallBeforeRemoveLiquidity() public { + PoolConfigBits config; + config = config.setShouldCallBeforeRemoveLiquidity(true); + assertEq( + config.shouldCallBeforeRemoveLiquidity(), + true, + "shouldCallBeforeRemoveLiquidity should be true (setter)" + ); } - function supportsRemoveLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(REMOVE_LIQUIDITY_CUSTOM_OFFSET); + function testShouldCallAfterRemoveLiquidity() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET) + ); + assertEq( + config.shouldCallAfterRemoveLiquidity(), + true, + "shouldCallAfterRemoveLiquidity should be true (getter)" + ); } - function setRemoveLiquidityCustom( - PoolConfigBits config, - bool enableRemoveLiquidityCustom - ) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(enableRemoveLiquidityCustom, REMOVE_LIQUIDITY_CUSTOM_OFFSET) - ); + function testSetShouldCallAfterRemoveLiquidity() public { + PoolConfigBits config; + config = config.setShouldCallAfterRemoveLiquidity(true); + assertEq( + config.shouldCallAfterRemoveLiquidity(), + true, + "shouldCallAfterRemoveLiquidity should be true (setter)" + ); } - function requireRemoveCustomLiquidityEnabled(PoolConfigBits config) internal pure { - if (config.supportsRemoveLiquidityCustom() == false) { - revert IVaultErrors.DoesNotSupportRemoveLiquidityCustom(); - } + function testToHooksConfig() public { + address hooksContract = address(0x1234567890123456789012345678901234567890); + + PoolConfigBits config; + config = config.setShouldCallBeforeInitialize(true); + config = config.setShouldCallAfterInitialize(true); + config = config.setShouldCallComputeDynamicSwapFee(true); + config = config.setShouldCallBeforeSwap(true); + config = config.setShouldCallAfterSwap(true); + config = config.setShouldCallBeforeAddLiquidity(true); + config = config.setShouldCallAfterAddLiquidity(true); + config = config.setShouldCallBeforeRemoveLiquidity(true); + config = config.setShouldCallAfterRemoveLiquidity(true); + + HooksConfig memory hooksConfig = config.toHooksConfig(hooksContract); + assertEq(hooksConfig.shouldCallBeforeInitialize, true, "shouldCallBeforeInitialize mismatch"); + assertEq(hooksConfig.shouldCallAfterInitialize, true, "shouldCallAfterInitialize mismatch"); + assertEq(hooksConfig.shouldCallComputeDynamicSwapFee, true, "shouldCallComputeDynamicSwapFee mismatch"); + + assertEq(hooksConfig.shouldCallBeforeSwap, true, "shouldCallBeforeSwap mismatch"); + assertEq(hooksConfig.shouldCallAfterSwap, true, "shouldCallAfterSwap mismatch"); + assertEq(hooksConfig.shouldCallBeforeAddLiquidity, true, "shouldCallBeforeAddLiquidity mismatch"); + assertEq(hooksConfig.shouldCallAfterAddLiquidity, true, "shouldCallAfterAddLiquidity mismatch"); + assertEq(hooksConfig.shouldCallBeforeRemoveLiquidity, true, "shouldCallBeforeRemoveLiquidity mismatch"); + assertEq(hooksConfig.shouldCallAfterRemoveLiquidity, true, "shouldCallAfterRemoveLiquidity mismatch"); + assertEq(hooksConfig.hooksContract, hooksContract, "hooksContract mismatch"); + } + // #region + + // #region Tests for uint values + + function testGetAggregateSwapFeePercentage() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertUint( + _MAX_UINT24_VALUE, + PoolConfigLib.AGGREGATE_SWAP_FEE_OFFSET, + FEE_BITLENGTH + ) + ); + + assertEq( + configBits.getAggregateSwapFeePercentage(), + _MAX_UINT24_VALUE * FEE_SCALING_FACTOR, + "getAggregateSwapFeePercentage mismatch (testGetAggregateSwapFeePercentage)" + ); } - // Convert from an array of decimal differences, to the encoded 24 bit value (only uses bottom 20 bits). - function toTokenDecimalDiffs(uint8[] memory tokenDecimalDiffs) internal pure returns (uint24) { - bytes32 value; + function testSetAggregateSwapFeePercentage() public { + PoolConfigBits configBits; + uint256 value = _MAX_UINT24_VALUE * FEE_SCALING_FACTOR; + configBits.setAggregateSwapFeePercentage(value); + assertEq( + configBits.getAggregateSwapFeePercentage(), + value, + "getAggregateSwapFeePercentage mismatch (testSetAggregateSwapFeePercentage)" + ); + } - for (uint256 i = 0; i < tokenDecimalDiffs.length; ++i) { - value = value.insertUint(tokenDecimalDiffs[i], i * _DECIMAL_DIFF_BITLENGTH, _DECIMAL_DIFF_BITLENGTH); - } + function testGetAggregateYieldFeePercentage() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertUint( + _MAX_UINT24_VALUE, + PoolConfigLib.AGGREGATE_YIELD_FEE_OFFSET, + FEE_BITLENGTH + ) + ); + + assertEq( + configBits.getAggregateYieldFeePercentage(), + _MAX_UINT24_VALUE * FEE_SCALING_FACTOR, + "getAggregateYieldFeePercentage mismatch (testGetAggregateYieldFeePercentage)" + ); + } + + function testSetAggregateYieldFeePercentage() public { + PoolConfigBits configBits; + uint256 value = _MAX_UINT24_VALUE * FEE_SCALING_FACTOR; + configBits.setAggregateYieldFeePercentage(value); + assertEq( + configBits.getAggregateYieldFeePercentage(), + value, + "getAggregateYieldFeePercentage mismatch (testSetAggregateYieldFeePercentage)" + ); + } - return uint24(uint256(value)); + function testGetTokenDecimalDiffs() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertUint( + _MAX_UINT24_VALUE, + PoolConfigLib.DECIMAL_SCALING_FACTORS_OFFSET, + _TOKEN_DECIMAL_DIFFS_BITLENGTH + ) + ); + assertEq( + configBits.getTokenDecimalDiffs(), + _MAX_UINT24_VALUE, + "tokenDecimalDiffs mismatch (testGetTokenDecimalDiffs)" + ); } - function getDecimalScalingFactors( - PoolConfigBits config, - uint256 numTokens - ) internal pure returns (uint256[] memory) { - uint256[] memory scalingFactors = new uint256[](numTokens); + function testSetTokenDecimalDiffs() public { + PoolConfigBits configBits; + uint24 value = uint24(_MAX_UINT24_VALUE); + configBits.setTokenDecimalDiffs(value); + assertEq(configBits.getTokenDecimalDiffs(), value, "tokenDecimalDiffs mismatch (testSetTokenDecimalDiffs)"); + } + + function testGetDecimalScalingFactors() public { + PoolConfigBits config; + uint256 valueOne = 5; + uint256 valueTwo = 20; - bytes32 tokenDecimalDiffs = bytes32(uint256(config.getTokenDecimalDiffs())); + bytes32 value = bytes32(0); + value = value.insertUint(valueOne, 0, _DECIMAL_DIFF_BITLENGTH).insertUint( + valueTwo, + _DECIMAL_DIFF_BITLENGTH, + _DECIMAL_DIFF_BITLENGTH + ); - for (uint256 i = 0; i < numTokens; ++i) { - uint256 decimalDiff = tokenDecimalDiffs.decodeUint(i * _DECIMAL_DIFF_BITLENGTH, _DECIMAL_DIFF_BITLENGTH); + config.setTokenDecimalDiffs(uint24(uint256(value))); - // This is equivalent to `10**(18+decimalsDifference)` but this form optimizes for 18 decimal tokens. - scalingFactors[i] = FixedPoint.ONE * 10 ** decimalDiff; - } + uint256[] memory scalingFactors = config.getDecimalScalingFactors(2); - return scalingFactors; + assertEq(scalingFactors[0], 1e23, "scalingFactors[0] mismatch"); + assertEq(scalingFactors[1], 1e38, "scalingFactors[1] mismatch"); } + + function testGetPauseWindowEndTime() public { + PoolConfigBits configBits; + configBits = PoolConfigBits.wrap( + PoolConfigBits.unwrap(configBits).insertUint( + _MAX_UINT32_VALUE, + PoolConfigLib.PAUSE_WINDOW_END_TIME_OFFSET, + _TIMESTAMP_BITLENGTH + ) + ); + assertEq( + configBits.getPauseWindowEndTime(), + _MAX_UINT32_VALUE, + "pauseWindowEndTime mismatch (testGetPauseWindowEndTime)" + ); + } + + function testSetPauseWindowEndTime() public { + PoolConfigBits configBits; + uint32 value = uint32(_MAX_UINT32_VALUE); + configBits.setPauseWindowEndTime(value); + assertEq(configBits.getPauseWindowEndTime(), value, "pauseWindowEndTime mismatch (testSetPauseWindowEndTime)"); + } + // #endregion + + function testToTokenDecimalDiffs() public { + uint8[] memory tokenDecimalDiffs = new uint8[](2); + tokenDecimalDiffs[0] = 1; + tokenDecimalDiffs[1] = 2; + + uint256 value = uint256( + bytes32(0).insertUint(tokenDecimalDiffs[0], 0, _DECIMAL_DIFF_BITLENGTH).insertUint( + tokenDecimalDiffs[1], + _DECIMAL_DIFF_BITLENGTH, + _DECIMAL_DIFF_BITLENGTH + ) + ); + + assertEq( + PoolConfigLib.toTokenDecimalDiffs(tokenDecimalDiffs), + value, + "tokenDecimalDiffs mismatch (testToTokenDecimalDiffs)" + ); + } + + // #endregion } From a011e730bb06e37a3a2c29784d010e2457a23af7 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 15:28:38 +0200 Subject: [PATCH 05/18] small fixes for PoolConfigLibTest --- .../test/foundry/unit/PoolConfigLib.t.sol | 164 +++++++++--------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol index f073d4228..fddf56b8d 100644 --- a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol @@ -88,77 +88,77 @@ contract PoolConfigLibTest is BaseBitsConfigTest { // #region Tests for main pool config settings function testIsPoolRegistered() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_REGISTERED_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_REGISTERED_OFFSET) ); - assertTrue(configBits.isPoolRegistered(), "isPoolRegistered is false"); + assertTrue(config.isPoolRegistered(), "isPoolRegistered is false"); } function testSetPoolRegistered() public { - PoolConfigBits configBits; - configBits.setPoolRegistered(true); - assertTrue(configBits.isPoolRegistered(), "isPoolRegistered is false"); + PoolConfigBits config; + config = config.setPoolRegistered(true); + assertTrue(config.isPoolRegistered(), "isPoolRegistered is false"); } function testIsPoolInitialized() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_INITIALIZED_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_INITIALIZED_OFFSET) ); - assertTrue(configBits.isPoolInitialized(), "isPoolInitialized is false"); + assertTrue(config.isPoolInitialized(), "isPoolInitialized is false"); } function testSetPoolInitialized() public { - PoolConfigBits configBits; - configBits.setPoolInitialized(true); - assertTrue(configBits.isPoolInitialized(), "isPoolInitialized is false"); + PoolConfigBits config; + config = config.setPoolInitialized(true); + assertTrue(config.isPoolInitialized(), "isPoolInitialized is false"); } function testIsPoolPaused() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_PAUSED_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_PAUSED_OFFSET) ); - assertTrue(configBits.isPoolPaused(), "isPoolPaused is false"); + assertTrue(config.isPoolPaused(), "isPoolPaused is false"); } function testSetPoolPaused() public { - PoolConfigBits configBits; - configBits.setPoolPaused(true); - assertTrue(configBits.isPoolPaused(), "isPoolPaused is false"); + PoolConfigBits config; + config = config.setPoolPaused(true); + assertTrue(config.isPoolPaused(), "isPoolPaused is false"); } function testIsPoolInRecoveryMode() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.POOL_RECOVERY_MODE_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_RECOVERY_MODE_OFFSET) ); - assertTrue(configBits.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); + assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); } function testSetPoolInRecoveryMode() public { - PoolConfigBits configBits; - configBits.setPoolInRecoveryMode(true); - assertTrue(configBits.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); + PoolConfigBits config; + config = config.setPoolInRecoveryMode(true); + assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); } // #endregion // #region Tests for liquidity operations function testSupportsUnbalancedLiquidity() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET) ); // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value - assertFalse(configBits.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); + assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); } function testSetDisableUnbalancedLiquidity() public { - PoolConfigBits configBits; - configBits.setDisableUnbalancedLiquidity(true); + PoolConfigBits config; + config = config.setDisableUnbalancedLiquidity(true); // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value - assertFalse(configBits.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); + assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); } function testRequireUnbalancedLiquidityEnabled() public pure { @@ -170,29 +170,29 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testRequireUnbalancedLiquidityRevertIfIsDisabled() public { PoolConfigBits config; - config.setDisableUnbalancedLiquidity(true); + config = config.setDisableUnbalancedLiquidity(true); vm.expectRevert(IVaultErrors.DoesNotSupportUnbalancedLiquidity.selector); config.requireUnbalancedLiquidityEnabled(); } function testSupportsAddLiquidityCustom() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET) ); - assertTrue(configBits.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); + assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); } function testSetAddLiquidityCustom() public { - PoolConfigBits configBits; - configBits.setAddLiquidityCustom(true); - assertTrue(configBits.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); + PoolConfigBits config; + config = config.setAddLiquidityCustom(true); + assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); } function testRequireAddCustomLiquidityEnabled() public pure { PoolConfigBits config; - config.setAddLiquidityCustom(true); + config = config.setAddLiquidityCustom(true); config.requireAddCustomLiquidityEnabled(); } @@ -205,22 +205,22 @@ contract PoolConfigLibTest is BaseBitsConfigTest { } function testSupportsRemoveLiquidityCustom() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertBool(true, PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET) + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET) ); - assertTrue(configBits.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); + assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); } function testSetRemoveLiquidityCustom() public { - PoolConfigBits configBits; - configBits.setRemoveLiquidityCustom(true); - assertTrue(configBits.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); + PoolConfigBits config; + config = config.setRemoveLiquidityCustom(true); + assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); } function testRequireRemoveCustomLiquidityEnabled() public pure { PoolConfigBits config; - config.setRemoveLiquidityCustom(true); + config = config.setRemoveLiquidityCustom(true); config.requireRemoveCustomLiquidityEnabled(); } @@ -245,7 +245,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testSetShouldCallBeforeInitialize() public { PoolConfigBits config; - config = config.setShouldCallBeforeInitialize(true); + config = config = config.setShouldCallBeforeInitialize(true); assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); } @@ -413,9 +413,9 @@ contract PoolConfigLibTest is BaseBitsConfigTest { // #region Tests for uint values function testGetAggregateSwapFeePercentage() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertUint( + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertUint( _MAX_UINT24_VALUE, PoolConfigLib.AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH @@ -423,27 +423,27 @@ contract PoolConfigLibTest is BaseBitsConfigTest { ); assertEq( - configBits.getAggregateSwapFeePercentage(), + config.getAggregateSwapFeePercentage(), _MAX_UINT24_VALUE * FEE_SCALING_FACTOR, "getAggregateSwapFeePercentage mismatch (testGetAggregateSwapFeePercentage)" ); } function testSetAggregateSwapFeePercentage() public { - PoolConfigBits configBits; + PoolConfigBits config; uint256 value = _MAX_UINT24_VALUE * FEE_SCALING_FACTOR; - configBits.setAggregateSwapFeePercentage(value); + config = config.setAggregateSwapFeePercentage(value); assertEq( - configBits.getAggregateSwapFeePercentage(), + config.getAggregateSwapFeePercentage(), value, "getAggregateSwapFeePercentage mismatch (testSetAggregateSwapFeePercentage)" ); } function testGetAggregateYieldFeePercentage() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertUint( + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertUint( _MAX_UINT24_VALUE, PoolConfigLib.AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH @@ -451,44 +451,44 @@ contract PoolConfigLibTest is BaseBitsConfigTest { ); assertEq( - configBits.getAggregateYieldFeePercentage(), + config.getAggregateYieldFeePercentage(), _MAX_UINT24_VALUE * FEE_SCALING_FACTOR, "getAggregateYieldFeePercentage mismatch (testGetAggregateYieldFeePercentage)" ); } function testSetAggregateYieldFeePercentage() public { - PoolConfigBits configBits; + PoolConfigBits config; uint256 value = _MAX_UINT24_VALUE * FEE_SCALING_FACTOR; - configBits.setAggregateYieldFeePercentage(value); + config = config.setAggregateYieldFeePercentage(value); assertEq( - configBits.getAggregateYieldFeePercentage(), + config.getAggregateYieldFeePercentage(), value, "getAggregateYieldFeePercentage mismatch (testSetAggregateYieldFeePercentage)" ); } function testGetTokenDecimalDiffs() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertUint( + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertUint( _MAX_UINT24_VALUE, PoolConfigLib.DECIMAL_SCALING_FACTORS_OFFSET, _TOKEN_DECIMAL_DIFFS_BITLENGTH ) ); assertEq( - configBits.getTokenDecimalDiffs(), + config.getTokenDecimalDiffs(), _MAX_UINT24_VALUE, "tokenDecimalDiffs mismatch (testGetTokenDecimalDiffs)" ); } function testSetTokenDecimalDiffs() public { - PoolConfigBits configBits; + PoolConfigBits config; uint24 value = uint24(_MAX_UINT24_VALUE); - configBits.setTokenDecimalDiffs(value); - assertEq(configBits.getTokenDecimalDiffs(), value, "tokenDecimalDiffs mismatch (testSetTokenDecimalDiffs)"); + config = config.setTokenDecimalDiffs(value); + assertEq(config.getTokenDecimalDiffs(), value, "tokenDecimalDiffs mismatch (testSetTokenDecimalDiffs)"); } function testGetDecimalScalingFactors() public { @@ -503,7 +503,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { _DECIMAL_DIFF_BITLENGTH ); - config.setTokenDecimalDiffs(uint24(uint256(value))); + config = config.setTokenDecimalDiffs(uint24(uint256(value))); uint256[] memory scalingFactors = config.getDecimalScalingFactors(2); @@ -512,26 +512,26 @@ contract PoolConfigLibTest is BaseBitsConfigTest { } function testGetPauseWindowEndTime() public { - PoolConfigBits configBits; - configBits = PoolConfigBits.wrap( - PoolConfigBits.unwrap(configBits).insertUint( + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertUint( _MAX_UINT32_VALUE, PoolConfigLib.PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH ) ); assertEq( - configBits.getPauseWindowEndTime(), + config.getPauseWindowEndTime(), _MAX_UINT32_VALUE, "pauseWindowEndTime mismatch (testGetPauseWindowEndTime)" ); } function testSetPauseWindowEndTime() public { - PoolConfigBits configBits; + PoolConfigBits config; uint32 value = uint32(_MAX_UINT32_VALUE); - configBits.setPauseWindowEndTime(value); - assertEq(configBits.getPauseWindowEndTime(), value, "pauseWindowEndTime mismatch (testSetPauseWindowEndTime)"); + config = config.setPauseWindowEndTime(value); + assertEq(config.getPauseWindowEndTime(), value, "pauseWindowEndTime mismatch (testSetPauseWindowEndTime)"); } // #endregion From 05fb262124e8ee1f20aa15ce71253b6b0521e92b Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 16:24:29 +0200 Subject: [PATCH 06/18] add description --- pkg/solidity-utils/contracts/helpers/Cache.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/solidity-utils/contracts/helpers/Cache.sol b/pkg/solidity-utils/contracts/helpers/Cache.sol index d9e461b5d..baca975ba 100644 --- a/pkg/solidity-utils/contracts/helpers/Cache.sol +++ b/pkg/solidity-utils/contracts/helpers/Cache.sol @@ -6,12 +6,19 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; +/** + * @dev This library allows to cache the storage value in memory to avoid multiple SLOADs. + */ library Cache { struct AddressCache { address value; bytes32 slot; } + /** + * @dev This function initializes the cache for address values. + * We use AddressSlot to get the storage slot value using assembly. + */ function initAddressCache( StorageSlot.AddressSlot storage addressSlot ) internal pure returns (AddressCache memory cache) { From c988f11dd0254c062979272ab731722f15eb8c46 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 17:32:49 +0200 Subject: [PATCH 07/18] fix after merge --- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- .../[WeightedPool] initialize with ETH | 2 +- .../[WeightedPool] initialize without ETH | 2 +- pkg/vault/contracts/Vault.sol | 2 +- pkg/vault/contracts/VaultExtension.sol | 11 +- pkg/vault/contracts/lib/HooksConfigLib.sol | 526 ------------------ pkg/vault/contracts/test/VaultMock.sol | 1 + .../test/foundry/HookAdjustedLiquidity.t.sol | 4 +- .../test/foundry/unit/PoolConfigLib.t.sol | 44 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- .../[PoolMock] initialize with ETH | 2 +- .../[PoolMock] initialize without ETH | 2 +- 18 files changed, 55 insertions(+), 557 deletions(-) delete mode 100644 pkg/vault/contracts/lib/HooksConfigLib.sol diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots index e32efaff2..a9c2aa6f0 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -175.7k \ No newline at end of file +175.8k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots index 885608b62..d95dc47f4 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -159.2k \ No newline at end of file +159.3k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots index 33862ebdb..f3b28ae4f 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -209.0k \ No newline at end of file +209.1k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots index fd76c368e..6cd0e7681 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -175.4k \ No newline at end of file +175.5k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH index 3996b04da..eeb7a6b86 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH @@ -1 +1 @@ -363.9k \ No newline at end of file +362.0k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH index 165c5a4e3..9d4d6c831 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH @@ -1 +1 @@ -337.9k \ No newline at end of file +336.0k \ No newline at end of file diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index 0236771af..7415caf2e 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -523,8 +523,8 @@ contract Vault is IVaultMain, VaultCommon, Proxy { Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); if ( poolData.poolConfigBits.callBeforeAddLiquidityHook( - maxAmountsInScaled18, msg.sender, + maxAmountsInScaled18, params, poolData, hooksContractCache diff --git a/pkg/vault/contracts/VaultExtension.sol b/pkg/vault/contracts/VaultExtension.sol index ee7b1ad53..252b38c63 100644 --- a/pkg/vault/contracts/VaultExtension.sol +++ b/pkg/vault/contracts/VaultExtension.sol @@ -294,6 +294,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { revert HookRegistrationFailed(params.poolHooksContract, pool, msg.sender); } + poolConfigBits = poolConfigBits.setHookAdjustedAmounts(hookFlags.enableHookAdjustedAmounts); poolConfigBits = poolConfigBits.setShouldCallBeforeInitialize(hookFlags.shouldCallBeforeInitialize); poolConfigBits = poolConfigBits.setShouldCallAfterInitialize(hookFlags.shouldCallAfterInitialize); poolConfigBits = poolConfigBits.setShouldCallComputeDynamicSwapFee( @@ -386,7 +387,8 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[pool]); if ( - poolData.poolConfigBits.callBeforeInitialize(exactAmountsInScaled18, userData, hooksContractCache) == true + poolData.poolConfigBits.callBeforeInitializeHook(exactAmountsInScaled18, userData, hooksContractCache) == + true ) { // The before hook is reentrant, and could have changed token rates. // Updating balances here is unnecessary since they're 0, but we do not special case before init @@ -402,7 +404,12 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { bptAmountOut = _initialize(pool, to, poolData, tokens, exactAmountsIn, exactAmountsInScaled18, minBptAmountOut); - poolData.poolConfigBits.callAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData, hooksContractCache); + poolData.poolConfigBits.callAfterInitializeHook( + exactAmountsInScaled18, + bptAmountOut, + userData, + hooksContractCache + ); } function _initialize( diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol deleted file mode 100644 index 425cb58b1..000000000 --- a/pkg/vault/contracts/lib/HooksConfigLib.sol +++ /dev/null @@ -1,526 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.24; - -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 { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; - -// @notice Config type to store entire configuration of the hooks. -type HooksConfigBits is bytes32; - -using HooksConfigLib for HooksConfigBits global; - -library HooksConfigLib { - using WordCodec for bytes32; - - // Bit offsets for pool config - uint8 public constant BEFORE_INITIALIZE_OFFSET = 0; - uint8 public constant ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET = BEFORE_INITIALIZE_OFFSET + 1; - uint8 public constant AFTER_INITIALIZE_OFFSET = ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET + 1; - uint8 public constant DYNAMIC_SWAP_FEE_OFFSET = AFTER_INITIALIZE_OFFSET + 1; - uint8 public constant BEFORE_SWAP_OFFSET = DYNAMIC_SWAP_FEE_OFFSET + 1; - uint8 public constant AFTER_SWAP_OFFSET = BEFORE_SWAP_OFFSET + 1; - uint8 public constant BEFORE_ADD_LIQUIDITY_OFFSET = AFTER_SWAP_OFFSET + 1; - uint8 public constant AFTER_ADD_LIQUIDITY_OFFSET = BEFORE_ADD_LIQUIDITY_OFFSET + 1; - uint8 public constant BEFORE_REMOVE_LIQUIDITY_OFFSET = AFTER_ADD_LIQUIDITY_OFFSET + 1; - uint8 public constant AFTER_REMOVE_LIQUIDITY_OFFSET = BEFORE_REMOVE_LIQUIDITY_OFFSET + 1; - uint8 public constant HOOKS_CONTRACT_OFFSET = AFTER_REMOVE_LIQUIDITY_OFFSET + 1; - - function enableHookAdjustedAmounts(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET); - } - - function setHookAdjustedAmounts(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return - HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET)); - } - - function shouldCallBeforeInitialize(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_INITIALIZE_OFFSET); - } - - function setShouldCallBeforeInitialize(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_INITIALIZE_OFFSET)); - } - - function shouldCallAfterInitialize(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_INITIALIZE_OFFSET); - } - - function setShouldCallAfterInitialize(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_INITIALIZE_OFFSET)); - } - - function shouldCallComputeDynamicSwapFee(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(DYNAMIC_SWAP_FEE_OFFSET); - } - - function setShouldCallComputeDynamicSwapFee( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, DYNAMIC_SWAP_FEE_OFFSET)); - } - - function shouldCallBeforeSwap(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_SWAP_OFFSET); - } - - function setShouldCallBeforeSwap(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_SWAP_OFFSET)); - } - - function shouldCallAfterSwap(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_SWAP_OFFSET); - } - - function setShouldCallAfterSwap(HooksConfigBits config, bool value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_SWAP_OFFSET)); - } - - function shouldCallBeforeAddLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_ADD_LIQUIDITY_OFFSET); - } - - function setShouldCallBeforeAddLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_ADD_LIQUIDITY_OFFSET)); - } - - function shouldCallAfterAddLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_ADD_LIQUIDITY_OFFSET); - } - - function setShouldCallAfterAddLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_ADD_LIQUIDITY_OFFSET)); - } - - function shouldCallBeforeRemoveLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(BEFORE_REMOVE_LIQUIDITY_OFFSET); - } - - function setShouldCallBeforeRemoveLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, BEFORE_REMOVE_LIQUIDITY_OFFSET)); - } - - function shouldCallAfterRemoveLiquidity(HooksConfigBits config) internal pure returns (bool) { - return HooksConfigBits.unwrap(config).decodeBool(AFTER_REMOVE_LIQUIDITY_OFFSET); - } - - function setShouldCallAfterRemoveLiquidity( - HooksConfigBits config, - bool value - ) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertBool(value, AFTER_REMOVE_LIQUIDITY_OFFSET)); - } - - function getHooksContract(HooksConfigBits config) internal pure returns (address) { - return HooksConfigBits.unwrap(config).decodeAddress(HOOKS_CONTRACT_OFFSET); - } - - function setHooksContract(HooksConfigBits config, address value) internal pure returns (HooksConfigBits) { - return HooksConfigBits.wrap(HooksConfigBits.unwrap(config).insertAddress(value, HOOKS_CONTRACT_OFFSET)); - } - - function toHooksConfig(HooksConfigBits config) internal pure returns (HooksConfig memory) { - return - HooksConfig({ - enableHookAdjustedAmounts: config.enableHookAdjustedAmounts(), - shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), - shouldCallAfterInitialize: config.shouldCallAfterInitialize(), - shouldCallBeforeAddLiquidity: config.shouldCallBeforeAddLiquidity(), - shouldCallAfterAddLiquidity: config.shouldCallAfterAddLiquidity(), - shouldCallBeforeRemoveLiquidity: config.shouldCallBeforeRemoveLiquidity(), - shouldCallAfterRemoveLiquidity: config.shouldCallAfterRemoveLiquidity(), - shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), - shouldCallBeforeSwap: config.shouldCallBeforeSwap(), - shouldCallAfterSwap: config.shouldCallAfterSwap(), - hooksContract: config.getHooksContract() - }); - } - - /** - * @dev Check if dynamic swap fee hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param swapParams The swap parameters used to calculate the fee - * @param staticSwapFeePercentage Value of the static swap fee, for reference - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - * @return swapFeePercentage the calculated swap fee percentage. 0 if hook is disabled - */ - function callComputeDynamicSwapFeeHook( - HooksConfigBits config, - IBasePool.PoolSwapParams memory swapParams, - uint256 staticSwapFeePercentage - ) internal view returns (bool, uint256) { - if (config.shouldCallComputeDynamicSwapFee() == false) { - return (false, staticSwapFeePercentage); - } - - (bool success, uint256 swapFeePercentage) = IHooks(config.getHooksContract()).onComputeDynamicSwapFee( - swapParams, - staticSwapFeePercentage - ); - - if (success == false) { - revert IVaultErrors.DynamicSwapFeeHookFailed(); - } - return (success, swapFeePercentage); - } - - /** - * @dev Check if before swap hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param swapParams The swap parameters used in the hook - * @param pool Pool address - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeSwapHook( - HooksConfigBits config, - IBasePool.PoolSwapParams memory swapParams, - address pool - ) internal returns (bool) { - if (config.shouldCallBeforeSwap() == false) { - // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) - return false; - } - - if (IHooks(config.getHooksContract()).onBeforeSwap(swapParams, pool) == false) { - // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. - revert IVaultErrors.BeforeSwapHookFailed(); - } - return true; - } - - /** - * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param amountCalculatedScaled18 Token amount calculated by the swap - * @param amountCalculatedRaw Token amount calculated by the swap - * @param router Router address - * @param params The swap parameters - * @param state Temporary state used in swap operations - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountCalculatedRaw New amount calculated, potentially modified by the hook - */ - function callAfterSwapHook( - HooksConfigBits config, - uint256 amountCalculatedScaled18, - uint256 amountCalculatedRaw, - address router, - SwapParams memory params, - SwapState memory state, - PoolData memory poolData - ) internal returns (uint256) { - if (config.shouldCallAfterSwap() == false) { - // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not - // change amountCalculatedRaw (no deltas) - return amountCalculatedRaw; - } - - // Adjust balances for the AfterSwap hook. - (uint256 amountInScaled18, uint256 amountOutScaled18) = params.kind == SwapKind.EXACT_IN - ? (state.amountGivenScaled18, amountCalculatedScaled18) - : (amountCalculatedScaled18, state.amountGivenScaled18); - - (bool success, uint256 hookAdjustedAmountCalculatedRaw) = IHooks(config.getHooksContract()).onAfterSwap( - IHooks.AfterSwapParams({ - kind: params.kind, - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, - amountInScaled18: amountInScaled18, - amountOutScaled18: amountOutScaled18, - tokenInBalanceScaled18: poolData.balancesLiveScaled18[state.indexIn], - tokenOutBalanceScaled18: poolData.balancesLiveScaled18[state.indexOut], - amountCalculatedScaled18: amountCalculatedScaled18, - amountCalculatedRaw: amountCalculatedRaw, - router: router, - pool: params.pool, - userData: params.userData - }) - ); - - if (success == false) { - // Hook contract implements onAfterSwap, but it has failed, so reverts the transaction. - revert IVaultErrors.AfterSwapHookFailed(); - } - - // If hook adjusted amounts is not enabled, ignore amounts returned by the hook - if (config.enableHookAdjustedAmounts() == false) { - return amountCalculatedRaw; - } - - if ( - (params.kind == SwapKind.EXACT_IN && hookAdjustedAmountCalculatedRaw < params.limitRaw) || - (params.kind == SwapKind.EXACT_OUT && hookAdjustedAmountCalculatedRaw > params.limitRaw) - ) { - revert IVaultErrors.HookAdjustedSwapLimit(hookAdjustedAmountCalculatedRaw, params.limitRaw); - } - - return hookAdjustedAmountCalculatedRaw; - } - - /** - * @dev Check if before add liquidity hook should be called and call it. Throws an error if the hook contract fails - * to execute the hook. - * - * @param config The encoded hooks configuration - * @param router Router address - * @param maxAmountsInScaled18 An array with maximum amounts for each input token of the add liquidity operation - * @param params The add liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeAddLiquidityHook( - HooksConfigBits config, - address router, - uint256[] memory maxAmountsInScaled18, - AddLiquidityParams memory params, - PoolData memory poolData - ) internal returns (bool) { - if (config.shouldCallBeforeAddLiquidity() == false) { - return false; - } - - if ( - IHooks(config.getHooksContract()).onBeforeAddLiquidity( - router, - params.pool, - params.kind, - maxAmountsInScaled18, - params.minBptAmountOut, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.BeforeAddLiquidityHookFailed(); - } - return true; - } - - /** - * @dev Check if after add liquidity hook should be called and call it. Throws an error if the hook contract fails - * to execute the hook. - * - * @param config The encoded hooks configuration - * @param router Router address - * @param amountsInScaled18 An array with amounts for each input token of the add liquidity operation - * @param amountsInRaw An array with amounts for each input token of the add liquidity operation - * @param bptAmountOut The BPT amount a user will receive after add liquidity operation succeeds - * @param params The add liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountsInRaw New amountsInRaw, potentially modified by the hook - */ - function callAfterAddLiquidityHook( - HooksConfigBits config, - address router, - uint256[] memory amountsInScaled18, - uint256[] memory amountsInRaw, - uint256 bptAmountOut, - AddLiquidityParams memory params, - PoolData memory poolData - ) internal returns (uint256[] memory) { - if (config.shouldCallAfterAddLiquidity() == false) { - return amountsInRaw; - } - - (bool success, uint256[] memory hookAdjustedAmountsInRaw) = IHooks(config.getHooksContract()) - .onAfterAddLiquidity( - router, - params.pool, - params.kind, - amountsInScaled18, - amountsInRaw, - bptAmountOut, - poolData.balancesLiveScaled18, - params.userData - ); - - if (success == false) { - revert IVaultErrors.AfterAddLiquidityHookFailed(); - } - - // If hook adjusted amounts is not enabled, ignore amounts returned by the hook - if (config.enableHookAdjustedAmounts() == false) { - return amountsInRaw; - } - - for (uint256 i = 0; i < hookAdjustedAmountsInRaw.length; i++) { - if (hookAdjustedAmountsInRaw[i] > params.maxAmountsIn[i]) { - revert IVaultErrors.HookAdjustedAmountInAboveMax( - poolData.tokens[i], - hookAdjustedAmountsInRaw[i], - params.maxAmountsIn[i] - ); - } - } - - return hookAdjustedAmountsInRaw; - } - - /** - * @dev Check if before remove liquidity hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param minAmountsOutScaled18 An array with minimum amounts for each output token of the remove liquidity - * operation - * @param router Router address - * @param params The remove liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeRemoveLiquidityHook( - HooksConfigBits config, - uint256[] memory minAmountsOutScaled18, - address router, - RemoveLiquidityParams memory params, - PoolData memory poolData - ) internal returns (bool) { - if (config.shouldCallBeforeRemoveLiquidity() == false) { - return false; - } - - if ( - IHooks(config.getHooksContract()).onBeforeRemoveLiquidity( - router, - params.pool, - params.kind, - params.maxBptAmountIn, - minAmountsOutScaled18, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.BeforeRemoveLiquidityHookFailed(); - } - return true; - } - - /** - * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * @param config The encoded hooks configuration - * @param router Router address - * @param amountsOutScaled18 Scaled amount of tokens to receive, sorted in token registration order - * @param amountsOutRaw Actual amount of tokens to receive, sorted in token registration order - * @param bptAmountIn The BPT amount a user will need burn to remove the liquidity of the pool - * @param params The remove liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountsOutRaw New amountsOutRaw, potentially modified by the hook - */ - function callAfterRemoveLiquidityHook( - HooksConfigBits config, - address router, - uint256[] memory amountsOutScaled18, - uint256[] memory amountsOutRaw, - uint256 bptAmountIn, - RemoveLiquidityParams memory params, - PoolData memory poolData - ) internal returns (uint256[] memory) { - if (config.shouldCallAfterRemoveLiquidity() == false) { - return amountsOutRaw; - } - - (bool success, uint256[] memory hookAdjustedAmountsOutRaw) = IHooks(config.getHooksContract()) - .onAfterRemoveLiquidity( - router, - params.pool, - params.kind, - bptAmountIn, - amountsOutScaled18, - amountsOutRaw, - poolData.balancesLiveScaled18, - params.userData - ); - - if (success == false) { - revert IVaultErrors.AfterRemoveLiquidityHookFailed(); - } - - // If hook adjusted amounts is not enabled, ignore amounts returned by the hook - if (config.enableHookAdjustedAmounts() == false) { - return amountsOutRaw; - } - - for (uint256 i = 0; i < hookAdjustedAmountsOutRaw.length; i++) { - if (hookAdjustedAmountsOutRaw[i] < params.minAmountsOut[i]) { - revert IVaultErrors.HookAdjustedAmountOutBelowMin( - poolData.tokens[i], - hookAdjustedAmountsOutRaw[i], - params.minAmountsOut[i] - ); - } - } - - return hookAdjustedAmountsOutRaw; - } - - /** - * @dev Check if before initialization hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param exactAmountsInScaled18 An array with the initial liquidity of the pool - * @param userData Additional (optional) data required for adding initial liquidity - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeInitializeHook( - HooksConfigBits config, - uint256[] memory exactAmountsInScaled18, - bytes memory userData - ) internal returns (bool) { - if (config.shouldCallBeforeInitialize() == false) { - return false; - } - - if (IHooks(config.getHooksContract()).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { - revert IVaultErrors.BeforeInitializeHookFailed(); - } - return true; - } - - /** - * @dev Check if after initialization hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param exactAmountsInScaled18 An array with the initial liquidity of the pool - * @param bptAmountOut The BPT amount a user will receive after initialization operation succeeds - * @param userData Additional (optional) data required for adding initial liquidity - */ - function callAfterInitializeHook( - HooksConfigBits config, - uint256[] memory exactAmountsInScaled18, - uint256 bptAmountOut, - bytes memory userData - ) internal { - if (config.shouldCallAfterInitialize() == false) { - return; - } - - if ( - IHooks(config.getHooksContract()).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false - ) { - revert IVaultErrors.AfterInitializeHookFailed(); - } - } -} diff --git a/pkg/vault/contracts/test/VaultMock.sol b/pkg/vault/contracts/test/VaultMock.sol index ca43ecccc..5f1317787 100644 --- a/pkg/vault/contracts/test/VaultMock.sol +++ b/pkg/vault/contracts/test/VaultMock.sol @@ -208,6 +208,7 @@ contract VaultMock is IVaultMainMock, Vault { function manualSetHooksConfig(address pool, HooksConfig memory hooksConfig) public { PoolConfigBits poolConfigBits = _poolConfigBits[pool]; + poolConfigBits = poolConfigBits.setHookAdjustedAmounts(hooksConfig.enableHookAdjustedAmounts); poolConfigBits = poolConfigBits.setShouldCallBeforeInitialize(hooksConfig.shouldCallBeforeInitialize); poolConfigBits = poolConfigBits.setShouldCallAfterInitialize(hooksConfig.shouldCallAfterInitialize); poolConfigBits = poolConfigBits.setShouldCallComputeDynamicSwapFee(hooksConfig.shouldCallComputeDynamicSwapFee); diff --git a/pkg/vault/test/foundry/HookAdjustedLiquidity.t.sol b/pkg/vault/test/foundry/HookAdjustedLiquidity.t.sol index 3f5cfac84..6b392d107 100644 --- a/pkg/vault/test/foundry/HookAdjustedLiquidity.t.sol +++ b/pkg/vault/test/foundry/HookAdjustedLiquidity.t.sol @@ -205,7 +205,7 @@ contract HookAdjustedLiquidityTest is BaseVaultTest { function testHookFeeAddLiquidityIgnoreHookAdjusted() public { HooksConfig memory config = vault.getHooksConfig(pool); config.enableHookAdjustedAmounts = false; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); // 10% fee uint256 hookFeePercentage = 1e17; @@ -378,7 +378,7 @@ contract HookAdjustedLiquidityTest is BaseVaultTest { function testHookFeeRemoveLiquidityIgnoreHookAdjusted() public { HooksConfig memory config = vault.getHooksConfig(pool); config.enableHookAdjustedAmounts = false; - vault.setHooksConfig(pool, config); + vault.manualSetHooksConfig(pool, config); // Add liquidity so bob has BPT to remove liquidity vm.prank(bob); diff --git a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol index 935605428..f9cb9cb54 100644 --- a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol @@ -32,6 +32,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { _checkBitsUsedOnce(PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET); _checkBitsUsedOnce(PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET); _checkBitsUsedOnce(PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET); + _checkBitsUsedOnce(PoolConfigLib.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET); _checkBitsUsedOnce(PoolConfigLib.BEFORE_INITIALIZE_OFFSET); _checkBitsUsedOnce(PoolConfigLib.AFTER_INITIALIZE_OFFSET); _checkBitsUsedOnce(PoolConfigLib.DYNAMIC_SWAP_FEE_OFFSET); @@ -51,6 +52,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testZeroConfigBytes() public { PoolConfigBits config; + assertEq(config.enableHookAdjustedAmounts(), false, "enableHookAdjustedAmounts mismatch (zero config)"); assertEq(config.isPoolRegistered(), false, "isPoolRegistered mismatch (zero config)"); assertEq(config.isPoolInitialized(), false, "isPoolInitialized mismatch (zero config)"); assertEq(config.isPoolPaused(), false, "isPoolPaused mismatch (zero config)"); @@ -87,18 +89,32 @@ contract PoolConfigLibTest is BaseBitsConfigTest { } // #region Tests for main pool config settings + function testEnableHookAdjustedAmounts() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET) + ); + assertTrue(config.enableHookAdjustedAmounts(), "enableHookAdjustedAmounts is false (getter)"); + } + + function testSetHookAdjustedAmounts() public { + PoolConfigBits config; + config = config.setHookAdjustedAmounts(true); + assertTrue(config.enableHookAdjustedAmounts(), "enableHookAdjustedAmounts is false (setter)"); + } + function testIsPoolRegistered() public { PoolConfigBits config; config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_REGISTERED_OFFSET) ); - assertTrue(config.isPoolRegistered(), "isPoolRegistered is false"); + assertTrue(config.isPoolRegistered(), "isPoolRegistered is false (getter)"); } function testSetPoolRegistered() public { PoolConfigBits config; config = config.setPoolRegistered(true); - assertTrue(config.isPoolRegistered(), "isPoolRegistered is false"); + assertTrue(config.isPoolRegistered(), "isPoolRegistered is false (setter)"); } function testIsPoolInitialized() public { @@ -106,25 +122,25 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_INITIALIZED_OFFSET) ); - assertTrue(config.isPoolInitialized(), "isPoolInitialized is false"); + assertTrue(config.isPoolInitialized(), "isPoolInitialized is false (getter)"); } function testSetPoolInitialized() public { PoolConfigBits config; config = config.setPoolInitialized(true); - assertTrue(config.isPoolInitialized(), "isPoolInitialized is false"); + assertTrue(config.isPoolInitialized(), "isPoolInitialized is false (setter)"); } function testIsPoolPaused() public { PoolConfigBits config; config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_PAUSED_OFFSET)); - assertTrue(config.isPoolPaused(), "isPoolPaused is false"); + assertTrue(config.isPoolPaused(), "isPoolPaused is false (getter)"); } function testSetPoolPaused() public { PoolConfigBits config; config = config.setPoolPaused(true); - assertTrue(config.isPoolPaused(), "isPoolPaused is false"); + assertTrue(config.isPoolPaused(), "isPoolPaused is false (setter)"); } function testIsPoolInRecoveryMode() public { @@ -132,13 +148,13 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_RECOVERY_MODE_OFFSET) ); - assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); + assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false (getter)"); } function testSetPoolInRecoveryMode() public { PoolConfigBits config; config = config.setPoolInRecoveryMode(true); - assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false"); + assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false (setter)"); } // #endregion @@ -149,14 +165,14 @@ contract PoolConfigLibTest is BaseBitsConfigTest { PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET) ); // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value - assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); + assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true (getter)"); } function testSetDisableUnbalancedLiquidity() public { PoolConfigBits config; config = config.setDisableUnbalancedLiquidity(true); // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value - assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true"); + assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true (setter)"); } function testRequireUnbalancedLiquidityEnabled() public pure { @@ -179,13 +195,13 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET) ); - assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); + assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false (getter)"); } function testSetAddLiquidityCustom() public { PoolConfigBits config; config = config.setAddLiquidityCustom(true); - assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false"); + assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false (setter)"); } function testRequireAddCustomLiquidityEnabled() public pure { @@ -207,13 +223,13 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET) ); - assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); + assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false (getter)"); } function testSetRemoveLiquidityCustom() public { PoolConfigBits config; config = config.setRemoveLiquidityCustom(true); - assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false"); + assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false (setter)"); } function testRequireRemoveCustomLiquidityEnabled() public pure { diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots index a1a7d1552..a9c2aa6f0 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -177.7k \ No newline at end of file +175.8k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots index 900b9b575..885608b62 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -161.1k \ No newline at end of file +159.2k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots index 1eb803cca..f3b28ae4f 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -211.0k \ No newline at end of file +209.1k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots index 58b81b2c5..fd76c368e 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -177.3k \ No newline at end of file +175.4k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH index 62821d84b..9efc7a804 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH @@ -1 +1 @@ -358.6k \ No newline at end of file +356.8k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH index c99842c6d..2ba5a0218 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH @@ -1 +1 @@ -332.6k \ No newline at end of file +330.8k \ No newline at end of file From a983c4e6e90942bb0fcdda4b4a99bca1db2bccbf Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 17:43:46 +0200 Subject: [PATCH 08/18] fix codestyle --- pkg/solidity-utils/contracts/helpers/Cache.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/solidity-utils/contracts/helpers/Cache.sol b/pkg/solidity-utils/contracts/helpers/Cache.sol index baca975ba..259c87912 100644 --- a/pkg/solidity-utils/contracts/helpers/Cache.sol +++ b/pkg/solidity-utils/contracts/helpers/Cache.sol @@ -24,6 +24,7 @@ library Cache { ) internal pure returns (AddressCache memory cache) { bytes32 slot; + // solhint-disable-next-line no-inline-assembly assembly { slot := addressSlot.slot } @@ -36,6 +37,7 @@ library Cache { address _value; bytes32 slot = cache.slot; + // solhint-disable-next-line no-inline-assembly assembly { _value := sload(slot) } From e712f273ddc0d47a89a43331e7f93045177cb23d Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 17:46:21 +0200 Subject: [PATCH 09/18] fix codestyle --- pkg/vault/test/foundry/unit/PoolConfigLib.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol index f9cb9cb54..dd94e3463 100644 --- a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol @@ -156,6 +156,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = config.setPoolInRecoveryMode(true); assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false (setter)"); } + // #endregion // #region Tests for liquidity operations @@ -423,6 +424,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { assertEq(hooksConfig.shouldCallAfterRemoveLiquidity, true, "shouldCallAfterRemoveLiquidity mismatch"); assertEq(hooksConfig.hooksContract, hooksContract, "hooksContract mismatch"); } + // #region // #region Tests for uint values @@ -548,6 +550,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = config.setPauseWindowEndTime(value); assertEq(config.getPauseWindowEndTime(), value, "pauseWindowEndTime mismatch (testSetPauseWindowEndTime)"); } + // #endregion function testToTokenDecimalDiffs() public { From 37691f2e8576ac622d69d807ac39d4c951fc7770 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 17:52:37 +0200 Subject: [PATCH 10/18] fix codestyle --- pkg/solidity-utils/contracts/helpers/Cache.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/solidity-utils/contracts/helpers/Cache.sol b/pkg/solidity-utils/contracts/helpers/Cache.sol index 259c87912..ee9dec2d3 100644 --- a/pkg/solidity-utils/contracts/helpers/Cache.sol +++ b/pkg/solidity-utils/contracts/helpers/Cache.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; +import { StorageSlot } from "../contracts/openzeppelin/StorageSlot.sol"; /** * @dev This library allows to cache the storage value in memory to avoid multiple SLOADs. From e99fe4c3563fcda814a103804c2c5fd22d3c31a8 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 20 Jun 2024 17:57:34 +0200 Subject: [PATCH 11/18] fix imports --- pkg/solidity-utils/contracts/helpers/Cache.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/solidity-utils/contracts/helpers/Cache.sol b/pkg/solidity-utils/contracts/helpers/Cache.sol index ee9dec2d3..253271f51 100644 --- a/pkg/solidity-utils/contracts/helpers/Cache.sol +++ b/pkg/solidity-utils/contracts/helpers/Cache.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { StorageSlot } from "../contracts/openzeppelin/StorageSlot.sol"; +import { StorageSlot } from "../openzeppelin/StorageSlot.sol"; /** * @dev This library allows to cache the storage value in memory to avoid multiple SLOADs. From 98306c0b0526eae4d2ba9699989fd6e96f2a25f5 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Tue, 25 Jun 2024 19:52:38 +0200 Subject: [PATCH 12/18] refactoring --- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- .../[WeightedPool] donation | 2 +- .../[WeightedPool] initialize with ETH | 2 +- .../[WeightedPool] initialize without ETH | 2 +- .../contracts/helpers/Cache.sol | 50 -- pkg/vault/contracts/Vault.sol | 28 +- pkg/vault/contracts/VaultExtension.sol | 20 +- pkg/vault/contracts/lib/HooksConfigLib.sol | 543 +++++++++++++++ pkg/vault/contracts/lib/PoolConfigConst.sol | 48 ++ pkg/vault/contracts/lib/PoolConfigLib.sol | 658 +++--------------- pkg/vault/contracts/test/VaultMock.sol | 4 +- pkg/vault/test/.contract-sizes/Vault | 4 +- pkg/vault/test/.contract-sizes/VaultExtension | 4 +- .../test/foundry/unit/HooksConfigLib.t.sol | 237 +++++++ .../test/foundry/unit/PoolConfigConst.t.sol | 39 ++ .../test/foundry/unit/PoolConfigLib.t.sol | 296 +------- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- .../.hardhat-snapshots/[PoolMock] donation | 2 +- .../[PoolMock] initialize with ETH | 2 +- .../[PoolMock] initialize without ETH | 2 +- 26 files changed, 1023 insertions(+), 936 deletions(-) delete mode 100644 pkg/solidity-utils/contracts/helpers/Cache.sol create mode 100644 pkg/vault/contracts/lib/HooksConfigLib.sol create mode 100644 pkg/vault/contracts/lib/PoolConfigConst.sol create mode 100644 pkg/vault/test/foundry/unit/HooksConfigLib.t.sol create mode 100644 pkg/vault/test/foundry/unit/PoolConfigConst.t.sol diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots index e32efaff2..63e7a6fbf 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -175.7k \ No newline at end of file +175.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots index 885608b62..e2eff8c73 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -159.2k \ No newline at end of file +159.1k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots index 33862ebdb..2937f7eca 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -209.0k \ No newline at end of file +208.9k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots index fd76c368e..de3bc9a92 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -175.4k \ No newline at end of file +175.3k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation index 68b670a11..afe0c0e3e 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation @@ -1 +1 @@ -178.8k \ No newline at end of file +178.7k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH index d5ce4e317..dcb44e41f 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH @@ -1 +1 @@ -348.3k \ No newline at end of file +348.1k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH index c7b5aac9d..15104d699 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH @@ -1 +1 @@ -335.7k \ No newline at end of file +335.6k \ No newline at end of file diff --git a/pkg/solidity-utils/contracts/helpers/Cache.sol b/pkg/solidity-utils/contracts/helpers/Cache.sol deleted file mode 100644 index 253271f51..000000000 --- a/pkg/solidity-utils/contracts/helpers/Cache.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.24; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { StorageSlot } from "../openzeppelin/StorageSlot.sol"; - -/** - * @dev This library allows to cache the storage value in memory to avoid multiple SLOADs. - */ -library Cache { - struct AddressCache { - address value; - bytes32 slot; - } - - /** - * @dev This function initializes the cache for address values. - * We use AddressSlot to get the storage slot value using assembly. - */ - function initAddressCache( - StorageSlot.AddressSlot storage addressSlot - ) internal pure returns (AddressCache memory cache) { - bytes32 slot; - - // solhint-disable-next-line no-inline-assembly - assembly { - slot := addressSlot.slot - } - - cache.slot = slot; - } - - function getValue(AddressCache memory cache) internal view returns (address) { - if (cache.value == address(0)) { - address _value; - bytes32 slot = cache.slot; - - // solhint-disable-next-line no-inline-assembly - assembly { - _value := sload(slot) - } - - cache.value = _value; - } - - return cache.value; - } -} diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index 36a2c9c9a..f10ab9c23 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -31,10 +31,10 @@ import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/Fixe import { BasePoolMath } from "@balancer-labs/v3-solidity-utils/contracts/math/BasePoolMath.sol"; import { EnumerableMap } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/EnumerableMap.sol"; import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; -import { Cache } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Cache.sol"; import { VaultStateLib, VaultStateBits, VaultStateBits } from "./lib/VaultStateLib.sol"; import { PoolConfigLib } from "./lib/PoolConfigLib.sol"; +import { HooksConfigLib } from "./lib/HooksConfigLib.sol"; import { PackedTokenBalance } from "./lib/PackedTokenBalance.sol"; import { PoolDataLib } from "./lib/PoolDataLib.sol"; import { BufferPackedTokenBalance } from "./lib/BufferPackedBalance.sol"; @@ -49,11 +49,11 @@ contract Vault is IVaultMain, VaultCommon, Proxy { using Address for *; using SafeERC20 for IERC20; using PoolConfigLib for PoolConfigBits; + using HooksConfigLib for PoolConfigBits; using ScalingHelpers for *; using BufferPackedTokenBalance for bytes32; using TransientStorageHelpers for *; using StorageSlot for *; - using Cache for Cache.AddressCache; using PoolDataLib for PoolData; constructor(IVaultExtension vaultExtension, IAuthorizer authorizer, IProtocolFeeController protocolFeeController) { @@ -204,9 +204,8 @@ contract Vault is IVaultMain, VaultCommon, Proxy { IBasePool.PoolSwapParams memory swapParams = _buildPoolSwapParams(params, state, poolData); - Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); - - if (poolData.poolConfigBits.callBeforeSwapHook(swapParams, params.pool, hooksContractCache)) { + StorageSlot.AddressSlot storage hooksContract = _hooksContracts[params.pool]; + if (poolData.poolConfigBits.callBeforeSwapHook(swapParams, params.pool, hooksContract)) { // The call to `onBeforeSwap` could potentially update token rates and balances. // We update `poolData.tokenRates`, `poolData.rawBalances` and `poolData.balancesLiveScaled18` // to ensure the `onSwap` and `onComputeDynamicSwapFee` are called with the current values. @@ -226,7 +225,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { (bool dynamicSwapFeeCalculated, uint256 dynamicSwapFee) = poolData.poolConfigBits.callComputeDynamicSwapFeeHook( swapParams, state.swapFeePercentage, - hooksContractCache + hooksContract ); if (dynamicSwapFeeCalculated) { state.swapFeePercentage = dynamicSwapFee; @@ -248,7 +247,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { params, state, poolData, - hooksContractCache + hooksContract ); if (params.kind == SwapKind.EXACT_IN) { @@ -530,14 +529,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); + StorageSlot.AddressSlot storage hooksContract = _hooksContracts[params.pool]; if ( poolData.poolConfigBits.callBeforeAddLiquidityHook( msg.sender, maxAmountsInScaled18, params, poolData, - hooksContractCache + hooksContract ) ) { // If the hook library returns true, the hook code was executed, and might have altered the balances, @@ -574,7 +573,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { bptAmountOut, params, poolData, - hooksContractCache + hooksContract ); } @@ -764,8 +763,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[params.pool]); - + StorageSlot.AddressSlot storage hooksContract = _hooksContracts[params.pool]; // Uses msg.sender as the router (the contract that called the vault) if ( poolData.poolConfigBits.callBeforeRemoveLiquidityHook( @@ -773,8 +771,8 @@ contract Vault is IVaultMain, VaultCommon, Proxy { msg.sender, params, poolData, - hooksContractCache - ) == true + hooksContract + ) ) { // The hook might alter the balances, so we need to read them again to ensure that the data is // fresh moving forward. @@ -808,7 +806,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { bptAmountIn, params, poolData, - hooksContractCache + hooksContract ); } diff --git a/pkg/vault/contracts/VaultExtension.sol b/pkg/vault/contracts/VaultExtension.sol index 69a467c71..ccf78ba47 100644 --- a/pkg/vault/contracts/VaultExtension.sol +++ b/pkg/vault/contracts/VaultExtension.sol @@ -34,10 +34,10 @@ import { ReentrancyGuardTransient } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/ReentrancyGuardTransient.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; -import { Cache } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Cache.sol"; import { VaultStateBits, VaultStateLib } from "./lib/VaultStateLib.sol"; import { PoolConfigLib } from "./lib/PoolConfigLib.sol"; +import { HooksConfigLib } from "./lib/HooksConfigLib.sol"; import { VaultExtensionsLib } from "./lib/VaultExtensionsLib.sol"; import { VaultCommon } from "./VaultCommon.sol"; import { PackedTokenBalance } from "./lib/PackedTokenBalance.sol"; @@ -61,6 +61,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { using EnumerableSet for EnumerableSet.AddressSet; using PackedTokenBalance for bytes32; using PoolConfigLib for PoolConfigBits; + using HooksConfigLib for PoolConfigBits; using InputHelpers for uint256; using ScalingHelpers for *; using VaultExtensionsLib for IVault; @@ -385,12 +386,8 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { poolData.tokenRates ); - Cache.AddressCache memory hooksContractCache = Cache.initAddressCache(_hooksContracts[pool]); - - if ( - poolData.poolConfigBits.callBeforeInitializeHook(exactAmountsInScaled18, userData, hooksContractCache) == - true - ) { + StorageSlot.AddressSlot storage hooksContract = _hooksContracts[pool]; + if (poolData.poolConfigBits.callBeforeInitializeHook(exactAmountsInScaled18, userData, hooksContract)) { // The before hook is reentrant, and could have changed token rates. // Updating balances here is unnecessary since they're 0, but we do not special case before init // for the sake of bytecode size. @@ -405,12 +402,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { bptAmountOut = _initialize(pool, to, poolData, tokens, exactAmountsIn, exactAmountsInScaled18, minBptAmountOut); - poolData.poolConfigBits.callAfterInitializeHook( - exactAmountsInScaled18, - bptAmountOut, - userData, - hooksContractCache - ); + poolData.poolConfigBits.callAfterInitializeHook(exactAmountsInScaled18, bptAmountOut, userData, hooksContract); } function _initialize( @@ -619,7 +611,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { _poolConfigBits[pool].callComputeDynamicSwapFeeHook( swapParams, _poolConfigBits[pool].getStaticSwapFeePercentage(), - Cache.initAddressCache(_hooksContracts[pool]) + _hooksContracts[pool] ); } diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol new file mode 100644 index 000000000..5999b9943 --- /dev/null +++ b/pkg/vault/contracts/lib/HooksConfigLib.sol @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.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 { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; + +import { PoolConfigConst } from "./PoolConfigConst.sol"; + +library HooksConfigLib { + using WordCodec for bytes32; + using HooksConfigLib for PoolConfigBits; + + // #region Bit offsets for hooks config + + function enableHookAdjustedAmounts(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET); + } + + function setHookAdjustedAmounts(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET) + ); + } + + function shouldCallBeforeInitialize(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.BEFORE_INITIALIZE_OFFSET); + } + + function setShouldCallBeforeInitialize(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.BEFORE_INITIALIZE_OFFSET) + ); + } + + function shouldCallAfterInitialize(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.AFTER_INITIALIZE_OFFSET); + } + + function setShouldCallAfterInitialize(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.AFTER_INITIALIZE_OFFSET) + ); + } + + function shouldCallComputeDynamicSwapFee(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.DYNAMIC_SWAP_FEE_OFFSET); + } + + function setShouldCallComputeDynamicSwapFee( + PoolConfigBits config, + bool value + ) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.DYNAMIC_SWAP_FEE_OFFSET) + ); + } + + function shouldCallBeforeSwap(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.BEFORE_SWAP_OFFSET); + } + + function setShouldCallBeforeSwap(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.BEFORE_SWAP_OFFSET)); + } + + function shouldCallAfterSwap(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.AFTER_SWAP_OFFSET); + } + + function setShouldCallAfterSwap(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.AFTER_SWAP_OFFSET)); + } + + function shouldCallBeforeAddLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.BEFORE_ADD_LIQUIDITY_OFFSET); + } + + function setShouldCallBeforeAddLiquidity(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.BEFORE_ADD_LIQUIDITY_OFFSET) + ); + } + + function shouldCallAfterAddLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.AFTER_ADD_LIQUIDITY_OFFSET); + } + + function setShouldCallAfterAddLiquidity(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.AFTER_ADD_LIQUIDITY_OFFSET) + ); + } + + function shouldCallBeforeRemoveLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.BEFORE_REMOVE_LIQUIDITY_OFFSET); + } + + function setShouldCallBeforeRemoveLiquidity( + PoolConfigBits config, + bool value + ) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.BEFORE_REMOVE_LIQUIDITY_OFFSET) + ); + } + + function shouldCallAfterRemoveLiquidity(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.AFTER_REMOVE_LIQUIDITY_OFFSET); + } + + function setShouldCallAfterRemoveLiquidity( + PoolConfigBits config, + bool value + ) internal pure returns (PoolConfigBits) { + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.AFTER_REMOVE_LIQUIDITY_OFFSET) + ); + } + + function toHooksConfig(PoolConfigBits config, address hooksContract) internal pure returns (HooksConfig memory) { + return + HooksConfig({ + enableHookAdjustedAmounts: config.enableHookAdjustedAmounts(), + shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), + shouldCallAfterInitialize: config.shouldCallAfterInitialize(), + shouldCallBeforeAddLiquidity: config.shouldCallBeforeAddLiquidity(), + shouldCallAfterAddLiquidity: config.shouldCallAfterAddLiquidity(), + shouldCallBeforeRemoveLiquidity: config.shouldCallBeforeRemoveLiquidity(), + shouldCallAfterRemoveLiquidity: config.shouldCallAfterRemoveLiquidity(), + shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), + shouldCallBeforeSwap: config.shouldCallBeforeSwap(), + shouldCallAfterSwap: config.shouldCallAfterSwap(), + hooksContract: hooksContract + }); + } + + // #endregion + + // #region Hooks helper functions + + /** + * @dev Check if dynamic swap fee hook should be called and call it. Throws an error if the hook contract fails to + * execute the hook. + * + * @param config The encoded hooks configuration + * @param swapParams The swap parameters used to calculate the fee + * @param staticSwapFeePercentage Value of the static swap fee, for reference + * @param hooksContract Storage slot with the address of the hooks contract + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + * @return swapFeePercentage the calculated swap fee percentage. 0 if hook is disabled + */ + function callComputeDynamicSwapFeeHook( + PoolConfigBits config, + IBasePool.PoolSwapParams memory swapParams, + uint256 staticSwapFeePercentage, + StorageSlot.AddressSlot storage hooksContract + ) internal view returns (bool, uint256) { + if (config.shouldCallComputeDynamicSwapFee() == false) { + return (false, staticSwapFeePercentage); + } + + (bool success, uint256 swapFeePercentage) = IHooks(hooksContract.value).onComputeDynamicSwapFee( + swapParams, + staticSwapFeePercentage + ); + + if (success == false) { + revert IVaultErrors.DynamicSwapFeeHookFailed(); + } + return (success, swapFeePercentage); + } + + /** + * @dev Check if before swap hook should be called and call it. Throws an error if the hook contract fails to + * execute the hook. + * + * @param config The encoded hooks configuration + * @param swapParams The swap parameters used in the hook + * @param pool Pool address + * @param hooksContract Storage slot with the address of the hooks contract + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function callBeforeSwapHook( + PoolConfigBits config, + IBasePool.PoolSwapParams memory swapParams, + address pool, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (bool) { + if (config.shouldCallBeforeSwap() == false) { + // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) + return false; + } + + if (IHooks(hooksContract.value).onBeforeSwap(swapParams, pool) == false) { + // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. + revert IVaultErrors.BeforeSwapHookFailed(); + } + return true; + } + + /** + * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to + * execute the hook. + * + * @param config The encoded hooks configuration + * @param amountCalculatedScaled18 Token amount calculated by the swap + * @param amountCalculatedRaw Token amount calculated by the swap + * @param router Router address + * @param params The swap parameters + * @param state Temporary state used in swap operations + * @param poolData Struct containing balance and token information of the pool + * @param hooksContract Storage slot with the address of the hooks contract + * @return hookAdjustedAmountCalculatedRaw New amount calculated, potentially modified by the hook + */ + function callAfterSwapHook( + PoolConfigBits config, + uint256 amountCalculatedScaled18, + uint256 amountCalculatedRaw, + address router, + SwapParams memory params, + SwapState memory state, + PoolData memory poolData, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (uint256) { + if (config.shouldCallAfterSwap() == false) { + // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not + // change amountCalculatedRaw (no deltas) + return amountCalculatedRaw; + } + + // Adjust balances for the AfterSwap hook. + (uint256 amountInScaled18, uint256 amountOutScaled18) = params.kind == SwapKind.EXACT_IN + ? (state.amountGivenScaled18, amountCalculatedScaled18) + : (amountCalculatedScaled18, state.amountGivenScaled18); + + (bool success, uint256 hookAdjustedAmountCalculatedRaw) = IHooks(hooksContract.value).onAfterSwap( + IHooks.AfterSwapParams({ + kind: params.kind, + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountInScaled18: amountInScaled18, + amountOutScaled18: amountOutScaled18, + tokenInBalanceScaled18: poolData.balancesLiveScaled18[state.indexIn], + tokenOutBalanceScaled18: poolData.balancesLiveScaled18[state.indexOut], + amountCalculatedScaled18: amountCalculatedScaled18, + amountCalculatedRaw: amountCalculatedRaw, + router: router, + pool: params.pool, + userData: params.userData + }) + ); + + if (success == false) { + // Hook contract implements onAfterSwap, but it has failed, so reverts the transaction. + revert IVaultErrors.AfterSwapHookFailed(); + } + + // If hook adjusted amounts is not enabled, ignore amounts returned by the hook + if (config.enableHookAdjustedAmounts() == false) { + return amountCalculatedRaw; + } + + if ( + (params.kind == SwapKind.EXACT_IN && hookAdjustedAmountCalculatedRaw < params.limitRaw) || + (params.kind == SwapKind.EXACT_OUT && hookAdjustedAmountCalculatedRaw > params.limitRaw) + ) { + revert IVaultErrors.HookAdjustedSwapLimit(hookAdjustedAmountCalculatedRaw, params.limitRaw); + } + + return hookAdjustedAmountCalculatedRaw; + } + + /** + * @dev Check if before add liquidity hook should be called and call it. Throws an error if the hook contract fails + * to execute the hook. + * + * @param config The encoded hooks configuration + * @param router Router address + * @param maxAmountsInScaled18 An array with maximum amounts for each input token of the add liquidity operation + * @param params The add liquidity parameters + * @param poolData Struct containing balance and token information of the pool + * @param hooksContract Storage slot with the address of the hooks contract + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function callBeforeAddLiquidityHook( + PoolConfigBits config, + address router, + uint256[] memory maxAmountsInScaled18, + AddLiquidityParams memory params, + PoolData memory poolData, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (bool) { + if (config.shouldCallBeforeAddLiquidity() == false) { + return false; + } + + if ( + IHooks(hooksContract.value).onBeforeAddLiquidity( + router, + params.pool, + params.kind, + maxAmountsInScaled18, + params.minBptAmountOut, + poolData.balancesLiveScaled18, + params.userData + ) == false + ) { + revert IVaultErrors.BeforeAddLiquidityHookFailed(); + } + return true; + } + + /** + * @dev Check if after add liquidity hook should be called and call it. Throws an error if the hook contract fails + * to execute the hook. + * + * @param config The encoded hooks configuration + * @param router Router address + * @param amountsInScaled18 An array with amounts for each input token of the add liquidity operation + * @param amountsInRaw An array with amounts for each input token of the add liquidity operation + * @param bptAmountOut The BPT amount a user will receive after add liquidity operation succeeds + * @param params The add liquidity parameters + * @param poolData Struct containing balance and token information of the pool + * @param hooksContract Storage slot with the address of the hooks contract + * @return hookAdjustedAmountsInRaw New amountsInRaw, potentially modified by the hook + */ + function callAfterAddLiquidityHook( + PoolConfigBits config, + address router, + uint256[] memory amountsInScaled18, + uint256[] memory amountsInRaw, + uint256 bptAmountOut, + AddLiquidityParams memory params, + PoolData memory poolData, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (uint256[] memory) { + if (config.shouldCallAfterAddLiquidity() == false) { + return amountsInRaw; + } + + (bool success, uint256[] memory hookAdjustedAmountsInRaw) = IHooks(hooksContract.value).onAfterAddLiquidity( + router, + params.pool, + params.kind, + amountsInScaled18, + amountsInRaw, + bptAmountOut, + poolData.balancesLiveScaled18, + params.userData + ); + + if (success == false) { + revert IVaultErrors.AfterAddLiquidityHookFailed(); + } + + // If hook adjusted amounts is not enabled, ignore amounts returned by the hook + if (config.enableHookAdjustedAmounts() == false) { + return amountsInRaw; + } + + for (uint256 i = 0; i < hookAdjustedAmountsInRaw.length; i++) { + if (hookAdjustedAmountsInRaw[i] > params.maxAmountsIn[i]) { + revert IVaultErrors.HookAdjustedAmountInAboveMax( + poolData.tokens[i], + hookAdjustedAmountsInRaw[i], + params.maxAmountsIn[i] + ); + } + } + + return hookAdjustedAmountsInRaw; + } + + /** + * @dev Check if before remove liquidity hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param minAmountsOutScaled18 An array with minimum amounts for each output token of the remove liquidity + * operation + * @param router Router address + * @param params The remove liquidity parameters + * @param poolData Struct containing balance and token information of the pool + * @param hooksContract Storage slot with the address of the hooks contract + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function callBeforeRemoveLiquidityHook( + PoolConfigBits config, + uint256[] memory minAmountsOutScaled18, + address router, + RemoveLiquidityParams memory params, + PoolData memory poolData, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (bool) { + if (config.shouldCallBeforeRemoveLiquidity() == false) { + return false; + } + + if ( + IHooks(hooksContract.value).onBeforeRemoveLiquidity( + router, + params.pool, + params.kind, + params.maxBptAmountIn, + minAmountsOutScaled18, + poolData.balancesLiveScaled18, + params.userData + ) == false + ) { + revert IVaultErrors.BeforeRemoveLiquidityHookFailed(); + } + return true; + } + + /** + * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * @param config The encoded hooks configuration + * @param router Router address + * @param amountsOutScaled18 Scaled amount of tokens to receive, sorted in token registration order + * @param amountsOutRaw Actual amount of tokens to receive, sorted in token registration order + * @param bptAmountIn The BPT amount a user will need burn to remove the liquidity of the pool + * @param params The remove liquidity parameters + * @param poolData Struct containing balance and token information of the pool + * @param hooksContract Storage slot with the address of the hooks contract + * @return hookAdjustedAmountsOutRaw New amountsOutRaw, potentially modified by the hook + */ + function callAfterRemoveLiquidityHook( + PoolConfigBits config, + address router, + uint256[] memory amountsOutScaled18, + uint256[] memory amountsOutRaw, + uint256 bptAmountIn, + RemoveLiquidityParams memory params, + PoolData memory poolData, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (uint256[] memory) { + if (config.shouldCallAfterRemoveLiquidity() == false) { + return amountsOutRaw; + } + + (bool success, uint256[] memory hookAdjustedAmountsOutRaw) = IHooks(hooksContract.value).onAfterRemoveLiquidity( + router, + params.pool, + params.kind, + bptAmountIn, + amountsOutScaled18, + amountsOutRaw, + poolData.balancesLiveScaled18, + params.userData + ); + + if (success == false) { + revert IVaultErrors.AfterRemoveLiquidityHookFailed(); + } + + // If hook adjusted amounts is not enabled, ignore amounts returned by the hook + if (config.enableHookAdjustedAmounts() == false) { + return amountsOutRaw; + } + + for (uint256 i = 0; i < hookAdjustedAmountsOutRaw.length; i++) { + if (hookAdjustedAmountsOutRaw[i] < params.minAmountsOut[i]) { + revert IVaultErrors.HookAdjustedAmountOutBelowMin( + poolData.tokens[i], + hookAdjustedAmountsOutRaw[i], + params.minAmountsOut[i] + ); + } + } + + return hookAdjustedAmountsOutRaw; + } + + /** + * @dev Check if before initialization hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param exactAmountsInScaled18 An array with the initial liquidity of the pool + * @param userData Additional (optional) data required for adding initial liquidity + * @param hooksContract Storage slot with the address of the hooks contract + * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute + */ + function callBeforeInitializeHook( + PoolConfigBits config, + uint256[] memory exactAmountsInScaled18, + bytes memory userData, + StorageSlot.AddressSlot storage hooksContract + ) internal returns (bool) { + if (config.shouldCallBeforeInitialize() == false) { + return false; + } + + if (IHooks(hooksContract.value).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { + revert IVaultErrors.BeforeInitializeHookFailed(); + } + return true; + } + + /** + * @dev Check if after initialization hook should be called and call it. Throws an error if the hook contract + * fails to execute the hook. + * + * @param config The encoded hooks configuration + * @param exactAmountsInScaled18 An array with the initial liquidity of the pool + * @param bptAmountOut The BPT amount a user will receive after initialization operation succeeds + * @param userData Additional (optional) data required for adding initial liquidity + * @param hooksContract Storage slot with the address of the hooks contract + */ + function callAfterInitializeHook( + PoolConfigBits config, + uint256[] memory exactAmountsInScaled18, + uint256 bptAmountOut, + bytes memory userData, + StorageSlot.AddressSlot storage hooksContract + ) internal { + if (config.shouldCallAfterInitialize() == false) { + return; + } + + if (IHooks(hooksContract.value).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false) { + revert IVaultErrors.AfterInitializeHookFailed(); + } + } + + // #endregion +} diff --git a/pkg/vault/contracts/lib/PoolConfigConst.sol b/pkg/vault/contracts/lib/PoolConfigConst.sol new file mode 100644 index 000000000..0499210c1 --- /dev/null +++ b/pkg/vault/contracts/lib/PoolConfigConst.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { FEE_BITLENGTH } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; + +library PoolConfigConst { + error InvalidSize(uint256 currentValue, uint256 expectedSize); + + // Bit offsets for main pool config settings + uint8 public constant POOL_REGISTERED_OFFSET = 0; + uint8 public constant POOL_INITIALIZED_OFFSET = POOL_REGISTERED_OFFSET + 1; + uint8 public constant POOL_PAUSED_OFFSET = POOL_INITIALIZED_OFFSET + 1; + uint8 public constant POOL_RECOVERY_MODE_OFFSET = POOL_PAUSED_OFFSET + 1; + + // Bit offsets for liquidity operations + uint8 public constant UNBALANCED_LIQUIDITY_OFFSET = POOL_RECOVERY_MODE_OFFSET + 1; + uint8 public constant ADD_LIQUIDITY_CUSTOM_OFFSET = UNBALANCED_LIQUIDITY_OFFSET + 1; + uint8 public constant REMOVE_LIQUIDITY_CUSTOM_OFFSET = ADD_LIQUIDITY_CUSTOM_OFFSET + 1; + uint8 public constant DONATION_OFFSET = REMOVE_LIQUIDITY_CUSTOM_OFFSET + 1; + + // Bit offsets for hooks config + uint8 public constant BEFORE_INITIALIZE_OFFSET = DONATION_OFFSET + 1; + uint8 public constant ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET = BEFORE_INITIALIZE_OFFSET + 1; + uint8 public constant AFTER_INITIALIZE_OFFSET = ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET + 1; + uint8 public constant DYNAMIC_SWAP_FEE_OFFSET = AFTER_INITIALIZE_OFFSET + 1; + uint8 public constant BEFORE_SWAP_OFFSET = DYNAMIC_SWAP_FEE_OFFSET + 1; + uint8 public constant AFTER_SWAP_OFFSET = BEFORE_SWAP_OFFSET + 1; + uint8 public constant BEFORE_ADD_LIQUIDITY_OFFSET = AFTER_SWAP_OFFSET + 1; + uint8 public constant AFTER_ADD_LIQUIDITY_OFFSET = BEFORE_ADD_LIQUIDITY_OFFSET + 1; + uint8 public constant BEFORE_REMOVE_LIQUIDITY_OFFSET = AFTER_ADD_LIQUIDITY_OFFSET + 1; + uint8 public constant AFTER_REMOVE_LIQUIDITY_OFFSET = BEFORE_REMOVE_LIQUIDITY_OFFSET + 1; + + // Bit offsets for uint values + uint8 public constant STATIC_SWAP_FEE_OFFSET = AFTER_REMOVE_LIQUIDITY_OFFSET + 1; + uint256 public constant AGGREGATE_SWAP_FEE_OFFSET = STATIC_SWAP_FEE_OFFSET + FEE_BITLENGTH; + uint256 public constant AGGREGATE_YIELD_FEE_OFFSET = AGGREGATE_SWAP_FEE_OFFSET + FEE_BITLENGTH; + uint256 public constant DECIMAL_SCALING_FACTORS_OFFSET = AGGREGATE_YIELD_FEE_OFFSET + FEE_BITLENGTH; + uint256 public constant PAUSE_WINDOW_END_TIME_OFFSET = + DECIMAL_SCALING_FACTORS_OFFSET + TOKEN_DECIMAL_DIFFS_BITLENGTH; + + // Uses a uint24 (3 bytes): least significant 20 bits to store the values, and a 4-bit pad. + // This maximum token count is also hard-coded in the Vault. + uint8 public constant TOKEN_DECIMAL_DIFFS_BITLENGTH = 24; + uint8 public constant DECIMAL_DIFF_BITLENGTH = 5; + + uint8 public constant TIMESTAMP_BITLENGTH = 32; +} diff --git a/pkg/vault/contracts/lib/PoolConfigLib.sol b/pkg/vault/contracts/lib/PoolConfigLib.sol index 1b188e77e..8744df92f 100644 --- a/pkg/vault/contracts/lib/PoolConfigLib.sol +++ b/pkg/vault/contracts/lib/PoolConfigLib.sol @@ -2,92 +2,60 @@ pragma solidity ^0.8.24; -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 { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; -import { Cache } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Cache.sol"; + +import { PoolConfigConst } from "./PoolConfigConst.sol"; library PoolConfigLib { using WordCodec for bytes32; using PoolConfigLib for PoolConfigBits; - using Cache for Cache.AddressCache; error InvalidSize(uint256 currentValue, uint256 expectedSize); - // Bit offsets for main pool config settings - uint8 public constant POOL_REGISTERED_OFFSET = 0; - uint8 public constant POOL_INITIALIZED_OFFSET = POOL_REGISTERED_OFFSET + 1; - uint8 public constant POOL_PAUSED_OFFSET = POOL_INITIALIZED_OFFSET + 1; - uint8 public constant POOL_RECOVERY_MODE_OFFSET = POOL_PAUSED_OFFSET + 1; - - // Bit offsets for liquidity operations - uint8 public constant UNBALANCED_LIQUIDITY_OFFSET = POOL_RECOVERY_MODE_OFFSET + 1; - uint8 public constant ADD_LIQUIDITY_CUSTOM_OFFSET = UNBALANCED_LIQUIDITY_OFFSET + 1; - uint8 public constant REMOVE_LIQUIDITY_CUSTOM_OFFSET = ADD_LIQUIDITY_CUSTOM_OFFSET + 1; - uint8 public constant DONATION_OFFSET = REMOVE_LIQUIDITY_CUSTOM_OFFSET + 1; - - // Bit offsets for hooks config - uint8 public constant BEFORE_INITIALIZE_OFFSET = DONATION_OFFSET + 1; - uint8 public constant ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET = BEFORE_INITIALIZE_OFFSET + 1; - uint8 public constant AFTER_INITIALIZE_OFFSET = ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET + 1; - uint8 public constant DYNAMIC_SWAP_FEE_OFFSET = AFTER_INITIALIZE_OFFSET + 1; - uint8 public constant BEFORE_SWAP_OFFSET = DYNAMIC_SWAP_FEE_OFFSET + 1; - uint8 public constant AFTER_SWAP_OFFSET = BEFORE_SWAP_OFFSET + 1; - uint8 public constant BEFORE_ADD_LIQUIDITY_OFFSET = AFTER_SWAP_OFFSET + 1; - uint8 public constant AFTER_ADD_LIQUIDITY_OFFSET = BEFORE_ADD_LIQUIDITY_OFFSET + 1; - uint8 public constant BEFORE_REMOVE_LIQUIDITY_OFFSET = AFTER_ADD_LIQUIDITY_OFFSET + 1; - uint8 public constant AFTER_REMOVE_LIQUIDITY_OFFSET = BEFORE_REMOVE_LIQUIDITY_OFFSET + 1; - - // Bit offsets for uint values - uint8 public constant STATIC_SWAP_FEE_OFFSET = AFTER_REMOVE_LIQUIDITY_OFFSET + 1; - uint256 public constant AGGREGATE_SWAP_FEE_OFFSET = STATIC_SWAP_FEE_OFFSET + FEE_BITLENGTH; - uint256 public constant AGGREGATE_YIELD_FEE_OFFSET = AGGREGATE_SWAP_FEE_OFFSET + FEE_BITLENGTH; - uint256 public constant DECIMAL_SCALING_FACTORS_OFFSET = AGGREGATE_YIELD_FEE_OFFSET + FEE_BITLENGTH; - uint256 public constant PAUSE_WINDOW_END_TIME_OFFSET = - DECIMAL_SCALING_FACTORS_OFFSET + _TOKEN_DECIMAL_DIFFS_BITLENGTH; - - // Uses a uint24 (3 bytes): least significant 20 bits to store the values, and a 4-bit pad. - // This maximum token count is also hard-coded in the Vault. - uint8 private constant _TOKEN_DECIMAL_DIFFS_BITLENGTH = 24; - uint8 private constant _DECIMAL_DIFF_BITLENGTH = 5; - - uint8 private constant _TIMESTAMP_BITLENGTH = 32; - // #region Bit offsets for main pool config settings function isPoolRegistered(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_REGISTERED_OFFSET); + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.POOL_REGISTERED_OFFSET); } function setPoolRegistered(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_REGISTERED_OFFSET)); + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.POOL_REGISTERED_OFFSET) + ); } function isPoolInitialized(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_INITIALIZED_OFFSET); + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.POOL_INITIALIZED_OFFSET); } function setPoolInitialized(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_INITIALIZED_OFFSET)); + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.POOL_INITIALIZED_OFFSET) + ); } function isPoolPaused(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_PAUSED_OFFSET); + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.POOL_PAUSED_OFFSET); } function setPoolPaused(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_PAUSED_OFFSET)); + return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.POOL_PAUSED_OFFSET)); } function isPoolInRecoveryMode(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(POOL_RECOVERY_MODE_OFFSET); + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.POOL_RECOVERY_MODE_OFFSET); } function setPoolInRecoveryMode(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, POOL_RECOVERY_MODE_OFFSET)); + return + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(value, PoolConfigConst.POOL_RECOVERY_MODE_OFFSET) + ); } // #endregion @@ -96,7 +64,7 @@ library PoolConfigLib { function supportsUnbalancedLiquidity(PoolConfigBits config) internal pure returns (bool) { // NOTE: The unbalanced liquidity flag is default-on (false means it is supported). // This function returns the inverted value. - return !PoolConfigBits.unwrap(config).decodeBool(UNBALANCED_LIQUIDITY_OFFSET); + return !PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.UNBALANCED_LIQUIDITY_OFFSET); } function requireUnbalancedLiquidityEnabled(PoolConfigBits config) internal pure { @@ -111,12 +79,15 @@ library PoolConfigLib { ) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(disableUnbalancedLiquidity, UNBALANCED_LIQUIDITY_OFFSET) + PoolConfigBits.unwrap(config).insertBool( + disableUnbalancedLiquidity, + PoolConfigConst.UNBALANCED_LIQUIDITY_OFFSET + ) ); } function supportsAddLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(ADD_LIQUIDITY_CUSTOM_OFFSET); + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.ADD_LIQUIDITY_CUSTOM_OFFSET); } function requireAddCustomLiquidityEnabled(PoolConfigBits config) internal pure { @@ -131,12 +102,15 @@ library PoolConfigLib { ) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(enableAddLiquidityCustom, ADD_LIQUIDITY_CUSTOM_OFFSET) + PoolConfigBits.unwrap(config).insertBool( + enableAddLiquidityCustom, + PoolConfigConst.ADD_LIQUIDITY_CUSTOM_OFFSET + ) ); } function supportsRemoveLiquidityCustom(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(REMOVE_LIQUIDITY_CUSTOM_OFFSET); + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.REMOVE_LIQUIDITY_CUSTOM_OFFSET); } function requireRemoveCustomLiquidityEnabled(PoolConfigBits config) internal pure { @@ -151,499 +125,27 @@ library PoolConfigLib { ) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(enableRemoveLiquidityCustom, REMOVE_LIQUIDITY_CUSTOM_OFFSET) + PoolConfigBits.unwrap(config).insertBool( + enableRemoveLiquidityCustom, + PoolConfigConst.REMOVE_LIQUIDITY_CUSTOM_OFFSET + ) ); } - // #endregion - - // #region Bit offsets for hooks config - function enableHookAdjustedAmounts(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET); - } - - function setHookAdjustedAmounts(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return - PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET)); - } - - function shouldCallBeforeInitialize(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(BEFORE_INITIALIZE_OFFSET); - } - - function setShouldCallBeforeInitialize(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_INITIALIZE_OFFSET)); - } - - function shouldCallAfterInitialize(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(AFTER_INITIALIZE_OFFSET); - } - - function setShouldCallAfterInitialize(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_INITIALIZE_OFFSET)); - } - - function shouldCallComputeDynamicSwapFee(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(DYNAMIC_SWAP_FEE_OFFSET); - } - - function setShouldCallComputeDynamicSwapFee( - PoolConfigBits config, - bool value - ) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, DYNAMIC_SWAP_FEE_OFFSET)); - } - - function shouldCallBeforeSwap(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(BEFORE_SWAP_OFFSET); - } - - function setShouldCallBeforeSwap(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_SWAP_OFFSET)); - } - - function shouldCallAfterSwap(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(AFTER_SWAP_OFFSET); - } - - function setShouldCallAfterSwap(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_SWAP_OFFSET)); - } - - function shouldCallBeforeAddLiquidity(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(BEFORE_ADD_LIQUIDITY_OFFSET); - } - - function setShouldCallBeforeAddLiquidity(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_ADD_LIQUIDITY_OFFSET)); - } - - function shouldCallAfterAddLiquidity(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(AFTER_ADD_LIQUIDITY_OFFSET); - } - - function setShouldCallAfterAddLiquidity(PoolConfigBits config, bool value) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_ADD_LIQUIDITY_OFFSET)); - } - - function shouldCallBeforeRemoveLiquidity(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(BEFORE_REMOVE_LIQUIDITY_OFFSET); - } - - function setShouldCallBeforeRemoveLiquidity( - PoolConfigBits config, - bool value - ) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, BEFORE_REMOVE_LIQUIDITY_OFFSET)); - } - - function shouldCallAfterRemoveLiquidity(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(AFTER_REMOVE_LIQUIDITY_OFFSET); - } - - function setShouldCallAfterRemoveLiquidity( - PoolConfigBits config, - bool value - ) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(value, AFTER_REMOVE_LIQUIDITY_OFFSET)); + function supportsDonation(PoolConfigBits config) internal pure returns (bool) { + return PoolConfigBits.unwrap(config).decodeBool(PoolConfigConst.DONATION_OFFSET); } - function toHooksConfig(PoolConfigBits config, address hooksContract) internal pure returns (HooksConfig memory) { + function setDonation(PoolConfigBits config, bool enableDonation) internal pure returns (PoolConfigBits) { return - HooksConfig({ - enableHookAdjustedAmounts: config.enableHookAdjustedAmounts(), - shouldCallBeforeInitialize: config.shouldCallBeforeInitialize(), - shouldCallAfterInitialize: config.shouldCallAfterInitialize(), - shouldCallBeforeAddLiquidity: config.shouldCallBeforeAddLiquidity(), - shouldCallAfterAddLiquidity: config.shouldCallAfterAddLiquidity(), - shouldCallBeforeRemoveLiquidity: config.shouldCallBeforeRemoveLiquidity(), - shouldCallAfterRemoveLiquidity: config.shouldCallAfterRemoveLiquidity(), - shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), - shouldCallBeforeSwap: config.shouldCallBeforeSwap(), - shouldCallAfterSwap: config.shouldCallAfterSwap(), - hooksContract: hooksContract - }); - } - - /** - * @dev Check if dynamic swap fee hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param swapParams The swap parameters used to calculate the fee - * @param staticSwapFeePercentage Value of the static swap fee, for reference - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - * @return swapFeePercentage the calculated swap fee percentage. 0 if hook is disabled - */ - function callComputeDynamicSwapFeeHook( - PoolConfigBits config, - IBasePool.PoolSwapParams memory swapParams, - uint256 staticSwapFeePercentage, - Cache.AddressCache memory hooksContractCache - ) internal view returns (bool, uint256) { - if (config.shouldCallComputeDynamicSwapFee() == false) { - return (false, staticSwapFeePercentage); - } - - (bool success, uint256 swapFeePercentage) = IHooks(hooksContractCache.getValue()).onComputeDynamicSwapFee( - swapParams, - staticSwapFeePercentage - ); - - if (success == false) { - revert IVaultErrors.DynamicSwapFeeHookFailed(); - } - return (success, swapFeePercentage); - } - - /** - * @dev Check if before swap hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param swapParams The swap parameters used in the hook - * @param pool Pool address - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeSwapHook( - PoolConfigBits config, - IBasePool.PoolSwapParams memory swapParams, - address pool, - Cache.AddressCache memory hooksContractCache - ) internal returns (bool) { - if (config.shouldCallBeforeSwap() == false) { - // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) - return false; - } - - if (IHooks(hooksContractCache.getValue()).onBeforeSwap(swapParams, pool) == false) { - // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. - revert IVaultErrors.BeforeSwapHookFailed(); - } - return true; - } - - /** - * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to - * execute the hook. - * - * @param config The encoded hooks configuration - * @param amountCalculatedScaled18 Token amount calculated by the swap - * @param amountCalculatedRaw Token amount calculated by the swap - * @param router Router address - * @param params The swap parameters - * @param state Temporary state used in swap operations - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountCalculatedRaw New amount calculated, potentially modified by the hook - */ - function callAfterSwapHook( - PoolConfigBits config, - uint256 amountCalculatedScaled18, - uint256 amountCalculatedRaw, - address router, - SwapParams memory params, - SwapState memory state, - PoolData memory poolData, - Cache.AddressCache memory hooksContractCache - ) internal returns (uint256) { - if (config.shouldCallAfterSwap() == false) { - // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not - // change amountCalculatedRaw (no deltas) - return amountCalculatedRaw; - } - - // Adjust balances for the AfterSwap hook. - (uint256 amountInScaled18, uint256 amountOutScaled18) = params.kind == SwapKind.EXACT_IN - ? (state.amountGivenScaled18, amountCalculatedScaled18) - : (amountCalculatedScaled18, state.amountGivenScaled18); - - (bool success, uint256 hookAdjustedAmountCalculatedRaw) = IHooks(hooksContractCache.getValue()).onAfterSwap( - IHooks.AfterSwapParams({ - kind: params.kind, - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, - amountInScaled18: amountInScaled18, - amountOutScaled18: amountOutScaled18, - tokenInBalanceScaled18: poolData.balancesLiveScaled18[state.indexIn], - tokenOutBalanceScaled18: poolData.balancesLiveScaled18[state.indexOut], - amountCalculatedScaled18: amountCalculatedScaled18, - amountCalculatedRaw: amountCalculatedRaw, - router: router, - pool: params.pool, - userData: params.userData - }) - ); - - if (success == false) { - // Hook contract implements onAfterSwap, but it has failed, so reverts the transaction. - revert IVaultErrors.AfterSwapHookFailed(); - } - - // If hook adjusted amounts is not enabled, ignore amounts returned by the hook - if (config.enableHookAdjustedAmounts() == false) { - return amountCalculatedRaw; - } - - if ( - (params.kind == SwapKind.EXACT_IN && hookAdjustedAmountCalculatedRaw < params.limitRaw) || - (params.kind == SwapKind.EXACT_OUT && hookAdjustedAmountCalculatedRaw > params.limitRaw) - ) { - revert IVaultErrors.HookAdjustedSwapLimit(hookAdjustedAmountCalculatedRaw, params.limitRaw); - } - - return hookAdjustedAmountCalculatedRaw; - } - - /** - * @dev Check if before add liquidity hook should be called and call it. Throws an error if the hook contract fails - * to execute the hook. - * - * @param config The encoded hooks configuration - * @param router Router address - * @param maxAmountsInScaled18 An array with maximum amounts for each input token of the add liquidity operation - * @param params The add liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeAddLiquidityHook( - PoolConfigBits config, - address router, - uint256[] memory maxAmountsInScaled18, - AddLiquidityParams memory params, - PoolData memory poolData, - Cache.AddressCache memory hooksContractCache - ) internal returns (bool) { - if (config.shouldCallBeforeAddLiquidity() == false) { - return false; - } - - if ( - IHooks(hooksContractCache.getValue()).onBeforeAddLiquidity( - router, - params.pool, - params.kind, - maxAmountsInScaled18, - params.minBptAmountOut, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.BeforeAddLiquidityHookFailed(); - } - return true; - } - - /** - * @dev Check if after add liquidity hook should be called and call it. Throws an error if the hook contract fails - * to execute the hook. - * - * @param config The encoded hooks configuration - * @param router Router address - * @param amountsInScaled18 An array with amounts for each input token of the add liquidity operation - * @param amountsInRaw An array with amounts for each input token of the add liquidity operation - * @param bptAmountOut The BPT amount a user will receive after add liquidity operation succeeds - * @param params The add liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountsInRaw New amountsInRaw, potentially modified by the hook - */ - function callAfterAddLiquidityHook( - PoolConfigBits config, - address router, - uint256[] memory amountsInScaled18, - uint256[] memory amountsInRaw, - uint256 bptAmountOut, - AddLiquidityParams memory params, - PoolData memory poolData, - Cache.AddressCache memory hooksContractCache - ) internal returns (uint256[] memory) { - if (config.shouldCallAfterAddLiquidity() == false) { - return amountsInRaw; - } - - (bool success, uint256[] memory hookAdjustedAmountsInRaw) = IHooks(hooksContractCache.getValue()) - .onAfterAddLiquidity( - router, - params.pool, - params.kind, - amountsInScaled18, - amountsInRaw, - bptAmountOut, - poolData.balancesLiveScaled18, - params.userData - ); - - if (success == false) { - revert IVaultErrors.AfterAddLiquidityHookFailed(); - } - - // If hook adjusted amounts is not enabled, ignore amounts returned by the hook - if (config.enableHookAdjustedAmounts() == false) { - return amountsInRaw; - } - - for (uint256 i = 0; i < hookAdjustedAmountsInRaw.length; i++) { - if (hookAdjustedAmountsInRaw[i] > params.maxAmountsIn[i]) { - revert IVaultErrors.HookAdjustedAmountInAboveMax( - poolData.tokens[i], - hookAdjustedAmountsInRaw[i], - params.maxAmountsIn[i] - ); - } - } - - return hookAdjustedAmountsInRaw; - } - - /** - * @dev Check if before remove liquidity hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param minAmountsOutScaled18 An array with minimum amounts for each output token of the remove liquidity - * operation - * @param router Router address - * @param params The remove liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeRemoveLiquidityHook( - PoolConfigBits config, - uint256[] memory minAmountsOutScaled18, - address router, - RemoveLiquidityParams memory params, - PoolData memory poolData, - Cache.AddressCache memory hooksContractCache - ) internal returns (bool) { - if (config.shouldCallBeforeRemoveLiquidity() == false) { - return false; - } - - if ( - IHooks(hooksContractCache.getValue()).onBeforeRemoveLiquidity( - router, - params.pool, - params.kind, - params.maxBptAmountIn, - minAmountsOutScaled18, - poolData.balancesLiveScaled18, - params.userData - ) == false - ) { - revert IVaultErrors.BeforeRemoveLiquidityHookFailed(); - } - return true; - } - - /** - * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * @param config The encoded hooks configuration - * @param router Router address - * @param amountsOutScaled18 Scaled amount of tokens to receive, sorted in token registration order - * @param amountsOutRaw Actual amount of tokens to receive, sorted in token registration order - * @param bptAmountIn The BPT amount a user will need burn to remove the liquidity of the pool - * @param params The remove liquidity parameters - * @param poolData Struct containing balance and token information of the pool - * @return hookAdjustedAmountsOutRaw New amountsOutRaw, potentially modified by the hook - */ - function callAfterRemoveLiquidityHook( - PoolConfigBits config, - address router, - uint256[] memory amountsOutScaled18, - uint256[] memory amountsOutRaw, - uint256 bptAmountIn, - RemoveLiquidityParams memory params, - PoolData memory poolData, - Cache.AddressCache memory hooksContractCache - ) internal returns (uint256[] memory) { - if (config.shouldCallAfterRemoveLiquidity() == false) { - return amountsOutRaw; - } - - (bool success, uint256[] memory hookAdjustedAmountsOutRaw) = IHooks(hooksContractCache.getValue()) - .onAfterRemoveLiquidity( - router, - params.pool, - params.kind, - bptAmountIn, - amountsOutScaled18, - amountsOutRaw, - poolData.balancesLiveScaled18, - params.userData + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(enableDonation, PoolConfigConst.DONATION_OFFSET) ); - - if (success == false) { - revert IVaultErrors.AfterRemoveLiquidityHookFailed(); - } - - // If hook adjusted amounts is not enabled, ignore amounts returned by the hook - if (config.enableHookAdjustedAmounts() == false) { - return amountsOutRaw; - } - - for (uint256 i = 0; i < hookAdjustedAmountsOutRaw.length; i++) { - if (hookAdjustedAmountsOutRaw[i] < params.minAmountsOut[i]) { - revert IVaultErrors.HookAdjustedAmountOutBelowMin( - poolData.tokens[i], - hookAdjustedAmountsOutRaw[i], - params.minAmountsOut[i] - ); - } - } - - return hookAdjustedAmountsOutRaw; } - /** - * @dev Check if before initialization hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param exactAmountsInScaled18 An array with the initial liquidity of the pool - * @param userData Additional (optional) data required for adding initial liquidity - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute - */ - function callBeforeInitializeHook( - PoolConfigBits config, - uint256[] memory exactAmountsInScaled18, - bytes memory userData, - Cache.AddressCache memory hooksContractCache - ) internal returns (bool) { - if (config.shouldCallBeforeInitialize() == false) { - return false; - } - - if (IHooks(hooksContractCache.getValue()).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { - revert IVaultErrors.BeforeInitializeHookFailed(); - } - return true; - } - - /** - * @dev Check if after initialization hook should be called and call it. Throws an error if the hook contract - * fails to execute the hook. - * - * @param config The encoded hooks configuration - * @param exactAmountsInScaled18 An array with the initial liquidity of the pool - * @param bptAmountOut The BPT amount a user will receive after initialization operation succeeds - * @param userData Additional (optional) data required for adding initial liquidity - */ - function callAfterInitializeHook( - PoolConfigBits config, - uint256[] memory exactAmountsInScaled18, - uint256 bptAmountOut, - bytes memory userData, - Cache.AddressCache memory hooksContractCache - ) internal { - if (config.shouldCallAfterInitialize() == false) { - return; - } - - if ( - IHooks(hooksContractCache.getValue()).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == - false - ) { - revert IVaultErrors.AfterInitializeHookFailed(); + function requireDonationEnabled(PoolConfigBits config) internal pure { + if (config.supportsDonation() == false) { + revert IVaultErrors.DoesNotSupportDonation(); } } @@ -651,7 +153,9 @@ library PoolConfigLib { // #region Bit offsets for uint values function getStaticSwapFeePercentage(PoolConfigBits config) internal pure returns (uint256) { - return PoolConfigBits.unwrap(config).decodeUint(STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; + return + PoolConfigBits.unwrap(config).decodeUint(PoolConfigConst.STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH) * + FEE_SCALING_FACTOR; } function setStaticSwapFeePercentage(PoolConfigBits config, uint256 value) internal pure returns (PoolConfigBits) { @@ -662,11 +166,15 @@ library PoolConfigLib { } return - PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertUint(value, STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH)); + PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertUint(value, PoolConfigConst.STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH) + ); } function getAggregateSwapFeePercentage(PoolConfigBits config) internal pure returns (uint256) { - return PoolConfigBits.unwrap(config).decodeUint(AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; + return + PoolConfigBits.unwrap(config).decodeUint(PoolConfigConst.AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH) * + FEE_SCALING_FACTOR; } function setAggregateSwapFeePercentage( @@ -681,12 +189,18 @@ library PoolConfigLib { return PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint(value, AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH) + PoolConfigBits.unwrap(config).insertUint( + value, + PoolConfigConst.AGGREGATE_SWAP_FEE_OFFSET, + FEE_BITLENGTH + ) ); } function getAggregateYieldFeePercentage(PoolConfigBits config) internal pure returns (uint256) { - return PoolConfigBits.unwrap(config).decodeUint(AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH) * FEE_SCALING_FACTOR; + return + PoolConfigBits.unwrap(config).decodeUint(PoolConfigConst.AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH) * + FEE_SCALING_FACTOR; } function setAggregateYieldFeePercentage( @@ -701,14 +215,21 @@ library PoolConfigLib { return PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint(value, AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH) + PoolConfigBits.unwrap(config).insertUint( + value, + PoolConfigConst.AGGREGATE_YIELD_FEE_OFFSET, + FEE_BITLENGTH + ) ); } function getTokenDecimalDiffs(PoolConfigBits config) internal pure returns (uint24) { return uint24( - PoolConfigBits.unwrap(config).decodeUint(DECIMAL_SCALING_FACTORS_OFFSET, _TOKEN_DECIMAL_DIFFS_BITLENGTH) + PoolConfigBits.unwrap(config).decodeUint( + PoolConfigConst.DECIMAL_SCALING_FACTORS_OFFSET, + PoolConfigConst.TOKEN_DECIMAL_DIFFS_BITLENGTH + ) ); } @@ -721,7 +242,10 @@ library PoolConfigLib { bytes32 tokenDecimalDiffs = bytes32(uint256(config.getTokenDecimalDiffs())); for (uint256 i = 0; i < numTokens; ++i) { - uint256 decimalDiff = tokenDecimalDiffs.decodeUint(i * _DECIMAL_DIFF_BITLENGTH, _DECIMAL_DIFF_BITLENGTH); + uint256 decimalDiff = tokenDecimalDiffs.decodeUint( + i * PoolConfigConst.DECIMAL_DIFF_BITLENGTH, + PoolConfigConst.DECIMAL_DIFF_BITLENGTH + ); // This is equivalent to `10**(18+decimalsDifference)` but this form optimizes for 18 decimal tokens. scalingFactors[i] = FixedPoint.ONE * 10 ** decimalDiff; @@ -735,45 +259,45 @@ library PoolConfigLib { PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertUint( value, - DECIMAL_SCALING_FACTORS_OFFSET, - _TOKEN_DECIMAL_DIFFS_BITLENGTH + PoolConfigConst.DECIMAL_SCALING_FACTORS_OFFSET, + PoolConfigConst.TOKEN_DECIMAL_DIFFS_BITLENGTH ) ); } function getPauseWindowEndTime(PoolConfigBits config) internal pure returns (uint32) { - return uint32(PoolConfigBits.unwrap(config).decodeUint(PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH)); + return + uint32( + PoolConfigBits.unwrap(config).decodeUint( + PoolConfigConst.PAUSE_WINDOW_END_TIME_OFFSET, + PoolConfigConst.TIMESTAMP_BITLENGTH + ) + ); } function setPauseWindowEndTime(PoolConfigBits config, uint32 value) internal pure returns (PoolConfigBits) { return PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertUint(value, PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH) + PoolConfigBits.unwrap(config).insertUint( + value, + PoolConfigConst.PAUSE_WINDOW_END_TIME_OFFSET, + PoolConfigConst.TIMESTAMP_BITLENGTH + ) ); } // #endregion - function supportsDonation(PoolConfigBits config) internal pure returns (bool) { - return PoolConfigBits.unwrap(config).decodeBool(DONATION_OFFSET); - } - - function setDonation(PoolConfigBits config, bool enableDonation) internal pure returns (PoolConfigBits) { - return PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(enableDonation, DONATION_OFFSET)); - } - - function requireDonationEnabled(PoolConfigBits config) internal pure { - if (config.supportsDonation() == false) { - revert IVaultErrors.DoesNotSupportDonation(); - } - } - // Convert from an array of decimal differences, to the encoded 24 bit value (only uses bottom 20 bits). function toTokenDecimalDiffs(uint8[] memory tokenDecimalDiffs) internal pure returns (uint24) { bytes32 value; for (uint256 i = 0; i < tokenDecimalDiffs.length; ++i) { - value = value.insertUint(tokenDecimalDiffs[i], i * _DECIMAL_DIFF_BITLENGTH, _DECIMAL_DIFF_BITLENGTH); + value = value.insertUint( + tokenDecimalDiffs[i], + i * PoolConfigConst.DECIMAL_DIFF_BITLENGTH, + PoolConfigConst.DECIMAL_DIFF_BITLENGTH + ); } return uint24(uint256(value)); diff --git a/pkg/vault/contracts/test/VaultMock.sol b/pkg/vault/contracts/test/VaultMock.sol index a3a46b572..1e5508533 100644 --- a/pkg/vault/contracts/test/VaultMock.sol +++ b/pkg/vault/contracts/test/VaultMock.sol @@ -26,7 +26,8 @@ import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzepp import { InputHelpersMock } from "@balancer-labs/v3-solidity-utils/contracts/test/InputHelpersMock.sol"; import { VaultStateLib, VaultStateBits, VaultStateBits } from "../lib/VaultStateLib.sol"; -import { PoolConfigBits, PoolConfigLib } from "../lib/PoolConfigLib.sol"; +import { PoolConfigLib } from "../lib/PoolConfigLib.sol"; +import { HooksConfigLib } from "../lib/HooksConfigLib.sol"; import { PoolFactoryMock } from "./PoolFactoryMock.sol"; import { Vault } from "../Vault.sol"; import { VaultExtension } from "../VaultExtension.sol"; @@ -46,6 +47,7 @@ contract VaultMock is IVaultMainMock, Vault { using ScalingHelpers for uint256; using PackedTokenBalance for bytes32; using PoolConfigLib for *; + using HooksConfigLib for *; using TransientStorageHelpers for *; using StorageSlot for *; using PoolDataLib for PoolData; diff --git a/pkg/vault/test/.contract-sizes/Vault b/pkg/vault/test/.contract-sizes/Vault index c228e836c..de4857a55 100644 --- a/pkg/vault/test/.contract-sizes/Vault +++ b/pkg/vault/test/.contract-sizes/Vault @@ -1,2 +1,2 @@ -Bytecode 24.462* -InitCode 25.744 \ No newline at end of file +Bytecode 24.363* +InitCode 25.646 \ No newline at end of file diff --git a/pkg/vault/test/.contract-sizes/VaultExtension b/pkg/vault/test/.contract-sizes/VaultExtension index 902604db0..292cfee76 100644 --- a/pkg/vault/test/.contract-sizes/VaultExtension +++ b/pkg/vault/test/.contract-sizes/VaultExtension @@ -1,2 +1,2 @@ -Bytecode 20.656 -InitCode 21.845 \ No newline at end of file +Bytecode 20.631 +InitCode 21.819 \ No newline at end of file diff --git a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol new file mode 100644 index 000000000..ab4436656 --- /dev/null +++ b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; + +import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; +import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; + +import { PoolConfigConst } from "@balancer-labs/v3-vault/contracts/lib/PoolConfigConst.sol"; +import { HooksConfigLib } from "@balancer-labs/v3-vault/contracts/lib/HooksConfigLib.sol"; +import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; + +contract HooksConfigLibTest is Test { + using WordCodec for bytes32; + using HooksConfigLib for PoolConfigBits; + + function testZeroConfigBytes() public pure { + PoolConfigBits config; + + assertEq(config.enableHookAdjustedAmounts(), false, "enableHookAdjustedAmounts mismatch (zero config)"); + assertEq(config.shouldCallBeforeInitialize(), false, "shouldCallBeforeInitialize mismatch (zero config)"); + assertEq(config.shouldCallAfterInitialize(), false, "shouldCallAfterInitialize mismatch (zero config)"); + assertEq( + config.shouldCallComputeDynamicSwapFee(), + false, + "shouldCallComputeDynamicSwapFee mismatch (zero config)" + ); + assertEq(config.shouldCallBeforeSwap(), false, "shouldCallBeforeSwap mismatch (zero config)"); + assertEq(config.shouldCallAfterSwap(), false, "shouldCallAfterSwap mismatch (zero config)"); + assertEq(config.shouldCallBeforeAddLiquidity(), false, "shouldCallBeforeAddLiquidity mismatch (zero config)"); + assertEq(config.shouldCallAfterAddLiquidity(), false, "shouldCallAfterAddLiquidity mismatch (zero config)"); + assertEq( + config.shouldCallBeforeRemoveLiquidity(), + false, + "shouldCallBeforeRemoveLiquidity mismatch (zero config)" + ); + assertEq( + config.shouldCallAfterRemoveLiquidity(), + false, + "shouldCallAfterRemoveLiquidity mismatch (zero config)" + ); + } + + function testEnableHookAdjustedAmounts() public { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET) + ); + assertTrue(config.enableHookAdjustedAmounts(), "enableHookAdjustedAmounts is false (getter)"); + } + + function testSetHookAdjustedAmounts() public { + PoolConfigBits config; + config = config.setHookAdjustedAmounts(true); + assertTrue(config.enableHookAdjustedAmounts(), "enableHookAdjustedAmounts is false (setter)"); + } + + function testShouldCallBeforeInitialize() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.BEFORE_INITIALIZE_OFFSET) + ); + assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (getter)"); + } + + function testSetShouldCallBeforeInitialize() public pure { + PoolConfigBits config; + config = config = config.setShouldCallBeforeInitialize(true); + assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); + } + + function testShouldCallAfterInitialize() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.AFTER_INITIALIZE_OFFSET) + ); + assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (getter)"); + } + + function testSetShouldCallAfterInitialize() public pure { + PoolConfigBits config; + config = config.setShouldCallAfterInitialize(true); + assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (setter)"); + } + + function testShouldCallComputeDynamicSwapFee() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.DYNAMIC_SWAP_FEE_OFFSET) + ); + assertEq( + config.shouldCallComputeDynamicSwapFee(), + true, + "shouldCallComputeDynamicSwapFee should be true (getter)" + ); + } + + function testSetShouldCallComputeDynamicSwapFee() public pure { + PoolConfigBits config; + config = config.setShouldCallComputeDynamicSwapFee(true); + assertEq( + config.shouldCallComputeDynamicSwapFee(), + true, + "shouldCallComputeDynamicSwapFee should be true (setter)" + ); + } + + function testShouldCallBeforeSwap() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.BEFORE_SWAP_OFFSET) + ); + assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (getter)"); + } + + function testSetShouldCallBeforeSwap() public pure { + PoolConfigBits config; + config = config.setShouldCallBeforeSwap(true); + assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (setter)"); + } + + function testShouldCallAfterSwap() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.AFTER_SWAP_OFFSET)); + assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (getter)"); + } + + function testSetShouldCallAfterSwap() public pure { + PoolConfigBits config; + config = config.setShouldCallAfterSwap(true); + assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (setter)"); + } + + function testShouldCallBeforeAddLiquidity() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.BEFORE_ADD_LIQUIDITY_OFFSET) + ); + assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (getter)"); + } + + function testSetShouldCallBeforeAddLiquidity() public pure { + PoolConfigBits config; + config = config.setShouldCallBeforeAddLiquidity(true); + assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (setter)"); + } + + function testShouldCallAfterAddLiquidity() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.AFTER_ADD_LIQUIDITY_OFFSET) + ); + assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (getter)"); + } + + function testSetShouldCallAfterAddLiquidity() public pure { + PoolConfigBits config; + config = config.setShouldCallAfterAddLiquidity(true); + assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (setter)"); + } + + function testShouldCallBeforeRemoveLiquidity() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.BEFORE_REMOVE_LIQUIDITY_OFFSET) + ); + assertEq( + config.shouldCallBeforeRemoveLiquidity(), + true, + "shouldCallBeforeRemoveLiquidity should be true (getter)" + ); + } + + function testSetShouldCallBeforeRemoveLiquidity() public pure { + PoolConfigBits config; + config = config.setShouldCallBeforeRemoveLiquidity(true); + assertEq( + config.shouldCallBeforeRemoveLiquidity(), + true, + "shouldCallBeforeRemoveLiquidity should be true (setter)" + ); + } + + function testShouldCallAfterRemoveLiquidity() public pure { + PoolConfigBits config; + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.AFTER_REMOVE_LIQUIDITY_OFFSET) + ); + assertEq( + config.shouldCallAfterRemoveLiquidity(), + true, + "shouldCallAfterRemoveLiquidity should be true (getter)" + ); + } + + function testSetShouldCallAfterRemoveLiquidity() public pure { + PoolConfigBits config; + config = config.setShouldCallAfterRemoveLiquidity(true); + assertEq( + config.shouldCallAfterRemoveLiquidity(), + true, + "shouldCallAfterRemoveLiquidity should be true (setter)" + ); + } + + function testToHooksConfig() public pure { + address hooksContract = address(0x1234567890123456789012345678901234567890); + + PoolConfigBits config; + config = config.setHookAdjustedAmounts(true); + config = config.setShouldCallBeforeInitialize(true); + config = config.setShouldCallAfterInitialize(true); + config = config.setShouldCallComputeDynamicSwapFee(true); + config = config.setShouldCallBeforeSwap(true); + config = config.setShouldCallAfterSwap(true); + config = config.setShouldCallBeforeAddLiquidity(true); + config = config.setShouldCallAfterAddLiquidity(true); + config = config.setShouldCallBeforeRemoveLiquidity(true); + config = config.setShouldCallAfterRemoveLiquidity(true); + + HooksConfig memory hooksConfig = config.toHooksConfig(hooksContract); + assertEq(hooksConfig.shouldCallBeforeInitialize, true, "shouldCallBeforeInitialize mismatch"); + assertEq(hooksConfig.shouldCallAfterInitialize, true, "shouldCallAfterInitialize mismatch"); + assertEq(hooksConfig.shouldCallComputeDynamicSwapFee, true, "shouldCallComputeDynamicSwapFee mismatch"); + + assertEq(hooksConfig.shouldCallBeforeSwap, true, "shouldCallBeforeSwap mismatch"); + assertEq(hooksConfig.shouldCallAfterSwap, true, "shouldCallAfterSwap mismatch"); + assertEq(hooksConfig.shouldCallBeforeAddLiquidity, true, "shouldCallBeforeAddLiquidity mismatch"); + assertEq(hooksConfig.shouldCallAfterAddLiquidity, true, "shouldCallAfterAddLiquidity mismatch"); + assertEq(hooksConfig.shouldCallBeforeRemoveLiquidity, true, "shouldCallBeforeRemoveLiquidity mismatch"); + assertEq(hooksConfig.shouldCallAfterRemoveLiquidity, true, "shouldCallAfterRemoveLiquidity mismatch"); + assertEq(hooksConfig.hooksContract, hooksContract, "hooksContract mismatch"); + } +} diff --git a/pkg/vault/test/foundry/unit/PoolConfigConst.t.sol b/pkg/vault/test/foundry/unit/PoolConfigConst.t.sol new file mode 100644 index 000000000..f82f4e8af --- /dev/null +++ b/pkg/vault/test/foundry/unit/PoolConfigConst.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import { FEE_BITLENGTH } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { PoolConfigConst } from "@balancer-labs/v3-vault/contracts/lib/PoolConfigConst.sol"; + +import { BaseBitsConfigTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseBitsConfigTest.sol"; + +contract PoolConfigConstTest is BaseBitsConfigTest { + function testOffsets() public { + _checkBitsUsedOnce(PoolConfigConst.POOL_REGISTERED_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.POOL_INITIALIZED_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.POOL_PAUSED_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.POOL_RECOVERY_MODE_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.UNBALANCED_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.ADD_LIQUIDITY_CUSTOM_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.REMOVE_LIQUIDITY_CUSTOM_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.DONATION_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.BEFORE_INITIALIZE_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.AFTER_INITIALIZE_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.DYNAMIC_SWAP_FEE_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.BEFORE_SWAP_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.AFTER_SWAP_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.BEFORE_ADD_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.AFTER_ADD_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.BEFORE_REMOVE_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.AFTER_REMOVE_LIQUIDITY_OFFSET); + _checkBitsUsedOnce(PoolConfigConst.STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH); + _checkBitsUsedOnce(PoolConfigConst.AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH); + _checkBitsUsedOnce(PoolConfigConst.AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH); + _checkBitsUsedOnce( + PoolConfigConst.DECIMAL_SCALING_FACTORS_OFFSET, + PoolConfigConst.TOKEN_DECIMAL_DIFFS_BITLENGTH + ); + _checkBitsUsedOnce(PoolConfigConst.PAUSE_WINDOW_END_TIME_OFFSET, PoolConfigConst.TIMESTAMP_BITLENGTH); + } +} diff --git a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol index dd94e3463..0c3d61589 100644 --- a/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/PoolConfigLib.t.sol @@ -1,58 +1,25 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.24; - +import "forge-std/Test.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 { PoolConfigConst } from "@balancer-labs/v3-vault/contracts/lib/PoolConfigConst.sol"; import { PoolConfigLib } from "@balancer-labs/v3-vault/contracts/lib/PoolConfigLib.sol"; import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -import { BaseBitsConfigTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseBitsConfigTest.sol"; - -contract PoolConfigLibTest is BaseBitsConfigTest { +contract PoolConfigLibTest is Test { using WordCodec for bytes32; using PoolConfigLib for PoolConfigBits; - // Uses a uint24 (3 bytes): least significant 20 bits to store the values, and a 4-bit pad. - // This maximum token count is also hard-coded in the Vault. - uint8 private constant _TOKEN_DECIMAL_DIFFS_BITLENGTH = 24; - uint8 private constant _DECIMAL_DIFF_BITLENGTH = 5; - - uint8 private constant _TIMESTAMP_BITLENGTH = 32; - uint256 private constant _MAX_UINT32_VALUE = type(uint32).max; uint256 private constant _MAX_UINT24_VALUE = type(uint24).max; - function testOffsets() public { - _checkBitsUsedOnce(PoolConfigLib.POOL_REGISTERED_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.POOL_INITIALIZED_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.POOL_PAUSED_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.POOL_RECOVERY_MODE_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.BEFORE_INITIALIZE_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.AFTER_INITIALIZE_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.DYNAMIC_SWAP_FEE_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.BEFORE_SWAP_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.AFTER_SWAP_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.AFTER_ADD_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET); - _checkBitsUsedOnce(PoolConfigLib.STATIC_SWAP_FEE_OFFSET, FEE_BITLENGTH); - _checkBitsUsedOnce(PoolConfigLib.AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH); - _checkBitsUsedOnce(PoolConfigLib.AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH); - _checkBitsUsedOnce(PoolConfigLib.DECIMAL_SCALING_FACTORS_OFFSET, _TOKEN_DECIMAL_DIFFS_BITLENGTH); - _checkBitsUsedOnce(PoolConfigLib.PAUSE_WINDOW_END_TIME_OFFSET, _TIMESTAMP_BITLENGTH); - } - function testZeroConfigBytes() public { PoolConfigBits config; - assertEq(config.enableHookAdjustedAmounts(), false, "enableHookAdjustedAmounts mismatch (zero config)"); assertEq(config.isPoolRegistered(), false, "isPoolRegistered mismatch (zero config)"); assertEq(config.isPoolInitialized(), false, "isPoolInitialized mismatch (zero config)"); assertEq(config.isPoolPaused(), false, "isPoolPaused mismatch (zero config)"); @@ -60,27 +27,6 @@ contract PoolConfigLibTest is BaseBitsConfigTest { assertEq(config.supportsUnbalancedLiquidity(), true, "supportsUnbalancedLiquidity mismatch (zero config)"); assertEq(config.supportsAddLiquidityCustom(), false, "supportsAddLiquidityCustom mismatch (zero config)"); assertEq(config.supportsRemoveLiquidityCustom(), false, "supportsRemoveLiquidityCustom mismatch (zero config)"); - assertEq(config.shouldCallBeforeInitialize(), false, "shouldCallBeforeInitialize mismatch (zero config)"); - assertEq(config.shouldCallAfterInitialize(), false, "shouldCallAfterInitialize mismatch (zero config)"); - assertEq( - config.shouldCallComputeDynamicSwapFee(), - false, - "shouldCallComputeDynamicSwapFee mismatch (zero config)" - ); - assertEq(config.shouldCallBeforeSwap(), false, "shouldCallBeforeSwap mismatch (zero config)"); - assertEq(config.shouldCallAfterSwap(), false, "shouldCallAfterSwap mismatch (zero config)"); - assertEq(config.shouldCallBeforeAddLiquidity(), false, "shouldCallBeforeAddLiquidity mismatch (zero config)"); - assertEq(config.shouldCallAfterAddLiquidity(), false, "shouldCallAfterAddLiquidity mismatch (zero config)"); - assertEq( - config.shouldCallBeforeRemoveLiquidity(), - false, - "shouldCallBeforeRemoveLiquidity mismatch (zero config)" - ); - assertEq( - config.shouldCallAfterRemoveLiquidity(), - false, - "shouldCallAfterRemoveLiquidity mismatch (zero config)" - ); assertEq(config.getStaticSwapFeePercentage(), 0, "getStaticSwapFeePercentage mismatch (zero config)"); assertEq(config.getAggregateSwapFeePercentage(), 0, "getAggregateSwapFeePercentage mismatch (zero config)"); assertEq(config.getAggregateYieldFeePercentage(), 0, "getAggregateYieldFeePercentage mismatch (zero config)"); @@ -89,24 +35,11 @@ contract PoolConfigLibTest is BaseBitsConfigTest { } // #region Tests for main pool config settings - function testEnableHookAdjustedAmounts() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.ENABLE_HOOK_ADJUSTED_AMOUNTS_OFFSET) - ); - assertTrue(config.enableHookAdjustedAmounts(), "enableHookAdjustedAmounts is false (getter)"); - } - - function testSetHookAdjustedAmounts() public { - PoolConfigBits config; - config = config.setHookAdjustedAmounts(true); - assertTrue(config.enableHookAdjustedAmounts(), "enableHookAdjustedAmounts is false (setter)"); - } function testIsPoolRegistered() public { PoolConfigBits config; config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_REGISTERED_OFFSET) + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.POOL_REGISTERED_OFFSET) ); assertTrue(config.isPoolRegistered(), "isPoolRegistered is false (getter)"); } @@ -120,7 +53,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testIsPoolInitialized() public { PoolConfigBits config; config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_INITIALIZED_OFFSET) + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.POOL_INITIALIZED_OFFSET) ); assertTrue(config.isPoolInitialized(), "isPoolInitialized is false (getter)"); } @@ -133,7 +66,9 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testIsPoolPaused() public { PoolConfigBits config; - config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_PAUSED_OFFSET)); + config = PoolConfigBits.wrap( + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.POOL_PAUSED_OFFSET) + ); assertTrue(config.isPoolPaused(), "isPoolPaused is false (getter)"); } @@ -146,7 +81,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testIsPoolInRecoveryMode() public { PoolConfigBits config; config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.POOL_RECOVERY_MODE_OFFSET) + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.POOL_RECOVERY_MODE_OFFSET) ); assertTrue(config.isPoolInRecoveryMode(), "isPoolInRecoveryMode is false (getter)"); } @@ -163,7 +98,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testSupportsUnbalancedLiquidity() public { PoolConfigBits config; config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.UNBALANCED_LIQUIDITY_OFFSET) + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.UNBALANCED_LIQUIDITY_OFFSET) ); // NOTE: assertFalse is here because supportsUnbalancedLiquidity reverse value assertFalse(config.supportsUnbalancedLiquidity(), "supportsUnbalancedLiquidity is true (getter)"); @@ -194,7 +129,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testSupportsAddLiquidityCustom() public { PoolConfigBits config; config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.ADD_LIQUIDITY_CUSTOM_OFFSET) + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.ADD_LIQUIDITY_CUSTOM_OFFSET) ); assertTrue(config.supportsAddLiquidityCustom(), "supportsAddLiquidityCustom is false (getter)"); } @@ -222,7 +157,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { function testSupportsRemoveLiquidityCustom() public { PoolConfigBits config; config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.REMOVE_LIQUIDITY_CUSTOM_OFFSET) + PoolConfigBits.unwrap(config).insertBool(true, PoolConfigConst.REMOVE_LIQUIDITY_CUSTOM_OFFSET) ); assertTrue(config.supportsRemoveLiquidityCustom(), "supportsRemoveLiquidityCustom is false (getter)"); } @@ -249,192 +184,13 @@ contract PoolConfigLibTest is BaseBitsConfigTest { // #endregion - // #region Tests for hooks config - function testShouldCallBeforeInitialize() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_INITIALIZE_OFFSET) - ); - assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (getter)"); - } - - function testSetShouldCallBeforeInitialize() public { - PoolConfigBits config; - config = config = config.setShouldCallBeforeInitialize(true); - assertEq(config.shouldCallBeforeInitialize(), true, "shouldCallBeforeInitialize should be true (setter)"); - } - - function testShouldCallAfterInitialize() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_INITIALIZE_OFFSET) - ); - assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (getter)"); - } - - function testSetShouldCallAfterInitialize() public { - PoolConfigBits config; - config = config.setShouldCallAfterInitialize(true); - assertEq(config.shouldCallAfterInitialize(), true, "shouldCallAfterInitialize should be true (setter)"); - } - - function testShouldCallComputeDynamicSwapFee() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.DYNAMIC_SWAP_FEE_OFFSET) - ); - assertEq( - config.shouldCallComputeDynamicSwapFee(), - true, - "shouldCallComputeDynamicSwapFee should be true (getter)" - ); - } - - function testSetShouldCallComputeDynamicSwapFee() public { - PoolConfigBits config; - config = config.setShouldCallComputeDynamicSwapFee(true); - assertEq( - config.shouldCallComputeDynamicSwapFee(), - true, - "shouldCallComputeDynamicSwapFee should be true (setter)" - ); - } - - function testShouldCallBeforeSwap() public { - PoolConfigBits config; - config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_SWAP_OFFSET)); - assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (getter)"); - } - - function testSetShouldCallBeforeSwap() public { - PoolConfigBits config; - config = config.setShouldCallBeforeSwap(true); - assertEq(config.shouldCallBeforeSwap(), true, "shouldCallBeforeSwap should be true (setter)"); - } - - function testShouldCallAfterSwap() public { - PoolConfigBits config; - config = PoolConfigBits.wrap(PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_SWAP_OFFSET)); - assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (getter)"); - } - - function testSetShouldCallAfterSwap() public { - PoolConfigBits config; - config = config.setShouldCallAfterSwap(true); - assertEq(config.shouldCallAfterSwap(), true, "shouldCallAfterSwap should be true (setter)"); - } - - function testShouldCallBeforeAddLiquidity() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_ADD_LIQUIDITY_OFFSET) - ); - assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (getter)"); - } - - function testSetShouldCallBeforeAddLiquidity() public { - PoolConfigBits config; - config = config.setShouldCallBeforeAddLiquidity(true); - assertEq(config.shouldCallBeforeAddLiquidity(), true, "shouldCallBeforeAddLiquidity should be true (setter)"); - } - - function testShouldCallAfterAddLiquidity() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_ADD_LIQUIDITY_OFFSET) - ); - assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (getter)"); - } - - function testSetShouldCallAfterAddLiquidity() public { - PoolConfigBits config; - config = config.setShouldCallAfterAddLiquidity(true); - assertEq(config.shouldCallAfterAddLiquidity(), true, "shouldCallAfterAddLiquidity should be true (setter)"); - } - - function testShouldCallBeforeRemoveLiquidity() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.BEFORE_REMOVE_LIQUIDITY_OFFSET) - ); - assertEq( - config.shouldCallBeforeRemoveLiquidity(), - true, - "shouldCallBeforeRemoveLiquidity should be true (getter)" - ); - } - - function testSetShouldCallBeforeRemoveLiquidity() public { - PoolConfigBits config; - config = config.setShouldCallBeforeRemoveLiquidity(true); - assertEq( - config.shouldCallBeforeRemoveLiquidity(), - true, - "shouldCallBeforeRemoveLiquidity should be true (setter)" - ); - } - - function testShouldCallAfterRemoveLiquidity() public { - PoolConfigBits config; - config = PoolConfigBits.wrap( - PoolConfigBits.unwrap(config).insertBool(true, PoolConfigLib.AFTER_REMOVE_LIQUIDITY_OFFSET) - ); - assertEq( - config.shouldCallAfterRemoveLiquidity(), - true, - "shouldCallAfterRemoveLiquidity should be true (getter)" - ); - } - - function testSetShouldCallAfterRemoveLiquidity() public { - PoolConfigBits config; - config = config.setShouldCallAfterRemoveLiquidity(true); - assertEq( - config.shouldCallAfterRemoveLiquidity(), - true, - "shouldCallAfterRemoveLiquidity should be true (setter)" - ); - } - - function testToHooksConfig() public { - address hooksContract = address(0x1234567890123456789012345678901234567890); - - PoolConfigBits config; - config = config.setHookAdjustedAmounts(true); - config = config.setShouldCallBeforeInitialize(true); - config = config.setShouldCallAfterInitialize(true); - config = config.setShouldCallComputeDynamicSwapFee(true); - config = config.setShouldCallBeforeSwap(true); - config = config.setShouldCallAfterSwap(true); - config = config.setShouldCallBeforeAddLiquidity(true); - config = config.setShouldCallAfterAddLiquidity(true); - config = config.setShouldCallBeforeRemoveLiquidity(true); - config = config.setShouldCallAfterRemoveLiquidity(true); - - HooksConfig memory hooksConfig = config.toHooksConfig(hooksContract); - assertEq(hooksConfig.shouldCallBeforeInitialize, true, "shouldCallBeforeInitialize mismatch"); - assertEq(hooksConfig.shouldCallAfterInitialize, true, "shouldCallAfterInitialize mismatch"); - assertEq(hooksConfig.shouldCallComputeDynamicSwapFee, true, "shouldCallComputeDynamicSwapFee mismatch"); - - assertEq(hooksConfig.shouldCallBeforeSwap, true, "shouldCallBeforeSwap mismatch"); - assertEq(hooksConfig.shouldCallAfterSwap, true, "shouldCallAfterSwap mismatch"); - assertEq(hooksConfig.shouldCallBeforeAddLiquidity, true, "shouldCallBeforeAddLiquidity mismatch"); - assertEq(hooksConfig.shouldCallAfterAddLiquidity, true, "shouldCallAfterAddLiquidity mismatch"); - assertEq(hooksConfig.shouldCallBeforeRemoveLiquidity, true, "shouldCallBeforeRemoveLiquidity mismatch"); - assertEq(hooksConfig.shouldCallAfterRemoveLiquidity, true, "shouldCallAfterRemoveLiquidity mismatch"); - assertEq(hooksConfig.hooksContract, hooksContract, "hooksContract mismatch"); - } - - // #region - // #region Tests for uint values - function testGetAggregateSwapFeePercentage() public { PoolConfigBits config; config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertUint( _MAX_UINT24_VALUE, - PoolConfigLib.AGGREGATE_SWAP_FEE_OFFSET, + PoolConfigConst.AGGREGATE_SWAP_FEE_OFFSET, FEE_BITLENGTH ) ); @@ -462,7 +218,7 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertUint( _MAX_UINT24_VALUE, - PoolConfigLib.AGGREGATE_YIELD_FEE_OFFSET, + PoolConfigConst.AGGREGATE_YIELD_FEE_OFFSET, FEE_BITLENGTH ) ); @@ -490,8 +246,8 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertUint( _MAX_UINT24_VALUE, - PoolConfigLib.DECIMAL_SCALING_FACTORS_OFFSET, - _TOKEN_DECIMAL_DIFFS_BITLENGTH + PoolConfigConst.DECIMAL_SCALING_FACTORS_OFFSET, + PoolConfigConst.TOKEN_DECIMAL_DIFFS_BITLENGTH ) ); assertEq( @@ -514,10 +270,10 @@ contract PoolConfigLibTest is BaseBitsConfigTest { uint256 valueTwo = 20; bytes32 value = bytes32(0); - value = value.insertUint(valueOne, 0, _DECIMAL_DIFF_BITLENGTH).insertUint( + value = value.insertUint(valueOne, 0, PoolConfigConst.DECIMAL_DIFF_BITLENGTH).insertUint( valueTwo, - _DECIMAL_DIFF_BITLENGTH, - _DECIMAL_DIFF_BITLENGTH + PoolConfigConst.DECIMAL_DIFF_BITLENGTH, + PoolConfigConst.DECIMAL_DIFF_BITLENGTH ); config = config.setTokenDecimalDiffs(uint24(uint256(value))); @@ -533,8 +289,8 @@ contract PoolConfigLibTest is BaseBitsConfigTest { config = PoolConfigBits.wrap( PoolConfigBits.unwrap(config).insertUint( _MAX_UINT32_VALUE, - PoolConfigLib.PAUSE_WINDOW_END_TIME_OFFSET, - _TIMESTAMP_BITLENGTH + PoolConfigConst.PAUSE_WINDOW_END_TIME_OFFSET, + PoolConfigConst.TIMESTAMP_BITLENGTH ) ); assertEq( @@ -559,10 +315,10 @@ contract PoolConfigLibTest is BaseBitsConfigTest { tokenDecimalDiffs[1] = 2; uint256 value = uint256( - bytes32(0).insertUint(tokenDecimalDiffs[0], 0, _DECIMAL_DIFF_BITLENGTH).insertUint( + bytes32(0).insertUint(tokenDecimalDiffs[0], 0, PoolConfigConst.DECIMAL_DIFF_BITLENGTH).insertUint( tokenDecimalDiffs[1], - _DECIMAL_DIFF_BITLENGTH, - _DECIMAL_DIFF_BITLENGTH + PoolConfigConst.DECIMAL_DIFF_BITLENGTH, + PoolConfigConst.DECIMAL_DIFF_BITLENGTH ) ); @@ -572,6 +328,4 @@ contract PoolConfigLibTest is BaseBitsConfigTest { "tokenDecimalDiffs mismatch (testToTokenDecimalDiffs)" ); } - - // #endregion } diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots index a9c2aa6f0..e32efaff2 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -175.8k \ No newline at end of file +175.7k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots index 885608b62..e2eff8c73 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -159.2k \ No newline at end of file +159.1k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots index f3b28ae4f..33862ebdb 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -209.1k \ No newline at end of file +209.0k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots index fd76c368e..de3bc9a92 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -175.4k \ No newline at end of file +175.3k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation index 34e80eef2..68b670a11 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation @@ -1 +1 @@ -178.9k \ No newline at end of file +178.8k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH index e332c8ee4..3f495065a 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH @@ -1 +1 @@ -343.0k \ No newline at end of file +342.9k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH index 65c719a49..c32c3e95f 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH @@ -1 +1 @@ -330.4k \ No newline at end of file +330.3k \ No newline at end of file From 9ca0d023a251fcd39187919fea135fae407750ca Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Tue, 25 Jun 2024 20:09:42 +0200 Subject: [PATCH 13/18] save snapshot --- .../.hardhat-snapshots/[WeightedPool] initialize without ETH | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH index 15104d699..8e808fb0d 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH @@ -1 +1 @@ -335.6k \ No newline at end of file +335.5k \ No newline at end of file From 820d45d3325276c740c96d83c27784dd794f4279 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Tue, 25 Jun 2024 20:34:19 +0200 Subject: [PATCH 14/18] rest merging artifacts --- pkg/vault/test/.contract-sizes/Vault | 9 ++------- pkg/vault/test/.contract-sizes/VaultAdmin | 9 ++------- pkg/vault/test/.contract-sizes/VaultExtension | 9 ++------- ...rd] swap single token exact in with fees - cold slots | 6 +----- ...rd] swap single token exact in with fees - warm slots | 6 +----- ...te] swap single token exact in with fees - cold slots | 6 +----- ...te] swap single token exact in with fees - warm slots | 6 +----- .../test/gas/.hardhat-snapshots/[PoolMock] donation | 6 +----- .../.hardhat-snapshots/[PoolMock] initialize with ETH | 6 +----- .../.hardhat-snapshots/[PoolMock] initialize without ETH | 6 +----- 10 files changed, 13 insertions(+), 56 deletions(-) diff --git a/pkg/vault/test/.contract-sizes/Vault b/pkg/vault/test/.contract-sizes/Vault index 0b6e9d0ba..1a7ca5cb6 100644 --- a/pkg/vault/test/.contract-sizes/Vault +++ b/pkg/vault/test/.contract-sizes/Vault @@ -1,7 +1,2 @@ -<<<<<<< HEAD -Bytecode 24.363* -InitCode 25.646 -======= -Bytecode 24.047* -InitCode 25.329 ->>>>>>> main +Bytecode 24.292* +InitCode 25.574 \ No newline at end of file diff --git a/pkg/vault/test/.contract-sizes/VaultAdmin b/pkg/vault/test/.contract-sizes/VaultAdmin index c4c025473..b457fb9b5 100644 --- a/pkg/vault/test/.contract-sizes/VaultAdmin +++ b/pkg/vault/test/.contract-sizes/VaultAdmin @@ -1,7 +1,2 @@ -<<<<<<< HEAD -Bytecode 11.848 -InitCode 12.885 -======= -Bytecode 11.618 -InitCode 12.655 ->>>>>>> main +Bytecode 11.810 +InitCode 12.847 \ No newline at end of file diff --git a/pkg/vault/test/.contract-sizes/VaultExtension b/pkg/vault/test/.contract-sizes/VaultExtension index 38a02c2f0..3b17e7630 100644 --- a/pkg/vault/test/.contract-sizes/VaultExtension +++ b/pkg/vault/test/.contract-sizes/VaultExtension @@ -1,7 +1,2 @@ -<<<<<<< HEAD -Bytecode 20.631 -InitCode 21.819 -======= -Bytecode 20.032 -InitCode 21.221 ->>>>>>> main +Bytecode 20.623 +InitCode 21.812 \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots index bfa73aa86..966e96749 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots @@ -1,5 +1 @@ -<<<<<<< HEAD -175.7k -======= -177.1k ->>>>>>> main +175.2k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots index cd61de20a..235d9b73c 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots @@ -1,5 +1 @@ -<<<<<<< HEAD -159.1k -======= -160.5k ->>>>>>> main +158.6k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots index dbed8bf8f..a635c111c 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots @@ -1,5 +1 @@ -<<<<<<< HEAD -209.0k -======= -210.4k ->>>>>>> main +208.5k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots index f9549f83f..743bc5607 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots @@ -1,5 +1 @@ -<<<<<<< HEAD -175.3k -======= -176.7k ->>>>>>> main +174.8k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation index 632205f98..8b4740447 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation @@ -1,5 +1 @@ -<<<<<<< HEAD -178.8k -======= -180.0k ->>>>>>> main +178.4k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH index 5d7b8df93..c540a1e62 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH @@ -1,5 +1 @@ -<<<<<<< HEAD -342.9k -======= -344.5k ->>>>>>> main +342.7k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH index 9db3c2131..d8b0b15d6 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH @@ -1,5 +1 @@ -<<<<<<< HEAD -330.3k -======= -332.0k ->>>>>>> main +330.1k \ No newline at end of file From a74ce66a0e4f2a7b1fe8e4fda678ec779d7da761 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Wed, 26 Jun 2024 19:37:02 +0200 Subject: [PATCH 15/18] fixes --- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- ...ngle token exact in with fees - cold slots | 2 +- ...ngle token exact in with fees - warm slots | 2 +- .../[WeightedPool] donation | 2 +- .../[WeightedPool] initialize with ETH | 2 +- .../[WeightedPool] initialize without ETH | 2 +- ...forkBoostedPoolEmptyBufferSwapExactIn.snap | 2 +- ...orkBoostedPoolEmptyBufferSwapExactOut.snap | 2 +- .../forkBoostedPoolSwapExactIn.snap | 2 +- .../forkBoostedPoolSwapExactOut.snap | 2 +- .../forkBoostedPoolSwapTooLarge-ExactIn.snap | 2 +- .../forkBoostedPoolSwapTooLarge-ExactOut.snap | 2 +- pkg/vault/contracts/Vault.sol | 106 ++++++++++-------- pkg/vault/contracts/VaultExtension.sol | 22 +++- pkg/vault/contracts/VaultStorage.sol | 2 +- pkg/vault/contracts/lib/HooksConfigLib.sol | 101 +++++------------ pkg/vault/contracts/test/VaultMock.sol | 2 +- .../test/foundry/unit/HooksConfigLib.t.sol | 2 +- 19 files changed, 118 insertions(+), 143 deletions(-) diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots index 4d8d00b20..973e2eec4 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -175.1k \ No newline at end of file +174.6k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots index 235d9b73c..70f835904 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -158.6k \ No newline at end of file +158.1k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots index ead95ac6c..df92d0062 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -208.4k \ No newline at end of file +207.9k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots index 743bc5607..483555e0b 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -174.8k \ No newline at end of file +174.3k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation index 8b4740447..3926676da 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] donation @@ -1 +1 @@ -178.4k \ No newline at end of file +177.8k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH index 7faabe4fc..bcae0eab2 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize with ETH @@ -1 +1 @@ -347.9k \ No newline at end of file +347.8k \ No newline at end of file diff --git a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH index 02b7f4b6d..a886fafae 100644 --- a/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH +++ b/pkg/pool-weighted/test/gas/.hardhat-snapshots/[WeightedPool] initialize without ETH @@ -1 +1 @@ -335.4k \ No newline at end of file +335.2k \ No newline at end of file diff --git a/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactIn.snap b/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactIn.snap index 643cf8589..525466901 100644 --- a/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactIn.snap +++ b/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactIn.snap @@ -1 +1 @@ -727080 \ No newline at end of file +726042 \ No newline at end of file diff --git a/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactOut.snap b/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactOut.snap index 59a0db97e..fcecd03b8 100644 --- a/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactOut.snap +++ b/pkg/vault/.forge-snapshots/forkBoostedPoolEmptyBufferSwapExactOut.snap @@ -1 +1 @@ -753961 \ No newline at end of file +752947 \ No newline at end of file diff --git a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactIn.snap b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactIn.snap index fd799d18c..191279514 100644 --- a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactIn.snap +++ b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactIn.snap @@ -1 +1 @@ -289582 \ No newline at end of file +289070 \ No newline at end of file diff --git a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactOut.snap b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactOut.snap index 129e9f500..f6135d90d 100644 --- a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactOut.snap +++ b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapExactOut.snap @@ -1 +1 @@ -312404 \ No newline at end of file +311892 \ No newline at end of file diff --git a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactIn.snap b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactIn.snap index b1ab32db3..dff03652a 100644 --- a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactIn.snap +++ b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactIn.snap @@ -1 +1 @@ -670685 \ No newline at end of file +669647 \ No newline at end of file diff --git a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactOut.snap b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactOut.snap index 66704186a..023d0b10a 100644 --- a/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactOut.snap +++ b/pkg/vault/.forge-snapshots/forkBoostedPoolSwapTooLarge-ExactOut.snap @@ -1 +1 @@ -697572 \ No newline at end of file +696558 \ No newline at end of file diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index 38fc272cf..b481221c1 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -204,8 +204,9 @@ contract Vault is IVaultMain, VaultCommon, Proxy { IBasePool.PoolSwapParams memory swapParams = _buildPoolSwapParams(params, state, poolData); - StorageSlot.AddressSlot storage hooksContract = _hooksContracts[params.pool]; - if (poolData.poolConfigBits.callBeforeSwapHook(swapParams, params.pool, hooksContract)) { + if (poolData.poolConfigBits.shouldCallBeforeSwap()) { + poolData.poolConfigBits.callBeforeSwapHook(swapParams, params.pool, _hooksContracts[params.pool]); + // The call to `onBeforeSwap` could potentially update token rates and balances. // We update `poolData.tokenRates`, `poolData.rawBalances` and `poolData.balancesLiveScaled18` // to ensure the `onSwap` and `onComputeDynamicSwapFee` are called with the current values. @@ -222,13 +223,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // At this point, the static swap fee percentage is loaded in the swap state as the default, // to be used unless the pool has a dynamic swap fee. It is also passed into the hook, to support common cases // where the dynamic fee computation logic uses it. - (bool dynamicSwapFeeCalculated, uint256 dynamicSwapFee) = poolData.poolConfigBits.callComputeDynamicSwapFeeHook( - swapParams, - state.swapFeePercentage, - hooksContract - ); - if (dynamicSwapFeeCalculated) { - state.swapFeePercentage = dynamicSwapFee; + if (poolData.poolConfigBits.shouldCallComputeDynamicSwapFee()) { + (bool dynamicSwapFeeCalculated, uint256 dynamicSwapFee) = poolData + .poolConfigBits + .callComputeDynamicSwapFeeHook(swapParams, state.swapFeePercentage, _hooksContracts[params.pool]); + + if (dynamicSwapFeeCalculated) { + state.swapFeePercentage = dynamicSwapFee; + } } // Non-reentrant call that updates accounting. @@ -240,15 +242,20 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // If the hook contract does not exist or does not implement onAfterSwap, PoolConfigLib returns the original // amountCalculated. Otherwise, the new amount calculated is 'amountCalculated + delta'. If the underlying // hook fails, or limits are violated, `onAfterSwap` will revert. - amountCalculated = poolData.poolConfigBits.callAfterSwapHook( - amountCalculatedScaled18, - amountCalculated, - msg.sender, - params, - state, - poolData, - hooksContract - ); + if (poolData.poolConfigBits.shouldCallAfterSwap()) { + // fix stack too deep + IHooks hooksContract = _hooksContracts[params.pool]; + + amountCalculated = poolData.poolConfigBits.callAfterSwapHook( + amountCalculatedScaled18, + amountCalculated, + msg.sender, + params, + state, + poolData, + hooksContract + ); + } if (params.kind == SwapKind.EXACT_IN) { amountOut = amountCalculated; @@ -529,16 +536,14 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - StorageSlot.AddressSlot storage hooksContract = _hooksContracts[params.pool]; - if ( + if (poolData.poolConfigBits.shouldCallBeforeAddLiquidity()) { poolData.poolConfigBits.callBeforeAddLiquidityHook( msg.sender, maxAmountsInScaled18, params, poolData, - hooksContract - ) - ) { + _hooksContracts[params.pool] + ); // If the hook library returns true, the hook code was executed, and might have altered the balances, // so we need to read them again to ensure that the data is fresh moving forward. // We also need to upscale (adding liquidity, so round up) again. @@ -566,15 +571,20 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // AmountsIn can be changed by onAfterAddLiquidity if the hook charges fees or gives discounts // Uses msg.sender as the router (the contract that called the vault) - amountsIn = poolData.poolConfigBits.callAfterAddLiquidityHook( - msg.sender, - amountsInScaled18, - amountsIn, - bptAmountOut, - params, - poolData, - hooksContract - ); + if (poolData.poolConfigBits.shouldCallAfterAddLiquidity()) { + // fix stack too deep + IHooks hooksContract = _hooksContracts[params.pool]; + + amountsIn = poolData.poolConfigBits.callAfterAddLiquidityHook( + msg.sender, + amountsInScaled18, + amountsIn, + bptAmountOut, + params, + poolData, + hooksContract + ); + } } /// @dev Avoid "stack too deep" - without polluting the Add/RemoveLiquidity params interface. @@ -763,17 +773,16 @@ contract Vault is IVaultMain, VaultCommon, Proxy { poolData.tokenRates ); - StorageSlot.AddressSlot storage hooksContract = _hooksContracts[params.pool]; // Uses msg.sender as the router (the contract that called the vault) - if ( + if (poolData.poolConfigBits.shouldCallBeforeRemoveLiquidity()) { poolData.poolConfigBits.callBeforeRemoveLiquidityHook( minAmountsOutScaled18, msg.sender, params, poolData, - hooksContract - ) - ) { + _hooksContracts[params.pool] + ); + // The hook might alter the balances, so we need to read them again to ensure that the data is // fresh moving forward. // We also need to upscale (removing liquidity, so round down) again. @@ -799,15 +808,20 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // AmountsOut can be changed by onAfterRemoveLiquidity if the hook charges fees or gives discounts // Uses msg.sender as the router (the contract that called the vault) - amountsOut = poolData.poolConfigBits.callAfterRemoveLiquidityHook( - msg.sender, - amountsOutScaled18, - amountsOut, - bptAmountIn, - params, - poolData, - hooksContract - ); + if (poolData.poolConfigBits.shouldCallAfterRemoveLiquidity()) { + // fix stack too deep + IHooks hooksContract = _hooksContracts[params.pool]; + + amountsOut = poolData.poolConfigBits.callAfterRemoveLiquidityHook( + msg.sender, + amountsOutScaled18, + amountsOut, + bptAmountIn, + params, + poolData, + hooksContract + ); + } } /** diff --git a/pkg/vault/contracts/VaultExtension.sol b/pkg/vault/contracts/VaultExtension.sol index ec3ec088e..003e40167 100644 --- a/pkg/vault/contracts/VaultExtension.sol +++ b/pkg/vault/contracts/VaultExtension.sol @@ -315,7 +315,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { } _poolConfigBits[pool] = poolConfigBits; - _hooksContracts[pool].value = params.poolHooksContract; + _hooksContracts[pool] = IHooks(params.poolHooksContract); } _setStaticSwapFeePercentage(pool, params.swapFeePercentage); @@ -328,7 +328,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { params.swapFeePercentage, params.pauseWindowEndTime, params.roleAccounts, - poolConfigBits.toHooksConfig(params.poolHooksContract), + poolConfigBits.toHooksConfig(IHooks(params.poolHooksContract)), params.liquidityManagement ); } @@ -386,8 +386,8 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { poolData.tokenRates ); - StorageSlot.AddressSlot storage hooksContract = _hooksContracts[pool]; - if (poolData.poolConfigBits.callBeforeInitializeHook(exactAmountsInScaled18, userData, hooksContract)) { + if (poolData.poolConfigBits.shouldCallBeforeInitialize()) { + poolData.poolConfigBits.callBeforeInitializeHook(exactAmountsInScaled18, userData, _hooksContracts[pool]); // The before hook is reentrant, and could have changed token rates. // Updating balances here is unnecessary since they're 0, but we do not special case before init // for the sake of bytecode size. @@ -402,7 +402,17 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { bptAmountOut = _initialize(pool, to, poolData, tokens, exactAmountsIn, exactAmountsInScaled18, minBptAmountOut); - poolData.poolConfigBits.callAfterInitializeHook(exactAmountsInScaled18, bptAmountOut, userData, hooksContract); + if (poolData.poolConfigBits.shouldCallAfterInitialize()) { + // fix stack too deep + IHooks hooksContract = _hooksContracts[pool]; + + poolData.poolConfigBits.callAfterInitializeHook( + exactAmountsInScaled18, + bptAmountOut, + userData, + hooksContract + ); + } } function _initialize( @@ -505,7 +515,7 @@ contract VaultExtension is IVaultExtension, VaultCommon, Proxy { function getHooksConfig( address pool ) external view onlyVaultDelegateCall withRegisteredPool(pool) returns (HooksConfig memory) { - return _poolConfigBits[pool].toHooksConfig(_hooksContracts[pool].value); + return _poolConfigBits[pool].toHooksConfig(_hooksContracts[pool]); } /// @inheritdoc IVaultExtension diff --git a/pkg/vault/contracts/VaultStorage.sol b/pkg/vault/contracts/VaultStorage.sol index 39cd07dc0..20db6ae73 100644 --- a/pkg/vault/contracts/VaultStorage.sol +++ b/pkg/vault/contracts/VaultStorage.sol @@ -71,7 +71,7 @@ contract VaultStorage { mapping(address => PoolConfigBits) internal _poolConfigBits; // Registry of pool hooks contracts. - mapping(address => StorageSlot.AddressSlot) internal _hooksContracts; + mapping(address => IHooks) internal _hooksContracts; // Pool -> (token -> PackedTokenBalance): structure containing the current raw and "last live" scaled balances. // Last live balances are used for yield fee computation, and since these have rates applied, they are stored diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol index 5999b9943..e2c806acf 100644 --- a/pkg/vault/contracts/lib/HooksConfigLib.sol +++ b/pkg/vault/contracts/lib/HooksConfigLib.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.24; -import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.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"; @@ -132,7 +130,7 @@ library HooksConfigLib { ); } - function toHooksConfig(PoolConfigBits config, address hooksContract) internal pure returns (HooksConfig memory) { + function toHooksConfig(PoolConfigBits config, IHooks hooksContract) internal pure returns (HooksConfig memory) { return HooksConfig({ enableHookAdjustedAmounts: config.enableHookAdjustedAmounts(), @@ -145,7 +143,7 @@ library HooksConfigLib { shouldCallComputeDynamicSwapFee: config.shouldCallComputeDynamicSwapFee(), shouldCallBeforeSwap: config.shouldCallBeforeSwap(), shouldCallAfterSwap: config.shouldCallAfterSwap(), - hooksContract: hooksContract + hooksContract: address(hooksContract) }); } @@ -168,13 +166,9 @@ library HooksConfigLib { PoolConfigBits config, IBasePool.PoolSwapParams memory swapParams, uint256 staticSwapFeePercentage, - StorageSlot.AddressSlot storage hooksContract + IHooks hooksContract ) internal view returns (bool, uint256) { - if (config.shouldCallComputeDynamicSwapFee() == false) { - return (false, staticSwapFeePercentage); - } - - (bool success, uint256 swapFeePercentage) = IHooks(hooksContract.value).onComputeDynamicSwapFee( + (bool success, uint256 swapFeePercentage) = hooksContract.onComputeDynamicSwapFee( swapParams, staticSwapFeePercentage ); @@ -193,24 +187,17 @@ library HooksConfigLib { * @param swapParams The swap parameters used in the hook * @param pool Pool address * @param hooksContract Storage slot with the address of the hooks contract - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute */ function callBeforeSwapHook( PoolConfigBits config, IBasePool.PoolSwapParams memory swapParams, address pool, - StorageSlot.AddressSlot storage hooksContract - ) internal returns (bool) { - if (config.shouldCallBeforeSwap() == false) { - // Hook contract does not implement onBeforeSwap, so success is false (hook was not executed) - return false; - } - - if (IHooks(hooksContract.value).onBeforeSwap(swapParams, pool) == false) { + IHooks hooksContract + ) internal { + if (hooksContract.onBeforeSwap(swapParams, pool) == false) { // Hook contract implements onBeforeSwap, but it has failed, so reverts the transaction. revert IVaultErrors.BeforeSwapHookFailed(); } - return true; } /** @@ -235,20 +222,14 @@ library HooksConfigLib { SwapParams memory params, SwapState memory state, PoolData memory poolData, - StorageSlot.AddressSlot storage hooksContract + IHooks hooksContract ) internal returns (uint256) { - if (config.shouldCallAfterSwap() == false) { - // Hook contract does not implement onAfterSwap, so success is false (hook was not executed) and do not - // change amountCalculatedRaw (no deltas) - return amountCalculatedRaw; - } - // Adjust balances for the AfterSwap hook. (uint256 amountInScaled18, uint256 amountOutScaled18) = params.kind == SwapKind.EXACT_IN ? (state.amountGivenScaled18, amountCalculatedScaled18) : (amountCalculatedScaled18, state.amountGivenScaled18); - (bool success, uint256 hookAdjustedAmountCalculatedRaw) = IHooks(hooksContract.value).onAfterSwap( + (bool success, uint256 hookAdjustedAmountCalculatedRaw) = hooksContract.onAfterSwap( IHooks.AfterSwapParams({ kind: params.kind, tokenIn: params.tokenIn, @@ -295,7 +276,6 @@ library HooksConfigLib { * @param params The add liquidity parameters * @param poolData Struct containing balance and token information of the pool * @param hooksContract Storage slot with the address of the hooks contract - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute */ function callBeforeAddLiquidityHook( PoolConfigBits config, @@ -303,14 +283,10 @@ library HooksConfigLib { uint256[] memory maxAmountsInScaled18, AddLiquidityParams memory params, PoolData memory poolData, - StorageSlot.AddressSlot storage hooksContract - ) internal returns (bool) { - if (config.shouldCallBeforeAddLiquidity() == false) { - return false; - } - + IHooks hooksContract + ) internal { if ( - IHooks(hooksContract.value).onBeforeAddLiquidity( + hooksContract.onBeforeAddLiquidity( router, params.pool, params.kind, @@ -322,7 +298,6 @@ library HooksConfigLib { ) { revert IVaultErrors.BeforeAddLiquidityHookFailed(); } - return true; } /** @@ -347,13 +322,9 @@ library HooksConfigLib { uint256 bptAmountOut, AddLiquidityParams memory params, PoolData memory poolData, - StorageSlot.AddressSlot storage hooksContract + IHooks hooksContract ) internal returns (uint256[] memory) { - if (config.shouldCallAfterAddLiquidity() == false) { - return amountsInRaw; - } - - (bool success, uint256[] memory hookAdjustedAmountsInRaw) = IHooks(hooksContract.value).onAfterAddLiquidity( + (bool success, uint256[] memory hookAdjustedAmountsInRaw) = hooksContract.onAfterAddLiquidity( router, params.pool, params.kind, @@ -391,13 +362,11 @@ library HooksConfigLib { * fails to execute the hook. * * @param config The encoded hooks configuration - * @param minAmountsOutScaled18 An array with minimum amounts for each output token of the remove liquidity - * operation + * @param minAmountsOutScaled18 Minimum amounts for each output token of the remove liquidity operation * @param router Router address * @param params The remove liquidity parameters * @param poolData Struct containing balance and token information of the pool * @param hooksContract Storage slot with the address of the hooks contract - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute */ function callBeforeRemoveLiquidityHook( PoolConfigBits config, @@ -405,14 +374,10 @@ library HooksConfigLib { address router, RemoveLiquidityParams memory params, PoolData memory poolData, - StorageSlot.AddressSlot storage hooksContract - ) internal returns (bool) { - if (config.shouldCallBeforeRemoveLiquidity() == false) { - return false; - } - + IHooks hooksContract + ) internal { if ( - IHooks(hooksContract.value).onBeforeRemoveLiquidity( + hooksContract.onBeforeRemoveLiquidity( router, params.pool, params.kind, @@ -424,12 +389,12 @@ library HooksConfigLib { ) { revert IVaultErrors.BeforeRemoveLiquidityHookFailed(); } - return true; } /** * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract * fails to execute the hook. + * * @param config The encoded hooks configuration * @param router Router address * @param amountsOutScaled18 Scaled amount of tokens to receive, sorted in token registration order @@ -448,13 +413,9 @@ library HooksConfigLib { uint256 bptAmountIn, RemoveLiquidityParams memory params, PoolData memory poolData, - StorageSlot.AddressSlot storage hooksContract + IHooks hooksContract ) internal returns (uint256[] memory) { - if (config.shouldCallAfterRemoveLiquidity() == false) { - return amountsOutRaw; - } - - (bool success, uint256[] memory hookAdjustedAmountsOutRaw) = IHooks(hooksContract.value).onAfterRemoveLiquidity( + (bool success, uint256[] memory hookAdjustedAmountsOutRaw) = hooksContract.onAfterRemoveLiquidity( router, params.pool, params.kind, @@ -495,22 +456,16 @@ library HooksConfigLib { * @param exactAmountsInScaled18 An array with the initial liquidity of the pool * @param userData Additional (optional) data required for adding initial liquidity * @param hooksContract Storage slot with the address of the hooks contract - * @return success false if hook is disabled, true if hooks is enabled and succeeded to execute */ function callBeforeInitializeHook( PoolConfigBits config, uint256[] memory exactAmountsInScaled18, bytes memory userData, - StorageSlot.AddressSlot storage hooksContract - ) internal returns (bool) { - if (config.shouldCallBeforeInitialize() == false) { - return false; - } - - if (IHooks(hooksContract.value).onBeforeInitialize(exactAmountsInScaled18, userData) == false) { + IHooks hooksContract + ) internal { + if (hooksContract.onBeforeInitialize(exactAmountsInScaled18, userData) == false) { revert IVaultErrors.BeforeInitializeHookFailed(); } - return true; } /** @@ -528,13 +483,9 @@ library HooksConfigLib { uint256[] memory exactAmountsInScaled18, uint256 bptAmountOut, bytes memory userData, - StorageSlot.AddressSlot storage hooksContract + IHooks hooksContract ) internal { - if (config.shouldCallAfterInitialize() == false) { - return; - } - - if (IHooks(hooksContract.value).onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false) { + if (hooksContract.onAfterInitialize(exactAmountsInScaled18, bptAmountOut, userData) == false) { revert IVaultErrors.AfterInitializeHookFailed(); } } diff --git a/pkg/vault/contracts/test/VaultMock.sol b/pkg/vault/contracts/test/VaultMock.sol index 4d96a1fd1..07e3d2001 100644 --- a/pkg/vault/contracts/test/VaultMock.sol +++ b/pkg/vault/contracts/test/VaultMock.sol @@ -211,7 +211,7 @@ contract VaultMock is IVaultMainMock, Vault { poolConfigBits = poolConfigBits.setShouldCallAfterRemoveLiquidity(hooksConfig.shouldCallAfterRemoveLiquidity); _poolConfigBits[pool] = poolConfigBits; - _hooksContracts[pool] = StorageSlot.AddressSlot({ value: hooksConfig.hooksContract }); + _hooksContracts[pool] = IHooks(hooksConfig.hooksContract); } function manualSetPoolConfigBits(address pool, PoolConfigBits config) public { diff --git a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol index 78e84a186..913150521 100644 --- a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol @@ -221,7 +221,7 @@ contract HooksConfigLibTest is Test { config = config.setShouldCallBeforeRemoveLiquidity(true); config = config.setShouldCallAfterRemoveLiquidity(true); - HooksConfig memory hooksConfig = config.toHooksConfig(hooksContract); + HooksConfig memory hooksConfig = config.toHooksConfig(IHooks(hooksContract)); assertEq(hooksConfig.shouldCallBeforeInitialize, true, "shouldCallBeforeInitialize mismatch"); assertEq(hooksConfig.shouldCallAfterInitialize, true, "shouldCallAfterInitialize mismatch"); assertEq(hooksConfig.shouldCallComputeDynamicSwapFee, true, "shouldCallComputeDynamicSwapFee mismatch"); From 92cf4467dc2aae6ebf338ab66b4b1d13343f784f Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Wed, 26 Jun 2024 19:41:04 +0200 Subject: [PATCH 16/18] remove unused import --- pkg/vault/test/foundry/unit/HooksConfigLib.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol index 913150521..daf213489 100644 --- a/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol +++ b/pkg/vault/test/foundry/unit/HooksConfigLib.t.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import { StorageSlot } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlot.sol"; - import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; From 4e7dcf22cafbf3f911df0e36fc7bae869e65de40 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 27 Jun 2024 22:45:59 +0200 Subject: [PATCH 17/18] small fixes --- pkg/vault/contracts/Vault.sol | 1 + pkg/vault/contracts/lib/HooksConfigLib.sol | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index b481221c1..bce1cf05f 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -242,6 +242,7 @@ contract Vault is IVaultMain, VaultCommon, Proxy { // If the hook contract does not exist or does not implement onAfterSwap, PoolConfigLib returns the original // amountCalculated. Otherwise, the new amount calculated is 'amountCalculated + delta'. If the underlying // hook fails, or limits are violated, `onAfterSwap` will revert. + // Uses msg.sender as the router (the contract that called the vault) if (poolData.poolConfigBits.shouldCallAfterSwap()) { // fix stack too deep IHooks hooksContract = _hooksContracts[params.pool]; diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol index e2c806acf..f9c0f70ca 100644 --- a/pkg/vault/contracts/lib/HooksConfigLib.sol +++ b/pkg/vault/contracts/lib/HooksConfigLib.sol @@ -155,7 +155,7 @@ library HooksConfigLib { * @dev Check if dynamic swap fee hook should be called and call it. Throws an error if the hook contract fails to * execute the hook. * - * @param config The encoded hooks configuration + * @param config config The encoded pool configuration * @param swapParams The swap parameters used to calculate the fee * @param staticSwapFeePercentage Value of the static swap fee, for reference * @param hooksContract Storage slot with the address of the hooks contract @@ -183,7 +183,7 @@ library HooksConfigLib { * @dev Check if before swap hook should be called and call it. Throws an error if the hook contract fails to * execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param swapParams The swap parameters used in the hook * @param pool Pool address * @param hooksContract Storage slot with the address of the hooks contract @@ -204,7 +204,7 @@ library HooksConfigLib { * @dev Check if after swap hook should be called and call it. Throws an error if the hook contract fails to * execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param amountCalculatedScaled18 Token amount calculated by the swap * @param amountCalculatedRaw Token amount calculated by the swap * @param router Router address @@ -270,7 +270,7 @@ library HooksConfigLib { * @dev Check if before add liquidity hook should be called and call it. Throws an error if the hook contract fails * to execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param router Router address * @param maxAmountsInScaled18 An array with maximum amounts for each input token of the add liquidity operation * @param params The add liquidity parameters @@ -304,7 +304,7 @@ library HooksConfigLib { * @dev Check if after add liquidity hook should be called and call it. Throws an error if the hook contract fails * to execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param router Router address * @param amountsInScaled18 An array with amounts for each input token of the add liquidity operation * @param amountsInRaw An array with amounts for each input token of the add liquidity operation @@ -361,7 +361,7 @@ library HooksConfigLib { * @dev Check if before remove liquidity hook should be called and call it. Throws an error if the hook contract * fails to execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param minAmountsOutScaled18 Minimum amounts for each output token of the remove liquidity operation * @param router Router address * @param params The remove liquidity parameters @@ -395,7 +395,7 @@ library HooksConfigLib { * @dev Check if after remove liquidity hook should be called and call it. Throws an error if the hook contract * fails to execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param router Router address * @param amountsOutScaled18 Scaled amount of tokens to receive, sorted in token registration order * @param amountsOutRaw Actual amount of tokens to receive, sorted in token registration order @@ -452,7 +452,7 @@ library HooksConfigLib { * @dev Check if before initialization hook should be called and call it. Throws an error if the hook contract * fails to execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param exactAmountsInScaled18 An array with the initial liquidity of the pool * @param userData Additional (optional) data required for adding initial liquidity * @param hooksContract Storage slot with the address of the hooks contract @@ -472,7 +472,7 @@ library HooksConfigLib { * @dev Check if after initialization hook should be called and call it. Throws an error if the hook contract * fails to execute the hook. * - * @param config The encoded hooks configuration + * @param config The encoded pool configuration * @param exactAmountsInScaled18 An array with the initial liquidity of the pool * @param bptAmountOut The BPT amount a user will receive after initialization operation succeeds * @param userData Additional (optional) data required for adding initial liquidity From 84f3f7f207e94d6d78e8659f6f5dbc0fd6016aa3 Mon Sep 17 00:00:00 2001 From: elshan_eth Date: Thu, 27 Jun 2024 22:57:37 +0200 Subject: [PATCH 18/18] add last snapshots --- pkg/vault/test/.contract-sizes/Vault | 4 ++-- pkg/vault/test/.contract-sizes/VaultExtension | 9 ++------- ...rd] swap single token exact in with fees - cold slots | 2 +- ...rd] swap single token exact in with fees - warm slots | 2 +- ...te] swap single token exact in with fees - cold slots | 2 +- ...te] swap single token exact in with fees - warm slots | 2 +- .../test/gas/.hardhat-snapshots/[PoolMock] donation | 2 +- .../.hardhat-snapshots/[PoolMock] initialize with ETH | 2 +- .../.hardhat-snapshots/[PoolMock] initialize without ETH | 2 +- 9 files changed, 11 insertions(+), 16 deletions(-) diff --git a/pkg/vault/test/.contract-sizes/Vault b/pkg/vault/test/.contract-sizes/Vault index 1a7ca5cb6..540079f13 100644 --- a/pkg/vault/test/.contract-sizes/Vault +++ b/pkg/vault/test/.contract-sizes/Vault @@ -1,2 +1,2 @@ -Bytecode 24.292* -InitCode 25.574 \ No newline at end of file +Bytecode 24.229* +InitCode 25.512 \ No newline at end of file diff --git a/pkg/vault/test/.contract-sizes/VaultExtension b/pkg/vault/test/.contract-sizes/VaultExtension index 3e06dd917..dc0a5cfa2 100644 --- a/pkg/vault/test/.contract-sizes/VaultExtension +++ b/pkg/vault/test/.contract-sizes/VaultExtension @@ -1,7 +1,2 @@ -<<<<<<< HEAD -Bytecode 20.623 -InitCode 21.812 -======= -Bytecode 20.028 -InitCode 21.217 ->>>>>>> main +Bytecode 20.457 +InitCode 21.646 \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots index 966e96749..853323aef 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - cold slots @@ -1 +1 @@ -175.2k \ No newline at end of file +174.7k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots index 235d9b73c..70f835904 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - Standard] swap single token exact in with fees - warm slots @@ -1 +1 @@ -158.6k \ No newline at end of file +158.1k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots index a635c111c..14ff2185b 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - cold slots @@ -1 +1 @@ -208.5k \ No newline at end of file +208.0k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots index 743bc5607..483555e0b 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock - With rate] swap single token exact in with fees - warm slots @@ -1 +1 @@ -174.8k \ No newline at end of file +174.3k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation index 8b4740447..3926676da 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] donation @@ -1 +1 @@ -178.4k \ No newline at end of file +177.8k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH index c540a1e62..c53897c8d 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize with ETH @@ -1 +1 @@ -342.7k \ No newline at end of file +342.5k \ No newline at end of file diff --git a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH index d8b0b15d6..33a795891 100644 --- a/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH +++ b/pkg/vault/test/gas/.hardhat-snapshots/[PoolMock] initialize without ETH @@ -1 +1 @@ -330.1k \ No newline at end of file +330.0k \ No newline at end of file