Vault

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/Vault.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// --- Balancer and OpenZeppelin imports for DeFi operations and security ---
import "../lib/balancer-v3-monorepo/pkg/interfaces/contracts/vault/ICompositeLiquidityRouter.sol";
import "../lib/balancer-v3-monorepo/pkg/interfaces/contracts/vault/IRouter.sol";
import "../lib/balancer-v3-monorepo/pkg/interfaces/contracts/vault/IBatchRouter.sol";
import "./interfaces/IVault.sol";
import "./interfaces/IGauge.sol";
import "./interfaces/INative.sol";
import "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "../lib/balancer-v3-monorepo/pkg/interfaces/contracts/pool-utils/IPoolInfo.sol";
import "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "./interfaces/IWRAPPED.sol";
import "./interfaces/IPermit2.sol";
import "./interfaces/IpesudoMinter.sol";

/**
 * @title Vault
 * @author Cashflowapp Team
 * @notice Automated liquidity management vault for Balancer pools that supports:
 * - Proportional and unbalanced token deposits/withdrawals
 * - Gauge staking for reward earning
 * - Automated harvesting and compound rewards via multicall
 * - Platform fee collection on rewards
 * - ETH/WETH handling for seamless user experience
 * @dev This contract manages liquidity in Balancer pools, allowing users to deposit tokens,
 * participate in gauge farming, and benefit from automated reward compounding. It issues ERC20 vault tokens
 * representing shares in the vault. The vault uses multicall functionality for advanced reward compounding
 * strategies executed by whitelisted addresses. Public addLiquidity functions are intended ONLY for
 * internal use via multicall for reward compounding - users should use deposit functions instead.
 *
 * Key Features:
 * - ERC4626-like vault mechanics with share-based accounting
 * - Balancer V3 integration with composite liquidity router
 * - Gauge integration for earning BAL and other rewards
 * - Permit2 support for gasless approvals
 * - Reentrancy protection on all external functions
 * - SafeERC20 usage for secure token operations
 * - Platform fee mechanism for sustainable operations
 */
