// SPDX-License-Identifier: GPL-3.0-or-later 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 "../solidity-utils/helpers/IRateProvider.sol"; /** * @notice Represents a pool's liquidity management configuration. * @param disableUnbalancedLiquidity If set, liquidity can only be added or removed proportionally * @param enableAddLiquidityCustom If set, the pool has implemented `onAddLiquidityCustom` * @param enableRemoveLiquidityCustom If set, the pool has implemented `onRemoveLiquidityCustom` * @param enableDonation If set, the pool will not revert if liquidity is added with AddLiquidityKind.DONATION */ struct LiquidityManagement { bool disableUnbalancedLiquidity; bool enableAddLiquidityCustom; bool enableRemoveLiquidityCustom; bool enableDonation; } // @notice Custom type to store the entire configuration of the pool. type PoolConfigBits is bytes32; /** * @notice Represents a pool's configuration (hooks configuration are separated in another struct). * @param liquidityManagement Flags related to adding/removing liquidity * @param staticSwapFeePercentage The pool's native swap fee * @param aggregateSwapFeePercentage The total swap fee charged, including protocol and pool creator components * @param aggregateYieldFeePercentage The total swap fee charged, including protocol and pool creator components * @param tokenDecimalDiffs Compressed storage of the token decimals of each pool token * @param pauseWindowEndTime Timestamp after which the pool cannot be paused * @param isPoolRegistered If true, the pool has been registered with the Vault * @param isPoolInitialized If true, the pool has been initialized with liquidity, and is available for trading * @param isPoolPaused If true, the pool has been paused (by governance or the pauseManager) * @param isPoolInRecoveryMode If true, the pool has been placed in recovery mode, enabling recovery mode withdrawals */ struct PoolConfig { LiquidityManagement liquidityManagement; uint256 staticSwapFeePercentage; uint256 aggregateSwapFeePercentage; uint256 aggregateYieldFeePercentage; uint40 tokenDecimalDiffs; uint32 pauseWindowEndTime; bool isPoolRegistered; bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; } /** * @notice The flag portion of the `HooksConfig`. * @dev `enableHookAdjustedAmounts` must be true for all contracts that modify the `amountCalculated` * in after hooks. Otherwise, the Vault will ignore any "hookAdjusted" amounts. Setting any "shouldCall" * flags to true will cause the Vault to call the corresponding hook during operations. */ struct HookFlags { bool enableHookAdjustedAmounts; bool shouldCallBeforeInitialize; bool shouldCallAfterInitialize; bool shouldCallComputeDynamicSwapFee; bool shouldCallBeforeSwap; bool shouldCallAfterSwap; bool shouldCallBeforeAddLiquidity; bool shouldCallAfterAddLiquidity; bool shouldCallBeforeRemoveLiquidity; bool shouldCallAfterRemoveLiquidity; } /// @notice Represents a hook contract configuration for a pool (HookFlags + hooksContract address). struct HooksConfig { bool enableHookAdjustedAmounts; bool shouldCallBeforeInitialize; bool shouldCallAfterInitialize; bool shouldCallComputeDynamicSwapFee; bool shouldCallBeforeSwap; bool shouldCallAfterSwap; bool shouldCallBeforeAddLiquidity; bool shouldCallAfterAddLiquidity; bool shouldCallBeforeRemoveLiquidity; bool shouldCallAfterRemoveLiquidity; address hooksContract; } /** * @notice Represents temporary state used during a swap operation. * @param indexIn The zero-based index of tokenIn * @param indexOut The zero-based index of tokenOut * @param amountGivenScaled18 The amountGiven (i.e., tokenIn for ExactIn), adjusted for token decimals * @param swapFeePercentage The swap fee to be applied (might be static or dynamic) */ struct SwapState { uint256 indexIn; uint256 indexOut; uint256 amountGivenScaled18; uint256 swapFeePercentage; } /** * @notice Represents the Vault's configuration. * @param isQueryDisabled If set to true, disables query functionality of the Vault. Can be modified by governance * @param isVaultPaused If set to true, swaps and add/remove liquidity operations are halted * @param areBuffersPaused If set to true, the Vault wrap/unwrap primitives associated with buffers will be disabled */ struct VaultState { bool isQueryDisabled; bool isVaultPaused; bool areBuffersPaused; } /** * @notice Represents the accounts holding certain roles for a given pool. This is passed in on pool registration. * @param pauseManager Account empowered to pause/unpause the pool (note that governance can always pause a pool) * @param swapFeeManager Account empowered to set static swap fees for a pool (or 0 to delegate to governance) * @param poolCreator Account empowered to set the pool creator fee (or 0 if all fees go to the protocol and LPs) */ struct PoolRoleAccounts { address pauseManager; address swapFeeManager; address poolCreator; } /******************************************************************************* Tokens *******************************************************************************/ // Note that the following tokens are unsupported by the Vault. This list is not meant to be exhaustive, but covers // many common types of tokens that will not work with the Vault architecture. (See https://github.com/d-xo/weird-erc20 // for examples of token features that are problematic for many protocols.) // // * Rebasing tokens (e.g., aDAI). The Vault keeps track of token balances in its internal accounting; any token whose // balance changes asynchronously (i.e., outside a swap or liquidity operation), would get out-of-sync with this // internal accounting. This category would also include "airdrop" tokens, whose balances can change unexpectedly. // // * Double entrypoint (e.g., old Synthetix tokens, now fixed). These could likewise bypass internal accounting by // registering the token under one address, then accessing it through another. This is especially troublesome // in v3, with the introduction of ERC4626 buffers. // // * Fee on transfer (e.g., PAXG). The Vault issues credits and debits according to given and calculated token amounts, // and settlement assumes that the send/receive transfer functions transfer exactly the given number of tokens. // If this is not the case, transactions will not settle. Unlike with the other types, which are fundamentally // incompatible, it would be possible to design a Router to handle this - but we didn't try it. In any case, it's // not supported in the current Routers. // // * Tokens with more than 18 decimals (e.g., YAM-V2). The Vault handles token scaling: i.e., handling I/O for // amounts in native token decimals, but doing calculations with full 18-decimal precision. This requires reading // and storing the decimals for each token. Since virtually all tokens are 18 or fewer decimals, and we have limited // storage space, 18 was a reasonable maximum. Unlike the other types, this is enforceable by the Vault. Attempting // to register such tokens will revert with `InvalidTokenDecimals`. Of course, we must also be able to read the token // decimals, so the Vault only supports tokens that implement `IERC20Metadata.decimals`, and return a value less than // or equal to 18. // // * Token decimals are checked and stored only once, on registration. Valid tokens store their decimals as immutable // variables or constants. Malicious tokens that don't respect this basic property would not work anywhere in DeFi. // // These types of tokens are supported but discouraged, as they don't tend to play well with AMMs generally. // // * Very low-decimal tokens (e.g., GUSD). The Vault has been extensively tested with 6-decimal tokens (e.g., USDC), // but going much below that may lead to unanticipated effects due to precision loss, especially with smaller trade // values. // // * Revert on zero value approval/transfer. The Vault has been tested against these, but peripheral contracts, such // as hooks, might not have been designed with this in mind. // // * Other types from "weird-erc20," such as upgradeable, pausable, or tokens with blocklists. We have seen cases // where a token upgrade fails, "bricking" the token - and many operations on pools containing that token. Any // sort of "permissioned" token that can make transfers fail can cause operations on pools containing them to // revert. Even Recovery Mode cannot help then, as it does a proportional withdrawal of all tokens. If one of // them is bricked, the whole operation will revert. Since v3 does not have "internal balances" like v2, there // is no recourse. // // Of course, many tokens in common use have some of these "features" (especially centralized stable coins), so // we have to support them anyway. Working with common centralized tokens is a risk common to all of DeFi. /** * @notice Token types supported by the Vault. * @dev In general, pools may contain any combination of these tokens. * * STANDARD tokens (e.g., BAL, WETH) have no rate provider. * WITH_RATE tokens (e.g., wstETH) require a rate provider. These may be tokens like wstETH, which need to be wrapped * because the underlying stETH token is rebasing, and such tokens are unsupported by the Vault. They may also be * tokens like sEUR, which track an underlying asset, but are not yield-bearing. Finally, this encompasses * yield-bearing ERC4626 tokens, which can be used to facilitate swaps without requiring wrapping or unwrapping * in most cases. The `paysYieldFees` flag can be used to indicate whether a token is yield-bearing (e.g., waDAI), * not yield-bearing (e.g., sEUR), or yield-bearing but exempt from fees (e.g., in certain nested pools, where * yield fees are charged elsewhere). * * NB: STANDARD must always be the first enum element, so that newly initialized data structures default to Standard. */ enum TokenType { STANDARD, WITH_RATE } /** * @notice Encapsulate the data required for the Vault to support a token of the given type. * @dev For STANDARD tokens, the rate provider address must be 0, and paysYieldFees must be false. All WITH_RATE tokens * need a rate provider, and may or may not be yield-bearing. * * At registration time, it is useful to include the token address along with the token parameters in the structure * passed to `registerPool`, as the alternative would be parallel arrays, which would be error prone and require * validation checks. `TokenConfig` is only used for registration, and is never put into storage (see `TokenInfo`). * * @param token The token address * @param tokenType The token type (see the enum for supported types) * @param rateProvider The rate provider for a token (see further documentation above) * @param paysYieldFees Flag indicating whether yield fees should be charged on this token */ struct TokenConfig { IERC20 token; TokenType tokenType; IRateProvider rateProvider; bool paysYieldFees; } /** * @notice This data structure is stored in `_poolTokenInfo`, a nested mapping from pool -> (token -> TokenInfo). * @dev Since the token is already the key of the nested mapping, it would be redundant (and an extra SLOAD) to store * it again in the struct. When we construct PoolData, the tokens are separated into their own array. * * @param tokenType The token type (see the enum for supported types) * @param rateProvider The rate provider for a token (see further documentation above) * @param paysYieldFees Flag indicating whether yield fees should be charged on this token */ struct TokenInfo { TokenType tokenType; IRateProvider rateProvider; bool paysYieldFees; } /** * @notice Data structure used to represent the current pool state in memory * @param poolConfigBits Custom type to store the entire configuration of the pool. * @param tokens Pool tokens, sorted in token registration order * @param tokenInfo Configuration data for each token, sorted in token registration order * @param balancesRaw Token balances in native decimals * @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates * @param tokenRates 18-decimal FP values for rate tokens (e.g., yield-bearing), or FP(1) for standard tokens * @param decimalScalingFactors Conversion factor used to adjust for token decimals for uniform precision in * calculations. It is 1e18 (FP 1) for 18-decimal tokens */ struct PoolData { PoolConfigBits poolConfigBits; IERC20[] tokens; TokenInfo[] tokenInfo; uint256[] balancesRaw; uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256[] decimalScalingFactors; } enum Rounding { ROUND_UP, ROUND_DOWN } /******************************************************************************* Swaps *******************************************************************************/ enum SwapKind { EXACT_IN, EXACT_OUT } // There are two "SwapParams" structs defined below. `VaultSwapParams` corresponds to the external swap API defined // in the Router contracts, which uses explicit token addresses, the amount given and limit on the calculated amount // expressed in native token decimals, and optional user data passed in from the caller. // // `PoolSwapParams` passes some of this information through (kind, userData), but "translates" the parameters to fit // the internal swap API used by `IBasePool`. It scales amounts to full 18-decimal precision, adds the token balances, // converts the raw token addresses to indices, and adds the address of the Router originating the request. It does // not need the limit, since this is checked at the Router level. /** * @notice Data passed into primary Vault `swap` operations. * @param kind Type of swap (Exact In or Exact Out) * @param pool The pool with the tokens being swapped * @param tokenIn The token entering the Vault (balance increases) * @param tokenOut The token leaving the Vault (balance decreases) * @param amountGivenRaw Amount specified for tokenIn or tokenOut (depending on the type of swap) * @param limitRaw Minimum or maximum value of the calculated amount (depending on the type of swap) * @param userData Additional (optional) user data */ struct VaultSwapParams { SwapKind kind; address pool; IERC20 tokenIn; IERC20 tokenOut; uint256 amountGivenRaw; uint256 limitRaw; bytes userData; } /** * @notice Data for a swap operation, used by contracts implementing `IBasePool`. * @param kind Type of swap (exact in or exact out) * @param amountGivenScaled18 Amount given based on kind of the swap (e.g., tokenIn for EXACT_IN) * @param balancesScaled18 Current pool balances * @param indexIn Index of tokenIn * @param indexOut Index of tokenOut * @param router The address (usually a router contract) that initiated a swap operation on the Vault * @param userData Additional (optional) data required for the swap */ struct PoolSwapParams { SwapKind kind; uint256 amountGivenScaled18; uint256[] balancesScaled18; uint256 indexIn; uint256 indexOut; address router; bytes userData; } /** * @notice Data for the hook after a swap operation. * @param kind Type of swap (exact in or exact out) * @param tokenIn Token to be swapped from * @param tokenOut Token to be swapped to * @param amountInScaled18 Amount of tokenIn (entering the Vault) * @param amountOutScaled18 Amount of tokenOut (leaving the Vault) * @param tokenInBalanceScaled18 Updated (after swap) balance of tokenIn * @param tokenOutBalanceScaled18 Updated (after swap) balance of tokenOut * @param amountCalculatedScaled18 Token amount calculated by the swap * @param amountCalculatedRaw Token amount calculated by the swap * @param router The address (usually a router contract) that initiated a swap operation on the Vault * @param pool Pool address * @param userData Additional (optional) data required for the swap */ struct AfterSwapParams { SwapKind kind; IERC20 tokenIn; IERC20 tokenOut; uint256 amountInScaled18; uint256 amountOutScaled18; uint256 tokenInBalanceScaled18; uint256 tokenOutBalanceScaled18; uint256 amountCalculatedScaled18; uint256 amountCalculatedRaw; address router; address pool; bytes userData; } /******************************************************************************* Add liquidity *******************************************************************************/ enum AddLiquidityKind { PROPORTIONAL, UNBALANCED, SINGLE_TOKEN_EXACT_OUT, DONATION, CUSTOM } /** * @notice Data for an add liquidity operation. * @param pool Address of the pool * @param to Address of user to mint to * @param maxAmountsIn Maximum amounts of input tokens * @param minBptAmountOut Minimum amount of output pool tokens * @param kind Add liquidity kind * @param userData Optional user data */ struct AddLiquidityParams { address pool; address to; uint256[] maxAmountsIn; uint256 minBptAmountOut; AddLiquidityKind kind; bytes userData; } /******************************************************************************* Remove liquidity *******************************************************************************/ enum RemoveLiquidityKind { PROPORTIONAL, SINGLE_TOKEN_EXACT_IN, SINGLE_TOKEN_EXACT_OUT, CUSTOM } /** * @notice Data for an remove liquidity operation. * @param pool Address of the pool * @param from Address of user to burn from * @param maxBptAmountIn Maximum amount of input pool tokens * @param minAmountsOut Minimum amounts of output tokens * @param kind Remove liquidity kind * @param userData Optional user data */ struct RemoveLiquidityParams { address pool; address from; uint256 maxBptAmountIn; uint256[] minAmountsOut; RemoveLiquidityKind kind; bytes userData; } /******************************************************************************* Remove liquidity *******************************************************************************/ enum WrappingDirection { WRAP, UNWRAP } /** * @notice Data for a wrap/unwrap operation. * @param kind Type of swap (Exact In or Exact Out) * @param direction Direction of the wrapping operation (Wrap or Unwrap) * @param wrappedToken Wrapped token, compatible with interface ERC4626 * @param amountGivenRaw Amount specified for tokenIn or tokenOut (depends on the type of swap and wrapping direction) * @param limitRaw Minimum or maximum amount specified for the other token (depends on the type of swap and wrapping * direction) */ struct BufferWrapOrUnwrapParams { SwapKind kind; WrappingDirection direction; IERC4626 wrappedToken; uint256 amountGivenRaw; uint256 limitRaw; } // Protocol Fees are 24-bit values. We transform them by multiplying by 1e11, so that they can be set to any value // between 0% and 100% (step 0.00001%). Protocol and pool creator fees are set in the `ProtocolFeeController`, and // ensure both constituent and aggregate fees do not exceed this precision. uint256 constant FEE_BITLENGTH = 24; uint256 constant FEE_SCALING_FACTOR = 1e11; // Used to ensure the safety of fee-related math (e.g., pools or hooks don't set it greater than 100%). // This value should work for practical purposes and is well within the max precision requirements. uint256 constant MAX_FEE_PERCENTAGE = 99.9999e16; // 99.9999%