contract Vault is IVault, ERC20, ReentrancyGuard, Ownable {
    using SafeERC20 for IERC20;

    // --- Constants and Immutables ---

    /// @notice Platform fee in basis points (e.g., 500 = 5%)
    uint256 public platformFeePercentage = 500;

    /// @notice Balancer router for liquidity operations
    ICompositeLiquidityRouter public immutable router;

    /// @notice Gauge for staking pool tokens and earning rewards
    IGauge public immutable gauge;

    /// @notice Minter for additional rewards
    IpesudoMinter public immutable pesudoMinter;

    /// @notice Balancer pool address
    address public immutable pool;

    /// @notice Underlying tokens in the pool
    IERC20[] public poolTokens;

    /// @notice Native token (e.g., WETH)
    address public immutable nativeToken;

    /// @notice Router for batch swaps
    IBatchRouter public immutable batchRouter;

    /// @notice protocol fee receiver address
    address public feeReceiver;

    /// @notice Access control for multicall
    mapping(address => bool) public multicallWhitelist;

    // --- Events ---

    /// @notice Emitted when a token approval is set
    event Approve(
        address indexed token,
        address indexed spender,
        uint256 amount
    );

    /// @notice Emitted when a Permit2 approval is set
    event Permit2Approve(
        address indexed permit2,
        address indexed token,
        address indexed spender,
        uint160 amount,
        uint48 expiration
    );

    /// @notice Emitted when the multicall whitelist is updated
    event MulticallWhitelistUpdated(address indexed target, bool allowed);

    /// @notice Emitted when the platform fee percentage is updated
    event FeePercentageUpdated(uint256 newFeePercentage);

    /// @notice Emitted when a deposit is made
    event Deposit(
        address indexed sender,
        address indexed recipient,
        uint256 bptAmount,
        uint256 shareAmount,
        bool proportional,
        uint256[] inputAmounts
    );

    /// @notice Emitted when a withdrawal is made
    event Withdraw(
        address indexed sender,
        uint256 sharesBurned,
        address[] tokensOut,
        uint256[] amountsOut
    );

    /// @notice Emitted when a multicall is executed
    event Multicall(address[] targets, bytes[] data);

    /// @notice Emitted when fees are deducted
    event FeeDeducted(address indexed token, uint256 amount);

    /// @notice Emitted when protocol fee receiver address updated
    event UpdatedFeeReceiver(address indexed feeReceiver);

    // --- Modifiers ---

    /// @dev Modifier to restrict access to only whitelisted multicall addresses
    modifier onlyWhitelisted() {
        if (!multicallWhitelist[msg.sender]) revert NotWhitelisted(msg.sender);
        _;
    }

    // --- Receive Ether ---

    /// @notice Receive function to accept ETH
    receive() external payable {}

    // --- Constructor ---

    /// @notice Vault constructor sets up all required contract references and approvals
    /// @param _pool Balancer pool address
    /// @param _poolTokens Array of pool token addresses
    /// @param name ERC20 name
    /// @param symbol ERC20 symbol
    /// @param _router Balancer router address
    /// @param _batchRouter Batch router address
    /// @param _nativeToken Native token address (e.g., WETH)
    /// @param _gauge Gauge address for staking
    /// @param _pesudoMinter Pseudo minter address for rewards
    /// @param _permit2 Permit2 contract address
    /// @param _owner Owner address
    constructor(
        address _pool,
        IERC20[] memory _poolTokens,
        string memory name,
        string memory symbol,
        address _router,
        address _batchRouter,
        address _nativeToken,
        address _gauge,
        address _pesudoMinter,
        address _permit2,
        address _owner,
        address _feeReceiver
    ) ERC20(name, symbol) Ownable(_owner) {
        if (
            _pool == address(0) ||
            _router == address(0) ||
            _batchRouter == address(0) ||
            _nativeToken == address(0) ||
            _pesudoMinter == address(0) ||
            _permit2 == address(0) ||
            _owner == address(0) ||
            _feeReceiver == address(0)
        ) revert ZeroAddress();
        if (_poolTokens.length == 0) revert ArrayLengthMismatch();

        pool = _pool;
        router = ICompositeLiquidityRouter(_router);
        gauge = IGauge(_gauge);
        poolTokens = _poolTokens;
        pesudoMinter = IpesudoMinter(_pesudoMinter);
        batchRouter = IBatchRouter(_batchRouter);
        nativeToken = _nativeToken;
        feeReceiver = _feeReceiver;

        // Approve pool tokens and routers for maximum allowance
        if (address(gauge) != address(0)) {
            IERC20(_pool).approve(_gauge, type(uint256).max);
        }
        IERC20(_pool).approve(address(router), type(uint256).max);
        for (uint256 i = 0; i < _poolTokens.length; ++i) {
            address token = address(_poolTokens[i]);
            if (token == address(0)) revert PoolTokenIsZero();
            IERC20(token).approve(_permit2, type(uint256).max);
            IPermit2(_permit2).approve(
                token,
                address(router),
                type(uint160).max,
                type(uint48).max
            );
            IPermit2(_permit2).approve(
                token,
                address(batchRouter),
                type(uint160).max,
                type(uint48).max
            );
            IERC20(token).approve(address(batchRouter), type(uint256).max);
        }
    }

    // --- External Functions ---

    /// @notice Approve a token for a spender (owner only)
    /// @param token The ERC20 token address
    /// @param spender The address allowed to spend the tokens
    /// @param amount The amount to approve
    function approve(
        address token,
        address spender,
        uint256 amount
    ) external override onlyOwner {
        IERC20(token).approve(spender, amount);
        emit Approve(token, spender, amount);
    }

    /// @notice Approve a token for a spender using Permit2 (owner only)
    /// @param permit2 The Permit2 contract address
    /// @param token The ERC20 token address
    /// @param spender The address allowed to spend the tokens
    /// @param amount The amount to approve
    /// @param expiration The expiration timestamp for the approval
    function permit2Approve(
        address permit2,
        address token,
        address spender,
        uint160 amount,
        uint48 expiration
    ) external override onlyOwner {
        IPermit2(permit2).approve(token, spender, amount, expiration);
        emit Permit2Approve(permit2, token, spender, amount, expiration);
    }

    /// @notice Set or unset an address as whitelisted for multicall
    /// @param target The address to whitelist or remove
    /// @param allowed True to whitelist, false to remove
    function setMulticallWhitelist(
        address target,
        bool allowed
    ) external override onlyOwner {
        if (target == address(0)) revert ZeroAddress();
        if (target == address(gauge)) revert GaugeTargetForbidden();
        multicallWhitelist[target] = allowed;
        emit MulticallWhitelistUpdated(target, allowed);
    }

    /// @notice Update the platform fee percentage (owner only)
    /// @param _platformFeePercentage New fee percentage in basis points (max 2000 = 20%)
    function updateFeePercentage(
        uint256 _platformFeePercentage
    ) external override onlyOwner {
        if (_platformFeePercentage > 2000) revert("Max 20% (2000 bps)");
        platformFeePercentage = _platformFeePercentage;
        emit FeePercentageUpdated(_platformFeePercentage);
    }

    /**
     * @notice update protocol fee receiver address
     * @param _feeReceiver new fee receiver address
     */
    function updateFeeReceiverAddress(
        address _feeReceiver
    ) external override onlyOwner {
        feeReceiver = _feeReceiver;
        emit UpdatedFeeReceiver(_feeReceiver);
    }

    /// @notice Deposit tokens in an unbalanced way (custom ratios)
    /// @param recipient The address to receive vault shares
    /// @param wrapUnderlying Array indicating if each token should be wrapped
    /// @param inputAmounts The amounts of each token to deposit
    /// @param minBptAmountOut Minimum BPT to receive from the pool
    /// @param wethIsEth Whether WETH should be treated as ETH
    /// @param userData Additional user data for the router
    /// @return shareAmount Amount of vault shares minted
    function depositUnbalanced(
        address recipient,
        bool[] memory wrapUnderlying,
        uint256[] memory inputAmounts,
        uint256 minBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable override nonReentrant returns (uint256 shareAmount) {
        _validateDeposit(
            recipient,
            wrapUnderlying,
            inputAmounts,
            minBptAmountOut
        );

        uint256[] memory balancesBefore = _collectAndSnapshot(
            inputAmounts,
            _collectPayment
        );
        // Capture backing before adding liquidity for correct share calculation
        uint256 backingBefore = totalSupply() == 0 ? 0 : (
            address(gauge) != address(0)
                ? IERC20(address(gauge)).balanceOf(address(this))
                : IERC20(pool).balanceOf(address(this))
        );
        uint256 bptAmountOut = addLiquidityUnbalanced(
            wrapUnderlying,
            inputAmounts,
            minBptAmountOut,
            wethIsEth,
            userData,
            msg.value
        );
        shareAmount = calculateDepositShares(bptAmountOut, backingBefore);
        _mint(recipient, shareAmount);
        _refundExcessAll(balancesBefore);
        emit Deposit(
            msg.sender,
            recipient,
            bptAmountOut,
            shareAmount,
            false,
            inputAmounts
        );
    }

    /// @notice Deposit tokens in a proportional way (fixed pool ratio)
    /// @param recipient The address to receive vault shares
    /// @param wrapUnderlying Array indicating if each token should be wrapped
    /// @param maxInputAmounts The max amounts of each token to deposit
    /// @param exactBptAmountOut Exact BPT to receive from the pool
    /// @param wethIsEth Whether WETH should be treated as ETH
    /// @param userData Additional user data for the router
    /// @return shareAmount Amount of vault shares minted
    function depositProportional(
        address recipient,
        bool[] memory wrapUnderlying,
        uint256[] memory maxInputAmounts,
        uint256 exactBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable override nonReentrant returns (uint256 shareAmount) {
        _validateDeposit(
            recipient,
            wrapUnderlying,
            maxInputAmounts,
            exactBptAmountOut
        );
        uint256[] memory balancesBefore = _collectAndSnapshot(
            maxInputAmounts,
            _collectPayment
        );
        // Capture backing before adding liquidity for correct share calculation
        uint256 backingBefore = totalSupply() == 0 ? 0 : (
            address(gauge) != address(0)
                ? IERC20(address(gauge)).balanceOf(address(this))
                : IERC20(pool).balanceOf(address(this))
        );
        uint256[] memory actualInputAmounts = addLiquidityProportional(
            wrapUnderlying,
            maxInputAmounts,
            exactBptAmountOut,
            wethIsEth,
            userData,
            msg.value
        );
        shareAmount = calculateDepositShares(exactBptAmountOut, backingBefore);
        _mint(recipient, shareAmount);
        _refundExcessAll(balancesBefore);
        emit Deposit(
            msg.sender,
            recipient,
            exactBptAmountOut,
            shareAmount,
            true,
            actualInputAmounts
        );
    }

    /// @notice Withdraw tokens from the vault, burning shares
    /// @param sharesToBurn Amount of vault shares to burn
    /// @param minAmountsOut Minimum amounts of each token to withdraw
    /// @param unwrapWrapped Array indicating if each token should be unwrapped
    /// @param wethIsEth Whether WETH should be treated as ETH
    /// @param userData Additional user data for the router
    /// @return tokensOut The addresses of tokens withdrawn
    /// @return amountsOut The amounts of tokens withdrawn
    function withdraw(
        uint256 sharesToBurn,
        uint256[] memory minAmountsOut,
        bool[] memory unwrapWrapped,
        bool wethIsEth,
        bytes memory userData
    )
        external
        override
        nonReentrant
        returns (address[] memory tokensOut, uint256[] memory amountsOut)
    {
        if (sharesToBurn == 0) revert InvalidAmount();
        if (
            minAmountsOut.length != poolTokens.length ||
            unwrapWrapped.length != poolTokens.length
        ) {
            revert ArrayLengthMismatch();
        }
        (tokensOut, amountsOut) = _withdrawAndRemoveLiquidity(
            sharesToBurn,
            minAmountsOut,
            unwrapWrapped,
            wethIsEth,
            userData
        );
        for (uint256 i = 0; i < tokensOut.length; ) {
            if (wethIsEth && tokensOut[i] == nativeToken) {
                (bool success, ) = msg.sender.call{value: amountsOut[i]}("");
                if (!success) revert ETHRefundFailed();
            } else {
                IERC20(tokensOut[i]).safeTransfer(msg.sender, amountsOut[i]);
            }

            unchecked {
                ++i;
            }
        }
        emit Withdraw(msg.sender, sharesToBurn, tokensOut, amountsOut);
    }

    /// @notice Claim rewards from the gauge and minter
    function claimRewards() external override nonReentrant {
        if (address(gauge) != address(0)) gauge.claim_rewards();
        if (address(pesudoMinter) != address(0)) {
            pesudoMinter.mint(address(gauge));
        }
    }

    /// @notice Multicall for batch execution of whitelisted contract calls (owner only)
    /// @dev Only whitelisted addresses can call this function. Each target must also be whitelisted.
    /// @param targets The contract addresses to call
    /// @param data The calldata for each call
    /// @return results The return data from each call
    function multicall(
        address[] calldata targets,
        bytes[] calldata data
    )
        external
        override
        nonReentrant
        onlyWhitelisted
        returns (bytes[] memory results)
    {
        if (targets.length != data.length) revert ArrayLengthMismatch();
        results = new bytes[](targets.length);
        for (uint256 i = 0; i < targets.length; ++i) {
            if (!multicallWhitelist[targets[i]]) {
                revert NotWhitelisted(targets[i]);  //bal, gho, weth-oseth //bal-weth gh
            }
            (bool success, bytes memory result) = targets[i].call(data[i]);
            if (!success) revert MulticallSubcallFailed(targets[i], data[i]);
            results[i] = result;
        }
        emit Multicall(targets, data);
    }

    /// @notice Deduct platform fees from a token balance
    /// @dev Only callable by the contract itself (e.g., via multicall)
    /// @param token The token address to deduct fees from
    function deductFees(address token) external override {
        if (msg.sender != address(this)) revert("NA");
        if (token == address(gauge) || token == pool) revert("NA");
        if (token == address(0)) revert ZeroAddress();
        //if token guage  then reward
        uint256 feesAmount = (IERC20(token).balanceOf(address(this)) *
            platformFeePercentage) / 10000;
        IERC20(token).safeTransfer(feeReceiver, feesAmount);
        emit FeeDeducted(token, feesAmount);
    }

    // --- Public Functions for Direct Pool Interaction ---
    // NOTE: These functions are intended for internal use (e.g., reward compounding via multicall)
    // and should NOT be called directly by users.

    /// @notice Add liquidity in a proportional way
    /// @dev For internal use (e.g., reward compounding via multicall). Users should not call directly.
    /// @param wrapUnderlying Array indicating if each token should be wrapped
    /// @param maxInputAmounts The max amounts of each token to deposit
    /// @param exactBptAmountOut Exact BPT to receive from the pool
    /// @param wethIsEth Whether WETH should be treated as ETH
    /// @param userData Additional user data
    /// @param ethAmount Amount of ETH sent (if any)
    /// @return actualInputAmounts The actual amounts of tokens deposited
    function addLiquidityProportional(
        bool[] memory wrapUnderlying,
        uint256[] memory maxInputAmounts,
        uint256 exactBptAmountOut,
        bool wethIsEth,
        bytes memory userData,
        uint256 ethAmount
    ) public returns (uint256[] memory actualInputAmounts) {
        // This function is intended for internal use (e.g., reward compounding via multicall).
        // Users should not call this directly.
        (, actualInputAmounts) = router.addLiquidityProportionalToERC4626Pool{
            value: ethAmount
        }(
            pool,
            wrapUnderlying,
            maxInputAmounts,
            exactBptAmountOut,
            wethIsEth,
            userData
        );
        // Only deposit into gauge if a gauge is configured
        if (address(gauge) != address(0)) {
            gauge.deposit(exactBptAmountOut);
        }
    }

    /// @notice Add liquidity in an unbalanced way
    /// @dev For internal use (e.g., reward compounding via multicall). Users should not call directly.
    /// @param wrapUnderlying Array indicating if each token should be wrapped
    /// @param inputAmounts The amounts of each token to deposit
    /// @param minBptAmountOut Minimum BPT to receive from the pool
    /// @param wethIsEth Whether WETH should be treated as ETH
    /// @param userData Additional user data
    /// @param ethAmount Amount of ETH sent (if any)
    /// @return bptAmountOut The amount of BPT received
    function addLiquidityUnbalanced(
        bool[] memory wrapUnderlying,
        uint256[] memory inputAmounts,
        uint256 minBptAmountOut,
        bool wethIsEth,
        bytes memory userData,
        uint256 ethAmount
    ) public returns (uint256 bptAmountOut) {
        // This function is intended for internal use (e.g., reward compounding via multicall).
        // Users should not call this directly.
        bptAmountOut = router.addLiquidityUnbalancedToERC4626Pool{
            value: ethAmount
        }(
            pool,
            wrapUnderlying,
            inputAmounts,
            minBptAmountOut,
            wethIsEth,
            userData
        );
        // Only deposit into gauge if a gauge is configured
        if (address(gauge) != address(0)) {
            gauge.deposit(bptAmountOut);
        }
    }

    /// @notice Internal function to calculate shares with a pre-captured backing amount
    /// @param bptAmountOut The amount of BPT received
    /// @param backingBefore The backing amount before adding liquidity
    /// @return shares The amount of shares to mint
    function calculateDepositShares(
        uint256 bptAmountOut,
        uint256 backingBefore
    ) public view returns (uint256 shares) {
        if (totalSupply() == 0) {
            shares = bptAmountOut;
        } else {
            shares = (bptAmountOut * totalSupply()) / backingBefore;
        }
    }

    /// @notice Calculate BPT amount to withdraw for a given share amount
    /// @param sharesToBurn The amount of shares to burn
    /// @return bptAmount The amount of BPT to withdraw
    function calculateWithdrawShares(
        uint256 sharesToBurn
    ) public view returns (uint256 bptAmount) {
        uint256 backing = address(gauge) != address(0)
            ? IERC20(address(gauge)).balanceOf(address(this))
            : IERC20(pool).balanceOf(address(this));
        bptAmount = (backing * sharesToBurn) / totalSupply();
    }

    // --- Internal Helper Functions ---

    /// @dev Collect payment from user and snapshot balances
    /// @param token Token address to collect
    /// @param amount Amount to collect
    function _collectPayment(address token, uint256 amount) internal {
        if (token == address(0)) revert ZeroAddress();
        if (amount == 0) revert InvalidAmount();
        if (token == nativeToken && msg.value > 0) {
            if (msg.value != amount) revert IncorrectETHSent();
        } else {
            IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        }
    }

    /// @dev Refund any excess tokens to the user
    /// @param token Token address to refund
    /// @param amount Amount to refund
    function _refundExcess(address token, uint256 amount) internal {
        if (token == address(0)) revert ZeroAddress();
        if (amount == 0) return;
        if (token == nativeToken && msg.value > 0) {
            (bool success, ) = msg.sender.call{value: amount}("");
            if (!success) revert ETHRefundFailed();
        } else {
            IERC20(token).safeTransfer(msg.sender, amount);
        }
    }

    /// @dev Collect tokens and snapshot balances before deposit
    /// @param inputAmounts Amounts to collect for each token
    /// @param collectFn Function to call for collecting tokens
    /// @return balancesBefore Array of balances before collection
    function _collectAndSnapshot(
        uint256[] memory inputAmounts,
        function(address, uint256) internal collectFn
    ) internal returns (uint256[] memory) {
        uint256[] memory balancesBefore = new uint256[](poolTokens.length);
        for (uint256 i = 0; i < poolTokens.length; ) {
            address token = address(poolTokens[i]);
            if (token == nativeToken && msg.value > 0) {
                balancesBefore[i] = address(this).balance - msg.value;
            } else {
                balancesBefore[i] = IERC20(token).balanceOf(address(this));
            }

            if (inputAmounts[i] > 0) {
                collectFn(token, inputAmounts[i]);
            }
            unchecked {
                ++i;
            }
        }
        return balancesBefore;
    }

    /// @dev Refund all excess tokens after deposit
    /// @param balancesBefore Array of balances before deposit
    function _refundExcessAll(uint256[] memory balancesBefore) internal {
        for (uint256 i = 0; i < poolTokens.length; ) {
            address token = address(poolTokens[i]);
            uint256 balanceAfter;
            if (token == nativeToken && msg.value > 0) {
                balanceAfter = address(this).balance;
            } else {
                balanceAfter = IERC20(token).balanceOf(address(this));
            }
            uint256 excess = balanceAfter > balancesBefore[i]
                ? balanceAfter - balancesBefore[i]
                : 0;
            if (excess > 0) {
                _refundExcess(token, excess);
            }
            unchecked {
                ++i;
            }
        }
    }

    /// @dev Withdraw and remove liquidity from the pool, burning shares
    /// @param sharesToBurn Amount of shares to burn
    /// @param minAmountsOut Minimum amounts to withdraw
    /// @param unwrapWrapped Array indicating if each token should be unwrapped
    /// @param wethIsEth Whether WETH should be treated as ETH
    /// @param userData Additional user data
    /// @return tokensOut Array of token addresses withdrawn
    /// @return amountsOut Array of amounts withdrawn
    function _withdrawAndRemoveLiquidity(
        uint256 sharesToBurn,
        uint256[] memory minAmountsOut,
        bool[] memory unwrapWrapped,
        bool wethIsEth,
        bytes memory userData
    )
        internal
        returns (address[] memory tokensOut, uint256[] memory amountsOut)
    {
        uint256 bptAmount = calculateWithdrawShares(sharesToBurn);

        // If gauge exists, withdraw staked BPT from gauge and remove the delta
        if (address(gauge) != address(0)) {
            uint256 lpAmountBefore = IERC20(pool).balanceOf(address(this));
            gauge.withdraw(bptAmount);
            uint256 lpAmountAfter = IERC20(pool).balanceOf(address(this));
            uint256 lpDiff = lpAmountAfter > lpAmountBefore
                ? lpAmountAfter - lpAmountBefore
                : 0;
            (tokensOut, amountsOut) = router.removeLiquidityProportionalFromERC4626Pool(
                pool,
                unwrapWrapped,
                lpDiff,
                minAmountsOut,
                wethIsEth,
                userData
            );
        } else {
            // No gauge configured: remove directly from pool using bptAmount
            (tokensOut, amountsOut) = router.removeLiquidityProportionalFromERC4626Pool(
                pool,
                unwrapWrapped,
                bptAmount,
                minAmountsOut,
                wethIsEth,
                userData
            );
        }

        _burn(msg.sender, sharesToBurn);
    }

    /// @dev Validate deposit input arrays and amounts
    /// @param recipient Recipient address
    /// @param wrapUnderlying Array indicating if each token should be wrapped
    /// @param maxInputAmounts Array of max input amounts
    /// @param exactBptAmountOut Exact BPT amount out
    function _validateDeposit(
        address recipient,
        bool[] memory wrapUnderlying,
        uint256[] memory maxInputAmounts,
        uint256 exactBptAmountOut
    ) internal view {
        if (recipient == address(0)) revert ZeroAddress();
        if (
            wrapUnderlying.length != poolTokens.length ||
            maxInputAmounts.length != poolTokens.length
        ) {
            revert ArrayLengthMismatch();
        }
        if (exactBptAmountOut == 0) revert InvalidAmount();
    }
}
"
    },
    "lib/balancer-v3-monorepo/pkg/interfaces/contracts/vault/ICompositeLiquidityRouter.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

/**
 * @notice The composite liquidity router supports add/remove liquidity operations on ERC4626 pools.
 * @dev This contract allow interacting with ERC4626 Pools (which contain wrapped ERC4626 tokens) using only underlying
 * standard tokens. For instance, with `addLiquidityUnbalancedToERC4626Pool` it is possible to add liquidity to an
 * ERC4626 Pool with [waDAI, waUSDC], using only DAI, only USDC, or an arbitrary amount of both. If the ERC4626 buffers
 * in the Vault have liquidity, these will be used to avoid wrapping/unwrapping through the wrapped token interface,
 * saving gas.
 *
 * For instance, adding only DAI to the pool above (and assuming an aDAI buffer with enough liquidity), would pull in
 * the DAI from the user, swap it for waDAI in the internal Vault buffer, and deposit the waDAI into the ERC4626 pool:
 * 1) without having to do any expensive ERC4626 wrapping operations; and
 * 2) without requiring the user to construct a batch operation containing the buffer swap.
 */
interface ICompositeLiquidityRouter {
    /// @notice `tokensOut` array does not have all the tokens from `expectedTokensOut`.
    error WrongTokensOut(address[] expectedTokensOut, address[] tokensOut);

    /***************************************************************************
                                   ERC4626 Pools
    ***************************************************************************/

    /**
     * @notice Add arbitrary amounts of tokens to an ERC4626 pool through the buffer.
     * @dev An "ERC4626 pool" contains IERC4626 yield-bearing tokens (e.g., waDAI). Ensure that any buffers associated
     * with the wrapped tokens in the ERC4626 pool have been initialized before initializing or adding liquidity to
     * the "parent" pool, and also make sure limits are set properly.
     *
     * @param pool Address of the liquidity pool
     * @param wrapUnderlying Flags indicating whether the corresponding token should be wrapped or
     * used as a standard ERC20
     * @param exactAmountsIn Exact amounts of underlying/wrapped tokens in, sorted in token registration order
     * @param minBptAmountOut Minimum amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data required for adding liquidity
     * @return bptAmountOut Actual amount of pool tokens received
     */
    function addLiquidityUnbalancedToERC4626Pool(
        address pool,
        bool[] memory wrapUnderlying,
        uint256[] memory exactAmountsIn,
        uint256 minBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 bptAmountOut);

    /**
     * @notice Add proportional amounts of tokens to an ERC4626 pool through the buffer.
     * @dev An "ERC4626 pool" contains IERC4626 yield-bearing tokens (e.g., waDAI). Ensure that any buffers associated
     * with the wrapped tokens in the ERC4626 pool have been initialized before initializing or adding liquidity to
     * the "parent" pool, and also make sure limits are set properly.
     *
     * @param pool Address of the liquidity pool
     * @param wrapUnderlying Flags indicating whether the corresponding token should be wrapped or
     * used as a standard ERC20
     * @param maxAmountsIn Maximum amounts of underlying/wrapped tokens in, sorted in token registration order
     * wrapped tokens in the pool
     * @param exactBptAmountOut Exact amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data required for adding liquidity
     * @return tokensIn Actual tokens added to the pool
     * @return amountsIn Actual amounts of tokens added to the pool
     */
    function addLiquidityProportionalToERC4626Pool(
        address pool,
        bool[] memory wrapUnderlying,
        uint256[] memory maxAmountsIn,
        uint256 exactBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (address[] memory tokensIn, uint256[] memory amountsIn);

    /**
     * @notice Remove proportional amounts of tokens from an ERC4626 pool, burning an exact pool token amount.
     * @dev An "ERC4626 pool" contains IERC4626 yield-bearing tokens (e.g., waDAI).
     * @param pool Address of the liquidity pool
     * @param unwrapWrapped Flags indicating whether the corresponding token should be unwrapped or
     * used as a standard ERC20
     * @param exactBptAmountIn Exact amount of pool tokens provided
     * @param minAmountsOut Minimum amounts of each token, corresponding to `tokensOut`
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data required for removing liquidity
     * @return tokensOut Actual tokens received
     * @return amountsOut Actual amounts of tokens received
     */
    function removeLiquidityProportionalFromERC4626Pool(
        address pool,
        bool[] memory unwrapWrapped,
        uint256 exactBptAmountIn,
        uint256[] memory minAmountsOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (address[] memory tokensOut, uint256[] memory amountsOut);

    /**
     * @notice Queries an `addLiquidityUnbalancedToERC4626Pool` operation without actually executing it.
     * @dev An "ERC4626 pool" contains IERC4626 yield-bearing tokens (e.g., waDAI).
     * @param pool Address of the liquidity pool
     * @param wrapUnderlying Flags indicating whether the corresponding token should be wrapped or
     * used as a standard ERC20
     * @param exactAmountsIn Exact amounts of underlying/wrapped tokens in, sorted in token registration order
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data required for the query
     * @return bptAmountOut Expected amount of pool tokens to receive
     */
    function queryAddLiquidityUnbalancedToERC4626Pool(
        address pool,
        bool[] memory wrapUnderlying,
        uint256[] memory exactAmountsIn,
        address sender,
        bytes memory userData
    ) external returns (uint256 bptAmountOut);

    /**
     * @notice Queries an `addLiquidityProportionalToERC4626Pool` operation without actually executing it.
     * @dev An "ERC4626 pool" contains IERC4626 yield-bearing tokens (e.g., waDAI).
     * @param pool Address of the liquidity pool
     * @param wrapUnderlying Flags indicating whether the corresponding token should be wrapped or
     * used as a standard ERC20
     * @param exactBptAmountOut Exact amount of pool tokens to be received
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data required for the query
     * @return tokensIn Expected tokens added to the pool
     * @return amountsIn Expected amounts of tokens added to the pool
     */
    function queryAddLiquidityProportionalToERC4626Pool(
        address pool,
        bool[] memory wrapUnderlying,
        uint256 exactBptAmountOut,
        address sender,
        bytes memory userData
    ) external returns (address[] memory tokensIn, uint256[] memory amountsIn);

    /**
     * @notice Queries a `removeLiquidityProportionalFromERC4626Pool` operation without actually executing it.
     * @dev An "ERC4626 pool" contains IERC4626 yield-bearing tokens (e.g., waDAI).
     * @param pool Address of the liquidity pool
     * @param unwrapWrapped Flags indicating whether the corresponding token should be unwrapped or
     * used as a standard ERC20
     * @param exactBptAmountIn Exact amount of pool tokens provided for the query
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data required for the query
     * @return tokensOut Expected tokens to receive
     * @return amountsOut Expected amounts of tokens to receive
     */
    function queryRemoveLiquidityProportionalFromERC4626Pool(
        address pool,
        bool[] memory unwrapWrapped,
        uint256 exactBptAmountIn,
        address sender,
        bytes memory userData
    ) external returns (address[] memory tokensOut, uint256[] memory amountsOut);
}
"
    },
    "lib/balancer-v3-monorepo/pkg/interfaces/contracts/vault/IRouter.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { AddLiquidityKind, RemoveLiquidityKind, SwapKind } from "./VaultTypes.sol";

/// @notice User-friendly interface to basic Vault operations: swap, add/remove liquidity, and associated queries.
interface IRouter {
    /***************************************************************************
                                Pool Initialization
    ***************************************************************************/

    /**
     * @notice Data for the pool initialization hook.
     * @param sender Account originating the pool initialization operation
     * @param pool Address of the liquidity pool
     * @param tokens Pool tokens, in token registration order
     * @param exactAmountsIn Exact amounts of tokens to be added, sorted in token registration order
     * @param minBptAmountOut Minimum amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to add initial liquidity
     */
    struct InitializeHookParams {
        address sender;
        address pool;
        IERC20[] tokens;
        uint256[] exactAmountsIn;
        uint256 minBptAmountOut;
        bool wethIsEth;
        bytes userData;
    }

    /**
     * @notice Initialize a liquidity pool.
     * @param pool Address of the liquidity pool
     * @param tokens Pool tokens, in token registration order
     * @param exactAmountsIn Exact amounts of tokens to be added, sorted in token registration order
     * @param minBptAmountOut Minimum amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to add initial liquidity
     * @return bptAmountOut Actual amount of pool tokens minted in exchange for initial liquidity
     */
    function initialize(
        address pool,
        IERC20[] memory tokens,
        uint256[] memory exactAmountsIn,
        uint256 minBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 bptAmountOut);

    /***************************************************************************
                                   Add Liquidity
    ***************************************************************************/

    /**
     * @notice Adds liquidity to a pool with proportional token amounts, receiving an exact amount of pool tokens.
     * @param pool Address of the liquidity pool
     * @param maxAmountsIn Maximum amounts of tokens to be added, sorted in token registration order
     * @param exactBptAmountOut Exact amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to add liquidity
     * @return amountsIn Actual amounts of tokens added, sorted in token registration order
     */
    function addLiquidityProportional(
        address pool,
        uint256[] memory maxAmountsIn,
        uint256 exactBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256[] memory amountsIn);

    /**
     * @notice Adds liquidity to a pool with arbitrary token amounts.
     * @param pool Address of the liquidity pool
     * @param exactAmountsIn Exact amounts of tokens to be added, sorted in token registration order
     * @param minBptAmountOut Minimum amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to add liquidity
     * @return bptAmountOut Actual amount of pool tokens received
     */
    function addLiquidityUnbalanced(
        address pool,
        uint256[] memory exactAmountsIn,
        uint256 minBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 bptAmountOut);

    /**
     * @notice Adds liquidity to a pool in a single token, receiving an exact amount of pool tokens.
     * @param pool Address of the liquidity pool
     * @param tokenIn Token used to add liquidity
     * @param maxAmountIn Maximum amount of tokens to be added
     * @param exactBptAmountOut Exact amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to add liquidity
     * @return amountIn Actual amount of tokens added
     */
    function addLiquiditySingleTokenExactOut(
        address pool,
        IERC20 tokenIn,
        uint256 maxAmountIn,
        uint256 exactBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 amountIn);

    /**
     * @notice Adds liquidity to a pool by donating the amounts in (no BPT out).
     * @dev To support donation, the pool config `enableDonation` flag must be set to true.
     * @param pool Address of the liquidity pool
     * @param amountsIn Amounts of tokens to be donated, sorted in token registration order
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to donate liquidity
     */
    function donate(address pool, uint256[] memory amountsIn, bool wethIsEth, bytes memory userData) external payable;

    /**
     * @notice Adds liquidity to a pool with a custom request.
     * @dev The given maximum and minimum amounts given may be interpreted as exact depending on the pool type.
     * In any case the caller can expect them to be hard boundaries for the request.
     *
     * @param pool Address of the liquidity pool
     * @param maxAmountsIn Maximum amounts of tokens to be added, sorted in token registration order
     * @param minBptAmountOut Minimum amount of pool tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to add liquidity
     * @return amountsIn Actual amounts of tokens added, sorted in token registration order
     * @return bptAmountOut Actual amount of pool tokens received
     * @return returnData Arbitrary (optional) data with an encoded response from the pool
     */
    function addLiquidityCustom(
        address pool,
        uint256[] memory maxAmountsIn,
        uint256 minBptAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData);

    /***************************************************************************
                                 Remove Liquidity
    ***************************************************************************/

    /**
     * @notice Removes liquidity with proportional token amounts from a pool, burning an exact pool token amount.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountIn Exact amount of pool tokens provided
     * @param minAmountsOut Minimum amounts of tokens to be received, sorted in token registration order
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to remove liquidity
     * @return amountsOut Actual amounts of tokens received, sorted in token registration order
     */
    function removeLiquidityProportional(
        address pool,
        uint256 exactBptAmountIn,
        uint256[] memory minAmountsOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256[] memory amountsOut);

    /**
     * @notice Removes liquidity from a pool via a single token, burning an exact pool token amount.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountIn Exact amount of pool tokens provided
     * @param tokenOut Token used to remove liquidity
     * @param minAmountOut Minimum amount of tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to remove liquidity
     * @return amountOut Actual amount of tokens received
     */
    function removeLiquiditySingleTokenExactIn(
        address pool,
        uint256 exactBptAmountIn,
        IERC20 tokenOut,
        uint256 minAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 amountOut);

    /**
     * @notice Removes liquidity from a pool via a single token, specifying the exact amount of tokens to receive.
     * @param pool Address of the liquidity pool
     * @param maxBptAmountIn Maximum amount of pool tokens provided
     * @param tokenOut Token used to remove liquidity
     * @param exactAmountOut Exact amount of tokens to be received
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to remove liquidity
     * @return bptAmountIn Actual amount of pool tokens burned
     */
    function removeLiquiditySingleTokenExactOut(
        address pool,
        uint256 maxBptAmountIn,
        IERC20 tokenOut,
        uint256 exactAmountOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 bptAmountIn);

    /**
     * @notice Removes liquidity from a pool with a custom request.
     * @dev The given maximum and minimum amounts given may be interpreted as exact depending on the pool type.
     * In any case the caller can expect them to be hard boundaries for the request.
     *
     * @param pool Address of the liquidity pool
     * @param maxBptAmountIn Maximum amount of pool tokens provided
     * @param minAmountsOut Minimum amounts of tokens to be received, sorted in token registration order
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the request to remove liquidity
     * @return bptAmountIn Actual amount of pool tokens burned
     * @return amountsOut Actual amounts of tokens received, sorted in token registration order
     * @return returnData Arbitrary (optional) data with an encoded response from the pool
     */
    function removeLiquidityCustom(
        address pool,
        uint256 maxBptAmountIn,
        uint256[] memory minAmountsOut,
        bool wethIsEth,
        bytes memory userData
    ) external payable returns (uint256 bptAmountIn, uint256[] memory amountsOut, bytes memory returnData);

    /**
     * @notice Removes liquidity proportionally, burning an exact pool token amount. Only available in Recovery Mode.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountIn Exact amount of pool tokens provided
     * @param minAmountsOut Minimum amounts of tokens to be received, sorted in token registration order
     * @return amountsOut Actual amounts of tokens received, sorted in token registration order
     */
    function removeLiquidityRecovery(
        address pool,
        uint256 exactBptAmountIn,
        uint256[] memory minAmountsOut
    ) external payable returns (uint256[] memory amountsOut);

    /***************************************************************************
                                       Swaps
    ***************************************************************************/

    /**
     * @notice Data for the swap hook.
     * @param sender Account initiating the swap operation
     * @param kind Type of swap (exact in or exact out)
     * @param pool Address of the liquidity pool
     * @param tokenIn Token to be swapped from
     * @param tokenOut Token to be swapped to
     * @param amountGiven Amount given based on kind of the swap (e.g., tokenIn for exact in)
     * @param limit Maximum or minimum amount based on the kind of swap (e.g., maxAmountIn for exact out)
     * @param deadline Deadline for the swap, after which it will revert
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the swap request
     */
    struct SwapSingleTokenHookParams {
        address sender;
        SwapKind kind;
        address pool;
        IERC20 tokenIn;
        IERC20 tokenOut;
        uint256 amountGiven;
        uint256 limit;
        uint256 deadline;
        bool wethIsEth;
        bytes userData;
    }

    /**
     * @notice Executes a swap operation specifying an exact input token amount.
     * @param pool Address of the liquidity pool
     * @param tokenIn Token to be swapped from
     * @param tokenOut Token to be swapped to
     * @param exactAmountIn Exact amounts of input tokens to send
     * @param minAmountOut Minimum amount of tokens to be received
     * @param deadline Deadline for the swap, after which it will revert
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @param userData Additional (optional) data sent with the swap request
     * @return amountOut Calculated amount of output tokens to be received in exchange for the given input tokens
     */
    function swapSingleTokenExactIn(
        address pool,
        IERC20 tokenIn,
        IERC20 tokenOut,
        uint256 exactAmountIn,
        uint256 minAmountOut,
        uint256 deadline,
        bool wethIsEth,
        bytes calldata userData
    ) external payable returns (uint256 amountOut);

    /**
     * @notice Executes a swap operation specifying an exact output token amount.
     * @param pool Address of the liquidity pool
     * @param tokenIn Token to be swapped from
     * @param tokenOut Token to be swapped to
     * @param exactAmountOut Exact amounts of input tokens to receive
     * @param maxAmountIn Maximum amount of tokens to be sent
     * @param deadline Deadline for the swap, after which it will revert
     * @param userData Additional (optional) data sent with the swap request
     * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
     * @return amountIn Calculated amount of input tokens to be sent in exchange for the requested output tokens
     */
    function swapSingleTokenExactOut(
        address pool,
        IERC20 tokenIn,
        IERC20 tokenOut,
        uint256 exactAmountOut,
        uint256 maxAmountIn,
        uint256 deadline,
        bool wethIsEth,
        bytes calldata userData
    ) external payable returns (uint256 amountIn);

    /***************************************************************************
                                      Queries
    ***************************************************************************/

    /**
     * @notice Queries an `addLiquidityProportional` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountOut Exact amount of pool tokens to be received
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountsIn Expected amounts of tokens to add, sorted in token registration order
     */
    function queryAddLiquidityProportional(
        address pool,
        uint256 exactBptAmountOut,
        address sender,
        bytes memory userData
    ) external returns (uint256[] memory amountsIn);

    /**
     * @notice Queries an `addLiquidityUnbalanced` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param exactAmountsIn Exact amounts of tokens to be added, sorted in token registration order
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return bptAmountOut Expected amount of pool tokens to receive
     */
    function queryAddLiquidityUnbalanced(
        address pool,
        uint256[] memory exactAmountsIn,
        address sender,
        bytes memory userData
    ) external returns (uint256 bptAmountOut);

    /**
     * @notice Queries an `addLiquiditySingleTokenExactOut` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param tokenIn Token used to add liquidity
     * @param exactBptAmountOut Expected exact amount of pool tokens to receive
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountIn Expected amount of tokens to add
     */
    function queryAddLiquiditySingleTokenExactOut(
        address pool,
        IERC20 tokenIn,
        uint256 exactBptAmountOut,
        address sender,
        bytes memory userData
    ) external returns (uint256 amountIn);

    /**
     * @notice Queries an `addLiquidityCustom` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param maxAmountsIn Maximum amounts of tokens to be added, sorted in token registration order
     * @param minBptAmountOut Expected minimum amount of pool tokens to receive
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountsIn Expected amounts of tokens to add, sorted in token registration order
     * @return bptAmountOut Expected amount of pool tokens to receive
     * @return returnData Arbitrary (optional) data with an encoded response from the pool
     */
    function queryAddLiquidityCustom(
        address pool,
        uint256[] memory maxAmountsIn,
        uint256 minBptAmountOut,
        address sender,
        bytes memory userData
    ) external returns (uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData);

    /**
     * @notice Queries a `removeLiquidityProportional` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountIn Exact amount of pool tokens provided for the query
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountsOut Expected amounts of tokens to receive, sorted in token registration order
     */
    function queryRemoveLiquidityProportional(
        address pool,
        uint256 exactBptAmountIn,
        address sender,
        bytes memory userData
    ) external returns (uint256[] memory amountsOut);

    /**
     * @notice Queries a `removeLiquiditySingleTokenExactIn` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountIn Exact amount of pool tokens provided for the query
     * @param tokenOut Token used to remove liquidity
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountOut Expected amount of tokens to receive
     */
    function queryRemoveLiquiditySingleTokenExactIn(
        address pool,
        uint256 exactBptAmountIn,
        IERC20 tokenOut,
        address sender,
        bytes memory userData
    ) external returns (uint256 amountOut);

    /**
     * @notice Queries a `removeLiquiditySingleTokenExactOut` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param tokenOut Token used to remove liquidity
     * @param exactAmountOut Expected exact amount of tokens to receive
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return bptAmountIn Expected amount of pool tokens to burn
     */
    function queryRemoveLiquiditySingleTokenExactOut(
        address pool,
        IERC20 tokenOut,
        uint256 exactAmountOut,
        address sender,
        bytes memory userData
    ) external returns (uint256 bptAmountIn);

    /**
     * @notice Queries a `removeLiquidityCustom` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param maxBptAmountIn Maximum amount of pool tokens provided
     * @param minAmountsOut Expected minimum amounts of tokens to receive, sorted in token registration order
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return bptAmountIn Expected amount of pool tokens to burn
     * @return amountsOut Expected amounts of tokens to receive, sorted in token registration order
     * @return returnData Arbitrary (optional) data with an encoded response from the pool
     */
    function queryRemoveLiquidityCustom(
        address pool,
        uint256 maxBptAmountIn,
        uint256[] memory minAmountsOut,
        address sender,
        bytes memory userData
    ) external returns (uint256 bptAmountIn, uint256[] memory amountsOut, bytes memory returnData);

    /**
     * @notice Queries a `removeLiquidityRecovery` operation without actually executing it.
     * @param pool Address of the liquidity pool
     * @param exactBptAmountIn Exact amount of pool tokens provided for the query
     * @return amountsOut Expected amounts of tokens to receive, sorted in token registration order
     */
    function queryRemoveLiquidityRecovery(
        address pool,
        uint256 exactBptAmountIn
    ) external returns (uint256[] memory amountsOut);

    /**
     * @notice Queries a swap operation specifying an exact input token amount without actually executing it.
     * @param pool Address of the liquidity pool
     * @param tokenIn Token to be swapped from
     * @param tokenOut Token to be swapped to
     * @param exactAmountIn Exact amounts of input tokens to send
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountOut Calculated amount of output tokens to be received in exchange for the given input tokens
     */
    function querySwapSingleTokenExactIn(
        address pool,
        IERC20 tokenIn,
        IERC20 tokenOut,
        uint256 exactAmountIn,
        address sender,
        bytes calldata userData
    ) external returns (uint256 amountOut);

    /**
     * @notice Queries a swap operation specifying an exact output token amount without actually executing it.
     * @param pool Address of the liquidity pool
     * @param tokenIn Token to be swapped from
     * @param tokenOut Token to be swapped to
     * @param exactAmountOut Exact amounts of input tokens to receive
     * @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
     * @param userData Additional (optional) data sent with the query request
     * @return amountIn Calculated amount of input tokens to be sent in exchange for the requested output tokens
     */
    function querySwapSingleTokenExactOut(
        address pool,
        IERC20 tokenIn,
        IERC20 tokenOut,
        uint256 exactAmountOut,
        address sender,
        bytes calldata userData
    ) external returns (uint256 amountIn);
}
"
    },
    "lib/balancer-v3-monorepo/pkg/interfaces/contracts/vault/IBatchRouter.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { AddLiquidityKind, RemoveLiquidityKind, SwapKind } from "./VaultTypes.sol";

/// @notice Interface for the `BatchRouter`, supporting multi-hop swaps.
interface IBatchRouter {
    /***************************************************************************
                                       Swaps
    ***************************************************************************/

    struct SwapPathStep {
        address pool;
        IERC20 tokenOut;
        // If true, the "pool" is an ERC4626 Buffer. Used to wrap/unwrap tokens if pool doesn't have enough liquidity.
        bool isBuffer;
    }

    struct SwapPathExactAmountIn {
        IERC20 tokenIn;
        // For each step:
        // If tokenIn == pool, use removeLiquidity SINGLE_TOKEN_EXACT_IN.
        // If tokenOut == pool, use addLiquidity UNBALANCED.
        SwapPathStep[] steps;
        uint256 exactAmountIn;
        uint256 minAmountOut;
    }

    struct SwapPathExactAmountOut {
        IERC20 tokenIn;
        // for each step:
        // If tokenIn == pool, use removeLiquidity SINGLE_TOKEN_EXACT_OUT.
        // If tokenOut == pool, use addLiquidity SINGLE_TOKEN_EXACT_OUT.
        SwapPathStep[] steps;
        uint256 maxAmountIn;
        uint256 exactAmountOut;
    }

    struct SwapExactInHookParams {
        address sender;
        SwapPathExactAmountIn[] paths;
        uint256 deadline;
        bool wethIsEth;
        bytes userData;
    }

    struct SwapExactOutHookParams {
        address sender;
        SwapPathExactAmountOut[] paths;
        uint256 deadline;
        bool wethIsEth;
        bytes userData;
    }

    /**
     * @notice Executes a swap operation involving multiple paths (steps), specifying exact input token amounts.
     * @param paths Swap paths from token

Tags:
ERC20, ERC165, Multisig, Mintable, Swap, Liquidity, Staking, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory|addr:0xddd3c106e23bfa39f0d0bd9588137f7443a1ba31|verified:true|block:23524899|tx:0x2d3dbc870523d7282ef3ad3cb490362b6896c26f80ac10c036132bb6cea8957c|first_check:1759830329

Submitted on: 2025-10-07 11:45:29

Comments

Log in to comment.

No comments yet.