// File: contracts/twault/CronV1Pool.sol



// SPDX-License-Identifier: BUSL-1.1

// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import { Math } from "./balancer-core-v2/lib/math/Math.sol";

import { IERC20 } from "./balancer-core-v2/lib/openzeppelin/IERC20.sol";
import { IERC20Decimals } from "./interfaces/IERC20Decimals.sol";
import { ReentrancyGuard } from "./balancer-core-v2/lib/openzeppelin/ReentrancyGuard.sol";

import { IMinimalSwapInfoPool } from "./balancer-core-v2/vault/interfaces/IMinimalSwapInfoPool.sol";
import { IBasePool } from "./balancer-core-v2/vault/interfaces/IBasePool.sol";
import { IVault } from "./balancer-core-v2/vault/interfaces/IVault.sol";
import { BalancerPoolToken } from "./balancer-core-v2/pools/BalancerPoolToken.sol";

import { IArbitrageurList } from "./interfaces/IArbitrageurList.sol";
import { ICronV1Pool } from "./interfaces/ICronV1Pool.sol";
import { ICronV1PoolFactory } from "./interfaces/ICronV1PoolFactory.sol";
import { ICronV1FactoryOwnerActions } from "./interfaces/pool/ICronV1FactoryOwnerActions.sol";
import { ICronV1PoolAdminActions } from "./interfaces/pool/ICronV1PoolAdminActions.sol";
import { ICronV1PoolArbitrageurActions } from "./interfaces/pool/ICronV1PoolArbitrageurActions.sol";
import { ICronV1PoolEnums } from "./interfaces/pool/ICronV1PoolEnums.sol";
import { ICronV1PoolEvents } from "./interfaces/pool/ICronV1PoolEvents.sol";
import { ICronV1PoolHelpers } from "./interfaces/pool/ICronV1PoolHelpers.sol";

import { C } from "./miscellany/Constants.sol";
import { requireErrCode, CronErrors } from "./miscellany/Errors.sol";
import { sqrt } from "./miscellany/Misc.sol";
import { BitPackingLib } from "./miscellany/BitPacking.sol";
import { VirtualOrders, OrderPools, Order, ExecVirtualOrdersMem, LoopMem, PriceOracle } from "./interfaces/Structs.sol";

/// @title  An implementation of a Time-Weighted Average Market Maker on Balancer V2 Vault Pools.
/// @author Zero Slippage (0slippage), Based upon the Paradigm paper TWAMM and the reference design
///         created by frankieislost with optimizations from FRAX incorporated for gas efficiency.
///
/// @notice For usage details, see the online Cron-Fi documentation at https://docs.cronfi.com/.
///
/// @dev Uses Balancer math library for overflow/underflow checks on standard U256 containers.
///      However, as many custom representations are used (i.e. non native word lengths) there
///      are a number of explicit checks against the maximum of other word lengths.
///      Furthermore there are unchecked operations (this code targets Solidity 0.7.x which
///      didn't yet feature implicit arithmetic checks or have the 'unchecked' block feature)
///      herein for reasons of efficiency or desired overflow. Wherever they appear they will
///      be documented and accompanied with one of the following tags:
///        - #unchecked
///        - #overUnderFlowIntended
///      Identified risks will be accompanied and described with the following tag:
///        - #RISK
///
/// @dev Conventions in the methods, variables and constants are as follows:
///
///      Prefixes:
///
///      - In constants, the prefix "Sn", where 1 <= n <= 4, denotes which slot the constant
///        pertains too. There are four storage slots that are bitpacked. For example,
///        "S2_OFFSET_ORACLE_TIMESTAMP" refers to the offset of the oracle timestamp in bit-
///        packed storage slot 2.
///
///      Suffixes:
///
///      - The suffix of a variable name denotes the type contained within the variable.
///        For instance "uint256 _incrementU96" is a 256-bit unsigned container representing
///        the 96-bit value "_increment".
///        In the case of "uint256 _balancerFeeDU1F18", the 256-bit unsigned container is
///        representing a 19 digit decimal value with 18 fractional digits. In this scenario,
///        the D=Decimal, U=Unsigned, F=Fractional.
///        Finally, "uint128 valueU128F64" is a 128-bit container representing a 128-bit value
///        with 64 fractional bits.
///
///      - The suffix of a function name denotes what slot it is proprietary too as a
///        matter of convention. While unchecked at run-time or by the compiler, the naming
///        convention easily aids in understanding what slot a packed value is stored within.
///        For instance the function "unpackFeeShiftS3" unpacks the fee shift from slot 3.
///        If the value of slot 2 were passed to this method, the unpacked value would be
///        incorrect.
///
/// @dev Fee Points (FP) is a system used herein to calculate applicable fees. THESE ABSOLUTELY
///      SHOULD NOT BE CONFUSED WITH BASIS POINTS--THEY ARE NOT BASIS POINTS! It consists of
///      fees, such as a swap fee, expressed in FP. The swap fee is multiplied by the amount
///      of token being swapped and divided by the total fee points (TOTAL_FP), which is 100,000,
///      to obtain the fee. For instance, a swap fee of 0.050% can be realized as follows:
///
///                    token_in x FEE_BP
///         swap_fee = -----------------
///                         TOTAL_FP
///
///                      token_in x 50
///                  = -----------------
///                          100000
///
contract CronV1Pool is ICronV1Pool, IMinimalSwapInfoPool, BalancerPoolToken, ReentrancyGuard {
  using Math for uint256;

  IVault private immutable VAULT;
  bytes32 public immutable override POOL_ID;
  IERC20 private immutable TOKEN0;
  IERC20 private immutable TOKEN1;
  PoolType public immutable override POOL_TYPE;
  uint16 private immutable ORDER_BLOCK_INTERVAL;
  uint24 private immutable MAX_ORDER_INTERVALS;
  uint256 private immutable ORDER_POOL0_PROCEEDS_SCALING;
  uint256 private immutable ORDER_POOL1_PROCEEDS_SCALING;
  address private immutable FACTORY;

  VirtualOrders private virtualOrders;

  /* Slot 1 Layout:
   *
   * The following variables are bit-mapped into uint256 slot1 in
   * container sizes related to their actual ranges as depicted below:
   *
   *   256-255  free (2-bits)
   *   -------
   *   254-245  shortTermFeeFP (10-bits)
   *   -------
   *   244-235  partnerFeeFP (10-bits)
   *   -------
   *   234-225  longTermFeeFP (10-bits)
   *   -------
   *   224-113  token0Orders (112-bits)
   *   -------
   *   112-  1  token1Orders (112-bits)
   *
   */
  uint256 internal slot1;

  /* Slot 2 Layout:
   *
   * The following variables are bit-mapped into uint256 slot2 in
   * container sizes related to their actual ranges as depicted below:
   *
   *   256-225  oracleTimeStamp (32-bits)
   *   -------
   *   224-113  token0Proceeds (112-bits)
   *   -------
   *   112-  1  token1Proceeds (112-bits)
   *
   */
  uint256 internal slot2;

  /* Slot 3 Layout:
   *
   * The following variables are bit-mapped into uint256 slot3 in
   * container sizes related to their actual ranges as depicted below:
   *
   *   256-226  free (31-bits)
   *   -------
   *   225-223  feeShiftU3 (3-bits)
   *   -------
   *   222-213  free (10-bits)
   *   -------
   *   212-193  free (20-bits)
   *   -------
   *   192- 97  token0CronFiFees (96-bits)
   *   -------
   *    96-  1  token1CronFiFees (96-bits)
   *
   */
  uint256 internal slot3;

  /* Slot 4 Layout
   *
   * The following variables are bit-mapped into uint256 slot4 in
   * container sizes related to their actual ranges as depicted below:
   *
   *       256  paused (1-bit)
   *   -------
   *       255  cronFeeEnabled (1-bit)
   *   -------
   *       254  collectBalancerFees (1-bit)
   *   -------
   *       253  zeroCronFiFees (1-bit)
   *   -------
   *   252-193  balancerFeeDU1F18 (60-bits)
   *   -------
   *   192- 97  token0BalancerFees (96-bits)
   *   -------
   *    96-  1  token1BalancerFees (96-bits)
   *
   */
  uint256 internal slot4;

  // @notice Uniswap V2 Style Oracle.
  // @dev Call function getOracleTimeStamp to get the timestamp associated
  //      with the oracle price values in this state variable.
  PriceOracle internal priceOracle;

  mapping(address => bool) internal adminAddrMap;
  mapping(address => address) internal partnerContractAddrMap;

  address internal feeAddr;

  mapping(address => uint256[]) private orderIdMap;

  /// @notice Ensure that the modified function is called by an address that is the factory owner.
  /// @dev    Cannot be used on Balancer Vault callbacks (onJoin, onExit,
  ///         onSwap) because msg.sender is the Vault address.
  ///
  modifier senderIsFactoryOwner() {
    _senderIsFactoryOwner(); // #contractsize optimization
    _;
  }

  /// @notice Ensures that the modified function is called by an address with administrator privileges.
  /// @dev    Cannot be used on Balancer Vault callbacks (onJoin, onExit,
  ///         onSwap) because msg.sender is the Vault address.
  ///
  modifier senderIsAdmin() {
    _senderIsAdmin(); // #contractsize optimization
    _;
  }

  /// @notice Ensures the modified function is called by an address with arbitrage partner privileges.
  /// @dev    Cannot be used on Balancer Vault callbacks (onJoin, onExit,
  ///         onSwap) because msg.sender is the Vault address.
  ///
  modifier senderIsArbitragePartner() {
    _senderIsArbitragePartner(); // #contractsize optimization
    _;
  }

  /// @notice Ensures that the modified function is not executed if the pool is currently paused.
  ///
  modifier poolNotPaused() {
    _poolNotPaused(); // #contractsize optimization
    _;
  }

  /// @notice Creates an instance of the Cron-Fi TWAMM pool. A Cron-Fi TWAMM pool features virtual order management and
  ///         virtualized reserves. Liquidity is managed through an instance of BalancerPoolToken.
  ///         The fees associated with the pool are configurable at run-time.
  ///
  ///         Importantly, the OBI cannot be changed after instantiation. If a pool's OBI is inappropriate to the
  ///         properties of the pair of tokens, it is recommended to create a new pool.
  ///
  ///         In the event of a failure, the pool can be paused which bypasses computation of virtual orders and allows
  ///         liquidity to be removed and long-term virtual orders to be withdrawn and refunded. Other operations are
  ///         blocked.
  ///
  ///         Management of the pool is performed by administrators who are able set gross swap fee amounts and
  ///         aribtrage partner status.
  ///
  ///         The pool factory owner is able to set the status of administrators, enable Cron-Fi fees, modify the
  ///         Cron-Fi fee address, adjust the fee-split between Cron-Fi and liquidity providers, and enable Balancer
  ///         fees.
  ///
  ///         Arbitrage partners are able to set and update a contract address that lists their arbitrageur's addresses,
  ///         which are able to swap at reduced fees as an incentive to provide better long-term order execution by
  ///         adjusting the bonding curve to compensate for the effect of virtual orders. These partners perform
  ///         accounting and capture a percentage of the trades or capture fees in another way which are periodically
  ///         remitted to the pool, rewarding the liquidity providers. This may be thought of as a constructive pay for
  ///         order flow or Maximal Extractable Value (MEV) recapture.
  ///
  /// @param _token0Inst The contract instance for token 0.
  /// @param _token1Inst The contract instance for token 1.
  /// @param _vaultInst The Balancer Vault instance this pool be a member of.
  /// @param _poolName The name for this pool.
  /// @param _poolSymbol The symbol for this pool.
  /// @param _poolType A value in the enumeration PoolType that controls the initial fee values and Order Block
  ///                  Interval (OBI) of the pool. See the documentation for the PoolType enumeration for details.
  ///
  constructor(
    IERC20 _token0Inst,
    IERC20 _token1Inst,
    IVault _vaultInst,
    string memory _poolName,
    string memory _poolSymbol,
    PoolType _poolType
  ) BalancerPoolToken(_poolName, _poolSymbol) {
    // Only factory can create pools, or else _senderIsFactoryOwner will revert:
    FACTORY = msg.sender;

    bytes32 poolIdValue = _vaultInst.registerPool(IVault.PoolSpecialization.TWO_TOKEN);

    IERC20[] memory tokens = new IERC20[](2);
    tokens[C.INDEX_TOKEN0] = _token0Inst;
    tokens[C.INDEX_TOKEN1] = _token1Inst;
    _vaultInst.registerTokens(
      poolIdValue,
      tokens,
      new address[](2) /* assetManagers */
    );

    VAULT = _vaultInst;
    POOL_ID = poolIdValue;
    TOKEN0 = _token0Inst;
    TOKEN1 = _token1Inst;

    POOL_TYPE = _poolType;

    // Compute the scaling factors for the order pool proceeds of each token. The addition of 1 is buffering the scaled
    // result to preserve additional precision.
    //
    uint256 token0Decimals = IERC20Decimals(address(_token0Inst)).decimals();
    requireErrCode(
      C.MIN_DECIMALS <= token0Decimals && token0Decimals <= C.MAX_DECIMALS,
      CronErrors.UNSUPPORTED_TOKEN_DECIMALS
    );
    ORDER_POOL0_PROCEEDS_SCALING = (10**(token0Decimals + 1));

    uint256 token1Decimals = IERC20Decimals(address(_token1Inst)).decimals();
    requireErrCode(
      C.MIN_DECIMALS <= token1Decimals && token1Decimals <= C.MAX_DECIMALS,
      CronErrors.UNSUPPORTED_TOKEN_DECIMALS
    );
    ORDER_POOL1_PROCEEDS_SCALING = (10**(token1Decimals + 1));

    // NOTE: Conditional assignment style / ternary operator hell required for immutables.
    //
    ORDER_BLOCK_INTERVAL = (_poolType == PoolType.Stable) ? C.STABLE_OBI : (_poolType == PoolType.Liquid)
      ? C.LIQUID_OBI
      : C.VOLATILE_OBI;
    MAX_ORDER_INTERVALS = (_poolType == PoolType.Stable) ? C.STABLE_MAX_INTERVALS : (_poolType == PoolType.Liquid)
      ? C.LIQUID_MAX_INTERVALS
      : C.VOLATILE_MAX_INTERVALS;

    if (_poolType == PoolType.Stable) {
      uint256 localSlot1 = BitPackingLib.packU10(0, C.STABLE_ST_FEE_FP, C.S1_OFFSET_SHORT_TERM_FEE_FP);
      localSlot1 = BitPackingLib.packU10(localSlot1, C.STABLE_ST_PARTNER_FEE_FP, C.S1_OFFSET_PARTNER_FEE_FP);
      slot1 = BitPackingLib.packU10(localSlot1, C.STABLE_LT_FEE_FP, C.S1_OFFSET_LONG_TERM_FEE_FP);
    } else if (_poolType == PoolType.Liquid) {
      uint256 localSlot1 = BitPackingLib.packU10(0, C.LIQUID_ST_FEE_FP, C.S1_OFFSET_SHORT_TERM_FEE_FP);
      localSlot1 = BitPackingLib.packU10(localSlot1, C.LIQUID_ST_PARTNER_FEE_FP, C.S1_OFFSET_PARTNER_FEE_FP);
      slot1 = BitPackingLib.packU10(localSlot1, C.LIQUID_LT_FEE_FP, C.S1_OFFSET_LONG_TERM_FEE_FP);
    } else {
      // PoolType.Volatile
      uint256 localSlot1 = BitPackingLib.packU10(0, C.VOLATILE_ST_FEE_FP, C.S1_OFFSET_SHORT_TERM_FEE_FP);
      localSlot1 = BitPackingLib.packU10(localSlot1, C.VOLATILE_ST_PARTNER_FEE_FP, C.S1_OFFSET_PARTNER_FEE_FP);
      slot1 = BitPackingLib.packU10(localSlot1, C.VOLATILE_LT_FEE_FP, C.S1_OFFSET_LONG_TERM_FEE_FP);
    }

    slot3 = BitPackingLib.packFeeShiftS3(slot3, C.DEFAULT_FEE_SHIFT);

    uint256 localSlot4 = BitPackingLib.packBit(0, 0, C.S4_OFFSET_PAUSE);
    localSlot4 = BitPackingLib.packBit(localSlot4, 0, C.S4_OFFSET_CRON_FEE_ENABLED);
    localSlot4 = BitPackingLib.packBit(localSlot4, 1, C.S4_OFFSET_COLLECT_BALANCER_FEES);
    slot4 = BitPackingLib.packBit(localSlot4, 1, C.S4_OFFSET_ZERO_CRONFI_FEES);

    adminAddrMap[C.CRON_DEPLOYER_ADMIN] = true;
    emit AdministratorStatusChange(msg.sender, C.CRON_DEPLOYER_ADMIN, true);

    emit FeeAddressChange(msg.sender, C.NULL_ADDR);
  }

  /// @notice Called by the vault when a user calls IVault.swap. Can be used to perform a Short-Term (ST)
  ///         swap, Long-Term (LT) swap, or Partner swap
  ///         ST swaps and Partner swaps behave like traditional Automated Market Maker atomic swaps
  ///         (think Uniswap V2 swaps).
  ///         LT swaps are virtual orders and behave differently, executing over successive blocks until
  ///         their expiry. Each LT swap is assigned an order id that is logged in a LongTermSwap event and
  ///         can also be fetched using getOrderIds for a given address. LT swaps can be withdrawn or
  ///         cancelled through the IVault.exit function (see onExitPool documentation).
  /// @param _swapRequest Is documented in Balancer's IPoolSwapStructs.sol. However, the userData field
  ///                     of this _swapRequest struct is a uint256 value, swapTypeU, followed by another
  ///                     uint256 value, argument, detailed below:
  ///                       * swapTypeU is decoded into the enum SwapType and determines if the transaction
  ///                         is a RegularSwap, LongTermSwap, or PartnerSwap.
  ///                         Min. = 0, Max. = 3
  ///                       * argument is a value, the use of which depends upon the SwapType value passed
  ///                                  into swapTypeU:
  ///                           - swapTypeU=0 (RegularSwap):  argument is ignored / not used.
  ///                           - swapTypeU=1 (LongTermSwap): argument is the number of order intervals for
  ///                                                         the LT trade before expiry.
  ///                           - swapTypeU=2 (PartnerSwap):  argument is the Partner address stored in a
  ///                                                         uint256. It is used to loop up the Partner's
  ///                                                         current arbitrage list contract address.
  ///                     Delegates:
  ///                     If the specified swapType is a LongTermSwap, the _swapRequest.to field can be
  ///                     used to specify a LT-Swap delegate. The delegate account is able to withdraw or
  ///                     cancel the LT-swap on behalf of the order owner (_swapRequest.from) at any time,
  ///                     so long as the recipient account specified for proceeds or refunds is the order
  ///                     owner. (The order owner does not have this restriction and direct proceeds or
  ///                     refunds to any desired account.)
  ///                     If the specified _swapRequest.to field is the null address or the order owner,
  ///                     then the delegate is disabled (and set to the null address).
  /// @param _currentBalanceTokenInU112 The Balancer Vault balance of the token being sold to the pool.
  ///                                   Min. = 0, Max. = (2**112) - 1
  /// @param _currentBalanceTokenOutU112 The Balancer Vault balance of the token being bought from the pool.
  ///                                    Min. = 0, Max. = (2**112) - 1
  /// @return amountOutU112 The amount of token being bought from the pool in this swap. For LT swaps this
  ///                       will always be zero. Proceeds from an LT swap can be withdrawn or the order
  ///                       refunded with an appropriate call to the IVault.exit function (see onExitPool
  ///                       documentation).
  ///
  function onSwap(
    SwapRequest memory _swapRequest,
    uint256 _currentBalanceTokenInU112,
    uint256 _currentBalanceTokenOutU112
  ) external override(IMinimalSwapInfoPool) poolNotPaused returns (uint256 amountOutU112) {
    requireErrCode(msg.sender == address(VAULT), CronErrors.NON_VAULT_CALLER);

    // NOTE: Not checking for balance overflow (amount + _currentBalanceTokenInU112) because this is
    //       handled by Balancer error BAL#526 BALANCE_TOTAL_OVERFLOW

    // #savegas #savesize
    IERC20 tokenIn = _swapRequest.tokenIn;
    address from = _swapRequest.from;
    uint256 amount = _swapRequest.amount;

    requireErrCode(_swapRequest.kind == IVault.SwapKind.GIVEN_IN, CronErrors.UNSUPPORTED_SWAP_KIND);

    // This style of decoding the data into the enum saves 16b of contract size.
    (uint256 swapTypeU, uint256 argument) = abi.decode(_swapRequest.userData, (uint256, uint256));
    SwapType swapType = SwapType(swapTypeU);

    bool token0To1 = (tokenIn == TOKEN0);
    (uint256 token0ReserveU112, uint256 token1ReserveU112) = _executeVirtualOrders(
      token0To1 ? _currentBalanceTokenInU112 : _currentBalanceTokenOutU112,
      token0To1 ? _currentBalanceTokenOutU112 : _currentBalanceTokenInU112,
      block.number
    );

    if (swapType == SwapType.LongTermSwap) {
      address delegate = _swapRequest.to;
      uint256 orderId = _longTermSwap(from, delegate, token0To1, amount, argument);
      emit LongTermSwap(from, delegate, address(tokenIn), amount, argument, orderId);
    } else {
      if (swapType == SwapType.PartnerSwap) {
        address contractAddress = partnerContractAddrMap[address(argument)];
        requireErrCode(IArbitrageurList(contractAddress).isArbitrageur(from), CronErrors.SENDER_NOT_PARTNER);
      }

      amountOutU112 = _shortTermSwap(
        token0To1,
        (swapType == SwapType.RegularSwap),
        amount,
        token0ReserveU112,
        token1ReserveU112
      );
      emit ShortTermSwap(from, address(tokenIn), amount, amountOutU112, swapTypeU);
    }
  }

  /// @notice Called by the Vault when a user calls IVault.joinPool. Can be use to add liquidity to
  ///         the pool in exchange for Liquidity Provider (LP) pool tokens or to reward the pool with
  ///         liquidity (MEV rewards from arbitrageurs).
  ///         WARNING: The initial liquidity provider, in a call to join the pool with joinTypeU=0
  ///                  (JoinType.Join), will sacrifice MINIMUM_LIQUIDITY, 1000, Liquidity Provider (LP)
  ///                  tokens. This may be an insignificant sacrifice for tokens with fewer decimal
  ///                  places and high worth (i.e. WBTC).
  ///         Importantly, the reward capability remains when the pool is paused to mitigate any
  ///         possible issue with underflowed pool reserves computed by differencing the pool accounting
  ///         from the pool token balances.
  /// @param _poolId The ID for this pool in the Balancer Vault
  /// @param _sender is the account performing the Join or Reward transaction (typically an LP or MEV reward
  ///                contract, respectively).
  /// @param _recipient is the account designated to receive pool shares in the form of LP tokens when
  ///                   Joining the pool. Can be set to _sender if sender wishes to receive the tokens
  ///                   and Join Events.
  /// @param _currentBalancesU112 an array containing the Balancer Vault balances of Token 0 and Token 1
  ///                             in this pool. The balances are in the same order that IVault.getPoolTokens
  ///                             returns.
  ///                             Min. = 0, Max. = (2**112) - 1
  /// @param _protocolFeeDU1F18 the Balancer protocol fee.
  ///                           Min. = 0, Max. = 10**18
  /// @param _userData is uint256 value, joinTypeU, followed by an array of 2 uint256 values, amounts,
  ///                  and another array of 2 uint256 values, minAmounts, detailed below:
  ///                    * joinTypeU is decoded into the enum JoinType and determines if the transaction is
  ///                                a Join or Reward.
  ///                                Min. = 0, Max. = 1
  ///                    * amountsInU112 are the amount of Token 0 and Token 1 to Join or Reward the pool
  ///                                    with, passed in the same array ordering that IVault.getPoolTokens
  ///                                    returns.
  ///                                    Min. = 0, Max. = (2**112) - 1
  ///                    * minAmountsU112 are the minimum amount of Token 0 and Token 1 prices at which
  ///                                     to Join the pool (protecting against sandwich attacks), passed
  ///                                     in the same array ordering that IVault.getPoolTokens returns.
  ///                                     The minAmountsU112 values are ignored unless joinTypeU is
  ///                                     0 (JoinType.Join). In the initial join, these values are
  ///                                     ignored.
  ///                                     Min. = 0, Max. = (2**112) - 1
  /// @return amountsInU112 is the amount of Token 0 and Token 1 provided to the pool as part of a Join or
  ///                       Reward transaction. Values are returned in the same array ordering that
  ///                       IVault.getPoolTokens returns.
  ///                       Min. = 0, Max. = (2**112) - 1
  /// @return dueProtocolFeeAmountsU96 the amount of Token 0 and Token 1 collected by the pool for
  ///                                  Balancer. Values are returned in the same array ordering that
  ///                                  IVault.getPoolTokens returns.
  ///                                  Min. = 0, Max. = (2**96) - 1
  ///
  function onJoinPool(
    bytes32 _poolId,
    address _sender,
    address _recipient,
    uint256[] memory _currentBalancesU112,
    uint256, /* lastChangeBlock - not used */
    uint256 _protocolFeeDU1F18,
    bytes calldata _userData
  ) external override(IBasePool) returns (uint256[] memory amountsInU112, uint256[] memory dueProtocolFeeAmountsU96) {
    _poolSafetyChecks(msg.sender, _poolId);

    JoinType joinType;
    uint256[] memory minAmountsU112;
    // Following block is a Stack Too Deep Workaround
    {
      // This style of decoding the data into the enum saves 16b of contract size.
      uint256 joinTypeU;
      (joinTypeU, amountsInU112, minAmountsU112) = abi.decode(_userData, (uint256, uint256[], uint256[]));
      joinType = JoinType(joinTypeU);
    }

    // NOTE: Not checking for balance overflows (amountsInU112 + _currentBalancesU112) because this is
    //       handled by Balancer error BAL#526 BALANCE_TOTAL_OVERFLOW

    ////////////////////////////////////////////////////////////////////////////////
    //                                                                            //
    // BEGIN WARNING: Moving the following 3 lines of code or separating them     //
    //                results in increased contract size (3b - ~53b or more       //
    //                depending on where the code is moved or how separated).     //
    //                                                                            //
    ////////////////////////////////////////////////////////////////////////////////
    // #savesize #savegas
    uint256 token0InU112 = amountsInU112[C.INDEX_TOKEN0];
    uint256 token1InU112 = amountsInU112[C.INDEX_TOKEN1];

    uint256 amountLP;
    ////////////////////////////////////////////////////////////////////////////////
    // END WARNING                                                                //
    ////////////////////////////////////////////////////////////////////////////////

    // NOTE:  If the pool is paused, JoinType.Join reverts.  JoinType.Reward does not revert, but proceeds
    //        without running execute virtual orders deliberately; this is because it is a fallback mitigation
    //        if _calculateReserves within the execute virtual orders process fails due to unforeseen finite
    //        precision effects (specifically if Balancer's accounting is less than the sum of this pool's
    //        orders, proceeds, and fees for either pool token--in this case a reward can be used to increase
    //        the balancer vault accounting preventing the failure and allowing withdraws, cancels, and pool
    //        exits that would otherwise be blocked by the failing _calculateReserves call).
    //
    //        If the pool is not paused, execute virtual orders is run for JoinType.Reward to ensure the added
    //        liquidity applies to the newest trades (i.e. those after the JoinType.Reward transaction).
    //
    if (!isPaused()) {
      uint256 token0ReserveU112;
      uint256 token1ReserveU112;

      if ((joinType == JoinType.Join && totalSupply() != 0) || (joinType == JoinType.Reward)) {
        (token0ReserveU112, token1ReserveU112) = _executeVirtualOrders(
          _currentBalancesU112[C.INDEX_TOKEN0],
          _currentBalancesU112[C.INDEX_TOKEN1],
          block.number
        );
      }

      if (joinType == JoinType.Join) {
        _joinMinimumCheck(
          token0InU112,
          token1InU112,
          minAmountsU112[C.INDEX_TOKEN0],
          minAmountsU112[C.INDEX_TOKEN1],
          token0ReserveU112,
          token1ReserveU112
        );
        amountLP = _join(_recipient, token0InU112, token1InU112, token0ReserveU112, token1ReserveU112);
      }
    } else {
      requireErrCode(joinType != JoinType.Join, CronErrors.POOL_PAUSED);
    }

    // When amountLP = 0, the PoolJoin event log doubles as a Reward event log.
    emit PoolJoin(_sender, _recipient, token0InU112, token1InU112, amountLP);

    dueProtocolFeeAmountsU96 = _handleBalancerFees(_protocolFeeDU1F18);
  }

  /// @notice Called by the Vault when a user calls IVault.exitPool. Can be used to remove liquidity from
  ///         the pool in exchange for Liquidity Provider (LP) pool tokens, to withdraw proceeds of a
  ///         Long-Term (LT) order, to cancel an LT order, or by the factory owner to withdraw protocol
  ///         fees if they are being collected.
  /// @param _poolId is the ID for this pool in the Balancer Vault
  /// @param _sender is the account performing the liquidity removal, LT order withdrawl, LT order
  ///                cancellation or the factory owner withdrawing fees.
  ///                For long term orders, a "delegate" may be specified, this address is able to
  ///                perform LT order withdraws and cancellations on behalf of the LT swap owner as
  ///                long as the recipient is the LT swap owner.
  /// @param _recipient For LT swaps the recipient must always be the original order owner (the address
  ///                   that issued the order) if a "delegate" address is performing the withdrawl or
  ///                   cancellation. If the order owner is performing the withdrawl or cancellation, the
  ///                   recipient can be set to whatever destination address desired.
  ///                   For other exit types (Exit & FeeWithdraw), the recipient can be set as desired
  ///                   so long as the sender is set to the authorized address.
  /// @param _currentBalancesU112 is an array containing the Balancer Vault balances of Token 0 and Token 1
  ///                             in this pool. The balances are in the same order that IVault.getPoolTokens
  ///                             returns.
  ///                             Min. = 0, Max. = (2**112) - 1
  /// @param _protocolFeeDU1F18 is the Balancer protocol fee.
  ///                           Min. = 0, Max. = 10**18
  /// @param _userData is uint256 value, exitTypeU, followed by a uint256, argument, detailed below:
  ///                    * exitTypeU is decoded into the enum ExitType and determines if the transaction is
  ///                                an Exit, Withdraw, Cancel, or FeeWithdraw.
  ///                                Min. = 0, Max. = 3
  ///                    * argument is used differently based on the ExitType value passed into exitTypeU:
  ///                                 - exitTypeU=0 (Exit):        argument is the number of LP tokens to
  ///                                                              redeem on Exit.
  ///                                 - exitTypeU=1 (Withdraw):    argument is the LT Swap order ID.
  ///                                 - exitTypeU=2 (Cancel):      argument is the LT Swap order ID.
  ///                                 - exitTypeU=3 (FeeWithdraw): argument is ignored / not used.
  /// @return amountsOutU112 is the amount of Token 0 and Token 1 provided by the pool as part of an Exit,
  ///                        LT Swap Withdrawl, LT Swap Cancel, or Fee Withdraw transaction. Values are
  ///                        returned in the same array ordering that IVault.getPoolTokens returns.
  ///                        Min. = 0, Max. = (2**112) - 1
  /// @return dueProtocolFeeAmountsU96 the amount of Token 0 and Token 1 collected by the pool for
  ///                                  Balancer. Values are returned in the same array ordering that
  ///                                  IVault.getPoolTokens returns.
  ///                                  Min. = 0, Max. = (2**96) - 1
  function onExitPool(
    bytes32 _poolId,
    address _sender,
    address _recipient,
    uint256[] memory _currentBalancesU112,
    uint256, /* lastChangeBlock - not used */
    uint256 _protocolFeeDU1F18,
    bytes calldata _userData
  ) external override(IBasePool) returns (uint256[] memory amountsOutU112, uint256[] memory dueProtocolFeeAmountsU96) {
    _poolSafetyChecks(msg.sender, _poolId);

    ExitType exitType;
    uint256 argument;
    // Following block is a Stack Too Deep Workaround
    {
      // This style of decoding the data into the enum saves 16b of contract size.
      uint256 exitTypeU;
      (exitTypeU, argument) = abi.decode(_userData, (uint256, uint256));
      exitType = ExitType(exitTypeU);
    }

    uint256 token0OutU112;
    uint256 token1OutU112;
    if (exitType == ExitType.FeeWithdraw) {
      (token0OutU112, token1OutU112) = _withdrawCronFees(_sender);
    } else {
      // Following block is a Stack Too Deep Workaround
      {
        // _executeVirtualOrders must be run for all exit types below, but it is in
        // this block b/c token0ReserveU112 and token1ReserveU112 variables are only for the
        // ExitType.Exit and cause Stack Too Deep otherwise.
        (uint256 token0ReserveU112, uint256 token1ReserveU112) = _executeVirtualOrders(
          _currentBalancesU112[C.INDEX_TOKEN0],
          _currentBalancesU112[C.INDEX_TOKEN1],
          block.number
        );

        if (exitType == ExitType.Exit) {
          (token0OutU112, token1OutU112) = _exit(_sender, argument, token0ReserveU112, token1ReserveU112);
        }
      }

      if (exitType == ExitType.Withdraw || exitType == ExitType.Cancel) {
        // NOTE: For all calls in this else block:
        //   - argument is the Order ID
        //   - _sender must be the original virtual order _sender
        (token0OutU112, token1OutU112) = _withdrawLongTermSwapWrapper(
          argument,
          _sender,
          _recipient,
          exitType == ExitType.Cancel
        );
      }
    }

    amountsOutU112 = new uint256[](2);
    amountsOutU112[C.INDEX_TOKEN0] = token0OutU112;
    amountsOutU112[C.INDEX_TOKEN1] = token1OutU112;

    dueProtocolFeeAmountsU96 = _handleBalancerFees(_protocolFeeDU1F18);
  }

  /// @notice Set the administrator status of the provided address, _admin. Status "true" gives
  ///         administrative privileges, "false" removes privileges.
  /// @param _admin  The address to add or remove administrative privileges from.
  /// @param _status Whether to grant (true) or deny (false) administrative privileges.
  /// @dev CAREFUL! You can remove all administrative privileges, rendering the contract unmanageable.
  /// @dev NOTE: Must be called by the factory owner.
  ///
  function setAdminStatus(address _admin, bool _status)
    external
    override(ICronV1FactoryOwnerActions)
    senderIsFactoryOwner
    nonReentrant
  {
    adminAddrMap[_admin] = _status;
    emit AdministratorStatusChange(msg.sender, _admin, _status);
  }

  /// @notice Enables Cron-Fi fee collection for Long-Term swaps when the provided address,
  ///         _feeDestination is not the null address.
  /// @param _feeDestination The address that can collect Cron-Fi Swap fees. If set to the null address,
  ///                        no Cron-Fi Long-Term swap fees are collected.
  /// @dev CAREFUL! Only the _feeDestination address can collect Cron-Fi fees from the pool.
  /// @dev NOTE: Must be called by the factory owner.
  ///
  function setFeeAddress(address _feeDestination)
    external
    override(ICronV1FactoryOwnerActions)
    senderIsFactoryOwner
    nonReentrant
  {
    feeAddr = _feeDestination;

    uint256 cronFeeEnabled = (_feeDestination != C.NULL_ADDR) ? 1 : 0;
    slot4 = BitPackingLib.packBit(slot4, cronFeeEnabled, C.S4_OFFSET_CRON_FEE_ENABLED);

    emit FeeAddressChange(msg.sender, _feeDestination);
  }

  /// @notice Sets whether the pool is paused or not. When the pool is paused:
  ///             * New swaps of any kind cannot be issued.
  ///             * Liquidity cannot be provided.
  ///             * Virtual orders are not executed for the remainder of allowable
  ///               operations, which include: removing liquidity, cancelling or
  ///               withdrawing a Long-Term swap order,
  ///         This is a safety measure that is not a part of expected pool operations.
  /// @param _pauseValue Pause the pool (true) or not (false).
  /// @dev NOTE: Must be called by an administrator.
  ///
  function setPause(bool _pauseValue) external override(ICronV1PoolAdminActions) senderIsAdmin nonReentrant {
    slot4 = BitPackingLib.packBit(slot4, _pauseValue ? 1 : 0, C.S4_OFFSET_PAUSE);

    emit BoolParameterChange(msg.sender, BoolParamType.Paused, _pauseValue);
  }

  /// @notice Set fee parameters.
  /// @param _paramTypeU A numerical value corresponding to the enum ParamType (see documentation
  ///                    for that above or values and corresponding ranges below).
  /// @param _value A value to set the specified parameter given in _paramTypeU. The values and
  ///               their ranges are as follows:
  ///
  ///     Short-Term Swap Fee Points:
  ///         * _paramTypeU = 0  (ParamType.SwapFeeFP)
  ///         * 0 <= _value <= C.MAX_FEE_FP (1000, ~1.000%)
  ///
  ///     Partner Swap Fee Points:
  ///         * _paramTypeU = 1  (ParamType.PartnerFeeFP)
  ///         * 0 <= _value <= C.MAX_FEE_FP (1000, ~1.000%)
  ///
  ///     Long-Term Swap Fee Points:
  ///         * _paramTypeU = 2  (ParamType.LongSwapFeeFP)
  ///         * 0 <= _value <= C.MAX_FEE_FP (1000, ~1.000%)
  ///
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out of 100,000.
  /// @dev NOTE: Must be called by an administrator.
  ///
  function setParameter(uint256 _paramTypeU, uint256 _value)
    external
    override(ICronV1PoolAdminActions)
    senderIsAdmin
    nonReentrant
  {
    ParamType paramType = ParamType(_paramTypeU);

    // 1. Determine the bit-offset for any values that require an offset to be set:
    //
    uint256 offset;
    if (paramType == ParamType.SwapFeeFP) {
      offset = C.S1_OFFSET_SHORT_TERM_FEE_FP;
    } else if (paramType == ParamType.PartnerFeeFP) {
      offset = C.S1_OFFSET_PARTNER_FEE_FP;
    } else if (paramType == ParamType.LongSwapFeeFP) {
      offset = C.S1_OFFSET_LONG_TERM_FEE_FP;
    } // else offset can be zero.

    // 2. Map _paramType numerically to the corresponding slot and then do numerical limits test
    //    and set the value if conformant. Otherwise revert with a numerical limit error from a
    //    call to BitPackingLib or a parameter error.
    //
    uint256 value = _value;
    if (paramType <= ParamType.LongSwapFeeFP && offset != 0 && _value <= C.MAX_FEE_FP) {
      slot1 = BitPackingLib.packU10(slot1, value, offset);
    } else {
      requireErrCode(
        false, /* error intentionally, 9b smaller than dedicated revert refactor of this */
        CronErrors.PARAM_ERROR
      );
    }

    emit ParameterChange(msg.sender, paramType, value);
  }

  /// @notice Enable or disable the collection of Balancer Fees. When enabled Balancer takes a
  ///         a portion of every fee collected in the pool. The pool remits fees to Balancer
  ///         automatically when onJoinPool and onExitPool are called. Disabling balancer fees
  ///         through this function supersedes any setting of balancer fee values in onJoinPool
  ///         and onExitPool.
  /// @param _collectBalancerFee When true, Balancer fees are collected, when false they are
  ///                            not collected.
  /// @dev NOTE: Must be called by the factory owner.
  ///
  function setCollectBalancerFees(bool _collectBalancerFee)
    external
    override(ICronV1FactoryOwnerActions)
    senderIsFactoryOwner
    nonReentrant
  {
    slot4 = BitPackingLib.packBit(slot4, _collectBalancerFee ? 1 : 0, C.S4_OFFSET_COLLECT_BALANCER_FEES);

    emit BoolParameterChange(msg.sender, BoolParamType.CollectBalancerFees, _collectBalancerFee);
  }

  /// @notice Sets the fee shift that splits Long-Term (LT) swap fees remaining after Balancer's cut
  ///         between the Liquidity Providers (LP) and Cron-Fi, if Cron-Fi fee collection is enabled.
  /// @param _feeShift A value between 1 and 4. Specifiying invalid values results in no-operation
  ///                  (percentages are approximate). The implication of these values are outlined
  ///                  below and are only applicable if Cron-Fi fees are being collected:
  ///
  ///                      _feeShift = 1:
  ///                          LP gets 2 fee shares (~66%), Cron-Fi gets 1 fee share (~33%)
  ///
  ///                      _feeShift = 2:
  ///                          LP gets 4 fee shares (~80%), Cron-Fi gets 1 fee share (~20%)
  ///
  ///                      _feeShift = 3:
  ///                          LP gets 8 fee shares (~88%), Cron-Fi gets 1 fee share (~12%)
  ///
  ///                      _feeShift = 4:
  ///                          LP gets 16 fee shares (~94%), Cron-Fi gets 1 fee share (~6%)
  ///
  /// @dev NOTE: Must be called by the factory owner.
  ///
  function setFeeShift(uint256 _feeShift)
    external
    override(ICronV1FactoryOwnerActions)
    senderIsFactoryOwner
    nonReentrant
  {
    requireErrCode(_feeShift >= 1 && _feeShift <= 4, CronErrors.PARAM_ERROR);

    slot3 = BitPackingLib.packFeeShiftS3(slot3, _feeShift);

    emit FeeShiftChange(msg.sender, _feeShift);
  }

  /// @notice Sets the arbitrageur list contract address, _arbitrageList, for an arbitrage
  ///         partner, _arbPartner. To clear an arbitrage partner, set _arbitrageList to the
  ///         null address.
  /// @param _arbPartner The address of the arbitrage partner. This should be the aribtrage
  ///                    partner's public address and shareable with members of the the
  ///                    arbitrage list contract.
  /// @param _arbitrageList The address of a deployed arbitrageur list contract for the
  ///                       specified arbitrage partner. The deployed contract should
  ///                       conform to the interface IArbitrageurList.
  ///
  /// @dev NOTE: Must be called by an administrator.
  ///
  function setArbitragePartner(address _arbPartner, address _arbitrageList)
    external
    override(ICronV1PoolAdminActions)
    senderIsAdmin
    nonReentrant
  {
    partnerContractAddrMap[_arbPartner] = _arbitrageList;
    emit UpdatedArbitragePartner(msg.sender, _arbPartner, _arbitrageList);
  }

  /// @notice Advances the specified arbitrage partner (msg.sender) arbitrageur list
  ///         contract to the newest contract, if available. See IArbitrageurList for
  ///         details on the calls made by this contract to that interface's nextList
  ///         function.
  /// @return the new arbitrageur list contract address.
  /// @dev NOTE: Must be called by an arbitrage partner.
  ///
  function updateArbitrageList()
    external
    override(ICronV1PoolArbitrageurActions)
    senderIsArbitragePartner
    nonReentrant
    returns (address)
  {
    address currentList;
    address oldList = partnerContractAddrMap[msg.sender];
    address nextList = oldList;

    do {
      currentList = nextList;
      nextList = IArbitrageurList(currentList).nextList();
    } while (nextList != C.NULL_ADDR);

    partnerContractAddrMap[msg.sender] = currentList;

    emit UpdatedArbitrageList(msg.sender, oldList, currentList);
    return currentList;
  }

  /// @notice Executes active virtual orders, Long-Term swaps, since the last virtual order block
  ///         executed,  updating reserve and other state variables to the current block.
  /// @param _maxBlock A block to update the virtual orders to. In most situations this would be
  ///                  the current block, however if the pool has been inactive for a considerable
  ///                  duration, specifying an earlier block allows gas use to be reduced in the event
  ///                  that the gas needed to update to the current block for a transaction to be
  ///                  performed exceeds the nominal or extended amounts available to an Ethereum
  ///                  transaction (15M & 30M respectively at the time of this writing).
  ///                  If the specified max block preceeds the last virtual order block then the
  ///                  current block number is automatically used.
  ///
  function executeVirtualOrdersToBlock(uint256 _maxBlock)
    external
    override(ICronV1PoolArbitrageurActions)
    nonReentrant
  {
    _triggerVaultReentrancyCheck();
    (, uint256[] memory balances, ) = VAULT.getPoolTokens(POOL_ID);

    _executeVirtualOrders(balances[C.INDEX_TOKEN0], balances[C.INDEX_TOKEN1], _maxBlock);

    emit ExecuteVirtualOrdersEvent(msg.sender, _maxBlock);
  }

  /// @notice Get the virtual price oracle data for the pool at the specified block, _maxBlock.
  ///
  ///         IMPORTANT - This function calls _getVirtualReserves, which triggers a re-entrancy check. Due
  ///                     to contract size challanges, there is no explicit call to that re-entrancy check
  ///                     in this function, where it's presence would be more obvious.
  ///
  ///         IMPORTANT - This function does not meaningfully modify state despite the lack of a "view"
  ///                     designator for state mutability. (The call to _triggerVaultReentrancyCheck
  ///                     Unfortunately prevents the "view" designator as meaningless value is written
  ///                     to state to trigger a reentracy check).
  ///
  ///         Runs virtual orders from the last virtual order block up to the current block to provide
  ///         visibility into the current accounting for the pool.
  ///         If the pool is paused, this function reflects the accounting values of the pool at
  ///         the last virtual order block (i.e. it does not execute virtual orders to deliver the result).
  /// @param _maxBlock is the block to determine the virtual oracle values at. Its value must be
  ///                  greater than the last virtual order block and less than or equal to the
  ///                  current block. Otherwise, current block is used in the computation and
  ///                  reflected in the return value, blockNumber.
  /// @return timestamp is the virtual timestamp in seconds corresponding to the price oracle
  ///                   values.
  /// @return token0U256F112 The virtual cumulative price of Token0 measured in amount of
  ///                        Token1 * seconds at timestamp.
  /// @return token1U256F112 The virtual cumulative price of Token1 measured in amount of
  ///                        Token0 * seconds at timestamp.
  /// @return blockNumber The block that the virtual oracle values were computed at. Should
  ///                     match parameter _maxBlock, unless _maxBlock was not greater than the
  ///                     last virtual order block or less than or equal to the current block.
  /// @dev Check that blockNumber matches _maxBlock to ensure that _maxBlock was correctly
  ///      specified.
  ///
  function getVirtualPriceOracle(uint256 _maxBlock)
    external
    override(ICronV1PoolHelpers)
    returns (
      uint256 timestamp,
      uint256 token0U256F112,
      uint256 token1U256F112,
      uint256 blockNumber
    )
  {
    ExecVirtualOrdersMem memory evoMem;
    (evoMem, blockNumber) = _getVirtualReserves(
      _maxBlock,
      false /* not paused */
    );

    if (virtualOrders.lastVirtualOrderBlock > 0) {
      uint32 timeElapsed;
      (timeElapsed, timestamp) = _getOracleTimeData(blockNumber);

      token0U256F112 = priceOracle.token0U256F112;
      token1U256F112 = priceOracle.token1U256F112;
      if (timeElapsed > 0) {
        // #overUnderFlowIntended
        //                        The UQ256x112 incremented result relies upon and expects overflow
        //                        and is unchecked. The reason is that it is the difference between
        //                        price oracle samples over time that matters, not the absolute value.
        //                        As noted above, see the Uniswap V2 whitepaper.
        token0U256F112 += evoMem.token0OracleU256F112;
        token1U256F112 += evoMem.token1OracleU256F112;
      }
    }
  }

  /// @notice Returns the TWAMM pool's reserves after the non-stateful execution of all virtual orders
  ///         up to the specified maximum block (unless an invalid block is specified, which results
  ///         in execution to the current block).
  ///
  ///         IMPORTANT - This function calls _getVirtualReserves, which triggers a re-entrancy check. Due
  ///                     to contract size challanges, there is no explicit call to that re-entrancy check
  ///                     in this function, where it's presence would be more obvious.
  ///
  ///         IMPORTANT - This function does not meaningfully modify state despite the lack of a "view"
  ///                     designator for state mutability. (The call to _triggerVaultReentrancyCheck
  ///                     Unfortunately prevents the "view" designator as meaningless value is written
  ///                     to state to trigger a reentracy check).
  ///
  /// @param _maxBlock a block to update virtual orders to. If less than or equal to the last virtual order
  ///                  block or greater than the current block, the value is set to the current
  ///                  block number.
  /// @param _paused is true to indicate the result should be returned as though the pool is in a paused
  ///                state where virtual orders are not executed and only withdraw, cancel and liquidations
  ///                are possible (check function isPaused to see if the pool is in that state). If false
  ///                then the virtual reserves are computed from virtual order execution to the specified
  ///                block.
  /// @return blockNumber The block that the virtual reserve values were computed at. Should
  ///                     match parameter _maxBlock, unless _maxBlock was not greater than the
  ///                     last virtual order block or less than or equal to the current block.
  /// @return token0ReserveU112 virtual reserves of Token0 in the TWAMM pool at blockNumber.
  /// @return token1ReserveU112 virtual reserves of Token1 in the TWAMM pool at blockNumber.
  /// @return token0OrdersU112 virtual amount of Token0 remaining to be sold to the pool in LT swap orders
  ///                          at blockNumber.
  /// @return token1OrdersU112 virtual amount of Token1 remaining to be sold to the pool in LT swap orders
  ///                          at blockNumber.
  /// @return token0ProceedsU112 virtual amount of Token0 purchased from the pool by LT swap orders
  ///                            at blockNumber.
  /// @return token1ProceedsU112 virtual amount of Token1 purchased from the pool by LT swap orders
  ///                            at blockNumber.
  /// @return token0BalancerFeesU96 virtual Balancer fees collected for all types of Token0-->Token1 swaps
  ///                               at blockNumber.
  /// @return token1BalancerFeesU96 virtual Balancer fees collected for all types of Token1-->Token0 swaps
  ///                               at blockNumber.
  /// @return token0CronFiFeesU96 virtual Cron-Fi fees collected for Token0-->Token1 Long-Term swaps at
  ///                             blockNumber.
  /// @return token1CronFiFeesU96 virtual Cron-Fi fees collected for Token1-->Token0 Long-Term swaps at
  ///                             blockNumber.
  ///
  function getVirtualReserves(uint256 _maxBlock, bool _paused)
    external
    override(ICronV1PoolHelpers)
    returns (
      uint256 blockNumber,
      uint256 token0ReserveU112,
      uint256 token1ReserveU112,
      uint256 token0OrdersU112,
      uint256 token1OrdersU112,
      uint256 token0ProceedsU112,
      uint256 token1ProceedsU112,
      uint256 token0BalancerFeesU96,
      uint256 token1BalancerFeesU96,
      uint256 token0CronFiFeesU96,
      uint256 token1CronFiFeesU96
    )
  {
    ExecVirtualOrdersMem memory evoMem;
    (evoMem, blockNumber) = _getVirtualReserves(_maxBlock, _paused);

    token0ReserveU112 = evoMem.token0ReserveU112;
    token1ReserveU112 = evoMem.token1ReserveU112;

    // Note that the order difference is a subtraction (virtual orders change orders
    // by selling them into the pool reserves):
    (uint256 t0Orders, uint256 t1Orders) = BitPackingLib.unpackPairU112(slot1);
    token0OrdersU112 = t0Orders - evoMem.token0OrdersU112;
    token1OrdersU112 = t1Orders - evoMem.token1OrdersU112;

    (uint256 t0Proceeds, uint256 t1Proceeds) = BitPackingLib.unpackPairU112(slot2);
    token0ProceedsU112 = t0Proceeds + evoMem.token0ProceedsU112;
    token1ProceedsU112 = t1Proceeds + evoMem.token1ProceedsU112;

    (uint256 t0BalFees, uint256 t1BalFees) = BitPackingLib.unpackPairU96(slot4);
    token0BalancerFeesU96 = t0BalFees + evoMem.token0BalancerFeesU96;
    token1BalancerFeesU96 = t1BalFees + evoMem.token1BalancerFeesU96;

    (uint256 t0CronFiFees, uint256 t1CronFiFees) = BitPackingLib.unpackPairU96(slot3);
    token0CronFiFeesU96 = t0CronFiFees + evoMem.token0CronFiFeesU96;
    token1CronFiFeesU96 = t1CronFiFees + evoMem.token1CronFiFeesU96;
  }

  /// @notice Get the price oracle data for the pool as of the last virtual order block.
  /// @return timestamp is the timestamp in seconds when the price oracle was last updated.
  /// @return token0U256F112 The cumulative price of Token0 measured in amount of
  ///                        Token1 * seconds.
  /// @return token1U256F112 The cumulative price of Token1 measured in amount of
  ///                        Token0 * seconds.
  ///
  function getPriceOracle()
    external
    view
    override(ICronV1PoolHelpers)
    returns (
      uint256 timestamp,
      uint256 token0U256F112,
      uint256 token1U256F112
    )
  {
    timestamp = BitPackingLib.unpackOracleTimeStampS2(slot2);
    token0U256F112 = priceOracle.token0U256F112;
    token1U256F112 = priceOracle.token1U256F112;
  }

  /// @notice Return an array of the order IDs for the specified user _owner. Allows all orders to be fetched
  ///         at once or pagination through the _offset and _maxResults parameters. For instance to get the
  ///         first 100 orderIds of a user, specify _offset=0 and _maxResults=100. To get the second 100
  ///         orderIds for the same user, specify _offset=100 and _maxResults=100. To get all results at once,
  ///         either specify all known results or 0 for _maxResults.
  /// @param  _owner is the address of the owner to fetch order ids for.
  /// @param  _offset is the number of elements from the end of the list to start fetching results from (
  ///                 consult the operating description above).
  /// @param  _maxResults is the maximum number of results to return when calling this function (i.e. if
  ///                     this is set to 1,000 and there are 10,000 results available, only 1,000 from the
  ///                     specified offset will be returned). If 0 is specified, then all results available are
  ///                     returned.
  /// @return orderIds A uint256 array of order IDs associated with user's address.
  /// @return numResults The number of order IDs returned (if a user has less than the specified maximum
  ///                    number of results, indices in the returned order ids array after numResults-1 will
  ///                    be zero).
  /// @return totalResults The total number of order IDs associated with this user's address.
  ///                      (Useful for pagination of results--i.e. increase _offset by 100 until
  ///                       totalResults - 100 is reached).
  ///
  function getOrderIds(
    address _owner,
    uint256 _offset,
    uint256 _maxResults
  )
    external
    view
    override(ICronV1PoolHelpers)
    returns (
      uint256[] memory orderIds,
      uint256 numResults,
      uint256 totalResults
    )
  {
    uint256[] storage orderIdArr = orderIdMap[_owner];
    totalResults = orderIdArr.length;

    uint256 maxResults = (_maxResults == 0) ? totalResults : _maxResults;
    orderIds = new uint256[](maxResults);

    for (uint256 index = _offset; numResults < maxResults && index < totalResults; index++) {
      orderIds[numResults++] = orderIdArr[index];
    }
  }

  /// @notice Return the order information of a given order id.
  /// @param _orderId is the id of the order to return.
  /// @return order is the order data corresponding to the given order id. See Order struct documentation
  ///               for additional information.
  ///
  function getOrder(uint256 _orderId) external view override(ICronV1PoolHelpers) returns (Order memory order) {
    return virtualOrders.orderMap[_orderId];
  }

  /// @notice Returns the number of virtual orders, Long-Term (LT) swaps, that have been transacted in
  ///         this pool.
  /// @return nextOrderId The number of virtual orders issued (also the next order ID that is
  ///                     assigned to a LT swap.)
  ///
  function getOrderIdCount() external view override(ICronV1PoolHelpers) returns (uint256 nextOrderId) {
    nextOrderId = virtualOrders.nextOrderId;
  }

  /// @notice Returns the sales rate of each of the two order pools at the last virtual order block.
  ///         This is the value persisted to state.
  /// @return salesRate0U112 order pool 0 sales rate. The amount of Token 0 sold to the pool, per block,
  ///                        in exchange for Token 1, on behalf of all active Long-Term (LT) swap orders,
  ///                        swapping Token0 for Token1, as of the last virtual order block.
  ///                        Min. = 0, Max. = (2**112) - 1
  /// @return salesRate1U112 order pool 1 sales rate. The amount of Token 1 sold to the pool, per block,
  ///                        in exchange for Token 0, on behalf of all active Long-Term (LT) swap orders,
  ///                        swapping Token1 for Token0, as of the last virtual order block.
  ///                        Min. = 0, Max. = (2**112) - 1
  ///
  function getSalesRates()
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 salesRate0U112, uint256 salesRate1U112)
  {
    uint256 salesRates = virtualOrders.orderPools.currentSalesRates;
    salesRate0U112 = (salesRates >> 112) & C.MAX_U112;
    salesRate1U112 = salesRates & C.MAX_U112;
  }

  /// @notice Get the Last Virtual Order Block (LVOB) for the pool. This is the block number indicating the last block
  ///         where virtual orders have been executed by the pool. If the LVOB is significantly less than the current
  ///         block number, it indicates that the pool has been inactive and that a call to any function that requires
  ///         the execution of virtual orders may incur siginificant gas use.
  /// @return lastVirtualOrderBlock is the last block number that virtual orders have been executed to.
  ///
  function getLastVirtualOrderBlock()
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 lastVirtualOrderBlock)
  {
    return virtualOrders.lastVirtualOrderBlock;
  }

  /// @notice Get the sales rate ending (per block) at the specified block number.
  /// @param salesRateEndingPerBlock0U112 the amount of Token 0 per block that will stop being sold
  ///                                     to the pool after the specified block.
  ///                                     Min. = 0, Max. = (2**112) - 1
  /// @param salesRateEndingPerBlock1U112 the amount of Token 0 per block that will stop being sold
  ///                                     to the pool after the specified block.
  ///                                     Min. = 0, Max. = (2**112) - 1
  /// @dev NOTE: these values are inserted into state at block numbers divisible by the Order Block
  ///            Interval (OBI)--specifiying block numbers other than those evenly divisible by the
  ///            OBI will result in the returned values being zero.
  ///
  function getSalesRatesEndingPerBlock(uint256 _blockNumber)
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 salesRateEndingPerBlock0U112, uint256 salesRateEndingPerBlock1U112)
  {
    (salesRateEndingPerBlock0U112, salesRateEndingPerBlock1U112) = BitPackingLib.unpackPairU112(
      virtualOrders.orderPools.salesRatesEndingPerBlock[_blockNumber]
    );
  }

  // Slot 1 Access Functions:
  //
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Gets the current Short-Term (ST) swap fee for the pool in Fee Points.
  /// @return The ST swap Fee Points (FP).
  ///         Min. = 0, Max. = 1000 (C.MAX_FEE_FP)
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out
  ///            of 100,000.
  ///
  function getShortTermFeePoints() external view override(ICronV1PoolHelpers) returns (uint256) {
    return BitPackingLib.unpackU10(slot1, C.S1_OFFSET_SHORT_TERM_FEE_FP);
  }

  /// @notice Gets the current Partner swap fee for the pool in Fee Points.
  /// @return The Partner swap Fee Points (FP).
  ///         Min. = 0, Max. = 1000 (C.MAX_FEE_FP)
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out
  ///            of 100,000.
  ///
  function getPartnerFeePoints() external view override(ICronV1PoolHelpers) returns (uint256) {
    return BitPackingLib.unpackU10(slot1, C.S1_OFFSET_PARTNER_FEE_FP);
  }

  /// @notice Gets the current Long-Term (LT) swap fee for the pool in Fee Points.
  /// @return The LT swap Fee Points (FP).
  ///         Min. = 0, Max. = 1000 (C.MAX_FEE_FP)
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out
  ///            of 100,000.
  ///
  function getLongTermFeePoints() external view override(ICronV1PoolHelpers) returns (uint256) {
    return BitPackingLib.unpackU10(slot1, C.S1_OFFSET_LONG_TERM_FEE_FP);
  }

  /// @notice Gets the amounts of Token0 and Token1 in active virtual orders waiting to
  ///         be sold to the pool as of the last virtual order block.
  /// @return orders0U112 is the aggregated amount of Token0 for active swaps from Token0 to Token1,
  ///         waiting to be sold to the pool since the last virtual order block.
  ///         Min. = 0, Max. = (2**112)-1
  /// @return orders1U112 is the aggregated amount of Token1 for active swaps from Token1 to Token0,
  ///         waiting to be sold to the pool since the last virtual order block.
  ///         Min. = 0, Max. = (2**112)-1
  ///
  function getOrderAmounts()
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 orders0U112, uint256 orders1U112)
  {
    (orders0U112, orders1U112) = BitPackingLib.unpackPairU112(slot1);
  }

  // Slot 2 Access Functions:
  //
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Get the proceeds of Token0 and Token1 resulting from
  ///         virtual orders, Long-Term swaps, up to the last virtual order block.
  /// @return proceeds0U112 is the aggregated amount of Token0 from swaps selling Token1 for Token0
  ///                       to the pool, waiting to be withdrawn, as of the last virtual order block.
  ///                       Min. = 0, Max. = (2**112)-1
  /// @return proceeds1U112 is the aggregated amount of Token1 from swaps selling Token1 for Token0
  ///                       to the pool, waiting to be withdrawn, as of the last virtual order block.
  ///                       Min. = 0, Max. = (2**112)-1
  ///
  function getProceedAmounts()
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 proceeds0U112, uint256 proceeds1U112)
  {
    (proceeds0U112, proceeds1U112) = BitPackingLib.unpackPairU112(slot2);
  }

  // Slot 3 Access Functions:
  //
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Gets the current value of the fee shift, which indicates how Long-Term (LT) swap fees are
  ///         split between Cron-Fi and Liquidity Providers (LPs) when Cron-Fi fee collection is enabled.
  /// @return A value between 1 and 4 that is the fee shift used to determine fee spliting between Cron-Fi
  ///         and LPs:
  ///
  ///               Fee Shift = 1:
  ///                   LP gets 2 fee shares (~66%), Cron-Fi gets 1 fee share (~33%)
  ///
  ///               Fee Shift = 2:
  ///                   LP gets 4 fee shares (~80%), Cron-Fi gets 1 fee share (~20%)
  ///
  ///               Fee Shift = 3:
  ///                   LP gets 8 fee shares (~88%), Cron-Fi gets 1 fee share (~12%)
  ///
  ///               Fee Shift = 4:
  ///                   LP gets 16 fee shares (~94%), Cron-Fi gets 1 fee share (~6%)
  ///
  function getFeeShift() external view override(ICronV1PoolHelpers) returns (uint256) {
    return BitPackingLib.unpackFeeShiftS3(slot3);
  }

  /// @notice Gets the amounts of Token0 and Token1 collected as Cron-Fi fees on Long-Term (LT) swaps
  ///         as of the last virtual order block.
  /// @return cronFee0U96 the amount of Token0 Cron-Fi fees collected as of the last virtual order block.
  ///                      Min. = 0, Max. = (2**96) - 1
  /// @return cronFee1U96 the amount of Token1 Cron-Fi fees collected as of the last virtual order block.
  ///                      Min. = 0, Max. = (2**96) - 1
  ///
  function getCronFeeAmounts()
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 cronFee0U96, uint256 cronFee1U96)
  {
    (cronFee0U96, cronFee1U96) = BitPackingLib.unpackPairU96(slot3);
  }

  // Slot 4 Access Functions:
  //
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Use to determine if the pool is collecting Cron-Fi fees currently (Cron-Fi fees are only
  ///         collected on Long-Term swaps if enabled).
  /// @return True if the pool is collecting Cron-Fi fees, false otherwise.
  ///
  function isCollectingCronFees() external view override(ICronV1PoolHelpers) returns (bool) {
    return BitPackingLib.unpackBit(slot4, C.S4_OFFSET_CRON_FEE_ENABLED) != C.FALSE;
  }

  /// @notice Use to determine if the pool is collecting Balancer fees currently (Balancer fees apply to
  ///         any fee collected by the pool--Short and Long Term swaps).
  /// @return True if the pool is collecting Balancer fees, false otherwise.
  ///
  function isCollectingBalancerFees() external view override(ICronV1PoolHelpers) returns (bool) {
    return BitPackingLib.unpackBit(slot4, C.S4_OFFSET_COLLECT_BALANCER_FEES) != C.FALSE;
  }

  /// @notice Get the Balancer Fee charged by the pool.
  /// @return The current Balancer Fee, a number that is divided by 1e18 (C.ONE_DU1_18) to arrive at a
  ///         fee multiplier between 0 and 1 with 18 fractional decimal digits.
  ///         Min. = 0.000000000000000000, Max. = 1.000000000000000000
  ///
  function getBalancerFee() external view override(ICronV1PoolHelpers) returns (uint256) {
    return BitPackingLib.unpackBalancerFeeS4(slot4);
  }

  /// @notice Gets the amounts of Token0 and Token1 collected as Balancer fees on all swaps as of the last
  ///         virtual order block.
  /// @return balFee0U96 the amount of Token0 Balancer fees collected as of the last virtual order block.
  ///                      Min. = 0, Max. = (2**96) - 1
  /// @return balFee1U96 the amount of Token1 Balancer fees collected as of the last virtual order block.
  ///                      Min. = 0, Max. = (2**96) - 1
  ///
  function getBalancerFeeAmounts()
    external
    view
    override(ICronV1PoolHelpers)
    returns (uint256 balFee0U96, uint256 balFee1U96)
  {
    (balFee0U96, balFee1U96) = BitPackingLib.unpackPairU96(slot4);
  }

  /// @notice Use to determine if the pool's virtual orders are currently paused. If virtual orders are
  ///         paused, the pool will allow Long-Term (LT) swaps to be cancelled and withdrawn from as well
  ///         as liquidity positions to be withdrawn.
  /// @return True if the pool is paused, false otherwise.
  ///
  function isPaused() public view override(ICronV1PoolHelpers) returns (bool) {
    return BitPackingLib.unpackBit(slot4, C.S4_OFFSET_PAUSE) != C.FALSE;
  }

  /// @notice Reverts with error if msg.sender is not the factory owner.
  /// @dev This internal function is a modifier contract size optimization.
  ///
  function _senderIsFactoryOwner() internal view {
    requireErrCode(msg.sender == ICronV1PoolFactory(FACTORY).owner(), CronErrors.SENDER_NOT_FACTORY_OWNER);
  }

  /// @notice Reverts with error if msg.sender is not a pool administrator.
  /// @dev This internal function is a modifier contract size optimization.
  ///
  function _senderIsAdmin() internal view {
    requireErrCode(adminAddrMap[msg.sender], CronErrors.SENDER_NOT_ADMIN);
  }

  /// @notice Reverts with error if msg.sender is not an arbitrage partner.
  /// @dev This internal function is a modifier contract size optimization.
  ///
  function _senderIsArbitragePartner() internal view {
    requireErrCode(partnerContractAddrMap[msg.sender] != C.NULL_ADDR, CronErrors.SENDER_NOT_ARBITRAGE_PARTNER);
  }

  /// @notice Reverts with error if the pool is paused.
  /// @dev This internal function is a modifier contract size optimization.
  ///
  function _poolNotPaused() internal view {
    requireErrCode(!isPaused(), CronErrors.POOL_PAUSED);
  }

  /// @notice Computes the proceeds of a virtual order, Long-Term (LT) swap, for withdrawl or
  ///         cancellation purposes. Proceeds are determined using the staking algorithm, where
  ///         the user's order sales rate, _stakedAmountU128, represents their stake and the
  ///         difference between the normalized proceeds at this juncture or their order end and
  ///         order start are used to calculate their share. The normalized proceeds are
  ///         stored in 128-bits with 64 fractional-bits, hence the scaling down by 64-bits below.
  /// @param _scaledProceedsU128 The current or order end normalized scaled proceeds value.
  /// @param _startScaledProceedsU128 The normalized scaled proceeds value at the start of the
  ///                                 order (or when it was last withdrawn from).
  /// @param _salesRateU112 The order's sales rate in token per block.
  /// @param _token0To1 the direction of this swap, true if selling Token 0 for Token1, false otherwise.
  /// @dev Note explanations for required underflow in this calculation below.
  ///
  function _calculateProceeds(
    uint256 _scaledProceedsU128,
    uint256 _startScaledProceedsU128,
    uint256 _salesRateU112,
    bool _token0To1
  ) internal view returns (uint256 proceedsU112) {
    // NOTE: uint128 casts used here instead of as arguments to this function for gas/size efficiency.

    // #overUnderFlowIntended
    //                        Underflow is required here because it's not the scaled proceeds value but the distance
    //                        between them that is important. Underflow maintains the distance relationship. Consult
    //                        the Cron-Fi TWAMM Numerical Analysis for a discussion and analysis on this matter.
    uint256 orderProceedsU128 = uint128(uint128(_scaledProceedsU128) - uint128(_startScaledProceedsU128));

    // #unchecked
    //            The multiplication below is not checked for overflow because the product of a U128 and U112
    //            cannot exceed a U256. (The order proceeds is correct by construction because it is recovered
    //            from a 128-bit storage that checks for 128-bit overflow when stored, the sales rate is known
    //            to be 112-bits or less because Balancer checks the amount in with BAL#526
    //            BALANCE_TOTAL_OVERFLOW, which is then divided by the number of sales blocks).
    //
    //            Proceeds is not examined to see if it exceeds MAX_U112 because it is checked downstream from
    //            its use here in the bit packing library's decrementPairU112 function call for underflow on
    //            subtraction from a U112.
    proceedsU112 =
      (orderProceedsU128 * _salesRateU112) /
      (_token0To1 ? ORDER_POOL0_PROCEEDS_SCALING : ORDER_POOL1_PROCEEDS_SCALING);
  }

  /// @notice executes existing Virtual Orders (Long-Term-swaps) since last virtual order block,
  ///         updating TWAMM reserve values and other TWAMM state variables up to the specified
  ///         maximum block.
  /// @param _balance0U112 The Balancer Vault balance of Token 0 for this pool.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _balance1U112 The Balancer Vault balance of Token 1 for this pool.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _maxBlock a block to update virtual orders to (useful to specify in emergency situation
  ///                  where inactive pool requires too much gas for a single successful call to
  ///                  executeVirtualOrders.) If less than or equal to the last virtual order block
  ///                  or greater than the current block, the value is set to the current block number.
  /// @param token0ReserveU112 is the Token 0 reserves of the pool as of _maxBlock.
  ///                          Min. = 0, Max. = (2**112) - 1
  /// @param token1ReserveU112 is the Token 1 reserves of the pool as of _maxBlock.
  ///                          Min. = 0, Max. = (2**112) - 1
  ///
  function _executeVirtualOrders(
    uint256 _balance0U112,
    uint256 _balance1U112,
    uint256 _maxBlock
  ) private returns (uint256 token0ReserveU112, uint256 token1ReserveU112) {
    if (!(virtualOrders.lastVirtualOrderBlock < _maxBlock && _maxBlock <= block.number)) {
      _maxBlock = block.number;
    }

    uint256 localSlot4 = slot4; // #savegas
    ExecVirtualOrdersMem memory evoMem = _getExecVirtualOrdersMem(_balance0U112, _balance1U112);

    if (!isPaused()) {
      _executeVirtualOrdersToBlock(evoMem, _maxBlock);

      // Optimization:
      //               (evoMem.feeShiftU3 != 0) --> cronFeeEnabled == true
      //
      //               Rather than read from storage again, use the value of feeShiftU3 that was set in
      //               _getExecVirtualOrdersMem based on the cron fee enabled flag (S4_OFFSET_CRON_FEE_ENABLED).
      //               (feeShiftU3 can only take on values 1 through 4 once Cron-Fi fees are enabled, and is 1
      //               on construction).
      if (evoMem.feeShiftU3 != 0) {
        // CAREFUL!  Note the change to localSlot4 here!!! It gets set below when balancer fees
        //           are updated.
        localSlot4 = BitPackingLib.packBit(localSlot4, 0, C.S4_OFFSET_ZERO_CRONFI_FEES);

        slot3 = BitPackingLib.incrementPairWithClampU96(slot3, evoMem.token0CronFiFeesU96, evoMem.token1CronFiFeesU96);
      }

      slot4 = BitPackingLib.incrementPairWithClampU96(
        localSlot4,
        evoMem.token0BalancerFeesU96,
        evoMem.token1BalancerFeesU96
      );

      // Update order accounting:
      //
      slot1 = BitPackingLib.decrementPairU112(slot1, evoMem.token0OrdersU112, evoMem.token1OrdersU112);

      // Update proceeds accounting:
      //
      slot2 = BitPackingLib.incrementPairU112(slot2, evoMem.token0ProceedsU112, evoMem.token1ProceedsU112);

      // As in UNI V2, the oracle price is based on the reserves before the current operation (i.e. swap).
      // For TWAMM, we augment that to incorporate the reserves after executing virtual operations.
      //
      // NOTE: We only update the oracle when not paused.  If paused, pricing info becomes distorted and
      //       shouldn't be incorporated into the oracle.
      //
      _updateOracle(evoMem, _maxBlock);
    }

    token0ReserveU112 = evoMem.token0ReserveU112;
    token1ReserveU112 = evoMem.token1ReserveU112;
  }

  /// @notice Executes all active virtual orders from the last virtual order block stored
  ///         in state to the specified order block, _blockNumber. The specified order block
  ///         should be greater than the last virtual order block. Writes updates and changes
  ///         to state.
  /// @param _evoMem aggregated information for gas efficient virtual order exection. See
  ///                documentation in Structs.sol.
  /// @param _blockNumber is the block number to execute active virtual orders up to from
  ///                     the last virtual order block.
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out of 100,000.
  ///
  function _executeVirtualOrdersToBlock(ExecVirtualOrdersMem memory _evoMem, uint256 _blockNumber) private {
    uint256 poolFeeLTFP = BitPackingLib.unpackU10(slot1, C.S1_OFFSET_LONG_TERM_FEE_FP);
    (LoopMem memory loopMem, uint256 expiryBlock) = _getLoopMem();

    // Loop through active virtual orders preceeding the final block interval, performing
    // aggregate Long-Term (LT) swaps and updating sales rates in memory and scaled proceeds
    // in state.
    //
    uint256 prevLVOB = loopMem.lastVirtualOrderBlock;
    while (expiryBlock < _blockNumber) {
      _executeVirtualTradesAndOrderExpiries(_evoMem, expiryBlock, loopMem, poolFeeLTFP);

      _calculateOracleIncrement(_evoMem, prevLVOB, expiryBlock);
      prevLVOB = loopMem.lastVirtualOrderBlock;

      // Handle orders expiring at end of interval
      _decrementSalesRates(expiryBlock, loopMem);
      _storeScaledProceeds(loopMem, expiryBlock);

      expiryBlock += ORDER_BLOCK_INTERVAL;
    }

    // Process the active virtual orders of the final block interval, performing
    // aggregate Long-Term (LT) swaps and updating sales rates in memory and scaled proceeds
    // in state.
    //
    if (loopMem.lastVirtualOrderBlock != _blockNumber) {
      expiryBlock = _blockNumber;
      _executeVirtualTradesAndOrderExpiries(_evoMem, expiryBlock, loopMem, poolFeeLTFP);

      _calculateOracleIncrement(_evoMem, prevLVOB, _blockNumber);

      // Handle orders expiring at end of interval
      _decrementSalesRates(expiryBlock, loopMem);
      _storeScaledProceeds(loopMem, expiryBlock);
    }

    // Update virtual order and order pool state from values in memory:
    //
    virtualOrders.lastVirtualOrderBlock = loopMem.lastVirtualOrderBlock;
    virtualOrders.orderPools.currentSalesRates = BitPackingLib.packPairU112(
      0,
      loopMem.currentSalesRate0U112,
      loopMem.currentSalesRate1U112
    );
    virtualOrders.orderPools.scaledProceeds = BitPackingLib.packPairU128(
      loopMem.scaledProceeds0U128,
      loopMem.scaledProceeds1U128
    );
  }

  /// @notice Stores the scaled proceeds contained in _loopMem in state at the given block number,
  ///         _expiryBlock.
  /// @param _loopMem aggregated information for gas efficient virtual order loop execution. See
  ///                 documentation in Structs.sol.
  /// @param _expiryBlock is the next block upon which virtual orders expire. It is aligned on
  ///                     multiples of the order block interval.
  ///
  function _storeScaledProceeds(LoopMem memory _loopMem, uint256 _expiryBlock) private {
    virtualOrders.scaledProceedsAtBlock[_expiryBlock] = BitPackingLib.packPairU128(
      _loopMem.scaledProceeds0U128,
      _loopMem.scaledProceeds1U128
    );
  }

  /// @notice Update the price oracle on the first transaction within a block (Uniswap V2 style Price
  ///         Oracle adapted for TWAMM).
  /// @param _evoMem aggregated information for gas efficient virtual order exection. See
  ///                documentation in Structs.sol.
  /// @param _updateBlock is the block number specified in the calling function. This may be less
  ///                     than the current block number (for example if executeVirtualOrdersToBlock
  ///                     is called with a number less than the current block number to free up
  ///                     excess gas load from inactivity).
  ///
  function _updateOracle(ExecVirtualOrdersMem memory _evoMem, uint256 _updateBlock) private {
    (uint32 timeElapsed, uint32 blockTimeStamp) = _getOracleTimeData(_updateBlock);

    if (timeElapsed > 0) {
      // #overUnderFlowIntended
      //                        The UQ256x112 incremented result relies upon and expects overflow
      //                        and is unchecked. The reason is that it is the difference between
      //                        price oracle samples over time that matters, not the absolute value.
      //                        As noted above, see the Uniswap V2 whitepaper.
      priceOracle.token0U256F112 += _evoMem.token0OracleU256F112;
      priceOracle.token1U256F112 += _evoMem.token1OracleU256F112;
    }

    slot2 = BitPackingLib.packOracleTimeStampS2(slot2, blockTimeStamp);
  }

  /// @notice Trigger Balancer Vault re-entrancy check for read-only re-entrancy vulnerability
  ///         described here:
  ///
  ///             https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345
  /// @dev Balancer describes this workaround as follows:
  ///
  ///          '... this is the cheapest way to trigger a reentrancy check on the Vault. In
  ///           short, it does nothing. The code "withdraws" an amount of 0 of token address(0)
  ///           from the Vault's internal balance for the calling contract (the pool) and sends
  ///           it to address(0).'
  ///
  function _triggerVaultReentrancyCheck() private {
    IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](1);
    ops[0].kind = IVault.UserBalanceOpKind.WITHDRAW_INTERNAL;
    ops[0].sender = address(this);
    VAULT.manageUserBalance(ops);
  }

  /// @notice Execute a Short-Term (ST) swap atomically in a single block on one token for the other as
  ///         described in the parameters below.
  /// @param _token0To1 the direction of this swap, true if selling Token 0 for Token1, false otherwise.
  /// @param _regularSwapType is true if the swap is SwapType.RegularSwap, false if it is
  ///                         SwapType.PartnerSwap.
  /// @param _amountInU112 is the amount of token being sold to the pool in this swap.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _token0ReserveU112 is the current Token 0 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @param _token1ReserveU112 is the current Token 1 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @return amountOutU112 is the amount of token being bought from the pool in this swap.
  ///                       Min. = 0, Max. = (2**112) - 1
  function _shortTermSwap(
    bool _token0To1,
    bool _regularSwapType,
    uint256 _amountInU112,
    uint256 _token0ReserveU112,
    uint256 _token1ReserveU112
  ) private returns (uint256 amountOutU112) {
    uint256 swapFeeU10 = BitPackingLib.unpackU10(
      slot1,
      _regularSwapType ? C.S1_OFFSET_SHORT_TERM_FEE_FP : C.S1_OFFSET_PARTNER_FEE_FP
    );
    // #unchecked
    //            Balancer confirms the amount in does not overflow the pool (BAL#526
    //            BALANCE_TOTAL_OVERFLOW), thus the multiply of _amountInU112 and a U10
    //            cannot overflow a U256.
    uint256 grossFee = (_amountInU112 * swapFeeU10).divDown(C.TOTAL_FP);

    uint256 localSlot4 = slot4; // #savegas
    uint256 collectBalancerFees = BitPackingLib.unpackBit(localSlot4, C.S4_OFFSET_COLLECT_BALANCER_FEES);
    if (collectBalancerFees != C.FALSE) {
      uint256 balancerFeeDU1F18 = BitPackingLib.unpackBalancerFeeS4(localSlot4);
      // #unchecked
      //            Multiplication of the grossFee (which at most could be U122 as described
      //            above--and is much less because of division by C.TOTAL_FP, i.e. U106) by
      //            balancerFeeDU1F18, a U60 at most, results in U166 to U182 at worst which
      //            will not overflow a U256. Further mitigated with the division by 1e18 (U59).
      uint256 balancerFeeU112 = (grossFee * balancerFeeDU1F18).divDown(C.DENOMINATOR_DU1_18);

      // #contractsize add balancer fees from swap
      slot4 = BitPackingLib.incrementPairWithClampU96(
        localSlot4,
        (_token0To1 ? balancerFeeU112 : 0),
        (_token0To1 ? 0 : balancerFeeU112)
      );
    }

    // NOTE: LP Fees are automatically collected in the vault balances and need not be calculated here.
    // #unchecked
    //            Subtraction unchecked below because grossFee is a fraction of _amountInU112 and
    //            cannot underflow.
    //            IMPORTANT: divDown in computing the grossFee above essential to prevent underflow
    //                       zero amount in attack.
    uint256 amountInLessFeesU112 = _amountInU112 - grossFee;

    // #unchecked
    //            Balancer confirms the amount in does not overflow the pool (BAL#526
    //            BALANCE_TOTAL_OVERFLOW), thus the multiply of _amountInU112 and a value,
    //            _tokenNReserve, less than the value confirmed to not overflow will
    //            not exceed the U256 container (worst case U113).
    uint256 nextTokenInReserveU112 = (
      /* token-in reserve: */
      _token0To1 ? _token0ReserveU112 : _token1ReserveU112
    ) + amountInLessFeesU112;

    // #unchecked
    //            Because of Balancer Vault overflow checks and the way the token reserves
    //            are computed from known U112 maximum values, the multiplication below is
    //            unchecked as the product of two U112 values is not going to overflow the
    //            U256 container.
    amountOutU112 = ((
      /* token-out reserve: */
      _token0To1 ? _token1ReserveU112 : _token0ReserveU112
    ) * amountInLessFeesU112).divDown(nextTokenInReserveU112);
  }

  /// @notice Execute a Long-Term swap (LT) Virtual Order, which sells an amount of one of the pool
  ///         tokens to the pool over a number of blocks, as described in the parameters below.
  ///         An LT swap can be withdrawn one or more times until the order has expired and all proceeds
  ///         have been withdrawn.
  ///         An LT swap can be cancelled anytime before expiry for a refund of unsold tokens and any
  ///         proceeds obtained before cancellation.
  ///         To withdraw or cancel, call the IVault.exit function (see documentaion for onExitPool).
  ///         WARNING: Any amount specified that does not divide evenly into the amount of blocks for
  ///         the virtual order will end up in pool reserves, yielding the user nothing for their capital.
  ///         For instance a trade of 224 Token-0 over two intervals (OBI=75) starting at block 75 would
  ///         result in a sales rate of 2 Token-0 per block for 150 blocks, 2 Order Block Intervals (OBI),
  ///         and the remaining 74 Token-0 would end up in the pool reserves yielding no benefit to the user.
  /// @param _sender is the account issuing the LT swap transaction. Only this account can withdraw the
  ///                order.
  /// @param _delegate is an account that is able to withdraw or cancel the LT swap on behalf of the
  ///                  sender account, as long as the recipient specified is the sender account.
  ///                  If the delegate is set to the sender account, then the delegate is set
  ///                  to the null address.
  /// @param _token0To1 is the direction of this swap, true if selling Token 0 for Token1, false otherwise.
  /// @param _amountInU112 is the amount of token being sold to the pool in this swap.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _orderIntervals is the number of intervals to execute the LT swap before expiring. An interval
  ///                        can be 75 blocks (Stable Pool), 300 blocks (Liquid Pool) or 1200 blocks (Volatile
  ///                        Pool).
  ///                        Min. = 0, Max. = STABLE_MAX_INTERVALS,
  ///                                         LIQUID_MAX_INTERVALS,
  ///                                         VOLATILE_MAX_INTERVALS (depending on POOL_TYPE).
  /// @return orderId is the order identifier for the LT swap virtual order created. Users can see what
  ///                 order identifiers they've created by callin getOrderIds.
  /// @dev The order id for an issued order is in the event log emitted by this function. No safety is provided
  ///      for checking existing order ids being reissued because the order id space is very large: (2**256) - 1.
  ///
  /// #RISK: An order-wrap attack (where a user trys to issue so many orders that the order ID counter
  ///        overflows, giving them access to order proceeds) is unlikely since the minimum measured gas
  ///        cost of a call to onSwap resulting in a call to _longTermSwap is ~220k gas (not including
  ///        approvals and transfers).
  ///
  function _longTermSwap(
    address _sender,
    address _delegate,
    bool _token0To1,
    uint256 _amountInU112,
    uint256 _orderIntervals
  ) private returns (uint256 orderId) {
    // #RISK: The accumulation of scaled proceeds works based on the difference between the proceeds
    //        at collection and the proceeds at submission. Underflow is supported when subtracting these
    //        values, however, there is a risk that an order goes so long that the proceeds overflow twice.
    //        It's an unlikely risk that occurs in pools with extremely asymmetric reserves, but this
    //        maximum order length serves to reduce that risk.
    requireErrCode(_orderIntervals <= MAX_ORDER_INTERVALS, CronErrors.MAX_ORDER_LENGTH_EXCEEDED);

    // Determine selling rate based on number of blocks to expiry and total amount:
    //
    // #unchecked:
    //             Subtraction in assignment of lastExpiryBlock won't underflow because the
    //             block number will always be greater than the ORDER_BLOCK_INTERVAL on mainnet until
    //             it overflows in a very long time (suspect the universe may have collapsed by then).
    //
    //             Overflow not possible in the multiplication and addition of the assignment of
    //             orderExpiry in a reasonable time (i.e. universe may collapse first). Reason is
    //             that max(_orderIntervals) = STABLE_MAX_INTERVALS (18-bits), max(ORDER_BLOCK_INTERVAL) =
    //             VOLATILE_OBI (16-bits), which the product of maxes at 34-bits. Thus lastExpiryBlock
    //             is the dominant term when the block.number approaches (2**256 - 1) - (2**34 - 1).
    //
    //             Underflow not possible in assignment to tradeBlocks since orderExpiry always >
    //             block.number.
    uint256 lastExpiryBlock = block.number - (block.number % ORDER_BLOCK_INTERVAL);
    uint256 orderExpiry = ORDER_BLOCK_INTERVAL * (_orderIntervals + 1) + lastExpiryBlock; // +1 protects from div 0
    uint256 tradeBlocks = orderExpiry - block.number;
    uint256 sellingRateU112 = _amountInU112 / tradeBlocks; // Intended: Solidity rounds towards zero.
    requireErrCode(sellingRateU112 > 0, CronErrors.ZERO_SALES_RATE);

    // Update the current and ending sales rates state:
    //
    // NOTE: re-using pair increment methods with single value for #contractsize reduction.
    OrderPools storage ops = virtualOrders.orderPools;
    ops.currentSalesRates = BitPackingLib.incrementPairU112(
      ops.currentSalesRates,
      _token0To1 ? sellingRateU112 : 0,
      _token0To1 ? 0 : sellingRateU112
    );
    ops.salesRatesEndingPerBlock[orderExpiry] = BitPackingLib.incrementPairU112(
      ops.salesRatesEndingPerBlock[orderExpiry],
      _token0To1 ? sellingRateU112 : 0,
      _token0To1 ? 0 : sellingRateU112
    );

    // NOTE: Cast to uint112 below for sellingRateU112 is safe because _amountInU112 must be less than 112-bits
    //       as checked by Balancer and reverted with Balancer error BAL#526 BALANCE_TOTAL_OVERFLOW.
    // NOTE: Cast to uint128 below for unpackPairU128 is safe because the result of that function
    //       cannot execeed 128-bits (correct by construction, i.e. function pulls a 128-bit value from
    //       packed storage in 256-bit slot).
    // NOTE: scaledProceeds0 is measured in amount of Token 1 received for selling Token 0 from Order Pool 0
    //       to the AMM (and vice-versa scaledProceeds1).
    //
    // #overUnderFlowIntended
    //                        In the very distant future, the nextOrderId will overflow. Given the 5 year
    //                        order length limit, it's improbable that 2**256 -1 orders will occur and
    //                        result in the overwriting of an existing order:
    //
    //                            * Assume 12s blocks.
    //                            * Five years in seconds 157788000 (365.25d / year).
    //                            * Blocks in 5 years = 13149000.
    //                            * Conservatively overestimate 1000 orders per block,
    //                              then 13149000000 orders in 5 years.
    //                            * 34-bits used for 5 years, assuming an excessive number of orders per block.
    //                            * Caveat Emptor on those who do not withdraw their proceeds in
    //                              an insufficient amount of time from order start.
    orderId = virtualOrders.nextOrderId++;
    virtualOrders.orderMap[orderId] = Order(
      _token0To1,
      uint112(sellingRateU112),
      uint128(BitPackingLib.unpackU128(ops.scaledProceeds, _token0To1)),
      _sender,
      ((_sender != _delegate) ? _delegate : C.NULL_ADDR),
      orderExpiry
    );
    orderIdMap[_sender].push(orderId);

    // Update order accounting (add the user's order amount to the global orders accounting):
    //
    // NOTE: The amount in (_amountInU112) is not the amount that will be swapped due to
    //       truncation. The amount that will be swapped is the number of blocks in the order
    //       multiplied by the order sales rate. The remainder is implicitly added to the pool
    //       reserves, augmenting LP rewards (Caveat Emptor Design Philosophy for Swap User).
    //
    // #unchecked
    //            Multiplication below is unchecked because sellingRate is maximum U112 and
    //            tradeBlocks is maximum U24 (STABLE_OBI * STABLE_MAX_INTERVALS = 13149000 blocks),
    //            the product of which will not overflow a U256. Also note the U112 limit is checked
    //            in the increment call.
    uint256 orderAmount = sellingRateU112 * tradeBlocks;
    slot1 = BitPackingLib.incrementPairU112(slot1, (_token0To1 ? orderAmount : 0), (_token0To1 ? 0 : orderAmount));
  }

  /// @notice Withdraws any collected Cron-Fi fees from the pool and resets the pool accounting of
  ///         Cron-Fi fees to zero for both Token0 and Token1.
  /// @param _sender must be the current fee address.
  /// @return token0OutU96 Amount of Token0 collected as Cron-Fi fees in Long-Term (LT) swaps.
  ///                      Min. = 0, Max = (2**96) - 1
  /// @return token1OutU96 Amount of Token1 collected as Cron-Fi fees in LT swaps.
  ///                      Min. = 0, Max = (2**96) - 1
  ///
  function _withdrawCronFees(address _sender) private returns (uint256 token0OutU96, uint256 token1OutU96) {
    // IMPORTANT: safety check to ensure user calling this is fee address:
    requireErrCode(feeAddr == _sender, CronErrors.SENDER_NOT_FEE_ADDRESS);

    // Optimization:
    //               The U96 Cron-Fi fees below are assigned to the output tokens in onExitPool and
    //               the pool's Cron-Fi fee counters in slot3 are cleared.
    //               A flag indicating they have been cleared in slot4 is also set. This reduces the
    //               gas used in calculating pool reserves for subsequent transactions (why read slot3
    //               if the values within are zero?). See function _calculateReserves for the use of
    //               this flag in slot4.
    //               The optimization is re-used here too (why read the Cron-Fi fees from slot3
    //               if they are zero).
    uint256 localSlot4 = slot4; // #savegas
    requireErrCode(
      BitPackingLib.unpackBit(localSlot4, C.S4_OFFSET_ZERO_CRONFI_FEES) == C.FALSE,
      CronErrors.NO_FEES_AVAILABLE
    );

    // Send the Cron-Fi fees out of the pool and zero our accounting of them:
    (slot3, token0OutU96, token1OutU96) = BitPackingLib.unpackAndClearPairU96(slot3);

    // Set the Zero Cron-Fi fees flag since we've cleared the Cron-Fi fees counters:
    slot4 = BitPackingLib.packBit(localSlot4, 1, C.S4_OFFSET_ZERO_CRONFI_FEES);

    emit FeeWithdraw(_sender, token0OutU96, token1OutU96);
  }

  /// @notice A wrapper around _withdrawLongTermSwap to mitigate stack depth limitations in that method.
  ///         See documentation of _withdrawLongTermSwap for more information.
  /// @param _orderId the order id to withdraw or cancel.
  /// @param _sender is the account performing the LT swap order withdrawl or cancellation.
  ///                For long term orders, a "delegate" may be specified, this address is able to
  ///                perform LT order withdraws and cancellations on behalf of the LT swap owner as
  ///                long as the recipient is the LT swap owner.
  /// @param _recipient the recipient must always be the original order owner (the address
  ///                   that issued the order) if a "delegate" address is performing the withdrawl or
  ///                   cancellation. If the order owner is performing the withdrawl or cancellation, the
  ///                   recipient can be set to whatever destination address desired.
  /// @param _cancel if true, cancel the order, otherwise if false, withdraw the order.
  /// @return token0OutU112 the amount of Token 0 remitted from the order as either proceeds or a refund.
  ///                       Min. = 0, Max. = (2**112)-1
  /// @return token1OutU112 the amount of Token 1 remitted from the order as either proceeds or a refund.
  ///                       Min. = 0, Max. = (2**112)-1
  ///
  function _withdrawLongTermSwapWrapper(
    uint256 _orderId,
    address _sender,
    address _recipient,
    bool _cancel
  ) private returns (uint256 token0OutU112, uint256 token1OutU112) {
    Order storage order = virtualOrders.orderMap[_orderId];

    requireErrCode(order.owner != C.NULL_ADDR, CronErrors.CLEARED_ORDER);
    if (_sender != order.owner) {
      requireErrCode(_recipient == order.owner, CronErrors.RECIPIENT_NOT_OWNER);
    }
    requireErrCode(
      (_sender == order.owner || _sender == order.delegate) && _sender != C.NULL_ADDR,
      CronErrors.SENDER_NOT_ORDER_OWNER_OR_DELEGATE
    );

    (bool token0To1, uint256 refundU112, uint256 proceedsU112) = _withdrawLongTermSwap(order, _cancel);

    emit WithdrawLongTermSwap(
      order.owner,
      (_cancel)
        ? ((token0To1) ? address(TOKEN0) : address(TOKEN1)) // sell token
        : C.NULL_ADDR,
      refundU112,
      (token0To1) ? address(TOKEN1) : address(TOKEN0), // buy token
      proceedsU112,
      _orderId,
      _sender
    );

    // NOTE: The execute virtual orders run prior to this function in function onExitPool updates the
    //       orders and proceeds amounts.
    token0OutU112 = (token0To1) ? refundU112 : proceedsU112;
    token1OutU112 = (token0To1) ? proceedsU112 : refundU112;
  }

  /// @notice This function performs a withdraw or a cancel of a Long-Term (LT) swap virtual order, which
  ///         remits proceeds and if cancelling, also refunds remaining order amounts.
  ///         Funds may be withdrawn from an order multiple times up until order expiry and the last of
  ///         the proceeds are withdrawn.
  ///         Orders may be cancelled up to order expiry. Both proceeds and remaining order funds will be
  ///         remitted upon cancellation.
  /// @param _order information of a specific LT swap order. See Order struct documentation in Structs.sol.
  /// @param _cancel indicates whether the virtual order is to be cancelled if true. If false, the order
  ///                proceeds are to be withdrawn.
  /// @return token0To1 is the direction of this LT swap. It is true if selling Token 0 for Token1, false
  ///                   otherwise.
  /// @return refundU112 is the amount of token to be refunded.
  ///                    Min. = 0, Max. = (2**112) - 1
  /// @return proceedsU112 is the amount of token already purchased in the LT swap to be remitted.
  ///                      Min. = 0, Max. = (2**112) - 1
  ///
  function _withdrawLongTermSwap(Order storage _order, bool _cancel)
    private
    returns (
      bool token0To1,
      uint256 refundU112,
      uint256 proceedsU112
    )
  {
    token0To1 = _order.token0To1;
    uint256 expiryBlock = _order.orderExpiry;
    uint256 salesRateU112 = _order.salesRate;

    // NOTE: There is no need to check if order is completed using orderExpiry
    //       or salesRate, because entire Order struct is zeroed/falsed on cancel
    //       or final withdraw. A 2nd cancel or withdraw attempt will revert with
    //       CronErrors.SENDER_NOT_ORDER_OWNER_OR_DELEGATE

    // #RISK: Although the NULL_ADDR technically owns zeroed cancelled orders,
    //        anyone owning that address would be able to withdraw nothing
    //        because the salesRateU112 (order.salesRate) is zerod after
    //        cancel.
    if (_cancel) {
      // Calculate unsold amount to refund:
      //
      // NOTE: Must use last virtual order block, not block.number to yield correct result
      //       when pool is paused.
      //
      // #unchecked
      //            The subtraction is unchecked because the error check confirms that expiryBlock
      //            is larger than lastVirtualOrderBlock, preventing underflow.
      //
      //            The multiplication is unchecked for overflow because the sales rate is
      //            computed from the amount in, which is checked by Balancer (BAL#526
      //            BALANCE_TOTAL_OVERFLOW) and known to be less than U112. The maximum
      //            difference between the expiry block and last virtual order block is
      //            constrained to be U18 (the MAX_ORDER_INTERVALS largest value,
      //            STABLE_MAX_INTERVALS), the product of which cannot exceed U130.
      requireErrCode(expiryBlock > virtualOrders.lastVirtualOrderBlock, CronErrors.CANT_CANCEL_COMPLETED_ORDER);
      refundU112 = (expiryBlock - virtualOrders.lastVirtualOrderBlock) * salesRateU112;
    }

    uint256 scaledProceedsU128 = BitPackingLib.unpackU128(
      (!_cancel && (block.number > expiryBlock))
        ? virtualOrders.scaledProceedsAtBlock[expiryBlock] // Expired or cancelled order - calculate scaled proceeds at expiryBlock.
        : virtualOrders.orderPools.scaledProceeds, // Unexpired order. Remit current proceeds.
      token0To1
    );
    proceedsU112 = _calculateProceeds(
      scaledProceedsU128,
      _order.scaledProceedsAtSubmissionU128,
      salesRateU112,
      token0To1
    );

    // CAREFUL: This must come after the calculation of proceedsU112
    if (!_cancel && (block.number <= expiryBlock)) {
      // Reset stake (sales rate) if not cancellation and not expired order

      // NOTE: Cast to uint128 below for scaledProceedsAtSubmissionU128 is safe because result of function unpackPairU128
      //       cannot execeed 128-bits (correct by construction, i.e. function pulls a 128-bit value from
      //       packed storage in 256-bit slot, value is checked for overflow when it was stored).
      _order.scaledProceedsAtSubmissionU128 = uint128(scaledProceedsU128);
    }

    requireErrCode(refundU112 > 0 || proceedsU112 > 0, CronErrors.NO_FUNDS_AVAILABLE);

    if (_cancel) {
      OrderPools storage ops = virtualOrders.orderPools;

      // Decrement the current and ending sales rates:
      ops.currentSalesRates = BitPackingLib.decrementPairU112(
        ops.currentSalesRates,
        token0To1 ? salesRateU112 : 0,
        token0To1 ? 0 : salesRateU112
      );
      ops.salesRatesEndingPerBlock[expiryBlock] = BitPackingLib.decrementPairU112(
        ops.salesRatesEndingPerBlock[expiryBlock],
        token0To1 ? salesRateU112 : 0,
        token0To1 ? 0 : salesRateU112
      );

      // Update the order accounting:
      slot1 = BitPackingLib.decrementPairU112(slot1, (token0To1 ? refundU112 : 0), (token0To1 ? 0 : refundU112));
    }

    // Update the proceed accounting:
    slot2 = BitPackingLib.decrementPairU112(slot2, (token0To1 ? 0 : proceedsU112), (token0To1 ? proceedsU112 : 0));

    if (_cancel || block.number > expiryBlock) {
      // Remove stake (sales rate) and clear order state for rebate and to indicate order complete:
      _order.token0To1 = false;
      _order.salesRate = 0;
      _order.scaledProceedsAtSubmissionU128 = 0;
      _order.owner = C.NULL_ADDR;
      _order.delegate = C.NULL_ADDR;
      _order.orderExpiry = 0;
    }
  }

  /// @notice This function performs a join given the amounts of Token 0 and Token 1 to add to the pool
  ///         along with the current virtual reserves.
  /// @param _recipient is the account designated to receive pool shares in the form of LP tokens when
  ///                   Joining the pool. Can be set to _sender if sender wishes to receive the tokens
  ///                   and Join Events.
  /// @param _token0InU112 is the amount of Token 0 to Join the pool with.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _token1InU112 is the amount of Token 1 to Join the pool with.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _token0ReserveU112 is the current Token 0 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @param _token1ReserveU112 is the current Token 1 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @return amountLP is the amount of Liquidity Provider (LP) tokens returned in exchange for the
  ///                  amounts of Token 0 and Token 1 provided to the pool.
  function _join(
    address _recipient,
    uint256 _token0InU112,
    uint256 _token1InU112,
    uint256 _token0ReserveU112,
    uint256 _token1ReserveU112
  ) private returns (uint256 amountLP) {
    requireErrCode(_recipient != C.NULL_ADDR, CronErrors.NULL_RECIPIENT_ON_JOIN);

    uint256 supplyLP = totalSupply();
    if (supplyLP == 0) {
      requireErrCode(
        (_token0InU112 > C.MINIMUM_LIQUIDITY) && (_token1InU112 > C.MINIMUM_LIQUIDITY),
        CronErrors.INSUFFICIENT_LIQUIDITY
      );

      // #unchecked
      //            The check of _token0InU112 and _token1InU112 ensure that both are less
      //            than 112-bit maximums. Multiplying two 112-bit numbers will not exceed
      //            224-bits, hence no checked mul operator here.
      //            The check that both _token0InU112 and _token1InU112 exceed
      //            C.MINIMUM_LIQUIDITY, means that the square root of their product cannot
      //            be less than C.MINIMUM_LIQUIDITY, hence no checked sub operator here.
      amountLP = sqrt(_token0InU112 * _token1InU112) - C.MINIMUM_LIQUIDITY;

      // Initial Join / Mint does not execute virtual orders, is only run once b/c of MINIMUM_LIQUIDITY,
      // thus a call to update the oracle resolves to simply setting the oracle timestamp:
      slot2 = BitPackingLib.packOracleTimeStampS2(slot2, block.timestamp);

      _mintPoolTokens(address(0), C.MINIMUM_LIQUIDITY); // Permanently locked for div / 0 safety.

      // Set the last virtual order block to the block number. This reduces the gas used
      // in virtual order execution iterations for the first call to execute virtual orders
      // now that the pool has liquidity.
      virtualOrders.lastVirtualOrderBlock = block.number;
    } else {
      amountLP = Math.min(
        _token0InU112.mul(supplyLP).divDown(_token0ReserveU112),
        _token1InU112.mul(supplyLP).divDown(_token1ReserveU112)
      );
    }

    requireErrCode(amountLP > 0, CronErrors.INSUFFICIENT_LIQUIDITY);
    _mintPoolTokens(_recipient, amountLP);
  }

  /// @notice This function performs an exit given the amount of Liquidity Provider (LP) tokens
  ///         to return to the pool along with the current virtual reserves.
  /// @param _sender the account performing the liquidity removal.
  /// @param _tokensLP the number of LP tokens to redeem in exchange for the pool's asset tokens.
  /// @param _token0ReserveU112 is the current Token 0 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @param _token1ReserveU112 is the current Token 1 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @return token0OutU112 is the amount of Token 0 returned by the pool in exchange for LP tokens.
  ///                       Min. = 0, Max. = (2**112) - 1
  /// @return token1OutU112 is the amount of Token 1 returned by the pool in exchange for LP tokens.
  ///                       Min. = 0, Max. = (2**112) - 1
  ///
  function _exit(
    address _sender,
    uint256 _tokensLP,
    uint256 _token0ReserveU112,
    uint256 _token1ReserveU112
  ) private returns (uint256 token0OutU112, uint256 token1OutU112) {
    // CAREFUL: Next line must preceed burning of pool liquidity provider tokens.
    uint256 lTotalSupply = totalSupply();
    _burnPoolTokens(_sender, _tokensLP);
    token0OutU112 = (_token0ReserveU112.mul(_tokensLP)).divDown(lTotalSupply);
    token1OutU112 = (_token1ReserveU112.mul(_tokensLP)).divDown(lTotalSupply);

    emit PoolExit(_sender, _tokensLP, token0OutU112, token1OutU112);
  }

  /// @notice Handles the updating of the stored Balancer fee multiplier and remittance of any collected
  ///         Balancer fees in onJoinPool and onExitPool function calls.
  /// @param _protocolFeeDU1F18 the newest Balancer Fee passed into the pool by the onJoinPool and
  ///                           onExitPool functions. This number is divided by 1e18 (C.ONE_DU1_18) to
  ///                           arrive at a fee multiplier between 0 and 1, inclusive, with 18
  ///                           fractional decimal digits.
  ///                           Min. = 0, Max. = 10**18
  /// @return dueProtocolFeeAmountsU96 the amount of Token 0 and Token 1 collected by the pool for
  ///                                  Balancer. Values are returned in the same array ordering that
  ///                                  IVault.getPoolTokens returns.
  ///                                  Min. = 0, Max. = (2**96) - 1
  ///
  function _handleBalancerFees(uint256 _protocolFeeDU1F18) private returns (uint256[] memory dueProtocolFeeAmountsU96) {
    uint256 localSlot4 = slot4;

    if (_protocolFeeDU1F18 <= C.ONE_DU1_18) {
      uint256 currentBalancerFee = BitPackingLib.unpackBalancerFeeS4(localSlot4);
      // #savegas: check before write
      if (currentBalancerFee != _protocolFeeDU1F18) {
        localSlot4 = BitPackingLib.packBalancerFeeS4(localSlot4, _protocolFeeDU1F18);
      }
    } else {
      // Ignore change and keep operating with old fee if new fee is too large but notify through logs.
      emit ProtocolFeeTooLarge(_protocolFeeDU1F18);
    }

    // Send the Balancer fees out of the pool and zero our accounting of them:
    dueProtocolFeeAmountsU96 = new uint256[](2);
    (slot4, dueProtocolFeeAmountsU96[C.INDEX_TOKEN0], dueProtocolFeeAmountsU96[C.INDEX_TOKEN1]) = BitPackingLib
      .unpackAndClearPairU96(localSlot4);
  }

  /// @notice Returns the TWAMM pool's reserves after the non-stateful execution of all virtual orders
  ///         up to the current block.
  ///         IMPORTANT - This function does not meaningfully modify state despite the lack of a "view"
  ///                     designator for state mutability. (The call to _triggerVaultReentrancyCheck
  ///                     Unfortunately prevents the "view" designator as meaningless value is written
  ///                     to state to trigger a reentracy check).
  ///         Runs virtual orders from the last virtual order block up to the current block to provide
  ///         visibility into the current accounting for the pool.
  ///         If the pool is paused, this function reflects the accounting values of the pool at
  ///         the last virtual order block (i.e. it does not execute virtual orders to deliver the result).
  /// @param _maxBlock a block to update virtual orders to. If less than or equal to the last virtual order
  ///                  block or greater than the current block, the value is set to the current
  ///                  block number.
  /// @param _paused is true to indicate the result should be returned as though the pool is in a paused
  ///                state where virtual orders are not executed and only withdraw, cancel and liquidations
  ///                are possible (check function isPaused to see if the pool is in that state). If false
  ///                then the virtual reserves are computed from virtual order execution to the specified
  ///                block.
  /// @return evoMem a struct containing the pool's virtual reserves for Token 0 and Token 1 along with
  ///                the changes to the Orders, Proceeds, and Fee accounting to the current state values
  ///                for Token 0 and Token 1. See the documentation of struct ExecVirtualOrdersMem for
  ///                details.
  ///                IMPORTANT Note above that struct values for token0ReserveU112 and token1ReserveU112
  ///                          are the virtual values of the reserves at the current block, whereas other
  ///                          values in the ExecVirtualOrdersMem struct are the DIFFERENCES from the
  ///                          state of their respective values and their virtual values.
  /// @return blockNumber The block that the virtual reserve values were computed at. Should
  ///                     match parameter _maxBlock, unless _maxBlock was not greater than the
  ///                     last virtual order block or less than or equal to the current block.
  ///
  function _getVirtualReserves(uint256 _maxBlock, bool _paused)
    private
    returns (ExecVirtualOrdersMem memory evoMem, uint256 blockNumber)
  {
    blockNumber = (virtualOrders.lastVirtualOrderBlock < _maxBlock && _maxBlock <= block.number)
      ? _maxBlock
      : block.number;

    _triggerVaultReentrancyCheck();
    (, uint256[] memory balancesU112, ) = VAULT.getPoolTokens(POOL_ID);

    evoMem = _getExecVirtualOrdersMem(balancesU112[C.INDEX_TOKEN0], balancesU112[C.INDEX_TOKEN1]);

    if (!_paused) {
      _executeVirtualOrdersView(evoMem, blockNumber);
    }
  }

  /// @notice Executes all virtual orders between current lastVirtualOrderBlock and blockNumber
  ///         also handles orders that expire at end of final block. This assumes that no orders
  ///         expire inside the given interval.
  ///
  /// @param _evoMem aggregated information for gas efficient virtual order execution. See
  ///                documentation in Structs.sol.
  /// @param _expiryBlock is the next block upon which virtual orders expire. It is aligned on
  ///                     multiples of the order block interval.
  /// @param _loopMem aggregated information for gas efficient virtual order loop execution. See
  ///                 documentation in Structs.sol.
  /// @param _poolFeeLTFP is the pool fee charged for long term swaps in Fee Points (FP).
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out of 100,000.
  ///
  function _executeVirtualTradesAndOrderExpiries(
    ExecVirtualOrdersMem memory _evoMem,
    uint256 _expiryBlock,
    LoopMem memory _loopMem,
    uint256 _poolFeeLTFP
  ) private view {
    // Determine how many blocks are in the current interval and compute
    // the amount of Token0 and Token1 being sold to the pool and any
    // fees. (Fees are collected as a percentage of tokens being sold to
    // the pool as opposed to tokens bought from the pool).
    //
    // #unchecked
    //            Subtraction below is unchecked because _expiryBlock is
    //            always greater than the lastVirtualOrderBlock by at least
    //            1, and up to a block interval (see function _getLoopMem
    //            where it's initialized).
    uint256 intervalBlocks = _expiryBlock - _loopMem.lastVirtualOrderBlock;

    uint256 currentSalesRate0U112 = _loopMem.currentSalesRate0U112; // #savegas
    uint256 currentSalesRate1U112 = _loopMem.currentSalesRate1U112; // #savegas

    // #unchecked
    //            The computation of token0In and token1In are not checked
    //            for overflow. The current sales rates will not exceed MAX_U112
    //            and the number of interval blocks is bounded to a maximum of
    //            1200 (VOLATILE_OBI), well within the safe range of MAX_U256.
    //
    //            Likewise calculation of grossFeeT0 and grossFeeT1 are not
    //            checked for overflow because token0In has a maximum of MAX_U123
    //            and it's multiplied by _poolFeeLTFP which has a maximum
    //            of 1000 (MAX_FEE_FP), well within the safe range of MAX_U256.
    //
    //            grossFeeT0 and grossFeeT1 thus have a practical maximum value of
    //              MAX_U112 * 1200 * 1000 / 100000 ---> 116-bits
    //
    // NOTE: division with rounding up is performed to favor the pool.
    uint256 token0In = currentSalesRate0U112 * intervalBlocks;
    uint256 grossFeeT0 = (token0In * _poolFeeLTFP).divUp(C.TOTAL_FP);
    //
    uint256 token1In = currentSalesRate1U112 * intervalBlocks;
    uint256 grossFeeT1 = (token1In * _poolFeeLTFP).divUp(C.TOTAL_FP);

    (uint256 lpFeeT0, uint256 lpFeeT1) = _updateFees(_evoMem, grossFeeT0, grossFeeT1);

    // Compute the amount of each token bought from the pool for the current
    // interval. Then update counts for the orders sold to the pool, the
    // proceeds bought from the pool, and add the LP fees into the reserves
    // after the swap is performed (LP fees are added in after the swap because
    // otherwise, they wouldn't be implicitly collected and the reserves wouldn't
    // grow and compensate the LPs).
    //
    // #unchecked
    //            The subtraction of grossFeeT0 and grossFeeT1 from token0In and
    //            token1In, respectively, are unchecked because gross fees are
    //            computed as a fraction less than one (multiplying by a numerator
    //            that is less than the denominator) of the token in values. The resulting
    //            gross fee values will always be less than their respective token in
    //            values.
    (uint256 token0Out, uint256 token1Out) = _computeVirtualReserves(
      _evoMem,
      token0In - grossFeeT0,
      token1In - grossFeeT1
    );

    // NOTE: For gas efficiency the order and proceed amounts are summed and then
    //       used to modify the global order and proceed accounting after all
    //       iterations are performed.
    //
    // #unchecked
    //            The incrementing of tokenNOrdersU112 below is unchecked because the
    //            order amounts in are checked by Balancer (BAL#526 BALANCE_TOTAL_OVERFLOW)
    //            when orders are taken in by the pool. Repeatedly adding them to a
    //            zero initialized value (tokenNOrdersU112) would take longer to overflow
    //            than this loop could run before it runs out of gas. At the end of the
    //            loop calling this method, these values are then subtracted from the order
    //            accounting and zeroed again for the next execution (the subtraction checks
    //            for underflow on a verified <= U112 value, see BitPackingLib.decrementPairU112).
    _evoMem.token0OrdersU112 += token0In;
    _evoMem.token1OrdersU112 += token1In;

    // The check below for values exceeding U112 cover the unchecked increment of tokenNProceedsU112
    // as safe for the duration of this loop. They also cover the call to _incrementScaledProceeds
    // which depends upon the tokenNOut values being <= U112.  (The two one sided active trade use
    // cases will never produce an output > U112, need to confirm the TWAMM approximation with
    // two active trades is similar, hence the check below.)
    requireErrCode(token0Out <= C.MAX_U112, CronErrors.OVERFLOW);
    requireErrCode(token1Out <= C.MAX_U112, CronErrors.OVERFLOW);
    _evoMem.token0ProceedsU112 += token0Out;
    _evoMem.token1ProceedsU112 += token1Out;

    // Compensate the LPs:
    //
    // #unchecked
    //            The incrementing below is unchecked because the lpFeeTN values are a fraction
    //            of the grossFeeTN values which themselves are a fraction of the tokenNIn values
    //            that are known to be limited to 112-bits by the Balancer check when the order is
    //            taken (BAL#526 BALANCE_TOTAL_OVERFLOW). (As discussed above the grossFeeTN values
    //            have a maximum practical value of 116-bits which won't result in a 256-bit overflow
    //            in the increment below before the loop ends).
    _evoMem.token0ReserveU112 += lpFeeT0;
    _evoMem.token1ReserveU112 += lpFeeT1;

    // Distribute proceeds to pools:
    _incrementScaledProceeds(_loopMem, token0Out, token1Out, currentSalesRate0U112, currentSalesRate1U112);

    _loopMem.lastVirtualOrderBlock = _expiryBlock;
  }

  /// @notice Read only execution of active virtual orders from the last virtual order block
  ///         in state to the current block number. Does not write updates or changes to state.
  /// @param _evoMem aggregated information for gas efficient virtual order exection. See
  ///                documentation in Structs.sol.
  /// @param _blockNumber is the block number to execute active virtual orders up to from
  ///                     the last virtual order block.
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out of 100,000.
  /// @dev This method's utility comes from the values stored in _evoMem at the end of
  ///      calling it; fees, orders, proceeds and reserves will all reflect the most up-to-date
  ///      values for virtual orders at the current block.
  ///
  function _executeVirtualOrdersView(ExecVirtualOrdersMem memory _evoMem, uint256 _blockNumber) private view {
    uint256 poolFeeLTFP = BitPackingLib.unpackU10(slot1, C.S1_OFFSET_LONG_TERM_FEE_FP);
    (LoopMem memory loopMem, uint256 expiryBlock) = _getLoopMem();

    // Loop through active virtual orders preceeding the final block interval, performing
    // aggregate Long-Term (LT) swaps and updating sales rates in memory
    //
    uint256 prevLVOB = loopMem.lastVirtualOrderBlock;
    while (expiryBlock < _blockNumber) {
      _executeVirtualTradesAndOrderExpiries(_evoMem, expiryBlock, loopMem, poolFeeLTFP);

      _calculateOracleIncrement(_evoMem, prevLVOB, expiryBlock);
      prevLVOB = loopMem.lastVirtualOrderBlock;

      // Handle orders expiring at end of interval
      _decrementSalesRates(expiryBlock, loopMem);

      expiryBlock += ORDER_BLOCK_INTERVAL;
    }

    // Process the active virtual orders of the final block interval, performing
    // aggregate Long-Term (LT) swaps
    //
    if (loopMem.lastVirtualOrderBlock != _blockNumber) {
      expiryBlock = _blockNumber;
      _executeVirtualTradesAndOrderExpiries(_evoMem, expiryBlock, loopMem, poolFeeLTFP);

      _calculateOracleIncrement(_evoMem, prevLVOB, _blockNumber);
    }
  }

  /// @notice Increments the current scaled proceeds of an order pool with the normalized
  ///         proceeds of recent swaps when given the current scaled proceeds, token out amount
  ///         and sales rate of the order pools.
  /// @param _loopMem aggregated information for gas efficient virtual order loop execution. See
  ///                 documentation in Structs.sol.
  /// @param _token0OutU112 The amount of Token 0 purchased in aggregate in an interval. This amount
  ///                       is normalized by the sales rate and then increments the scaled proceeds.
  /// @param _token1OutU112 The amount of Token 1 purchased in aggregate in an interval. This amount
  ///                       is normalized by the sales rate and then increments the scaled proceeds.
  /// @param _currentSalesRate0U112 The current amount of Token 0 sold per block to the order pool.
  /// @param _currentSalesRate1U112 The current amount of Token 1 sold per block to the order pool.
  ///
  function _incrementScaledProceeds(
    LoopMem memory _loopMem,
    uint256 _token0OutU112,
    uint256 _token1OutU112,
    uint256 _currentSalesRate0U112,
    uint256 _currentSalesRate1U112
  ) private view {
    // #overUnderFlowIntended The addition of scaledProceeds below is required to overflow a uint128
    //                        to work properly. This is because when a user withdraws or
    //                        cancels their order, the distance between the scaled proceeds
    //                        at the time of withdraw/cancel from when their order started is
    //                        used to determine their portion.  In other words it's not the
    //                        value of their scaled proceeds that determines their portion but
    //                        the distance between scaled proceeds at withdraw/cancel and order
    //                        start.  As long as the scaled proceeds doesn't overflow twice
    //                        during an order, they have not lost proceeds--overflowing twice
    //                        is highly unlikely because of the scaling by the number of decimal
    //                        places of the source token in the stored result. Consult Cron-Fi TWAMM
    //                        Numerical Analysis for a discussion on this matter.
    //
    if (_currentSalesRate0U112 != 0) {
      _loopMem.scaledProceeds0U128 = uint128(
        uint128(_loopMem.scaledProceeds0U128) +
          uint128((_token1OutU112 * ORDER_POOL0_PROCEEDS_SCALING) / _currentSalesRate0U112)
      );
    }

    if (_currentSalesRate1U112 != 0) {
      _loopMem.scaledProceeds1U128 = uint128(
        uint128(_loopMem.scaledProceeds1U128) +
          uint128((_token0OutU112 * ORDER_POOL1_PROCEEDS_SCALING) / _currentSalesRate1U112)
      );
    }
  }

  /// @notice Decrements the sales rates expiring in the specified block, _expiryBlock, from the
  ///         current sales rates.
  /// @param _expiryBlock is the next block upon which virtual orders expire. It is aligned on
  ///                     multiples of the order block interval.
  /// @param _loopMem aggregated information for gas efficient virtual order loop execution. See
  ///                 documentation in Structs.sol.
  ///
  function _decrementSalesRates(uint256 _expiryBlock, LoopMem memory _loopMem) private view {
    // NOTE: Stored sales rates are correct by construction (the packPairU112 method checks for exceeding
    //       MAX_U112), thus these fetched values here are within the range of MAX_U112.
    (uint256 salesRateEndingPerBlock0U112, uint256 salesRateEndingPerBlock1U112) = BitPackingLib.unpackPairU112(
      virtualOrders.orderPools.salesRatesEndingPerBlock[_expiryBlock]
    );

    if (salesRateEndingPerBlock0U112 > 0) {
      requireErrCode(_loopMem.currentSalesRate0U112 >= salesRateEndingPerBlock0U112, CronErrors.UNDERFLOW);
      _loopMem.currentSalesRate0U112 -= salesRateEndingPerBlock0U112;
    }
    if (salesRateEndingPerBlock1U112 > 0) {
      requireErrCode(_loopMem.currentSalesRate1U112 >= salesRateEndingPerBlock1U112, CronErrors.UNDERFLOW);
      _loopMem.currentSalesRate1U112 -= salesRateEndingPerBlock1U112;
    }
  }

  /// @notice Converts the provided block number, _updateBlock, to a timestamp and returns the computed
  ///         time elapsed between the timestamp and the previous oracle timestamp.
  ///         If _updateBlock is the current block number, the block.timestamp is used for the timestamp.
  ///         Otherwise the timestamp is computed by subtracting the block time, 12s, multiplied by the
  ///         number of blocks between the current block, block.number, and the provided _updateBlock.
  /// @param _updateBlock A block number to compute a timestamp for and the elapsed time since the
  ///                     previous oracle timestamp.
  ///                     NOTE: Expects that all calling functions ensure that updateBlock <= block.number.
  /// @return timeElapsed The difference between the computed timestamp and the previous oracle timestamp.
  /// @return blockTimestamp The computed timestamp.
  ///
  function _getOracleTimeData(uint256 _updateBlock) private view returns (uint32 timeElapsed, uint32 blockTimestamp) {
    uint32 oracleTimeStamp = uint32(BitPackingLib.unpackOracleTimeStampS2(slot2));

    if (_updateBlock == block.number) {
      // _updateBlock is the current block number, use the current block timestamp:
      blockTimestamp = uint32(block.timestamp % 2**32);
    } else {
      // _update block is less than the current block, use the current block minus an approximate
      // amount of time based on the difference multiplied by the block time in seconds guaranteed
      // by the beacon chain, to generate a timestamp approximation between the current block and
      // previous last virtual order block:
      //
      // #unchecked:
      //             The subtraction of _updateBlock below is unchecked here b/c the calls to
      //             _updateOracle are guaranteed to either pass in a value less than or equal
      //             to the current block number.
      //
      //             The subtraction of the unelapsed blocks multiplied by the block time from
      //             the timestamp should be able to underflow with the modulus result being
      //             correct for the purposes herein (i.e. the nature of this entire system being
      //             based in differences makes that acceptable).
      blockTimestamp = uint32((block.timestamp - C.SECONDS_PER_BLOCK * (block.number - _updateBlock)) % 2**32);
    }

    // #overUnderFlowIntended
    //                        As documented in the Uniswap V2 whitepaper, the 32-bits allocated to
    //                        store the timestamp will overflow approximately every 136 years. As
    //                        long as users take 1 sample every 136 years, then the resulting underflow
    //                        that would occur in the following 32-bit subtraction will be appropriate
    //                        as it will yield the correct distance in seconds between the two samples.
    timeElapsed = blockTimestamp - oracleTimeStamp;
  }

  /// @notice Balancer custom pool specific safety checks.
  /// @param _sender The message sender. Must be the vault address.
  /// @param _poolId The pool ID in the calling function. Must be this pool.
  ///
  function _poolSafetyChecks(address _sender, bytes32 _poolId) private view {
    requireErrCode(_sender == address(VAULT), CronErrors.NON_VAULT_CALLER);
    requireErrCode(_poolId == POOL_ID, CronErrors.INCORRECT_POOL_ID);
  }

  /// @notice Gets an initialized instance of struct ExecVirtualOrdersMem. The
  ///         orders, proceeds and fees struct members are all initialized to zero.
  ///         The reserves are initialized to the difference between the
  ///         Balancer Vault's notion of the pool's token balances and the sum
  ///         of the orders, proceeds, and fee counters in state.
  ///         State is also used to configure the fee percentages, which are
  ///         explained in more detail below, see NOTE.
  /// @param _balance0U112 The Balancer Vault balance of Token 0 for this pool.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _balance1U112 The Balancer Vault balance of Token 1 for this pool.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @return evoMem A Struct for executing virtual orders across functions
  ///                efficiently (saves on gas use from continual accesses to
  ///                storage/state). See documentation for ExecVirtualOrdersMem
  ///                struct.
  /// @dev NOTE: This getter also sets struct members lpFeeU60, feeShareU60 and
  ///            feeShiftU3 based on current state. Explanations for these are
  ///            detailed in the documentation for the ExecVirtualOrdersMem struct.
  ///            For convenience though, note that when Cron-Fi fees are not being
  ///            collected (cronFeeEnabled == C.FALSE), feeShiftU3 and feeShareU60
  ///            are set to zero as they are not needed since Cron-Fi fees need not
  ///            be calculated. When Cron-Fi fees are collected, then the following
  ///            equations are applicable:
  ///
  ///                lpFeeU60 = ONE_DU1_18 - balancerFeeDU1F18                (1)
  ///
  ///                                  lpFeeU60
  ///                feeShareU60 = -----------------                          (2)
  ///                              1 + 2**feeShiftU3
  ///
  ///            The results of equations 1 & 2 above are used in the function
  ///            _updateFees to divide the gross fees between
  ///            Cron-Fi and the Liquidity Providers (LPs), efficiently. The
  ///            sum of those collected fees are then subtracted from the gross fees
  ///            and the remainder goes to Balancer.
  ///            A similar strategy is used when Cron-Fi fees are not collected,
  ///            however there is no need to split fees between Cron-Fi and the LPs
  ///            in that scenario, so feeShiftU3 and feeShareU60 are not needed.
  ///
  function _getExecVirtualOrdersMem(uint256 _balance0U112, uint256 _balance1U112)
    private
    view
    returns (ExecVirtualOrdersMem memory evoMem)
  {
    (uint256 token0ReserveU112, uint256 token1ReserveU112) = _calculateReserves(_balance0U112, _balance1U112);

    uint256 localSlot = slot4; // #savegas
    uint256 cronFeeEnabled = BitPackingLib.unpackBit(localSlot, C.S4_OFFSET_CRON_FEE_ENABLED);
    uint256 collectBalancerFees = BitPackingLib.unpackBit(localSlot, C.S4_OFFSET_COLLECT_BALANCER_FEES);
    uint256 balancerFeeDU1F18 = BitPackingLib.unpackBalancerFeeS4(localSlot);

    // #unchecked
    //            The subtraction below is unchecked because the Balancer Fee, balancerFeeDU1F18, is
    //            retrieved from storage that does not permit values > C.ONE_DU1_18 to be stored.
    //            See predicate in function _handleBalancerFees that ensures potential stored values
    //            are <= C.ONE_DU1_18.
    uint256 lpFeeU60 = (collectBalancerFees != C.FALSE) ? C.ONE_DU1_18 - balancerFeeDU1F18 : C.ONE_DU1_18;

    uint256 feeShiftU3;
    uint256 feeShareU60;
    if (cronFeeEnabled != C.FALSE) {
      feeShiftU3 = BitPackingLib.unpackFeeShiftS3(slot3);
      feeShareU60 = lpFeeU60 / (1 + 2**feeShiftU3);
    }

    evoMem = ExecVirtualOrdersMem(
      token0ReserveU112,
      token1ReserveU112,
      lpFeeU60,
      feeShareU60,
      feeShiftU3,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    );
  }

  /// @notice Constructs an execute virtual order loop memory struct from virtual orders
  ///         state and the order block interval.
  ///
  function _getLoopMem() private view returns (LoopMem memory loopMem, uint256 expiryBlock) {
    // #unchecked
    //            The subtraction of lvob from the mod of it by the ORDER_BLOCK_INTERVAL
    //            is unchecked because lvob will always be greater than it's modulus by
    //            ORDER_BLOCK_INTERVAL and can't underflow. This is because it is initialized
    //            in the constructor to block.number, which is much greater than the maximum
    //            value of ORDER_BLOCK_INTERVAL (1200, VOLATILE_OBI).
    //
    //            The addition of ORDER_BLOCK_INTERVAL is unchecked because it is
    //            at most adding 1200 (VOLATILE_OBI) to the previous result,
    //            which is unlikely to overflow unless that result is already near overflow
    //            because block.number is approaching 2**256 - 1.
    //
    uint256 lvob = virtualOrders.lastVirtualOrderBlock; // #savegas
    expiryBlock = lvob - (lvob % ORDER_BLOCK_INTERVAL) + ORDER_BLOCK_INTERVAL;
    loopMem.lastVirtualOrderBlock = lvob;

    (loopMem.currentSalesRate0U112, loopMem.currentSalesRate1U112) = BitPackingLib.unpackPairU112(
      virtualOrders.orderPools.currentSalesRates
    );

    (loopMem.scaledProceeds0U128, loopMem.scaledProceeds1U128) = BitPackingLib.unpackPairU128(
      virtualOrders.orderPools.scaledProceeds
    );
  }

  /// @notice Calculates the pool's reserves at the current state or Last Virtual Order Block (LVOB) (i.e.
  ///         not considering unexecuted virtual orders). The calculation is the difference between the
  ///         Balancer Vault notion of the pool's token balances and the sum of the pool's internal
  ///         accounting of orders, proceeds, and fees for both tokens.
  /// @param _balance0U112 The Balancer Vault balance of Token 0 for this pool.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _balance1U112 The Balancer Vault balance of Token 1 for this pool.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @return token0ReserveU112 The reserves of Token 0 in this pool as of the LVOB.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @return token1ReserveU112 The reserves of Token 0 in this pool as of the LVOB.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @dev NOTE: _balance0U112 and _balance1U112 can not exceed (2**112) - 1 according to Balancer
  ///            documentation and code comments. This was further confirmed in conversations with
  ///            Balancer's engineering team.
  ///            [https://dev.balancer.fi/deep-dive/guided-tour-of-balancer-vault/episode-2-joins#vault-poolbalances.sol-5]
  ///            That means that if this function does not revert, we are assured that token0ReserveU112
  ///            and token1ReserveU112 are < MAX_U112.
  /// @dev #RISK: If the _balance0U112 and/or _balance1U112 values exceed the sum of the respective
  ///             token accounting (orders, proceeds, fees), this function will revert, rendering the
  ///             pool unusable. Even if paused!
  ///             We have mitigated this risk using the onJoinPool function when JoinType.Reward is
  ///             specified. In this scenario, a pool problem will have been identified and the pool
  ///             will have been paused. Anyone could then reward the pool with sufficient liquidity
  ///             to ensure the difference no longer underflows, enabling withdraw, cancel, and exit
  ///             operations.
  ///
  function _calculateReserves(uint256 _balance0U112, uint256 _balance1U112)
    private
    view
    returns (uint256 token0ReserveU112, uint256 token1ReserveU112)
  {
    (uint256 token0OrdersU112, uint256 token1OrdersU112) = BitPackingLib.unpackPairU112(slot1);
    (uint256 token0ProceedsU112, uint256 token1ProceedsU112) = BitPackingLib.unpackPairU112(slot2);

    uint256 localSlot4 = slot4;
    (uint256 token0BalancerFeesU96, uint256 token1BalancerFeesU96) = BitPackingLib.unpackPairU96(localSlot4);

    // NOTE: Optimization - prevent un-necessary read of slot3 by checking whether the Cron-Fi Fees stored
    //       in slot3 are zero (in flag zeroCronFiFees stored in slot4):
    //
    uint256 token0CronFiFeesU96;
    uint256 token1CronFiFeesU96;
    uint256 zeroCronFiFees = BitPackingLib.unpackBit(localSlot4, C.S4_OFFSET_ZERO_CRONFI_FEES);
    if (zeroCronFiFees == C.FALSE) {
      (token0CronFiFeesU96, token1CronFiFeesU96) = BitPackingLib.unpackPairU96(slot3);
    }

    /// #unchecked
    ///            The orders, proceeds and fees summed below are correct by construction (that is to say,
    ///            their values cannot exceed 112-bits or 96-bits, because they are stored as only 112-bits
    ///            or 96-bits and checked at storage time to ensure these limits aren't exceeded).
    ///            The sum of four 112-bit numbers cannot exceed 116-bits, and thus the unchecked addition
    ///            because a 256-bit overflow is impossible.
    ///            Safe math is used for the subtraction though in case an unforeseen error results in
    ///            the sum of orders, proceeds and fees exceeding Balancer's notion of the the pool's total
    ///            balance of either token.
    token0ReserveU112 = _balance0U112.sub(
      token0OrdersU112 + token0ProceedsU112 + token0BalancerFeesU96 + token0CronFiFeesU96
    );
    token1ReserveU112 = _balance1U112.sub(
      token1OrdersU112 + token1ProceedsU112 + token1BalancerFeesU96 + token1CronFiFeesU96
    );
  }

  /// @notice Computes the virtual reserves and output amounts of the pool for active
  ///         virtual orders. Handles the no-active orders, swap of Token0 to Token1 only
  ///         active orders, Token1 to Token0 only active orders and concurrent swap between
  ///         Token0 and Token1 active orders.
  ///         Updated virtual reserves are returned in the _evoMem struct and output amounts
  ///         through the token0OutU112 and token1OutU112 variables.
  /// @param _evoMem aggregated information for gas efficient virtual order exection. See
  ///                documentation in Structs.sol.
  /// @param _token0InU112 amount of Token0 to sell to the pool in exchange for Token1 for active
  ///                  virtual orders swapping in this direction.
  /// @param _token1InU112 amount of Token1 to sell to the pool in exchange for Token0 for active
  ///                  virtual orders swapping in this direction.
  /// @return token0OutU112 amount of Token0 received from swapping _token1InU112 Token1 with the pool.
  /// @return token1OutU112 amount of Token1 received from swapping _token0InU112 Token0 with the pool.
  ///
  function _computeVirtualReserves(
    ExecVirtualOrdersMem memory _evoMem,
    uint256 _token0InU112,
    uint256 _token1InU112
  ) private pure returns (uint256 token0OutU112, uint256 token1OutU112) {
    if (_token0InU112 != 0 || _token1InU112 != 0) {
      uint256 token0ReserveU112 = _evoMem.token0ReserveU112; // #savegas #savesize
      uint256 token1ReserveU112 = _evoMem.token1ReserveU112;

      if (_token0InU112 == 0) {
        // For single pool selling, use CPAMM formula:
        //
        // #unchecked
        //            The increment of token1ReserveU112 is unchecked below because the
        //            _token1InU112 value is derived from the orders which are checked along
        //            with the reserves by Balancer not to exceed U112 (BAL#526
        //            BALANCE_TOTAL_OVERFLOW). Also, at the start of execute virtual orders
        //            cycles, the token1ReserveU112 is computed from a known U112 value (the
        //            Balancer Vault balance) by subtracting orders, proceeds and fees accounting.
        //
        //            The multiplication of token0ReserveU112 and _token1InU112 is similarly
        //            constrained to U112 because of the BAL#526 checks performed. The maximum
        //            product of these two values is therefore a U224, which will not overflow
        //            the U256 container (especially given the division by token1ReserveU112).
        //
        //            The subtraction of token0OutU112 from token0ReserveU112 is unchecked
        //            because token0OutU112 is derived from multiplying token0ReserveU112 by
        //            values that amount to being less than 1, thus reducing it in comparison
        //            to token0ReserveU112.
        token1ReserveU112 += _token1InU112;
        token0OutU112 = (token0ReserveU112 * _token1InU112) / token1ReserveU112;
        token0ReserveU112 = token0ReserveU112 - token0OutU112;
      } else if (_token1InU112 == 0) {
        // For single pool selling, use CPAMM formula:
        //
        // #unchecked
        //            The following 3 lines are unchecked using the same rationale presented
        //            above for trades in the opposite direction.
        token0ReserveU112 += _token0InU112;
        token1OutU112 = (token1ReserveU112 * _token0InU112) / token0ReserveU112;
        token1ReserveU112 = token1ReserveU112 - token1OutU112;
      } else {
        // When both pools sell, apply the TWAMM formula in the form of the FRAX Approximation
        //
        // #unchecked
        //            The addition of tokenNReserveU112 to _tokenNInU112 below is unchecked
        //            because both values are known to be <= U112 by Balancer overflow checks
        //            (BAL#526 BALANCE_TOTAL_OVERFLOW). Also as noted in the single-sided rationale
        //            above, the reserves are computed by subtracting order, proceeds and fees
        //            accounting from known U112 values at the start of any execute virtual orders
        //            cycle (the Balancer Vault balance).
        uint256 sum0 = token0ReserveU112 + _token0InU112;
        uint256 sum1 = token1ReserveU112 + _token1InU112;

        // NOTE: purposely using standard rounding (divDown = /) here to reduce operating error.
        //
        // #unchecked
        //            Multiplication of token0ReserveU112 and sum1 is unchecked because these
        //            values are known to max out at U112 and U113, the product of which is U225,
        //            much less than the U256 container they're stored in.
        //
        //            Multiplication by token0ReserveU112 and token1ReserveU112, values known to
        //            be within U112 by Balancer overflow checks and their construction at the start
        //            of the execute virtual orders cycle, cannot exceed a U224 value, thus safe
        //            from overflow.
        uint256 ammEndToken0 = (token1ReserveU112 * sum0) / sum1;
        uint256 ammEndToken1 = (token0ReserveU112 * sum1) / sum0;
        token0ReserveU112 = ammEndToken0;
        token1ReserveU112 = ammEndToken1;

        // #unchecked
        //            Both subtractions below are unchecked for underflow because they take the form:
        //
        //                     x * z
        //                z - -------
        //                       y
        //
        //                where x <= y
        //
        //
        //            Full explanation for token0OutU112 & token1OutU112:
        //
        //                token0OutU112 = sum0 - ammEndToken0
        //
        //                                        sum0 * token1ReserveU112
        //                              = sum0 - --------------------------
        //                                                 sum1
        //
        //                                            sum0 * token1ReserveU112
        //                              = sum0 - -----------------------------------
        //                                        token1ReserveU112 + _token1InU112
        //
        //                    where: token1ReserveU112 <= token1ReserveU112 + token1InU112
        //
        //
        //                token1OutU112 = sum1 - ammEndToken1
        //
        //                                        sum1 * token0ReserveU112
        //                token1OutU112 = sum1 - --------------------------
        //                                                  sum0
        //
        //                                              sum1 * token0ReserveU112
        //                              = sum1 - ------------------------------------
        //                                        (token0ReserveU112 + token0InU112)
        //
        //                    where: token0ReserveU112 <= token0ReserveU112 + token0InU112
        //
        token0OutU112 = sum0 - ammEndToken0;
        token1OutU112 = sum1 - ammEndToken1;
      }

      _evoMem.token0ReserveU112 = token0ReserveU112;
      _evoMem.token1ReserveU112 = token1ReserveU112;
    }
  }

  /// @notice Updates the fees collected for Balancer, Liquidity Providers (LPs) and Cron-Fi (if
  ///         enabled) in the provided struct, _evoMem, for later addition to the pool.
  /// @param _grossFeeT0 is the gross fee amount collected by the pool for performing LT swaps.
  ///                    Min. = 0, Max. = (2**116) - 1
  /// @param _grossFeeT1 is the gross fee amount collected by the pool for performing LT swaps.
  ///                    Min. = 0, Max. = (2**116) - 1
  ///
  /// @dev see _executeVirtualTradesAndOrderExpiries for analysis explaining
  ///      why _grossFeeT0 and _grossFeeT1 are limited to MAX_U116.
  ///
  function _updateFees(
    ExecVirtualOrdersMem memory _evoMem,
    uint256 _grossFeeT0,
    uint256 _grossFeeT1
  ) private pure returns (uint256 lpFeeT0, uint256 lpFeeT1) {
    uint256 feeShiftU3 = _evoMem.feeShiftU3; // #savegas #savesize
    uint256 lpFeeU60 = _evoMem.lpFeeU60; // #savegas #savesize

    if (feeShiftU3 == 0) {
      // Cron-Fi Fees Not Collected:

      // #unchecked
      //            Multiplication of the gross fees, _grossFeeT0 and _grossFeeT1,
      //            with lp fee, lpFeeU60, is unchecked because the gross fees
      //            max out at 116-bits and the lp fee, lpFeeU60, at 60-bits, the
      //            product of which has a theoretical maximum of 186-bits, far less
      //            than the 256-bit result container.
      //
      // NOTE: division with rounding down is performed to protect against underflow
      //       in subtraction below.
      lpFeeT0 = (_grossFeeT0 * lpFeeU60).divDown(C.DENOMINATOR_DU1_18);
      lpFeeT1 = (_grossFeeT1 * lpFeeU60).divDown(C.DENOMINATOR_DU1_18);

      // Accumulate fees for balancer
      // #unchecked
      //            The accumulation of gross fees for balancer involves unchecked
      //            subtraction because _grossFeeT0 and _grossFeeT1 will always
      //            be greater than lpFeeT0 and lpFeeT1, respectively. This is
      //            because lpFee0 and lpFee1 are derived from the gross fees
      //            by multipling them with a fraction less than one (multiplying
      //            by a numerator that is less than the denominator, C.DENOMINATOR_FP18).
      //
      //            Overflow is also not checked here in the addition because the
      //            values are clamped to 96-bits when stored in state and the
      //            theoretical maximum _grossFee of 116-bits when added to
      //            96-bits would not exceed the 256-bit intermediate container resolution.
      //
      // Predicate prevents unintended rounding error based balancer fee collection when
      // balancer fees are zero. (see CronV1Pool.sol::_getExecVirtualOrdersMem documentation).
      if (lpFeeU60 < C.ONE_DU1_18) {
        _evoMem.token0BalancerFeesU96 += _grossFeeT0 - lpFeeT0;
        _evoMem.token1BalancerFeesU96 += _grossFeeT1 - lpFeeT1;
      }
    } else {
      // Cron-Fi Fees Collected:
      //
      // NOTE: When the fee address is set, Cron-Fi splits the fees with the LPs
      //       and balancer.  The feeShareU60 value is 1/(1+2**feeShift) multiplied by the
      //       fee remaining from collecting the Balancer Protocol fee. This value is then
      //       aportioned with 1 share going to Cron-Fi and (2**feeShift) shares to the LPs.
      //       For example if the Balancer Protocol fee is half of all fees collected
      //       and feeShift=1, then 1/6 of the total collected fee goes to Cron-Fi and
      //       1/3 of the fees collected go to the LPs (the other 1/2 of collected fees
      //       going to Balancer).
      uint256 feeShareU60 = _evoMem.feeShareU60; // #savegas #savesize

      // #unchecked
      //            Multiplication of the gross fees, _grossFeeT0 and _grossFeeT1,
      //            with fee share, feeShareU60, is unchecked because the gross fees
      //            max out at 116-bits and the lp fee, feeShareU60, at 60-bits, the
      //            product of which has a theoretical maximum of 186-bits, far less
      //            than the 256-bit result container.
      //
      // NOTE: division with rounding down is performed to protect against underflow
      //       in subtraction below.
      uint256 feeShareT0 = (_grossFeeT0 * feeShareU60).divDown(C.DENOMINATOR_DU1_18);
      uint256 feeShareT1 = (_grossFeeT1 * feeShareU60).divDown(C.DENOMINATOR_DU1_18);

      // LPs get 2**_evoMem.feeShiftU3 of the fee shares, Cron Fi gets one share.
      //
      // #unchecked
      //            The left shifts of feeShareT0 and feeShareT1 below are unchecked
      //            because the maximum shift, feeShiftU3, is limited to 4 (see
      //            check in CronV1Pool.sol::setFeeShift. The worst case value of
      //            feeShareT0 and feeShareT1 is 186-bits before division by
      //            C.DENOMINATOR_DU1_18, well within the 256-bit container limit.
      lpFeeT0 = feeShareT0 << feeShiftU3;
      lpFeeT1 = feeShareT1 << feeShiftU3;

      // Accumulate fees for balancer
      // #unchecked
      //            The accumulation of fees for balancer involves unchecked
      //            subtraction because _grossFeeT0 and _grossFeeT1 will always
      //            be greater than lpFeeT0, lpFeeT1 feeShareT0, and feeShareT1 combined,
      //            respectively. This is because lpFee0, lpFee1, feeShareT0 and feeShareT1
      //            are derived from the gross fees by multipling them with a fraction less
      //            than or equal to one (multiplying by a numerator that is less than or equal
      //            to the denominator, C.DENOMINATOR_FP18, which rounds down). (The value of
      //            feeShareU60 is such that multiplying it by 2**feeShift and then adding it
      //            to itself will sum to the gross fee minus fees collected for balancer).
      //
      //            Overflow is also not checked here in the addition because the
      //            values are clamped to 96-bits when stored in state and the
      //            theortical maximum _grossFee of 116-bits when added to
      //            96-bits would not exceed the 256-bit intermediate container range.
      //
      // Predicate prevents unintended rounding error based balancer fee collection when
      // balancer fees are zero. (see CronV1Pool.sol::_getExecVirtualOrdersMem documentation).
      if (lpFeeU60 < C.ONE_DU1_18) {
        _evoMem.token0BalancerFeesU96 += (_grossFeeT0 - lpFeeT0) - feeShareT0;
        _evoMem.token1BalancerFeesU96 += (_grossFeeT1 - lpFeeT1) - feeShareT1;
      }

      // Accumulate fees for CronFi
      // #unchecked
      //            Similar to above, overflow is not checked in the addition because
      //            Cron-Fi fees are clamped to 96-bits when stored in state and the
      //            theoretical maximum _grossFee of 116-bits (which greatly exceeds
      //            the feeShareT0 and feeShareT1 values) when added to 96-bits would not
      //            exceed the 256-bit intermediate container range.
      _evoMem.token0CronFiFeesU96 += feeShareT0;
      _evoMem.token1CronFiFeesU96 += feeShareT1;
    }
  }

  /// @notice Calculate the oracle increment by incrementing the oracle change struct members
  ///         (token0OracleU256F112 and token1OracleU256F112) of struct _evoMem based on the
  ///         current reserve values and the time elapsed since the last reserve values.
  /// @param _evoMem aggregated information for gas efficient virtual order exection. See
  ///                documentation in Structs.sol.
  /// @param _lastVirtualOrderBlock is the ethereum block number before the last virtual
  ///                               orders were executed.
  /// @param _expiryBlock is the next block upon which virtual orders expire. It is aligned
  ///                     on multiples of the order block interval.
  function _calculateOracleIncrement(
    ExecVirtualOrdersMem memory _evoMem,
    uint256 _lastVirtualOrderBlock,
    uint256 _expiryBlock
  ) private pure {
    // #unchecked
    //            Overflow is not possible in the shift below: U256 > (U112 << 112).
    //            Token reserve values known to be U112 because computed from Balancer Vault values
    //            by subtracting accounting and funds going in/out of them all known to sum to
    //            less than U112 mathematically.
    //
    //            The subtraction of lastVirtualOrderBlock from _expiryBlock is unchecked below
    //            because the following functions that call this function ensure that _expiryBlock
    //            is greater than _lastVirtualOrderBlock in their call to _getLoopMem, which sets
    //            expiryBlock to the next block number divisible by order block interval after the
    //            last virtual order block:
    //                - executeVirtualOrdersToBlock
    //                - executeVirtualOrdersView
    //
    // #overUnderFlowIntended
    //                        The UQ256x112 incremented result relies upon and expects overflow
    //                        and is unchecked. The reason is that it is the difference between
    //                        price oracle samples over time that matters, not the absolute value.
    //                        As noted above, see the Uniswap V2 whitepaper.
    //                        That said, this is a partial increment that will be added to the
    //                        stored accumulation of values and is unlikely to overflow during
    //                        virtual order execution because both tokenXOracleU256F112 values
    //                        are initialized to zero at the start of execution (the time elapsed
    //                        would need to be on the order of 2 ** 32 to overflow the worst case
    //                        ratio of token reserves ((2 ** 112-1) / 1)--that means
    //                        (2**32-1/12s) = 357913941 blocks must elapse since the last oracle
    //                        update and the worst case reserves to overflow this incremental value
    //                        and cause an oracle price error.
    //
    uint256 timeElapsed = C.SECONDS_PER_BLOCK * (_expiryBlock - _lastVirtualOrderBlock);
    if (_evoMem.token0ReserveU112 > 0) {
      _evoMem.token0OracleU256F112 += ((_evoMem.token1ReserveU112 << 112) / _evoMem.token0ReserveU112) * timeElapsed;
    }
    if (_evoMem.token1ReserveU112 > 0) {
      _evoMem.token1OracleU256F112 += ((_evoMem.token0ReserveU112 << 112) / _evoMem.token1ReserveU112) * timeElapsed;
    }
  }

  /// @notice
  /// @param _token0InU112 is the amount of Token 0 to Join the pool with.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _token1InU112 is the amount of Token 1 to Join the pool with.
  ///                      Min. = 0, Max. = (2**112) - 1
  /// @param _token0MinU112 is the minimum price of Token 0 permitted.
  ///                       Min. = 0, Max. = (2**112) - 1
  /// @param _token1MinU112 is the minimum price of Token 1 permitted.
  ///                       Min. = 0, Max. = (2**112) - 1
  /// @param _token0ReserveU112 is the current Token 0 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  /// @param _token1ReserveU112 is the current Token 1 reserves of the pool.
  ///                           Min. = 0, Max. = (2**112) - 1
  ///
  function _joinMinimumCheck(
    uint256 _token0InU112,
    uint256 _token1InU112,
    uint256 _token0MinU112,
    uint256 _token1MinU112,
    uint256 _token0ReserveU112,
    uint256 _token1ReserveU112
  ) private pure {
    if (_token0ReserveU112 > 0 && _token1ReserveU112 > 0) {
      // #unchecked
      //            The multiplication below is unchecked for overflow because the product
      //            of two 112-bit values is less than 256-bits. (Reserves are correct by
      //            construction and Balancer ensures that the tokens in do not cause the
      //            pool to exceed 112-bits).
      uint256 nominalToken0 = (_token1InU112 * _token0ReserveU112) / _token1ReserveU112;
      uint256 nominalToken1 = (_token0InU112 * _token1ReserveU112) / _token0ReserveU112;
      requireErrCode(
        nominalToken0 >= _token0MinU112 && nominalToken1 >= _token1MinU112,
        CronErrors.MINIMUM_NOT_SATISFIED
      );
    }
  }
}

// File: contracts/twault/balancer-core-v2/lib/helpers/Authentication.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

import "./BalancerErrors.sol";
import "./IAuthentication.sol";

/**
 * @dev Building block for performing access control on external functions.
 *
 * This contract is used via the `authenticate` modifier (or the `_authenticateCaller` function), which can be applied
 * to external functions to only make them callable by authorized accounts.
 *
 * Derived contracts must implement the `_canPerform` function, which holds the actual access control logic.
 */
abstract contract Authentication is IAuthentication {
    bytes32 private immutable _actionIdDisambiguator;

    /**
     * @dev The main purpose of the `actionIdDisambiguator` is to prevent accidental function selector collisions in
     * multi contract systems.
     *
     * There are two main uses for it:
     *  - if the contract is a singleton, any unique identifier can be used to make the associated action identifiers
     *    unique. The contract's own address is a good option.
     *  - if the contract belongs to a family that shares action identifiers for the same functions, an identifier
     *    shared by the entire family (and no other contract) should be used instead.
     */
    constructor(bytes32 actionIdDisambiguator) {
        _actionIdDisambiguator = actionIdDisambiguator;
    }

    /**
     * @dev Reverts unless the caller is allowed to call this function. Should only be applied to external functions.
     */
    modifier authenticate() {
        _authenticateCaller();
        _;
    }

    /**
     * @dev Reverts unless the caller is allowed to call the entry point function.
     */
    function _authenticateCaller() internal view {
        bytes32 actionId = getActionId(msg.sig);
        _require(_canPerform(actionId, msg.sender), Errors.SENDER_NOT_ALLOWED);
    }

    function getActionId(bytes4 selector) public view override returns (bytes32) {
        // Each external function is dynamically assigned an action identifier as the hash of the disambiguator and the
        // function selector. Disambiguation is necessary to avoid potential collisions in the function selectors of
        // multiple contracts.
        return keccak256(abi.encodePacked(_actionIdDisambiguator, selector));
    }

    function _canPerform(bytes32 actionId, address user) internal view virtual returns (bool);
}

// File: contracts/twault/balancer-core-v2/lib/helpers/BalancerErrors.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

// solhint-disable

/**
 * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
 * supported.
 */
function _require(bool condition, uint256 errorCode) pure {
    if (!condition) _revert(errorCode);
}

/**
 * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
 */
function _revert(uint256 errorCode) pure {
    // We're going to dynamically create a revert string based on the error code, with the following format:
    // 'BAL#{errorCode}'
    // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).
    //
    // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a
    // number (8 to 16 bits) than the individual string characters.
    //
    // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a
    // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a
    // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.
    assembly {
        // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999
        // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for
        // the '0' character.

        let units := add(mod(errorCode, 10), 0x30)

        errorCode := div(errorCode, 10)
        let tenths := add(mod(errorCode, 10), 0x30)

        errorCode := div(errorCode, 10)
        let hundreds := add(mod(errorCode, 10), 0x30)

        // With the individual characters, we can now construct the full string. The "BAL#" part is a known constant
        // (0x42414c23): we simply shift this by 24 (to provide space for the 3 bytes of the error code), and add the
        // characters to it, each shifted by a multiple of 8.
        // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits
        // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte
        // array).

        let revertReason := shl(200, add(0x42414c23000000, add(add(units, shl(8, tenths)), shl(16, hundreds))))

        // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded
        // message will have the following layout:
        // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]

        // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We
        // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.
        mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
        // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).
        mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
        // The string length is fixed: 7 characters.
        mstore(0x24, 7)
        // Finally, the string itself is stored.
        mstore(0x44, revertReason)

        // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of
        // the encoded message is therefore 4 + 32 + 32 + 32 = 100.
        revert(0, 100)
    }
}

library Errors {
    // Math
    uint256 internal constant ADD_OVERFLOW = 0;
    uint256 internal constant SUB_OVERFLOW = 1;
    uint256 internal constant SUB_UNDERFLOW = 2;
    uint256 internal constant MUL_OVERFLOW = 3;
    uint256 internal constant ZERO_DIVISION = 4;
    uint256 internal constant DIV_INTERNAL = 5;
    uint256 internal constant X_OUT_OF_BOUNDS = 6;
    uint256 internal constant Y_OUT_OF_BOUNDS = 7;
    uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;
    uint256 internal constant INVALID_EXPONENT = 9;

    // Input
    uint256 internal constant OUT_OF_BOUNDS = 100;
    uint256 internal constant UNSORTED_ARRAY = 101;
    uint256 internal constant UNSORTED_TOKENS = 102;
    uint256 internal constant INPUT_LENGTH_MISMATCH = 103;
    uint256 internal constant ZERO_TOKEN = 104;

    // Shared pools
    uint256 internal constant MIN_TOKENS = 200;
    uint256 internal constant MAX_TOKENS = 201;
    uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;
    uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;
    uint256 internal constant MINIMUM_BPT = 204;
    uint256 internal constant CALLER_NOT_VAULT = 205;
    uint256 internal constant UNINITIALIZED = 206;
    uint256 internal constant BPT_IN_MAX_AMOUNT = 207;
    uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;
    uint256 internal constant EXPIRED_PERMIT = 209;

    // Pools
    uint256 internal constant MIN_AMP = 300;
    uint256 internal constant MAX_AMP = 301;
    uint256 internal constant MIN_WEIGHT = 302;
    uint256 internal constant MAX_STABLE_TOKENS = 303;
    uint256 internal constant MAX_IN_RATIO = 304;
    uint256 internal constant MAX_OUT_RATIO = 305;
    uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;
    uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;
    uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;
    uint256 internal constant INVALID_TOKEN = 309;
    uint256 internal constant UNHANDLED_JOIN_KIND = 310;
    uint256 internal constant ZERO_INVARIANT = 311;
    uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;
    uint256 internal constant ORACLE_NOT_INITIALIZED = 313;
    uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;
    uint256 internal constant ORACLE_INVALID_INDEX = 315;
    uint256 internal constant ORACLE_BAD_SECS = 316;

    // Lib
    uint256 internal constant REENTRANCY = 400;
    uint256 internal constant SENDER_NOT_ALLOWED = 401;
    uint256 internal constant PAUSED = 402;
    uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;
    uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;
    uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;
    uint256 internal constant INSUFFICIENT_BALANCE = 406;
    uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;
    uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;
    uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;
    uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;
    uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;
    uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;
    uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;
    uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;
    uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;
    uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;
    uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;
    uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;
    uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;
    uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;
    uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;
    uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;
    uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;
    uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;
    uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;

    // Vault
    uint256 internal constant INVALID_POOL_ID = 500;
    uint256 internal constant CALLER_NOT_POOL = 501;
    uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;
    uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;
    uint256 internal constant INVALID_SIGNATURE = 504;
    uint256 internal constant EXIT_BELOW_MIN = 505;
    uint256 internal constant JOIN_ABOVE_MAX = 506;
    uint256 internal constant SWAP_LIMIT = 507;
    uint256 internal constant SWAP_DEADLINE = 508;
    uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;
    uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;
    uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;
    uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;
    uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;
    uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;
    uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;
    uint256 internal constant INSUFFICIENT_ETH = 516;
    uint256 internal constant UNALLOCATED_ETH = 517;
    uint256 internal constant ETH_TRANSFER = 518;
    uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;
    uint256 internal constant TOKENS_MISMATCH = 520;
    uint256 internal constant TOKEN_NOT_REGISTERED = 521;
    uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;
    uint256 internal constant TOKENS_ALREADY_SET = 523;
    uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;
    uint256 internal constant NONZERO_TOKEN_BALANCE = 525;
    uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;
    uint256 internal constant POOL_NO_TOKENS = 527;
    uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;

    // Fees
    uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;
    uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;
    uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;
}

// File: contracts/twault/balancer-core-v2/lib/helpers/IAuthentication.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

interface IAuthentication {
    /**
     * @dev Returns the action identifier associated with the external function described by `selector`.
     */
    function getActionId(bytes4 selector) external view returns (bytes32);
}

// File: contracts/twault/balancer-core-v2/lib/helpers/ISignaturesValidator.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

/**
 * @dev Interface for the SignatureValidator helper, used to support meta-transactions.
 */
interface ISignaturesValidator {
    /**
     * @dev Returns the EIP712 domain separator.
     */
    function getDomainSeparator() external view returns (bytes32);

    /**
     * @dev Returns the next nonce used by an address to sign messages.
     */
    function getNextNonce(address user) external view returns (uint256);
}

// File: contracts/twault/balancer-core-v2/lib/helpers/ITemporarilyPausable.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

/**
 * @dev Interface for the TemporarilyPausable helper.
 */
interface ITemporarilyPausable {
    /**
     * @dev Emitted every time the pause state changes by `_setPaused`.
     */
    event PausedStateChanged(bool paused);

    /**
     * @dev Returns the current paused state.
     */
    function getPausedState()
        external
        view
        returns (
            bool paused,
            uint256 pauseWindowEndTime,
            uint256 bufferPeriodEndTime
        );
}

// File: contracts/twault/balancer-core-v2/lib/helpers/InputHelpers.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

import "../openzeppelin/IERC20.sol";

import "./BalancerErrors.sol";

import "../../vault/interfaces/IAsset.sol";

library InputHelpers {
    function ensureInputLengthMatch(uint256 a, uint256 b) internal pure {
        _require(a == b, Errors.INPUT_LENGTH_MISMATCH);
    }

    function ensureInputLengthMatch(
        uint256 a,
        uint256 b,
        uint256 c
    ) internal pure {
        _require(a == b && b == c, Errors.INPUT_LENGTH_MISMATCH);
    }

    function ensureArrayIsSorted(IAsset[] memory array) internal pure {
        address[] memory addressArray;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            addressArray := array
        }
        ensureArrayIsSorted(addressArray);
    }

    function ensureArrayIsSorted(IERC20[] memory array) internal pure {
        address[] memory addressArray;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            addressArray := array
        }
        ensureArrayIsSorted(addressArray);
    }

    function ensureArrayIsSorted(address[] memory array) internal pure {
        if (array.length < 2) {
            return;
        }

        address previous = array[0];
        for (uint256 i = 1; i < array.length; ++i) {
            address current = array[i];
            _require(previous < current, Errors.UNSORTED_ARRAY);
            previous = current;
        }
    }
}

// File: contracts/twault/balancer-core-v2/lib/math/Math.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

import "../helpers/BalancerErrors.sol";

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow checks.
 * Adapted from OpenZeppelin's SafeMath library
 */
library Math {
    /**
     * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        _require(c >= a, Errors.ADD_OVERFLOW);
        return c;
    }

    /**
     * @dev Returns the addition of two signed integers, reverting on overflow.
     */
    function add(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a + b;
        _require((b >= 0 && c >= a) || (b < 0 && c < a), Errors.ADD_OVERFLOW);
        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        _require(b <= a, Errors.SUB_OVERFLOW);
        uint256 c = a - b;
        return c;
    }

    /**
     * @dev Returns the subtraction of two signed integers, reverting on overflow.
     */
    function sub(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a - b;
        _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW);
        return c;
    }

    /**
     * @dev Returns the largest of two numbers of 256 bits.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers of 256 bits.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a * b;
        _require(a == 0 || c / a == b, Errors.MUL_OVERFLOW);
        return c;
    }

    function divDown(uint256 a, uint256 b) internal pure returns (uint256) {
        _require(b != 0, Errors.ZERO_DIVISION);
        return a / b;
    }

    function divUp(uint256 a, uint256 b) internal pure returns (uint256) {
        _require(b != 0, Errors.ZERO_DIVISION);

        if (a == 0) {
            return 0;
        } else {
            return 1 + (a - 1) / b;
        }
    }
}

// File: contracts/twault/balancer-core-v2/lib/openzeppelin/EIP712.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
 * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
 * they need in their contracts using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * _Available since v3.4._
 */
abstract contract EIP712 {
    /* solhint-disable var-name-mixedcase */
    bytes32 private immutable _HASHED_NAME;
    bytes32 private immutable _HASHED_VERSION;
    bytes32 private immutable _TYPE_HASH;

    /* solhint-enable var-name-mixedcase */

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _HASHED_NAME = keccak256(bytes(name));
        _HASHED_VERSION = keccak256(bytes(version));
        _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view virtual returns (bytes32) {
        return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _getChainId(), address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
    }

    function _getChainId() private view returns (uint256 chainId) {
        // Silence state mutability warning without generating bytecode.
        // See https://github.com/ethereum/solidity/issues/10090#issuecomment-741789128 and
        // https://github.com/ethereum/solidity/issues/2691
        this;

        // solhint-disable-next-line no-inline-assembly
        assembly {
            chainId := chainid()
        }
    }
}

// File: contracts/twault/balancer-core-v2/lib/openzeppelin/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: contracts/twault/balancer-core-v2/lib/openzeppelin/IERC20Permit.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,
     * given `owner`'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// File: contracts/twault/balancer-core-v2/lib/openzeppelin/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT

// Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce bytecode size.
// Modifier code is inlined by the compiler, which causes its code to appear multiple times in the codebase. By using
// private functions, we achieve the same end result with slightly higher runtime gas costs, but reduced bytecode size.

pragma solidity ^0.7.0;

import "../helpers/BalancerErrors.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _enterNonReentrant();
        _;
        _exitNonReentrant();
    }

    function _enterNonReentrant() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        _require(_status != _ENTERED, Errors.REENTRANCY);

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _exitNonReentrant() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

// File: contracts/twault/balancer-core-v2/lib/openzeppelin/SafeERC20.sol



// SPDX-License-Identifier: MIT

// Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce gas costs.
// The `safeTransfer` and `safeTransferFrom` functions assume that `token` is a contract (an account with code), and
// work differently from the OpenZeppelin version if it is not.

pragma solidity ^0.7.0;

import "../helpers/BalancerErrors.sol";

import "./IERC20.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(address(token), abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(address(token), abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     *
     * WARNING: `token` is assumed to be a contract: calls to EOAs will *not* revert.
     */
    function _callOptionalReturn(address token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves.
        (bool success, bytes memory returndata) = token.call(data);

        // If the low-level call didn't succeed we return whatever was returned from it.
        assembly {
            if eq(success, 0) {
                returndatacopy(0, 0, returndatasize())
                revert(0, returndatasize())
            }
        }

        // Finally we check the returndata size is either zero or true - note that this check will always pass for EOAs
        _require(returndata.length == 0 || abi.decode(returndata, (bool)), Errors.SAFE_ERC20_CALL_FAILED);
    }
}

// File: contracts/twault/balancer-core-v2/pools/BalancerPoolToken.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

import "../lib/math/Math.sol";
import "../lib/openzeppelin/IERC20.sol";
import "../lib/openzeppelin/IERC20Permit.sol";
import "../lib/openzeppelin/EIP712.sol";

/**
 * @title Highly opinionated token implementation
 * @author Balancer Labs
 * @dev
 * - Includes functions to increase and decrease allowance as a workaround
 *   for the well-known issue with `approve`:
 *   https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
 * - Allows for 'infinite allowance', where an allowance of 0xff..ff is not
 *   decreased by calls to transferFrom
 * - Lets a token holder use `transferFrom` to send their own tokens,
 *   without first setting allowance
 * - Emits 'Approval' events whenever allowance is changed by `transferFrom`
 */
contract BalancerPoolToken is IERC20, IERC20Permit, EIP712 {
    using Math for uint256;

    // State variables

    uint8 private constant _DECIMALS = 18;

    mapping(address => uint256) private _balance;
    mapping(address => mapping(address => uint256)) private _allowance;
    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    mapping(address => uint256) private _nonces;

    // solhint-disable-next-line var-name-mixedcase
    bytes32 private immutable _PERMIT_TYPE_HASH = keccak256(
        "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
    );

    // Function declarations

    constructor(string memory tokenName, string memory tokenSymbol) EIP712(tokenName, "1") {
        _name = tokenName;
        _symbol = tokenSymbol;
    }

    // External functions

    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowance[owner][spender];
    }

    function balanceOf(address account) external view override returns (uint256) {
        return _balance[account];
    }

    function approve(address spender, uint256 amount) external override returns (bool) {
        _setAllowance(msg.sender, spender, amount);

        return true;
    }

    function increaseApproval(address spender, uint256 amount) external returns (bool) {
        _setAllowance(msg.sender, spender, _allowance[msg.sender][spender].add(amount));

        return true;
    }

    function decreaseApproval(address spender, uint256 amount) external returns (bool) {
        uint256 currentAllowance = _allowance[msg.sender][spender];

        if (amount >= currentAllowance) {
            _setAllowance(msg.sender, spender, 0);
        } else {
            _setAllowance(msg.sender, spender, currentAllowance.sub(amount));
        }

        return true;
    }

    function transfer(address recipient, uint256 amount) external override returns (bool) {
        _move(msg.sender, recipient, amount);

        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external override returns (bool) {
        uint256 currentAllowance = _allowance[sender][msg.sender];
        _require(msg.sender == sender || currentAllowance >= amount, Errors.INSUFFICIENT_ALLOWANCE);

        _move(sender, recipient, amount);

        if (msg.sender != sender && currentAllowance != uint256(-1)) {
            // Because of the previous require, we know that if msg.sender != sender then currentAllowance >= amount
            _setAllowance(sender, msg.sender, currentAllowance - amount);
        }

        return true;
    }

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        // solhint-disable-next-line not-rely-on-time
        _require(block.timestamp <= deadline, Errors.EXPIRED_PERMIT);

        uint256 nonce = _nonces[owner];

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPE_HASH, owner, spender, value, nonce, deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ecrecover(hash, v, r, s);
        _require((signer != address(0)) && (signer == owner), Errors.INVALID_SIGNATURE);

        _nonces[owner] = nonce + 1;
        _setAllowance(owner, spender, value);
    }

    // Public functions

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public pure returns (uint8) {
        return _DECIMALS;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function nonces(address owner) external view override returns (uint256) {
        return _nonces[owner];
    }

    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    // Internal functions

    function _mintPoolTokens(address recipient, uint256 amount) internal {
        _balance[recipient] = _balance[recipient].add(amount);
        _totalSupply = _totalSupply.add(amount);
        emit Transfer(address(0), recipient, amount);
    }

    function _burnPoolTokens(address sender, uint256 amount) internal {
        uint256 currentBalance = _balance[sender];
        _require(currentBalance >= amount, Errors.INSUFFICIENT_BALANCE);

        _balance[sender] = currentBalance - amount;
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(sender, address(0), amount);
    }

    function _move(
        address sender,
        address recipient,
        uint256 amount
    ) internal {
        uint256 currentBalance = _balance[sender];
        _require(currentBalance >= amount, Errors.INSUFFICIENT_BALANCE);
        // Prohibit transfers to the zero address to avoid confusion with the
        // Transfer event emitted by `_burnPoolTokens`
        _require(recipient != address(0), Errors.ERC20_TRANSFER_TO_ZERO_ADDRESS);

        _balance[sender] = currentBalance - amount;
        _balance[recipient] = _balance[recipient].add(amount);

        emit Transfer(sender, recipient, amount);
    }

    // Private functions

    function _setAllowance(
        address owner,
        address spender,
        uint256 amount
    ) private {
        _allowance[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
}

// File: contracts/twault/balancer-core-v2/pools/factories/BasePoolFactory.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "../../vault/interfaces/IVault.sol";
import "../../vault/interfaces/IBasePool.sol";

/**
 * @dev Base contract for Pool factories.
 *
 * Pools are deployed from factories to allow third parties to reason about them. Unknown Pools may have arbitrary
 * logic: being able to assert that a Pool's behavior follows certain rules (those imposed by the contracts created by
 * the factory) is very powerful.
 */
abstract contract BasePoolFactory {
    IVault private immutable _vault;
    mapping(address => bool) private _isPoolFromFactory;

    event PoolCreated(address indexed pool);

    constructor(IVault vault) {
        _vault = vault;
    }

    /**
     * @dev Returns the Vault's address.
     */
    function getVault() public view returns (IVault) {
        return _vault;
    }

    /**
     * @dev Returns true if `pool` was created by this factory.
     */
    function isPoolFromFactory(address pool) external view returns (bool) {
        return _isPoolFromFactory[pool];
    }

    /**
     * @dev Registers a new created pool.
     *
     * Emits a `PoolCreated` event.
     */
    function _register(address pool) internal {
        _isPoolFromFactory[pool] = true;
        emit PoolCreated(pool);
    }
}

// File: contracts/twault/balancer-core-v2/vault/ProtocolFeesCollector.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "../lib/openzeppelin/IERC20.sol";
import "../lib/helpers/InputHelpers.sol";
import "../lib/helpers/Authentication.sol";
import "../lib/openzeppelin/ReentrancyGuard.sol";
import "../lib/openzeppelin/SafeERC20.sol";

import "./interfaces/IVault.sol";
import "./interfaces/IAuthorizer.sol";

/**
 * @dev This an auxiliary contract to the Vault, deployed by it during construction. It offloads some of the tasks the
 * Vault performs to reduce its overall bytecode size.
 *
 * The current values for all protocol fee percentages are stored here, and any tokens charged as protocol fees are
 * sent to this contract, where they may be withdrawn by authorized entities. All authorization tasks are delegated
 * to the Vault's own authorizer.
 */
contract ProtocolFeesCollector is Authentication, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // Absolute maximum fee percentages (1e18 = 100%, 1e16 = 1%).
    uint256 private constant _MAX_PROTOCOL_SWAP_FEE_PERCENTAGE = 50e16; // 50%
    uint256 private constant _MAX_PROTOCOL_FLASH_LOAN_FEE_PERCENTAGE = 1e16; // 1%

    IVault public immutable vault;

    // All fee percentages are 18-decimal fixed point numbers.

    // The swap fee is charged whenever a swap occurs, as a percentage of the fee charged by the Pool. These are not
    // actually charged on each individual swap: the `Vault` relies on the Pools being honest and reporting fees due
    // when users join and exit them.
    uint256 private _swapFeePercentage;

    // The flash loan fee is charged whenever a flash loan occurs, as a percentage of the tokens lent.
    uint256 private _flashLoanFeePercentage;

    event SwapFeePercentageChanged(uint256 newSwapFeePercentage);
    event FlashLoanFeePercentageChanged(uint256 newFlashLoanFeePercentage);

    constructor(IVault _vault)
        // The ProtocolFeesCollector is a singleton, so it simply uses its own address to disambiguate action
        // identifiers.
        Authentication(bytes32(uint256(address(this))))
    {
        vault = _vault;
    }

    function withdrawCollectedFees(
        IERC20[] calldata tokens,
        uint256[] calldata amounts,
        address recipient
    ) external nonReentrant authenticate {
        InputHelpers.ensureInputLengthMatch(tokens.length, amounts.length);

        for (uint256 i = 0; i < tokens.length; ++i) {
            IERC20 token = tokens[i];
            uint256 amount = amounts[i];
            token.safeTransfer(recipient, amount);
        }
    }

    // authenticate disabled for testing close to production
    // function setSwapFeePercentage(uint256 newSwapFeePercentage) external authenticate {
    function setSwapFeePercentage(uint256 newSwapFeePercentage) external {
        _require(newSwapFeePercentage <= _MAX_PROTOCOL_SWAP_FEE_PERCENTAGE, Errors.SWAP_FEE_PERCENTAGE_TOO_HIGH);
        _swapFeePercentage = newSwapFeePercentage;
        emit SwapFeePercentageChanged(newSwapFeePercentage);
    }

    function setFlashLoanFeePercentage(uint256 newFlashLoanFeePercentage) external authenticate {
        _require(
            newFlashLoanFeePercentage <= _MAX_PROTOCOL_FLASH_LOAN_FEE_PERCENTAGE,
            Errors.FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH
        );
        _flashLoanFeePercentage = newFlashLoanFeePercentage;
        emit FlashLoanFeePercentageChanged(newFlashLoanFeePercentage);
    }

    function getSwapFeePercentage() external view returns (uint256) {
        return _swapFeePercentage;
    }

    function getFlashLoanFeePercentage() external view returns (uint256) {
        return _flashLoanFeePercentage;
    }

    function getCollectedFeeAmounts(IERC20[] memory tokens) external view returns (uint256[] memory feeAmounts) {
        feeAmounts = new uint256[](tokens.length);
        for (uint256 i = 0; i < tokens.length; ++i) {
            feeAmounts[i] = tokens[i].balanceOf(address(this));
        }
    }

    function getAuthorizer() external view returns (IAuthorizer) {
        return _getAuthorizer();
    }

    function _canPerform(bytes32 actionId, address account) internal view override returns (bool) {
        return _getAuthorizer().canPerform(actionId, account, address(this));
    }

    function _getAuthorizer() internal view returns (IAuthorizer) {
        return vault.getAuthorizer();
    }
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IAsset.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

/**
 * @dev This is an empty interface used to represent either ERC20-conforming token contracts or ETH (using the zero
 * address sentinel value). We're just relying on the fact that `interface` can be used to declare new address-like
 * types.
 *
 * This concept is unrelated to a Pool's Asset Managers.
 */
interface IAsset {
    // solhint-disable-previous-line no-empty-blocks
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IAuthorizer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

interface IAuthorizer {
    /**
     * @dev Returns true if `account` can perform the action described by `actionId` in the contract `where`.
     */
    function canPerform(
        bytes32 actionId,
        address account,
        address where
    ) external view returns (bool);
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IBasePool.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "./IVault.sol";
import "./IPoolSwapStructs.sol";

/**
 * @dev Interface for adding and removing liquidity that all Pool contracts should implement. Note that this is not
 * the complete Pool contract interface, as it is missing the swap hooks. Pool contracts should also inherit from
 * either IGeneralPool or IMinimalSwapInfoPool
 */
interface IBasePool is IPoolSwapStructs {
    /**
     * @dev Called by the Vault when a user calls `IVault.joinPool` to add liquidity to this Pool. Returns how many of
     * each registered token the user should provide, as well as the amount of protocol fees the Pool owes to the Vault.
     * The Vault will then take tokens from `sender` and add them to the Pool's balances, as well as collect
     * the reported amount in protocol fees, which the pool should calculate based on `protocolSwapFeePercentage`.
     *
     * Protocol fees are reported and charged on join events so that the Pool is free of debt whenever new users join.
     *
     * `sender` is the account performing the join (from which tokens will be withdrawn), and `recipient` is the account
     * designated to receive any benefits (typically pool shares). `currentBalances` contains the total balances
     * for each token the Pool registered in the Vault, in the same order that `IVault.getPoolTokens` would return.
     *
     * `lastChangeBlock` is the last block in which *any* of the Pool's registered tokens last changed its total
     * balance.
     *
     * `userData` contains any pool-specific instructions needed to perform the calculations, such as the type of
     * join (e.g., proportional given an amount of pool shares, single-asset, multi-asset, etc.)
     *
     * Contracts implementing this function should check that the caller is indeed the Vault before performing any
     * state-changing operations, such as minting pool shares.
     */
    function onJoinPool(
        bytes32 poolId,
        address sender,
        address recipient,
        uint256[] memory balances,
        uint256 lastChangeBlock,
        uint256 protocolSwapFeePercentage,
        bytes memory userData
    ) external returns (uint256[] memory amountsIn, uint256[] memory dueProtocolFeeAmounts);

    /**
     * @dev Called by the Vault when a user calls `IVault.exitPool` to remove liquidity from this Pool. Returns how many
     * tokens the Vault should deduct from the Pool's balances, as well as the amount of protocol fees the Pool owes
     * to the Vault. The Vault will then take tokens from the Pool's balances and send them to `recipient`,
     * as well as collect the reported amount in protocol fees, which the Pool should calculate based on
     * `protocolSwapFeePercentage`.
     *
     * Protocol fees are charged on exit events to guarantee that users exiting the Pool have paid their share.
     *
     * `sender` is the account performing the exit (typically the pool shareholder), and `recipient` is the account
     * to which the Vault will send the proceeds. `currentBalances` contains the total token balances for each token
     * the Pool registered in the Vault, in the same order that `IVault.getPoolTokens` would return.
     *
     * `lastChangeBlock` is the last block in which *any* of the Pool's registered tokens last changed its total
     * balance.
     *
     * `userData` contains any pool-specific instructions needed to perform the calculations, such as the type of
     * exit (e.g., proportional given an amount of pool shares, single-asset, multi-asset, etc.)
     *
     * Contracts implementing this function should check that the caller is indeed the Vault before performing any
     * state-changing operations, such as burning pool shares.
     */
    function onExitPool(
        bytes32 poolId,
        address sender,
        address recipient,
        uint256[] memory balances,
        uint256 lastChangeBlock,
        uint256 protocolSwapFeePercentage,
        bytes memory userData
    ) external returns (uint256[] memory amountsOut, uint256[] memory dueProtocolFeeAmounts);
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IFlashLoanRecipient.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

// Inspired by Aave Protocol's IFlashLoanReceiver.

import "../../lib/openzeppelin/IERC20.sol";

interface IFlashLoanRecipient {
    /**
     * @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
     *
     * At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
     * call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
     * Vault, or else the entire flash loan will revert.
     *
     * `userData` is the same value passed in the `IVault.flashLoan` call.
     */
    function receiveFlashLoan(
        IERC20[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external;
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IMinimalSwapInfoPool.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "./IBasePool.sol";

/**
 * @dev Pool contracts with the MinimalSwapInfo or TwoToken specialization settings should implement this interface.
 *
 * This is called by the Vault when a user calls `IVault.swap` or `IVault.batchSwap` to swap with this Pool.
 * Returns the number of tokens the Pool will grant to the user in a 'given in' swap, or that the user will grant
 * to the pool in a 'given out' swap.
 *
 * This can often be implemented by a `view` function, since many pricing algorithms don't need to track state
 * changes in swaps. However, contracts implementing this in non-view functions should check that the caller is
 * indeed the Vault.
 */
interface IMinimalSwapInfoPool is IBasePool {
    function onSwap(
        SwapRequest memory swapRequest,
        uint256 currentBalanceTokenIn,
        uint256 currentBalanceTokenOut
    ) external returns (uint256 amount);
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IPoolSwapStructs.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "../../lib/openzeppelin/IERC20.sol";

import "./IVault.sol";

interface IPoolSwapStructs {
    // This is not really an interface - it just defines common structs used by other interfaces: IGeneralPool and
    // IMinimalSwapInfoPool.
    //
    // This data structure represents a request for a token swap, where `kind` indicates the swap type ('given in' or
    // 'given out') which indicates whether or not the amount sent by the pool is known.
    //
    // The pool receives `tokenIn` and sends `tokenOut`. `amount` is the number of `tokenIn` tokens the pool will take
    // in, or the number of `tokenOut` tokens the Pool will send out, depending on the given swap `kind`.
    //
    // All other fields are not strictly necessary for most swaps, but are provided to support advanced scenarios in
    // some Pools.
    //
    // `poolId` is the ID of the Pool involved in the swap - this is useful for Pool contracts that implement more than
    // one Pool.
    //
    // The meaning of `lastChangeBlock` depends on the Pool specialization:
    //  - Two Token or Minimal Swap Info: the last block in which either `tokenIn` or `tokenOut` changed its total
    //    balance.
    //  - General: the last block in which *any* of the Pool's registered tokens changed its total balance.
    //
    // `from` is the origin address for the funds the Pool receives, and `to` is the destination address
    // where the Pool sends the outgoing tokens.
    //
    // `userData` is extra data provided by the caller - typically a signature from a trusted party.
    struct SwapRequest {
        IVault.SwapKind kind;
        IERC20 tokenIn;
        IERC20 tokenOut;
        uint256 amount;
        // Misc data
        bytes32 poolId;
        uint256 lastChangeBlock;
        address from;
        address to;
        bytes userData;
    }
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IVault.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma experimental ABIEncoderV2;

import "../../lib/openzeppelin/IERC20.sol";

import "./IWETH.sol";
import "./IAsset.sol";
import "./IAuthorizer.sol";
import "./IFlashLoanRecipient.sol";
import "../ProtocolFeesCollector.sol";

import "../../lib/helpers/ISignaturesValidator.sol";
import "../../lib/helpers/ITemporarilyPausable.sol";

pragma solidity ^0.7.0;

/**
 * @dev Full external interface for the Vault core contract - no external or public methods exist in the contract that
 * don't override one of these declarations.
 */
interface IVault is ISignaturesValidator, ITemporarilyPausable {
    // Generalities about the Vault:
    //
    // - Whenever documentation refers to 'tokens', it strictly refers to ERC20-compliant token contracts. Tokens are
    // transferred out of the Vault by calling the `IERC20.transfer` function, and transferred in by calling
    // `IERC20.transferFrom`. In these cases, the sender must have previously allowed the Vault to use their tokens by
    // calling `IERC20.approve`. The only deviation from the ERC20 standard that is supported is functions not returning
    // a boolean value: in these scenarios, a non-reverting call is assumed to be successful.
    //
    // - All non-view functions in the Vault are non-reentrant: calling them while another one is mid-execution (e.g.
    // while execution control is transferred to a token contract during a swap) will result in a revert. View
    // functions can be called in a re-reentrant way, but doing so might cause them to return inconsistent results.
    // Contracts calling view functions in the Vault must make sure the Vault has not already been entered.
    //
    // - View functions revert if referring to either unregistered Pools, or unregistered tokens for registered Pools.

    // Authorizer
    //
    // Some system actions are permissioned, like setting and collecting protocol fees. This permissioning system exists
    // outside of the Vault in the Authorizer contract: the Vault simply calls the Authorizer to check if the caller
    // can perform a given action.

    /**
     * @dev Returns the Vault's Authorizer.
     */
    function getAuthorizer() external view returns (IAuthorizer);

    /**
     * @dev Sets a new Authorizer for the Vault. The caller must be allowed by the current Authorizer to do this.
     *
     * Emits an `AuthorizerChanged` event.
     */
    function setAuthorizer(IAuthorizer newAuthorizer) external;

    /**
     * @dev Emitted when a new authorizer is set by `setAuthorizer`.
     */
    event AuthorizerChanged(IAuthorizer indexed newAuthorizer);

    // Relayers
    //
    // Additionally, it is possible for an account to perform certain actions on behalf of another one, using their
    // Vault ERC20 allowance and Internal Balance. These accounts are said to be 'relayers' for these Vault functions,
    // and are expected to be smart contracts with sound authentication mechanisms. For an account to be able to wield
    // this power, two things must occur:
    //  - The Authorizer must grant the account the permission to be a relayer for the relevant Vault function. This
    //    means that Balancer governance must approve each individual contract to act as a relayer for the intended
    //    functions.
    //  - Each user must approve the relayer to act on their behalf.
    // This double protection means users cannot be tricked into approving malicious relayers (because they will not
    // have been allowed by the Authorizer via governance), nor can malicious relayers approved by a compromised
    // Authorizer or governance drain user funds, since they would also need to be approved by each individual user.

    /**
     * @dev Returns true if `user` has approved `relayer` to act as a relayer for them.
     */
    function hasApprovedRelayer(address user, address relayer) external view returns (bool);

    /**
     * @dev Allows `relayer` to act as a relayer for `sender` if `approved` is true, and disallows it otherwise.
     *
     * Emits a `RelayerApprovalChanged` event.
     */
    function setRelayerApproval(
        address sender,
        address relayer,
        bool approved
    ) external;

    /**
     * @dev Emitted every time a relayer is approved or disapproved by `setRelayerApproval`.
     */
    event RelayerApprovalChanged(address indexed relayer, address indexed sender, bool approved);

    // Internal Balance
    //
    // Users can deposit tokens into the Vault, where they are allocated to their Internal Balance, and later
    // transferred or withdrawn. It can also be used as a source of tokens when joining Pools, as a destination
    // when exiting them, and as either when performing swaps. This usage of Internal Balance results in greatly reduced
    // gas costs when compared to relying on plain ERC20 transfers, leading to large savings for frequent users.
    //
    // Internal Balance management features batching, which means a single contract call can be used to perform multiple
    // operations of different kinds, with different senders and recipients, at once.

    /**
     * @dev Returns `user`'s Internal Balance for a set of tokens.
     */
    function getInternalBalance(address user, IERC20[] memory tokens) external view returns (uint256[] memory);

    /**
     * @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)
     * and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as
     * it lets integrators reuse a user's Vault allowance.
     *
     * For each operation, if the caller is not `sender`, it must be an authorized relayer for them.
     */
    function manageUserBalance(UserBalanceOp[] memory ops) external payable;

    /**
     * @dev Data for `manageUserBalance` operations, which include the possibility for ETH to be sent and received
     without manual WETH wrapping or unwrapping.
     */
    struct UserBalanceOp {
        UserBalanceOpKind kind;
        IAsset asset;
        uint256 amount;
        address sender;
        address payable recipient;
    }

    // There are four possible operations in `manageUserBalance`:
    //
    // - DEPOSIT_INTERNAL
    // Increases the Internal Balance of the `recipient` account by transferring tokens from the corresponding
    // `sender`. The sender must have allowed the Vault to use their tokens via `IERC20.approve()`.
    //
    // ETH can be used by passing the ETH sentinel value as the asset and forwarding ETH in the call: it will be wrapped
    // and deposited as WETH. Any ETH amount remaining will be sent back to the caller (not the sender, which is
    // relevant for relayers).
    //
    // Emits an `InternalBalanceChanged` event.
    //
    //
    // - WITHDRAW_INTERNAL
    // Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient`.
    //
    // ETH can be used by passing the ETH sentinel value as the asset. This will deduct WETH instead, unwrap it and send
    // it to the recipient as ETH.
    //
    // Emits an `InternalBalanceChanged` event.
    //
    //
    // - TRANSFER_INTERNAL
    // Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient`.
    //
    // Reverts if the ETH sentinel value is passed.
    //
    // Emits an `InternalBalanceChanged` event.
    //
    //
    // - TRANSFER_EXTERNAL
    // Transfers tokens from `sender` to `recipient`, using the Vault's ERC20 allowance. This is typically used by
    // relayers, as it lets them reuse a user's Vault allowance.
    //
    // Reverts if the ETH sentinel value is passed.
    //
    // Emits an `ExternalBalanceTransfer` event.

    enum UserBalanceOpKind { DEPOSIT_INTERNAL, WITHDRAW_INTERNAL, TRANSFER_INTERNAL, TRANSFER_EXTERNAL }

    /**
     * @dev Emitted when a user's Internal Balance changes, either from calls to `manageUserBalance`, or through
     * interacting with Pools using Internal Balance.
     *
     * Because Internal Balance works exclusively with ERC20 tokens, ETH deposits and withdrawals will use the WETH
     * address.
     */
    event InternalBalanceChanged(address indexed user, IERC20 indexed token, int256 delta);

    /**
     * @dev Emitted when a user's Vault ERC20 allowance is used by the Vault to transfer tokens to an external account.
     */
    event ExternalBalanceTransfer(IERC20 indexed token, address indexed sender, address recipient, uint256 amount);

    // Pools
    //
    // There are three specialization settings for Pools, which allow for cheaper swaps at the cost of reduced
    // functionality:
    //
    //  - General: no specialization, suited for all Pools. IGeneralPool is used for swap request callbacks, passing the
    // balance of all tokens in the Pool. These Pools have the largest swap costs (because of the extra storage reads),
    // which increase with the number of registered tokens.
    //
    //  - Minimal Swap Info: IMinimalSwapInfoPool is used instead of IGeneralPool, which saves gas by only passing the
    // balance of the two tokens involved in the swap. This is suitable for some pricing algorithms, like the weighted
    // constant product one popularized by Balancer V1. Swap costs are smaller compared to general Pools, and are
    // independent of the number of registered tokens.
    //
    //  - Two Token: only allows two tokens to be registered. This achieves the lowest possible swap gas cost. Like
    // minimal swap info Pools, these are called via IMinimalSwapInfoPool.

    enum PoolSpecialization { GENERAL, MINIMAL_SWAP_INFO, TWO_TOKEN }

    /**
     * @dev Registers the caller account as a Pool with a given specialization setting. Returns the Pool's ID, which
     * is used in all Pool-related functions. Pools cannot be deregistered, nor can the Pool's specialization be
     * changed.
     *
     * The caller is expected to be a smart contract that implements either `IGeneralPool` or `IMinimalSwapInfoPool`,
     * depending on the chosen specialization setting. This contract is known as the Pool's contract.
     *
     * Note that the same contract may register itself as multiple Pools with unique Pool IDs, or in other words,
     * multiple Pools may share the same contract.
     *
     * Emits a `PoolRegistered` event.
     */
    function registerPool(PoolSpecialization specialization) external returns (bytes32);

    /**
     * @dev Emitted when a Pool is registered by calling `registerPool`.
     */
    event PoolRegistered(bytes32 indexed poolId, address indexed poolAddress, PoolSpecialization specialization);

    /**
     * @dev Returns a Pool's contract address and specialization setting.
     */
    function getPool(bytes32 poolId) external view returns (address, PoolSpecialization);

    /**
     * @dev Registers `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
     *
     * Pools can only interact with tokens they have registered. Users join a Pool by transferring registered tokens,
     * exit by receiving registered tokens, and can only swap registered tokens.
     *
     * Each token can only be registered once. For Pools with the Two Token specialization, `tokens` must have a length
     * of two, that is, both tokens must be registered in the same `registerTokens` call, and they must be sorted in
     * ascending order.
     *
     * The `tokens` and `assetManagers` arrays must have the same length, and each entry in these indicates the Asset
     * Manager for the corresponding token. Asset Managers can manage a Pool's tokens via `managePoolBalance`,
     * depositing and withdrawing them directly, and can even set their balance to arbitrary amounts. They are therefore
     * expected to be highly secured smart contracts with sound design principles, and the decision to register an
     * Asset Manager should not be made lightly.
     *
     * Pools can choose not to assign an Asset Manager to a given token by passing in the zero address. Once an Asset
     * Manager is set, it cannot be changed except by deregistering the associated token and registering again with a
     * different Asset Manager.
     *
     * Emits a `TokensRegistered` event.
     */
    function registerTokens(
        bytes32 poolId,
        IERC20[] memory tokens,
        address[] memory assetManagers
    ) external;

    /**
     * @dev Emitted when a Pool registers tokens by calling `registerTokens`.
     */
    event TokensRegistered(bytes32 indexed poolId, IERC20[] tokens, address[] assetManagers);

    /**
     * @dev Deregisters `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
     *
     * Only registered tokens (via `registerTokens`) can be deregistered. Additionally, they must have zero total
     * balance. For Pools with the Two Token specialization, `tokens` must have a length of two, that is, both tokens
     * must be deregistered in the same `deregisterTokens` call.
     *
     * A deregistered token can be re-registered later on, possibly with a different Asset Manager.
     *
     * Emits a `TokensDeregistered` event.
     */
    function deregisterTokens(bytes32 poolId, IERC20[] memory tokens) external;

    /**
     * @dev Emitted when a Pool deregisters tokens by calling `deregisterTokens`.
     */
    event TokensDeregistered(bytes32 indexed poolId, IERC20[] tokens);

    /**
     * @dev Returns detailed information for a Pool's registered token.
     *
     * `cash` is the number of tokens the Vault currently holds for the Pool. `managed` is the number of tokens
     * withdrawn and held outside the Vault by the Pool's token Asset Manager. The Pool's total balance for `token`
     * equals the sum of `cash` and `managed`.
     *
     * Internally, `cash` and `managed` are stored using 112 bits. No action can ever cause a Pool's token `cash`,
     * `managed` or `total` balance to be greater than 2^112 - 1.
     *
     * `lastChangeBlock` is the number of the block in which `token`'s total balance was last modified (via either a
     * join, exit, swap, or Asset Manager update). This value is useful to avoid so-called 'sandwich attacks', for
     * example when developing price oracles. A change of zero (e.g. caused by a swap with amount zero) is considered a
     * change for this purpose, and will update `lastChangeBlock`.
     *
     * `assetManager` is the Pool's token Asset Manager.
     */
    function getPoolTokenInfo(bytes32 poolId, IERC20 token)
        external
        view
        returns (
            uint256 cash,
            uint256 managed,
            uint256 lastChangeBlock,
            address assetManager
        );

    /**
     * @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of
     * the tokens' `balances` changed.
     *
     * The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all
     * Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.
     *
     * If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same
     * order as passed to `registerTokens`.
     *
     * Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are
     * the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`
     * instead.
     */
    function getPoolTokens(bytes32 poolId)
        external
        view
        returns (
            IERC20[] memory tokens,
            uint256[] memory balances,
            uint256 lastChangeBlock
        );

    /**
     * @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will
     * trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized
     * Pool shares.
     *
     * If the caller is not `sender`, it must be an authorized relayer for them.
     *
     * The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount
     * to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces
     * these maximums.
     *
     * If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable
     * this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the
     * WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent
     * back to the caller (not the sender, which is important for relayers).
     *
     * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
     * interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be
     * sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final
     * `assets` array might not be sorted. Pools with no registered tokens cannot be joined.
     *
     * If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only
     * be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be
     * withdrawn from Internal Balance: attempting to do so will trigger a revert.
     *
     * This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement
     * their own custom logic. This typically requires additional information from the user (such as the expected number
     * of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed
     * directly to the Pool's contract, as is `recipient`.
     *
     * Emits a `PoolBalanceChanged` event.
     */
    function joinPool(
        bytes32 poolId,
        address sender,
        address recipient,
        JoinPoolRequest memory request
    ) external payable;

    struct JoinPoolRequest {
        IAsset[] assets;
        uint256[] maxAmountsIn;
        bytes userData;
        bool fromInternalBalance;
    }

    /**
     * @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will
     * trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized
     * Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see
     * `getPoolTokenInfo`).
     *
     * If the caller is not `sender`, it must be an authorized relayer for them.
     *
     * The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum
     * token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:
     * it just enforces these minimums.
     *
     * If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To
     * enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead
     * of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.
     *
     * `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
     * interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must
     * be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the
     * final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.
     *
     * If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,
     * an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to
     * do so will trigger a revert.
     *
     * `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the
     * `tokens` array. This array must match the Pool's registered tokens.
     *
     * This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement
     * their own custom logic. This typically requires additional information from the user (such as the expected number
     * of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and
     * passed directly to the Pool's contract.
     *
     * Emits a `PoolBalanceChanged` event.
     */
    function exitPool(
        bytes32 poolId,
        address sender,
        address payable recipient,
        ExitPoolRequest memory request
    ) external;

    struct ExitPoolRequest {
        IAsset[] assets;
        uint256[] minAmountsOut;
        bytes userData;
        bool toInternalBalance;
    }

    /**
     * @dev Emitted when a user joins or exits a Pool by calling `joinPool` or `exitPool`, respectively.
     */
    event PoolBalanceChanged(
        bytes32 indexed poolId,
        address indexed liquidityProvider,
        IERC20[] tokens,
        int256[] deltas,
        uint256[] protocolFeeAmounts
    );

    enum PoolBalanceChangeKind { JOIN, EXIT }

    // Swaps
    //
    // Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,
    // they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be
    // aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.
    //
    // The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.
    // In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),
    // and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').
    // More complex swaps, such as one token in to multiple tokens out can be achieved by batching together
    // individual swaps.
    //
    // There are two swap kinds:
    //  - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the
    // `onSwap` hook) the amount of tokens out (to send to the recipient).
    //  - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines
    // (via the `onSwap` hook) the amount of tokens in (to receive from the sender).
    //
    // Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with
    // the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated
    // tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended
    // swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at
    // the final intended token.
    //
    // In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal
    // Balance) after all individual swaps have been completed, and the net token balance change computed. This makes
    // certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost
    // much less gas than they would otherwise.
    //
    // It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple
    // Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only
    // updating the Pool's internal accounting).
    //
    // To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token
    // involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the
    // minimum amount of tokens to receive (by passing a negative value) is specified.
    //
    // Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after
    // this point in time (e.g. if the transaction failed to be included in a block promptly).
    //
    // If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do
    // the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be
    // passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the
    // same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).
    //
    // Finally, Internal Balance can be used when either sending or receiving tokens.

    enum SwapKind { GIVEN_IN, GIVEN_OUT }

    /**
     * @dev Performs a swap with a single Pool.
     *
     * If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens
     * taken from the Pool, which must be greater than or equal to `limit`.
     *
     * If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens
     * sent to the Pool, which must be less than or equal to `limit`.
     *
     * Internal Balance usage and the recipient are determined by the `funds` struct.
     *
     * Emits a `Swap` event.
     */
    function swap(
        SingleSwap memory singleSwap,
        FundManagement memory funds,
        uint256 limit,
        uint256 deadline
    ) external payable returns (uint256);

    /**
     * @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on
     * the `kind` value.
     *
     * `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).
     * Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.
     *
     * The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
     * used to extend swap behavior.
     */
    struct SingleSwap {
        bytes32 poolId;
        SwapKind kind;
        IAsset assetIn;
        IAsset assetOut;
        uint256 amount;
        bytes userData;
    }

    /**
     * @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either
     * the amount of tokens sent to or received from the Pool, depending on the `kind` value.
     *
     * Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the
     * Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at
     * the same index in the `assets` array.
     *
     * Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a
     * Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or
     * `amountOut` depending on the swap kind.
     *
     * Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out
     * of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal
     * the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.
     *
     * The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,
     * or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and
     * out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to
     * or unwrapped from WETH by the Vault.
     *
     * Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies
     * the minimum or maximum amount of each token the vault is allowed to transfer.
     *
     * `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the
     * equivalent `swap` call.
     *
     * Emits `Swap` events.
     */
    function batchSwap(
        SwapKind kind,
        BatchSwapStep[] memory swaps,
        IAsset[] memory assets,
        FundManagement memory funds,
        int256[] memory limits,
        uint256 deadline
    ) external payable returns (int256[] memory);

    /**
     * @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the
     * `assets` array passed to that function, and ETH assets are converted to WETH.
     *
     * If `amount` is zero, the multihop mechanism is used to determine the actual amount based on the amount in/out
     * from the previous swap, depending on the swap kind.
     *
     * The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
     * used to extend swap behavior.
     */
    struct BatchSwapStep {
        bytes32 poolId;
        uint256 assetInIndex;
        uint256 assetOutIndex;
        uint256 amount;
        bytes userData;
    }

    /**
     * @dev Emitted for each individual swap performed by `swap` or `batchSwap`.
     */
    event Swap(
        bytes32 indexed poolId,
        IERC20 indexed tokenIn,
        IERC20 indexed tokenOut,
        uint256 amountIn,
        uint256 amountOut
    );

    /**
     * @dev All tokens in a swap are either sent from the `sender` account to the Vault, or from the Vault to the
     * `recipient` account.
     *
     * If the caller is not `sender`, it must be an authorized relayer for them.
     *
     * If `fromInternalBalance` is true, the `sender`'s Internal Balance will be preferred, performing an ERC20
     * transfer for the difference between the requested amount and the User's Internal Balance (if any). The `sender`
     * must have allowed the Vault to use their tokens via `IERC20.approve()`. This matches the behavior of
     * `joinPool`.
     *
     * If `toInternalBalance` is true, tokens will be deposited to `recipient`'s internal balance instead of
     * transferred. This matches the behavior of `exitPool`.
     *
     * Note that ETH cannot be deposited to or withdrawn from Internal Balance: attempting to do so will trigger a
     * revert.
     */
    struct FundManagement {
        address sender;
        bool fromInternalBalance;
        address payable recipient;
        bool toInternalBalance;
    }

    /**
     * @dev Simulates a call to `batchSwap`, returning an array of Vault asset deltas. Calls to `swap` cannot be
     * simulated directly, but an equivalent `batchSwap` call can and will yield the exact same result.
     *
     * Each element in the array corresponds to the asset at the same index, and indicates the number of tokens (or ETH)
     * the Vault would take from the sender (if positive) or send to the recipient (if negative). The arguments it
     * receives are the same that an equivalent `batchSwap` call would receive.
     *
     * Unlike `batchSwap`, this function performs no checks on the sender or recipient field in the `funds` struct.
     * This makes it suitable to be called by off-chain applications via eth_call without needing to hold tokens,
     * approve them for the Vault, or even know a user's address.
     *
     * Note that this function is not 'view' (due to implementation details): the client code must explicitly execute
     * eth_call instead of eth_sendTransaction.
     */
    function queryBatchSwap(
        SwapKind kind,
        BatchSwapStep[] memory swaps,
        IAsset[] memory assets,
        FundManagement memory funds
    ) external returns (int256[] memory assetDeltas);

    // Flash Loans

    /**
     * @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
     * and then reverting unless the tokens plus a proportional protocol fee have been returned.
     *
     * The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
     * for each token contract. `tokens` must be sorted in ascending order.
     *
     * The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
     * `receiveFlashLoan` call.
     *
     * Emits `FlashLoan` events.
     */
    function flashLoan(
        IFlashLoanRecipient recipient,
        IERC20[] memory tokens,
        uint256[] memory amounts,
        bytes memory userData
    ) external;

    /**
     * @dev Emitted for each individual flash loan performed by `flashLoan`.
     */
    event FlashLoan(IFlashLoanRecipient indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount);

    // Asset Management
    //
    // Each token registered for a Pool can be assigned an Asset Manager, which is able to freely withdraw the Pool's
    // tokens from the Vault, deposit them, or assign arbitrary values to its `managed` balance (see
    // `getPoolTokenInfo`). This makes them extremely powerful and dangerous. Even if an Asset Manager only directly
    // controls one of the tokens in a Pool, a malicious manager could set that token's balance to manipulate the
    // prices of the other tokens, and then drain the Pool with swaps. The risk of using Asset Managers is therefore
    // not constrained to the tokens they are managing, but extends to the entire Pool's holdings.
    //
    // However, a properly designed Asset Manager smart contract can be safely used for the Pool's benefit,
    // for example by lending unused tokens out for interest, or using them to participate in voting protocols.
    //
    // This concept is unrelated to the IAsset interface.

    /**
     * @dev Performs a set of Pool balance operations, which may be either withdrawals, deposits or updates.
     *
     * Pool Balance management features batching, which means a single contract call can be used to perform multiple
     * operations of different kinds, with different Pools and tokens, at once.
     *
     * For each operation, the caller must be registered as the Asset Manager for `token` in `poolId`.
     */
    function managePoolBalance(PoolBalanceOp[] memory ops) external;

    struct PoolBalanceOp {
        PoolBalanceOpKind kind;
        bytes32 poolId;
        IERC20 token;
        uint256 amount;
    }

    /**
     * Withdrawals decrease the Pool's cash, but increase its managed balance, leaving the total balance unchanged.
     *
     * Deposits increase the Pool's cash, but decrease its managed balance, leaving the total balance unchanged.
     *
     * Updates don't affect the Pool's cash balance, but because the managed balance changes, it does alter the total.
     * The external amount can be either increased or decreased by this call (i.e., reporting a gain or a loss).
     */
    enum PoolBalanceOpKind { WITHDRAW, DEPOSIT, UPDATE }

    /**
     * @dev Emitted when a Pool's token Asset Manager alters its balance via `managePoolBalance`.
     */
    event PoolBalanceManaged(
        bytes32 indexed poolId,
        address indexed assetManager,
        IERC20 indexed token,
        int256 cashDelta,
        int256 managedDelta
    );

    // Protocol Fees
    //
    // Some operations cause the Vault to collect tokens in the form of protocol fees, which can then be withdrawn by
    // permissioned accounts.
    //
    // There are two kinds of protocol fees:
    //
    //  - flash loan fees: charged on all flash loans, as a percentage of the amounts lent.
    //
    //  - swap fees: a percentage of the fees charged by Pools when performing swaps. For a number of reasons, including
    // swap gas costs and interface simplicity, protocol swap fees are not charged on each individual swap. Rather,
    // Pools are expected to keep track of how much they have charged in swap fees, and pay any outstanding debts to the
    // Vault when they are joined or exited. This prevents users from joining a Pool with unpaid debt, as well as
    // exiting a Pool in debt without first paying their share.

    /**
     * @dev Returns the current protocol fee module.
     */
    function getProtocolFeesCollector() external view returns (ProtocolFeesCollector);

    /**
     * @dev Safety mechanism to pause most Vault operations in the event of an emergency - typically detection of an
     * error in some part of the system.
     *
     * The Vault can only be paused during an initial time period, after which pausing is forever disabled.
     *
     * While the contract is paused, the following features are disabled:
     * - depositing and transferring internal balance
     * - transferring external balance (using the Vault's allowance)
     * - swaps
     * - joining Pools
     * - Asset Manager interactions
     *
     * Internal Balance can still be withdrawn, and Pools exited.
     */
    function setPaused(bool paused) external;

    /**
     * @dev Returns the Vault's WETH instance.
     */
    function WETH() external view returns (IWETH);
    // solhint-disable-previous-line func-name-mixedcase
}

// File: contracts/twault/balancer-core-v2/vault/interfaces/IWETH.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

import "../../lib/openzeppelin/IERC20.sol";

/**
 * @dev Interface for the WETH token contract used internally for wrapping and unwrapping, to support
 * sending and receiving ETH in joins, swaps, and internal balance deposits and withdrawals.
 */
interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint256 amount) external;
}

// File: contracts/twault/factories/CronV1PoolFactory.sol



// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.7.6;

import { IERC20 } from "../balancer-core-v2/lib/openzeppelin/IERC20.sol";
import { IVault } from "../balancer-core-v2/vault/interfaces/IVault.sol";
import { BasePoolFactory } from "../balancer-core-v2/pools/factories/BasePoolFactory.sol";
import { ICronV1PoolFactory } from "../interfaces/ICronV1PoolFactory.sol";
import { ICronV1PoolEnums } from "../interfaces/pool/ICronV1PoolEnums.sol";
import { CronV1Pool } from "../CronV1Pool.sol";
import { requireErrCode, CronErrors } from "../miscellany/Errors.sol";

/// @author Cron Finance
/// @title Cron V1 Pool Factory
contract CronV1PoolFactory is ICronV1PoolFactory, BasePoolFactory {
  address public override owner;
  address public override pendingOwner;
  mapping(address => mapping(address => mapping(uint256 => address))) internal poolMap;

  /// @notice Only allows the `owner` to execute the function.
  modifier onlyOwner() {
    requireErrCode(msg.sender == owner, CronErrors.INVALID_FACTORY_OWNER);
    _;
  }

  /// @notice This function constructs the pool
  /// @param _vault The balancer v2 vault
  constructor(IVault _vault) BasePoolFactory(_vault) {
    owner = msg.sender;
    emit OwnerChanged(address(0), owner);
  }

  /// @notice Deploys a new `CronV1Pool`
  /// @param _token0 The asset which is converged to ie "base'
  /// @param _token1 The asset which converges to the underlying
  /// @param _poolType The type of pool (stable, liquid, volatile)
  /// @param _name The name of the balancer v2 lp token for this pool
  /// @param _symbol The symbol of the balancer v2 lp token for this pool
  /// @return The new pool address
  function create(
    address _token0,
    address _token1,
    string memory _name,
    string memory _symbol,
    uint256 _poolType
  ) external override(ICronV1PoolFactory) returns (address) {
    ICronV1PoolEnums.PoolType poolType = ICronV1PoolEnums.PoolType(_poolType);
    requireErrCode(_token0 != _token1, CronErrors.IDENTICAL_TOKEN_ADDRESSES);
    (address token0, address token1) = _token0 < _token1 ? (_token0, _token1) : (_token1, _token0);
    requireErrCode(token0 != address(0), CronErrors.ZERO_TOKEN_ADDRESSES);
    requireErrCode(poolMap[token0][token1][_poolType] == address(0), CronErrors.EXISTING_POOL);
    address pool = address(new CronV1Pool(IERC20(token0), IERC20(token1), getVault(), _name, _symbol, poolType));
    // Register the pool with the vault
    _register(pool);
    // Stores pool information to prevent duplicates
    poolMap[token0][token1][_poolType] = pool;
    // Emit a creation event
    emit CronV1PoolCreated(pool, _token0, _token1, poolType);
    return pool;
  }

  /// @notice Sets `CronV1Pool` address in the mapping
  /// @param _token0 address of token0
  /// @param _token1 address of token1
  /// @param _poolType type of pool (stable, liquid, volatile)
  /// @param _pool address of pool to set in the mapping
  function set(
    address _token0,
    address _token1,
    uint256 _poolType,
    address _pool
  ) external override(ICronV1PoolFactory) onlyOwner {
    poolMap[_token0][_token1][_poolType] = _pool;
    ICronV1PoolEnums.PoolType poolType = ICronV1PoolEnums.PoolType(_poolType);
    emit CronV1PoolSet(_pool, _token0, _token1, poolType);
  }

  /// @notice Removes an already deployed `CronV1Pool` from the mapping
  ///         WARNING - Best practice to disable Cron-Fi fees before
  ///         removing it from the factory pool mapping. Also advisable
  ///         to notify LPs / LT swappers in some way that this is
  ///         occurring.
  /// @param _token0 address of token0
  /// @param _token1 address of token1
  /// @param _poolType type of pool (stable, liquid, volatile)
  function remove(
    address _token0,
    address _token1,
    uint256 _poolType
  ) external override(ICronV1PoolFactory) onlyOwner {
    address pool = poolMap[_token0][_token1][_poolType];
    requireErrCode(pool != address(0), CronErrors.NON_EXISTING_POOL);
    poolMap[_token0][_token1][_poolType] = address(0);
    ICronV1PoolEnums.PoolType poolType = ICronV1PoolEnums.PoolType(_poolType);
    emit CronV1PoolRemoved(pool, _token0, _token1, poolType);
  }

  /// @notice Transfers ownership to `_newOwner`. Either directly or claimable by the new pending owner.
  /// Can only be invoked by the current `owner`.
  /// @param _newOwner Address of the new owner.
  /// @param _direct True if `_newOwner` should be set immediately. False if `_newOwner` needs to use `claimOwnership`.
  /// @param _renounce Allows the `_newOwner` to be `address(0)` if `_direct` and `_renounce` is True. Has no effect otherwise.
  function transferOwnership(
    address _newOwner,
    bool _direct,
    bool _renounce
  ) external override(ICronV1PoolFactory) onlyOwner {
    if (_direct) {
      requireErrCode(_newOwner != address(0) || _renounce, CronErrors.ZERO_TOKEN_ADDRESSES);
      emit OwnerChanged(owner, _newOwner);
      owner = _newOwner;
      pendingOwner = address(0);
    } else {
      pendingOwner = _newOwner;
    }
  }

  /// @notice Needs to be called by `pendingOwner` to claim ownership.
  function claimOwnership() external override(ICronV1PoolFactory) {
    address _pendingOwner = pendingOwner;
    requireErrCode(msg.sender == _pendingOwner, CronErrors.INVALID_PENDING_OWNER);
    emit OwnerChanged(owner, _pendingOwner);
    owner = _pendingOwner;
    pendingOwner = address(0);
  }

  /// @notice Gets existing pool for given address pair post sort and pool type
  /// @param _token0 address of token 0
  /// @param _token1 address of token 1
  /// @param _poolType type of pool
  function getPool(
    address _token0,
    address _token1,
    uint256 _poolType
  ) external view override(ICronV1PoolFactory) returns (address) {
    (address token0, address token1) = _token0 < _token1 ? (_token0, _token1) : (_token1, _token0);
    return poolMap[token0][token1][_poolType];
  }
}

// File: contracts/twault/interfaces/IArbitrageurList.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.7.6;

/// @notice Interface for managing list of addresses permitted to perform preferred rate
///         arbitrage swaps on Cron-Fi TWAMM V1.0.
///
interface IArbitrageurList {
  /// @param sender is the address that called the function changing list owner permissions.
  /// @param listOwner is the address to change list owner permissions on.
  /// @param permission is true if the address specified in listOwner is granted list owner
  ///        permissions. Is false otherwise.
  ///
  event ListOwnerPermissions(address indexed sender, address indexed listOwner, bool indexed permission);

  /// @param sender is the address that called the function changing arbitrageur permissions.
  /// @param arbitrageurs is a list of addresses to change arbitrage permissions on.
  /// @param permission is true if the addresses specified in arbitrageurs is granted
  ///        arbitrage permissions. Is false otherwise.
  ///
  event ArbitrageurPermissions(address indexed sender, address[] arbitrageurs, bool indexed permission);

  /// @param sender is the address that called the function changing the next list address.
  /// @param nextListAddress is the address the return value of the nextList function is set to.
  ///
  event NextList(address indexed sender, address indexed nextListAddress);

  /// @notice Returns true if the provide address is permitted the preferred
  ///         arbitrage rate in the partner swap method of a Cron-Fi TWAMM pool.
  ///         Returns false otherwise.
  /// @param _address the address to check for arbitrage rate permissions.
  ///
  function isArbitrageur(address _address) external returns (bool);

  /// @notice Returns the address of the next contract implementing the next list of arbitrageurs.
  ///         If the return value is the NULL address, address(0), then the TWAMM contract's update
  ///         list method will keep the existing address it is storing to check for arbitrage permissions.
  ///
  function nextList() external returns (address);
}

// File: contracts/twault/interfaces/ICronV1Pool.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

pragma experimental ABIEncoderV2;

import { ICronV1FactoryOwnerActions } from "./pool/ICronV1FactoryOwnerActions.sol";
import { ICronV1PoolAdminActions } from "./pool/ICronV1PoolAdminActions.sol";
import { ICronV1PoolArbitrageurActions } from "./pool/ICronV1PoolArbitrageurActions.sol";
import { ICronV1PoolEnums } from "./pool/ICronV1PoolEnums.sol";
import { ICronV1PoolEvents } from "./pool/ICronV1PoolEvents.sol";
import { ICronV1PoolHelpers } from "./pool/ICronV1PoolHelpers.sol";
import { IERC20 } from "../balancer-core-v2/lib/openzeppelin/IERC20.sol";

interface ICronV1Pool is
  ICronV1FactoryOwnerActions,
  ICronV1PoolAdminActions,
  ICronV1PoolArbitrageurActions,
  ICronV1PoolEnums,
  ICronV1PoolEvents,
  ICronV1PoolHelpers,
  IERC20
{}

// File: contracts/twault/interfaces/ICronV1PoolFactory.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.7.6;

import { ICronV1Pool } from "../interfaces/ICronV1Pool.sol";

interface ICronV1PoolFactory {
  /// @notice This event tracks pool creations from this factory
  /// @param pool the address of the pool
  /// @param token0 The token 0 in this pool
  /// @param token1 The token 1 in this pool
  /// @param poolType The poolType set for this pool
  event CronV1PoolCreated(
    address indexed pool,
    address indexed token0,
    address indexed token1,
    ICronV1Pool.PoolType poolType
  );

  /// @notice This event tracks pool being set from this factory
  /// @param pool the address of the pool
  /// @param token0 The token 0 in this pool
  /// @param token1 The token 1 in this pool
  /// @param poolType The poolType set for this pool
  event CronV1PoolSet(
    address indexed pool,
    address indexed token0,
    address indexed token1,
    ICronV1Pool.PoolType poolType
  );

  /// @notice This event tracks pool deletions from this factory
  /// @param pool the address of the pool
  /// @param token0 The token 0 in this pool
  /// @param token1 The token 1 in this pool
  /// @param poolType The poolType set for this pool
  event CronV1PoolRemoved(
    address indexed pool,
    address indexed token0,
    address indexed token1,
    ICronV1Pool.PoolType poolType
  );

  /// @notice This event tracks pool creations from this factory
  /// @param oldAdmin the address of the previous admin
  /// @param newAdmin the address of the new admin
  event OwnerChanged(address indexed oldAdmin, address indexed newAdmin);

  // Functions
  function create(
    address _token0,
    address _token1,
    string memory _name,
    string memory _symbol,
    uint256 _poolType
  ) external returns (address);

  function set(
    address _token0,
    address _token1,
    uint256 _poolType,
    address _pool
  ) external;

  function remove(
    address _token0,
    address _token1,
    uint256 _poolType
  ) external;

  function transferOwnership(
    address _newOwner,
    bool _direct,
    bool _renounce
  ) external;

  function claimOwnership() external;

  function owner() external view returns (address);

  function pendingOwner() external view returns (address);

  function getPool(
    address _token0,
    address _token1,
    uint256 _poolType
  ) external view returns (address pool);
}

// File: contracts/twault/interfaces/IERC20Decimals.sol



// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.7.6;

import { IERC20 } from "../balancer-core-v2/lib/openzeppelin/IERC20.sol";

interface IERC20Decimals is IERC20 {
  // Non standard but almost all erc20 have this
  function decimals() external view returns (uint8);
}

// File: contracts/twault/interfaces/Structs.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.7.6;

/// @dev Conventions in the methods, variables and constants are as follows:
///
///      Prefixes:
///
///      - In constants, the prefix "Sn", where 1 <= n <= 4, denotes which slot the constant
///        pertains too. There are four storage slots that are bitpacked. For example,
///        "S2_OFFSET_ORACLE_TIMESTAMP" refers to the offset of the oracle timestamp in bit-
///        packed storage slot 2.
///
///      Suffixes:
///
///      - The suffix of a variable name denotes the type contained within the variable.
///        For instance "uint256 _incrementU96" is a 256-bit unsigned container representing
///        the 96-bit value "_increment".
///        In the case of "uint256 _balancerFeeDU1F18", the 256-bit unsigned container is
///        representing a 19 digit decimal value with 18 fractional digits. In this scenario,
///        the D=Decimal, U=Unsigned, F=Fractional.
///        Finally, "uint128 valueU128F64" is a 128-bit container representing a 128-bit value
///        with 64 fractional bits.
///
///      - The suffix of a function name denotes what slot it is proprietary too as a
///        matter of convention. While unchecked at run-time or by the compiler, the naming
///        convention easily aids in understanding what slot a packed value is stored within.
///        For instance the function "unpackFeeShiftS3" unpacks the fee shift from slot 3. If
///        the value of slot 2 were passed to this method, the unpacked value would be
///        incorrect.

//
// Structs Related to Virtual Orders
////////////////////////////////////////////////////////////////////////////////

/// @notice Virtual Order details for a single user's Long-Term (LT) swap. An LT swap from
///         Token0 to Token1 is described as a user selling Token0 to the pool to buy Token1
///         from the pool.  Vice-versa if the swap is from Token1 to Token0.
/// @member token0To1 Swap direction, true swapping Token0 for Token1. False otherwise.
/// @member salesRate Amount of token sold to the pool per block for LT swap duration.
/// @member scaledProceedsAtSubmissionU128 The normalized proceeds of the pool for the token
///                                        being purchased at the block the order is
///                                        submitted. For example, for an LT swap of Token0
///                                        for Token1, this value would be the normalized
///                                        proceeds of Token1 for the pool. The normalized
///                                        value is also scaled for precision reasons.
///                                        Min. = 0, Max. = (2**128) - 1
/// @member owner The address issuing the LT swap virtual order; exclusively able to cancel or
///               withdraw the order.
/// @member delegate Is an address that is able to withdraw or cancel the LT swap on behalf
///                  of owner account, as long as the recipient specified is the owner
///                  account.
/// @member orderExpiry is the block in which this order expires.
struct Order {
  bool token0To1;
  uint112 salesRate;
  uint128 scaledProceedsAtSubmissionU128;
  address owner;
  address delegate;
  uint256 orderExpiry;
}

/// @notice This struct abstracts two order pools representing pooled Long-Term (LT) swaps in
///         each swap direction along with the current proceeds and a mapping of the sales
///         rate of each token at the end of a block. This allows the grouping of swaps in
///         the two swap directions for gas efficient execution when virutal orders are
///         executed. It is an adaptation of the staking algorithm desribed here:
///         - https://uploads-ssl.webflow.com/5ad71ffeb79acc67c8bcdaba/5ad8d1193a40977462982470_scalable-reward-distribution-paper.pdf
/// @member currentSalesRates stores the current sales rate of both Token0 and Token1 per block
///                           as 112-bit numbers packed into the 256-bit container. Token0
///                           occupies bits 224 downto 113 and Token1 bits 112 downto 1.
/// @member scaledProceeds stores the normalized, scaled, proceeds of each order pool together as
///                        128-bit numbers packed into the 256-bit container. Scaled proceeds0
///                        occupies bits 256 downto 129 and scaled proceeds1 occupies
///                        bits 128 downto 1.
///                        WARNING: Scaled proceeds0 and scaled proceeds1 described above are
///                        not the proceeds of Token0 and Token1 as would be expected, but rather
///                        the proceeds of order pool 0 and order pool 1 respectively. This means
///                        that scaled proceeds0 is actually the amount of Token1 obtained for
///                        users selling Token0 to the pool and vice-versa for proceeds1.
/// @member salesRateEndingPerBlock is a mapping of a block number to the sales rates of Token0
///                                 and Token1 expiring on that block number for each order pool.
///                                 The 112-bit sales rates are stored in a single 256-bit slot
///                                 together for efficiency. The sales rate for Token0 occupies
///                                 bits 224 downto 113 while the sales rate for Token1 occupies
///                                 bits 112 townto 1.
///
struct OrderPools {
  uint256 currentSalesRates;
  uint256 scaledProceeds;
  mapping(uint256 => uint256) salesRatesEndingPerBlock;
}

/// @notice This struct contains the order pool data for virtual orders comprising of sales of
///         Token0 for Token1 and vice-versa over multiple blocks. Each order pool is stored
///         herein, tracking the current sales rates and proceeds along with expiring sales
///         rates.
///         This struct also stores the scaled proceeds at each block, allowing an individual
///         user's proceeds to be calculated for a given interval. Each user's order is stored
///         with a mapping to their order id and the most recently executed virtual order block
///         and next order id are also stored herein.
/// @member orderPools is a struct containing the sale rate and proceeds for each of the two
///                    order pools along with expiring orders sales rates mapped by block.
/// @member scaledProceedsAtBlock is a mapping of a block number to the normalized, scaled,
///                               proceeds of each order pool together as 128-bit numbers packed
///                               into the 256-bit container. Scaled proceeds0 occupies
///                               bits 256 downto 129 and scaled proceeds1 occupies
///                               bits 128 downto 1.
///                               WARNING: Scaled proceeds0 and scaled proceeds1 described above are
///                               not the proceeds of Token0 and Token1 as would be expected, but rather
///                               the proceeds of order pool 0 and order pool 1 respectively. This means
///                               that scaled proceeds0 is actually the amount of Token1 obtained for
///                               users selling Token0 to the pool and vice-versa for proceeds1.
/// @dev The values contained in scaledProceedsAtBlock are always increasing and are expected to
///      overflow. Their difference when measured between two blocks, determines the proceeds in a
///      particular time-interval. A user's sales rate multiplying that amount determines the user's
///      share of the proceeds (scaledProceeds are normalized by the total sales rate and scaled up for
///      maintaining precision). The subtraction of the two points is also expected to underflow.
/// @member orderMap maps a particular order id to information about that order.
/// @member lastVirtualOrderBlock The ethereum block number before the last virtual orders were executed.
/// @member nextOrderId Is the next order id issued when a user places a Long-Term swap virtual order.
///
struct VirtualOrders {
  OrderPools orderPools;
  mapping(uint256 => uint256) scaledProceedsAtBlock;
  mapping(uint256 => Order) orderMap;
  uint256 lastVirtualOrderBlock;
  uint256 nextOrderId;
}

//
// Structs Related to Other Pool Features
////////////////////////////////////////////////////////////////////////////////

/// @notice The cumulative prices of Token0 and Token1 as of the start of the
///         last executed block (the timestamp of which can be fetched using
///         getOracleTimeStamp).
/// @member token0U256F112 The cumulative price of Token0 measured in amount of
///                        Token1 seconds.
/// @member token1U256F112 The cumulative price of Token1 measured in amount of
///                        Token0 seconds.
/// @dev These values have 112 fractional bits and are expected to overflow.
///      Behavior is identical to the price oracle introduced in Uniswap V2 with
///      similar limitations and vulnerabilities.
/// @dev The average price over an interval can be obtained by sampling these
///      values and their measurement times (see getOracleTimeStamp) and
///      computing the difference over the given interval.
struct PriceOracle {
  uint256 token0U256F112;
  uint256 token1U256F112;
}

//
// Structs for Gas Efficiency / Stack Depth Limitations
////////////////////////////////////////////////////////////////////////////////

/// @notice Struct for executing virtual orders across functions efficiently.
/// @member token0ReserveU112 reserves of Token0 in the TWAMM pool.
/// @member token1ReserveU112 reserves of Token1 in the TWAMM pool.
/// @member lpFeeU60 This is the portion of fees to be distributed to Liquidity Providers
///                  (LPs) after Balancer's portion is collected. The portioning is based
///                  on fractions of 10**18 and the value is computed by subtracting
///                  Balancer's portion from 10**18. If Cron-Fi fees are being collected
///                  this value is used to compute the fee share, feeShareU60.
/// @member feeShareU60 If Cron-Fi fees are being collected, this amount represents a
///                     single share of the fees remaining after Balancer's portion. A
///                     single share goes to Cron-Fi and multiples of a single share go
///                     to the Liquidity Providers (LPs) based on the fee shift value,
///                     feeShiftU3.
/// @member feeShiftU3 If Cron-Fi fees are being collected, this represents the amount of
///                    bits shifted to partition fees between Liquidity Providers (LPs)
///                    and Cron-Fi. For example, if this is 1, then 2 shares of fees
///                    collected after Balancer's portion go to the LPs and 1 share goes
///                    to Cron-Fi. If it is 2, then 4 shares go to the LPs and 1 share
///                    goes to Cron-Fi.
/// @member orderPool0ProceedsScaling is the amount to scale proceeds of order pool 0 (Long-
///                                   Term swaps of Token 0 to Token 1) based on the number
///                                   of decimal places in Token 0.
/// @member orderPool0ProceedsScaling is the amount to scale proceeds of order pool 1 (Long-
///                                   Term swaps of Token 1 to Token 0) based on the number
///                                   of decimal places in Token 1.
/// @member token0BalancerFeesU96 Balancer fees collected for Token0-->Token1 swaps.
/// @member token1BalancerFeesU96 Balancer fees collected for Token1-->Token0 swaps.
/// @member token0CronFiFeesU96 Cron-Fi fees collected for Token0-->Token1 Long-Term swaps.
/// @member token1CronFiFeesU96 Cron-Fi fees collected for Token1-->Token0 Long-Term swaps.
/// @member token0OrdersU112 Amount of Token0 sold to the pool in virtual orders.
/// @member token1OrdersU112 Amount of Token1 sold to the pool in virtual orders.
/// @member token0ProceedsU112 Amount of Token0 bought from the pool in virtual orders.
/// @member token1ProceedsU112 Amount of Token1 bought from the pool in virtual orders.
/// @member token0OracleU256F112 The computed increment for the price oracle for Token 0.
/// @member token1OracleU256F112 The computed increment for the price oracle for Token 1.
/// @member oracleTimeStampU32 The oracle time stamp.
///
struct ExecVirtualOrdersMem {
  uint256 token0ReserveU112;
  uint256 token1ReserveU112;
  uint256 lpFeeU60;
  uint256 feeShareU60;
  uint256 feeShiftU3;
  uint256 token0BalancerFeesU96;
  uint256 token1BalancerFeesU96;
  uint256 token0CronFiFeesU96;
  uint256 token1CronFiFeesU96;
  uint256 token0OrdersU112;
  uint256 token1OrdersU112;
  uint256 token0ProceedsU112;
  uint256 token1ProceedsU112;
  uint256 token0OracleU256F112;
  uint256 token1OracleU256F112;
}

/// @notice Struct for executing the virtual order loop efficiently (reduce
///         storage reads/writes). Advantages increase when pool is inactive
///         for longer multiples of the Order Block Interval.
/// @member lastVirtualOrderBlock The ethereum block number before the last virtual orders were
///                               executed.
/// @member scaledProceeds0U128 The normalized scaled proceeds of order pool 0 in Token1. For
///                             example, for an LT swap of Token0 for Token1, this value
///                             would be the normalized proceeds of Token1 for the pool. The
///                             normalized value is also scaled for precision reasons.
///                             Min. = 0, Max. = (2**128) - 1
/// @member scaledProceeds1U128 The normalized scaled proceeds of order pool 1 in Token0.
///                             Min. = 0, Max. = (2**128) - 1
/// @member currentSalesRate0U112 The current sales rate of Token0 per block.
///                               Min. = 0, Max. = (2**112) - 1
/// @member currentSalesRate1U112 The current sales rate of Token1 per block.
///                               Min. = 0, Max. = (2**112) - 1
///
struct LoopMem {
  // Block Numbers:
  uint256 lastVirtualOrderBlock;
  // Order Pool Items:
  uint256 scaledProceeds0U128;
  uint256 scaledProceeds1U128;
  uint256 currentSalesRate0U112;
  uint256 currentSalesRate1U112;
}

// File: contracts/twault/interfaces/pool/ICronV1FactoryOwnerActions.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

interface ICronV1FactoryOwnerActions {
  function setAdminStatus(address _admin, bool _status) external;

  function setFeeAddress(address _feeDestination) external;

  function setFeeShift(uint256 _feeShift) external;

  function setCollectBalancerFees(bool _collectValue) external;
}

// File: contracts/twault/interfaces/pool/ICronV1PoolAdminActions.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

interface ICronV1PoolAdminActions {
  function setPause(bool _pauseValue) external;

  function setParameter(uint256 _paramTypeU, uint256 _value) external;

  function setArbitragePartner(address _arbPartner, address _arbitrageList) external;
}

// File: contracts/twault/interfaces/pool/ICronV1PoolArbitrageurActions.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

interface ICronV1PoolArbitrageurActions {
  function updateArbitrageList() external returns (address);

  function executeVirtualOrdersToBlock(uint256 _maxBlock) external;
}

// File: contracts/twault/interfaces/pool/ICronV1PoolEnums.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

interface ICronV1PoolEnums {
  /// @notice Enumeration for the type of TWAMM pool created; the type determines the default fees and the immutable block
  ///         interval that the pool will operate with for it's lifetime. Each enumeration value is described in more
  ///         detail below (Fee Points = FP):
  ///
  ///           Stable:
  ///
  ///             - Intended for pool tokens that trade frequently; features lower fees and more frequent Long-Term order
  ///               expiries in exchange for higher gas use.
  ///             Short-Term Swap Fee  = 10 FP (0.010%)
  ///             Arbitrageur Swap Fee =  5 FP (0.005%)
  ///             Long-Term Swap Fee   = 30 FP (0.030%)
  ///             Order Block Interval = 75 blocks (~15 minutes)
  ///
  ///           Liquid:
  ///
  ///             - The middle ground setting between tokens that trade frequently and those that trade infrequently with
  ///               low-liquidity. Mid-range fees and order expiry frequency.
  ///             Short-Term Swap Fee  =  50 FP (0.050%)
  ///             Arbitrageur Swap Fee =  25 FP (0.025%)
  ///             Long-Term Swap Fee   = 150 FP (0.150%)
  ///             Order Block Interval = 300 blocks (~1 hour)
  ///
  ///           Volatile:
  ///
  ///             - Intended for pool tokens that trade infrequently with low-liquidity; features higher fees and less
  ///               frequent Long-Term order expiries in exchange for reduced gas use.
  ///             Short-Term Swap Fee  = 100 FP (0.100%)
  ///             Arbitrageur Swap Fee =  50 FP (0.050%)
  ///             Long-Term Swap Fee   = 300 FP (0.300%)
  ///             Order Block Interval = 1200 blocks (~ 4 hours)
  ///
  enum PoolType {
    Stable, // 0
    Liquid, // 1
    Volatile // 2
  }

  /// @notice Enumeration for functionality when joining the pool:
  ///         - Join performs the standard Join/Mint functionality, taking the provided tokens in exchange for
  ///           pool Liquidity Provider (LP) tokens.
  ///         - Reward performs a donation of the provided tokens to the pool with no LP tokens provided in return.
  ///
  enum JoinType {
    Join, // 0
    Reward // 1
  }

  /// @notice Enumeration for functionality when swapping with the pool:
  ///         - RegularSwap performs a standard swap of the specified token for its opposing token using the Constant
  ///           Product Automated Market Maker (CPAMM) formula.
  ///         - LongTermSwap performs a swap of the spcified token for its opposing token over more than one block.
  ///         - PartnerSwap performs a reduced fee RegularSwap with registered arbitrage partner's arbitrageurs.
  ///
  enum SwapType {
    RegularSwap, // 0
    LongTermSwap, // 1
    PartnerSwap // 2
  }

  /// @notice Enumeration for functionality when exiting the pool:
  ///         - Exit performs a standard exit or burn functionality, taking provided LP tokens in exchange for the
  ///           proportional amount of pool tokens.
  ///         - Withdraw performs a Long-Term swap order proceeds withdrawl.
  ///         - Cancel performs a Long-Term swap order cancellation, remitting proceeds and refunding unspent deposits.
  ///         - FeeWithdraw performs a withdraw of Cron-Fi fees to the fee address if enabled.
  ///
  enum ExitType {
    Exit, // 0
    Withdraw, // 1
    Cancel, // 2
    FeeWithdraw // 3
  }

  /// @notice Enumeration for shared parameterization setting function to specify parameter being set:
  ///         - SwapFeeFP is the short term swap fee in Fee Points (FP).
  ///         - PartnerFeeFP is the arbitrage partner swap fee in FP.
  ///         - LongSwapFeeFP is the Long-Term swap fee in FP.
  /// @dev NOTE: Total FP = 100,000. Thus a fee portion is the number of FP out of 100,000.
  ///
  enum ParamType {
    // Slot 1:
    SwapFeeFP, // 0
    PartnerFeeFP, // 1
    LongSwapFeeFP // 2
  }

  /// @notice Enumeration for shared event log for boolean parameter state changes. The event
  ///         BoolParameterChange will contain one of the following enum values to indicate a
  ///         change to the respective one--the pool's paused state or collection of balancer fees.
  ///
  enum BoolParamType {
    Paused, // 0
    CollectBalancerFees // 1
  }
}

// File: contracts/twault/interfaces/pool/ICronV1PoolEvents.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

import { ICronV1PoolEnums } from "./ICronV1PoolEnums.sol";

interface ICronV1PoolEvents is ICronV1PoolEnums {
  /// @notice ShortTermSwap event is emitted for Short-Term (ST) swap transactions and
  ///         arbitrage partner ST swap transactions. To differentiate, examine the value of
  ///         swapType in the emitted event.
  ///
  event ShortTermSwap(
    address indexed sender,
    address indexed tokenIn,
    uint256 amountIn,
    uint256 amountOut,
    uint256 swapType
  );

  /// @notice LongTermSwap event is emitted when Long-Term (LT) swaps transaction are issued to
  ///         the pool.
  ///
  event LongTermSwap(
    address indexed sender,
    address indexed delegate,
    address indexed tokenIn,
    uint256 amountIn,
    uint256 intervals,
    uint256 orderId
  );

  /// @notice PoolJoin events are emitted for Join/Mint and Reward transactions. A Reward
  ///         transaction can be identified from a Join/Mint transaction by examining the
  ///         emitted event's poolTokenAmt to see if is zero.
  ///
  event PoolJoin(
    address indexed sender,
    address indexed recipient,
    uint256 token0In,
    uint256 token1In,
    uint256 poolTokenAmt
  );

  /// @notice WithdrawLongTermSwap events are emitted when an LT swap order is withdrawn or cancelled
  ///         in a transaction. To differentiate between the two, only a cancellation will have non-zero
  ///         values for refundOut.
  ///
  event WithdrawLongTermSwap(
    address indexed owner,
    address indexed refundToken,
    uint256 refundOut,
    address indexed proceedsToken,
    uint256 proceedsOut,
    uint256 orderId,
    address sender
  );

  /// @notice FeeWithdraw events are emitted when Cron-Fi fees are withdrawn from the pool.
  ///
  event FeeWithdraw(address indexed sender, uint256 token0Out, uint256 token1Out);

  /// @notice PoolExit events are emitted when a Liquidity Provider (LP) redeems LP tokens for
  ///         their share of tokens remaining in the pool.
  ///
  event PoolExit(address indexed sender, uint256 poolTokenAmt, uint256 token0Out, uint256 token1Out);

  /// @notice AdministratorStatusChange events are emitted when an administrator address, admin,
  ///         is given administrator privileges (status == true) or when they are taken away
  ///         (status == false).
  ///
  event AdministratorStatusChange(address indexed sender, address indexed admin, bool status);

  /// @notice ProtocolFeeTooLarge is emitted if the protocol fee passed in by balancer ever exceeds
  ///         1e18 (in which case the change is ignored and fees continue with the last good value).
  ///
  event ProtocolFeeTooLarge(uint256 suggestedProtocolFee);

  /// @notice ParameterChange is emitted when a parameter value is changed to value. Consult the
  ///         enum ParmType for the parameter undergoing change.
  ///
  event ParameterChange(address indexed sender, ParamType paramType, uint256 value);

  /// @notice FeeAddressChange is emitted when the fee address, feeAddress, is changed.
  ///
  event FeeAddressChange(address indexed sender, address indexed feeAddress);

  /// @notice FeeShiftChange is emitted when the fee shift, feeShift is changed.
  ///
  event FeeShiftChange(address indexed sender, uint256 feeShift);

  /// @notice BoolParameterChange is emitted when a boolean value parameter is changed. Consult the
  ///         enum BoolParmType for the parameter undergoing change.
  ///
  event BoolParameterChange(address indexed sender, BoolParamType boolParam, bool value);

  /// @notice UpdatedArbitragePartner is emitted when an arbitrage partner's arbitrageur list is
  ///         updated to a new contract address.
  ///
  event UpdatedArbitragePartner(address indexed sender, address partner, address list);

  /// @notice UpdatedArbitrageList is emitted when an arbitrage partner's updates their arbitrageur
  ///         list is to a new contract address through the updateArbitrageList function.
  ///
  event UpdatedArbitrageList(address indexed partner, address indexed oldList, address indexed newList);

  /// @notice ExecuteVirtualOrdersEvent is emitted on calls to executeVirtualOrdersToBlock.
  ///
  event ExecuteVirtualOrdersEvent(address indexed sender, uint256 block);
}

// File: contracts/twault/interfaces/pool/ICronV1PoolHelpers.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;

pragma experimental ABIEncoderV2;

import { ICronV1PoolEnums } from "./ICronV1PoolEnums.sol";
import { Order, PriceOracle, ExecVirtualOrdersMem } from "../Structs.sol";

interface ICronV1PoolHelpers {
  function getVirtualPriceOracle(uint256 _maxBlock)
    external
    returns (
      uint256 timestamp,
      uint256 token0U256F112,
      uint256 token1U256F112,
      uint256 blockNumber
    );

  function getVirtualReserves(uint256 _maxBlock, bool _paused)
    external
    returns (
      uint256 blockNumber,
      uint256 token0ReserveU112,
      uint256 token1ReserveU112,
      uint256 token0OrdersU112,
      uint256 token1OrdersU112,
      uint256 token0ProceedsU112,
      uint256 token1ProceedsU112,
      uint256 token0BalancerFeesU96,
      uint256 token1BalancerFeesU96,
      uint256 token0CronFiFeesU96,
      uint256 token1CronFiFeesU96
    );

  // solhint-disable-next-line func-name-mixedcase
  function POOL_ID() external view returns (bytes32);

  // solhint-disable-next-line func-name-mixedcase
  function POOL_TYPE() external view returns (ICronV1PoolEnums.PoolType);

  function getPriceOracle()
    external
    view
    returns (
      uint256 timestamp,
      uint256 token0U256F112,
      uint256 token1U256F112
    );

  function getOrderIds(
    address _owner,
    uint256 _offset,
    uint256 _maxResults
  )
    external
    view
    returns (
      uint256[] memory orderIds,
      uint256 numResults,
      uint256 totalResults
    );

  function getOrder(uint256 _orderId) external view returns (Order memory order);

  function getOrderIdCount() external view returns (uint256 nextOrderId);

  function getSalesRates() external view returns (uint256 salesRate0U112, uint256 salesRate1U112);

  function getLastVirtualOrderBlock() external view returns (uint256 lastVirtualOrderBlock);

  function getSalesRatesEndingPerBlock(uint256 _blockNumber)
    external
    view
    returns (uint256 salesRateEndingPerBlock0U112, uint256 salesRateEndingPerBlock1U112);

  function getShortTermFeePoints() external view returns (uint256);

  function getPartnerFeePoints() external view returns (uint256);

  function getLongTermFeePoints() external view returns (uint256);

  function getOrderAmounts() external view returns (uint256 orders0U112, uint256 orders1U112);

  function getProceedAmounts() external view returns (uint256 proceeds0U112, uint256 proceeds1U112);

  function getFeeShift() external view returns (uint256);

  function getCronFeeAmounts() external view returns (uint256 cronFee0U96, uint256 cronFee1U96);

  function isPaused() external view returns (bool);

  function isCollectingCronFees() external view returns (bool);

  function isCollectingBalancerFees() external view returns (bool);

  function getBalancerFee() external view returns (uint256);

  function getBalancerFeeAmounts() external view returns (uint256 balFee0U96, uint256 balFee1U96);
}

// File: contracts/twault/miscellany/BitPacking.sol



// (c) Copyright 2023, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.7.6;

import { C } from "./Constants.sol";
import { requireErrCode, CronErrors } from "./Errors.sol";

/// @notice Library for bit-packing generic and specific values pertinent to Cron-Fi TWAMM
///         into storage-slots efficiently (both for gas use and contract size).
///
/// @dev Many custom representations are used herein (i.e. non native word lengths) and there
///      are a number of explicit checks against the maximum of these non native word lengths.
///      Furthermore there are unchecked operations (this code targets Solidity 0.7.x which
///      didn't yet feature implicit arithmetic checks or have the 'unchecked' block feature)
///      in this library for reasons of efficiency or desired overflow. Wherever they appear
///      they will be documented and accompanied with one of the following tags:
///        - #unchecked
///        - #overUnderFlowIntended
///      Identified risks will be accompanied and described with the following tag:
///        - #RISK
///
/// @dev Generic shifting methods were eschewed because of their additional gas use.
/// @dev Conventions in the methods below are as follows:
///
///      Suffixes:
///
///      - The suffix of a variable name denotes the type contained within the variable.
///        For instance "uint256 _incrementU96" is a 256-bit unsigned container representing
///        a 96-bit value, _increment.
///        In the case of "uint256 _balancerFeeDU1F18", the 256-bit unsigned container is
///        representing a 19 digit decimal value with 18 fractional digits. In this scenario,
///        the D=Decimal, U=Unsigned, F=Fractional.
///
///      - The suffix of a function name denotes what slot it is proprietary too as a
///        matter of convention. While unchecked at run-time or by the compiler, the naming
///        convention easily aids in understanding what slot a packed value is stored within.
///        For instance the function "unpackFeeShiftS3" unpacks the fee shift from slot 3. If
///        the value of slot 2 were passed to this method, the unpacked value would be
///        incorrect.
///
///      Bit-Numbering:
///
///      - Bits are counted starting with the least-significant bit (LSB) from 1. Thus for
///        a 256-bit slot, the most-significant bit (MSB) is bit 256 and the LSB is bit 1.
///
///
///      Offsets:
///
///      - Offsets are the distance from the LSB to the desired LSB of the word being
///        placed within a slot. For instance, to store an 8-bit word in a 256-bit slot
///        at bits 16 down to 9, an offset of 8-bits should be specified.
///
///
///      Pairs
///
///      - The following methods which operate upon pairs follow the convention that a
///        pair consists of two same sized words, with word0 stored above word1 within
///        a slot. For example, a pair of 96-bit words will be stored with word0
///        occupying bits 192 downto 97 and word1 occupying bits 96 downto 1. The following
///        diagram depicts this scenario:
///
///              bit-256     bit-192      bit-96      bit-1
///                 |           |            |           |
///                 v           v            v           v
///
///           MSB < I    ???   II   word0   II   word1   I > LSB
///
///                            ^            ^
///                            |            |
///                         bit-193      bit-97
///
library BitPackingLib {
  //
  // Generic Packing Functions
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Packs bit _bitU1 into the provided 256-bit slot, _slot, at location _offsetU8
  ///         bits from the provided slot's LSB, bit 1.
  /// @param _slot A 256-bit container to pack bit _bitU1 within.
  /// @param _bitU1 A 1-bit value to pack into the provided slot.
  ///               Min. = 0, Max. = 1.
  /// @param _offsetU8 The distance in bits from the provided slot's LSB to store _bitU1 at.
  ///                  Min. = 0, Max. = 255.
  /// @dev WARNING: No checks of _offsetU8 are performed for efficiency!
  /// @return slot The modified slot containing _bitU1 at bit position _offsetU8 + 1.
  ///
  function packBit(
    uint256 _slot,
    uint256 _bitU1,
    uint256 _offsetU8
  ) internal pure returns (uint256 slot) {
    requireErrCode(_bitU1 <= C.MAX_U1, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    uint256 mask = C.MAX_U1 << _offsetU8;
    slot = (_bitU1 != 0) ? (_slot | mask) : (_slot & (~mask));
  }

  /// @notice Unpacks bitU1 from the provided 256-bit slot, _slot, at location _offsetU8
  ///         bits from the provided slot's LSB, bit 1.
  /// @param _slot A 256-bit container to unpack bitU1 from.
  /// @param _offsetU8 The distance in bits from the provided slot's LSB to unpack bitU1 from.
  ///                  Min. = 0, Max. = 255.
  /// @dev WARNING: No checks of _offsetU8 are performed for efficiency!
  /// @return bitU1 The 1-bit value unpacked from the provided slot.
  ///               Min. = 0, Max. = 1.
  ///
  function unpackBit(uint256 _slot, uint256 _offsetU8) internal pure returns (uint256 bitU1) {
    bitU1 = ((_slot >> _offsetU8) & C.MAX_U1);
  }

  /// @notice Packs ten-bit word, _wordU10, into the provided 256-bit slot, _slot, at location
  ///         _offsetU8 bits from the provided slot's LSB, bit 1.
  /// @param _slot A 256-bit container to pack bit _wordU10 within.
  /// @param _wordU10 A ten-bit word to pack into the provided slot.
  ///                 Min. = 0, Max. = (2**10)-1.
  /// @param _offsetU8 The distance in bits from the provided slot's LSB to store _bitU1 at.
  ///                  Min. = 0, Max. = 255.
  /// @dev WARNING: No checks of _offsetU8 are performed for efficiency!
  /// @return slot The modified slot containing _wordU10 at bit position _offsetU8 + 1.
  ///
  function packU10(
    uint256 _slot,
    uint256 _wordU10,
    uint256 _offsetU8
  ) internal pure returns (uint256 slot) {
    requireErrCode(_wordU10 <= C.MAX_U10, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    uint256 clearMask = ~(C.MAX_U10 << _offsetU8);
    uint256 setMask = _wordU10 << _offsetU8;
    slot = (_slot & clearMask) | setMask;
  }

  /// @notice Unpacks wordU10 from the provided 256-bit slot, _slot, at location _offsetU8
  ///         bits from the provided slot's LSB, bit 1.
  /// @param _slot A 256-bit container to unpack wordU10 from.
  /// @param _offsetU8 The distance in bits from the provided slot's LSB to unpack wordU10 from.
  ///                  Min. = 0, Max. = 255.
  /// @dev WARNING: No checks of _offsetU8 are performed for efficiency!
  /// @return wordU10 The ten-bit word unpacked from the provided slot.
  ///                  Min. = 0, Max. = (2**10)-1.
  function unpackU10(uint256 _slot, uint256 _offsetU8) internal pure returns (uint256 wordU10) {
    wordU10 = (_slot >> _offsetU8) & C.MAX_U10;
  }

  /// @notice Increments the 96-bit words, word0 and/or word1, stored within the provided
  ///         256-bit slot, _slot, by the values provided in _increment0U96 and _increment1U96
  ///         respectively. Importantly, if the increment results in overflow, the value
  ///         will "clamp" to the maximum value (2**96)-1.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 96-bit words, word0 and word1.
  /// @param _increment0U96 The amount to increment word0 by.
  ///                       Min. = 0, Max. = (2**96)-1.
  /// @param _increment1U96 The amount to increment word1 by.
  ///                       Min. = 0, Max. = (2**96)-1.
  /// @return slot The modified slot containing incremented values of word0 and/or word1.
  ///
  function incrementPairWithClampU96(
    uint256 _slot,
    uint256 _increment0U96,
    uint256 _increment1U96
  ) internal pure returns (uint256 slot) {
    uint256 word0U96 = ((_slot >> 96) & C.MAX_U96);
    uint256 word1U96 = (_slot & C.MAX_U96);

    if (_increment0U96 > 0) {
      requireErrCode(C.MAX_U256 - word0U96 >= _increment0U96, CronErrors.OVERFLOW);
      // #unchecked
      //            safe from overflow in increment below because of check above.
      word0U96 += _increment0U96;
      // Clamp the resulting value to (2**96)+1 on overflow of U96 beyond MAX_U96:
      if (word0U96 > C.MAX_U96) {
        word0U96 = C.MAX_U96;
      }
    }

    if (_increment1U96 > 0) {
      requireErrCode(C.MAX_U256 - word1U96 >= _increment1U96, CronErrors.OVERFLOW);
      // #unchecked
      //            safe from overflow in increment below because of check above.
      word1U96 += _increment1U96;
      // Clamp the resulting value to (2**96)+1 on overflow of U96 beyond MAX_U96:
      if (word1U96 > C.MAX_U96) {
        word1U96 = C.MAX_U96;
      }
    }

    // NOTE: No need create set masks as _value*U96 above checked against MAX_U96/clamped.
    slot = (_slot & C.CLEAR_MASK_PAIR_U96) | (word0U96 << 96) | word1U96;
  }

  /// @notice Unpacks the two 96-bit values, word0 and word1, from the provided slot, _slot,
  ///         returning them along with the provided slot modified to clear the values
  ///         or word0 and word1 to zero.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 96-bit words, word0 and word1.
  /// @return slot The modified slot containing cleared values of word0 and word1.
  /// @return word0U96 The value of word0 prior to clearing it.
  ///                  Min. = 0, Max. = (2**96)-1.
  /// @return word1U96 The value of word1 prior to clearing it.
  ///                  Min. = 0, Max. = (2**96)-1.
  ///
  function unpackAndClearPairU96(uint256 _slot)
    internal
    pure
    returns (
      uint256 slot,
      uint256 word0U96,
      uint256 word1U96
    )
  {
    word0U96 = (_slot >> 96) & C.MAX_U96;
    word1U96 = _slot & C.MAX_U96;

    slot = _slot & C.CLEAR_MASK_PAIR_U96;
  }

  /// @notice Unpacks and returns the two 96-bit values, word0 and word1, from the provided slot.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 96-bit words, word0 and word1.
  /// @return word0U96 The value of word0.
  ///                  Min. = 0, Max. = (2**96)-1.
  /// @return word1U96 The value of word1.
  ///                  Min. = 0, Max. = (2**96)-1.
  ///
  function unpackPairU96(uint256 _slot) internal pure returns (uint256 word0U96, uint256 word1U96) {
    word0U96 = (_slot >> 96) & C.MAX_U96;
    word1U96 = _slot & C.MAX_U96;
  }

  /// @notice Packs the two provided 112-bit words, word0 and word1, into the provided 256-bit
  ///         slot, _slot.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 112-bit words, word0 and word1.
  /// @param _word0U112 The value of word0 to pack.
  ///                   Min. = 0, Max. = (2**112)-1.
  /// @param _word1U112 The value of word1 to pack.
  ///                   Min. = 0, Max. = (2**112)-1.
  /// @return slot The modified slot containing the values of word0 and word1.
  ///
  function packPairU112(
    uint256 _slot,
    uint256 _word0U112,
    uint256 _word1U112
  ) internal pure returns (uint256 slot) {
    requireErrCode(_word0U112 <= C.MAX_U112, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);
    requireErrCode(_word1U112 <= C.MAX_U112, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    slot = (_slot & C.CLEAR_MASK_PAIR_U112) | (_word0U112 << 112) | _word1U112;
  }

  /// @notice Unpacks and returns the two 112-bit values, word0 and word1, from the provided slot.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 112-bit words, word0 and word1.
  /// @return word0U112 The value of word0.
  ///                  Min. = 0, Max. = (2**112)-1.
  /// @return word1U112 The value of word1.
  ///                  Min. = 0, Max. = (2**112)-1.
  ///
  function unpackPairU112(uint256 _slot) internal pure returns (uint256 word0U112, uint256 word1U112) {
    word0U112 = (_slot >> 112) & C.MAX_U112;
    word1U112 = _slot & C.MAX_U112;
  }

  /// @notice Increments the 112-bit words, word0 and/or word1, stored within the provided
  ///         256-bit slot, _slot, by the values provided in _increment0U112 and _increment1U112
  ///         respectively. Errors on overflow.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 112-bit words, word0 and word1.
  /// @param _increment0U112 The amount to increment word0 by.
  ///                        Min. = 0, Max. = (2**112)-1.
  /// @param _increment1U112 The amount to increment word1 by.
  ///                        Min. = 0, Max. = (2**112)-1.
  /// @return slot The modified slot containing incremented values of word0 and/or word1.
  ///
  function incrementPairU112(
    uint256 _slot,
    uint256 _increment0U112,
    uint256 _increment1U112
  ) internal pure returns (uint256 slot) {
    uint256 word0U112 = ((_slot >> 112) & C.MAX_U112);
    uint256 word1U112 = (_slot & C.MAX_U112);

    if (_increment0U112 > 0) {
      requireErrCode(C.MAX_U112 - word0U112 >= _increment0U112, CronErrors.OVERFLOW);
      // #unchecked
      //            safe from overflow in increment below because of check above.
      word0U112 += _increment0U112;
    }
    if (_increment1U112 > 0) {
      requireErrCode(C.MAX_U112 - word1U112 >= _increment1U112, CronErrors.OVERFLOW);
      // #unchecked
      //            safe from overflow in increment below because of check above.
      word1U112 += _increment1U112;
    }

    // NOTE: No need to create set masks as _value*U112 above checked against MAX_U112.
    slot = (_slot & C.CLEAR_MASK_PAIR_U112) | (word0U112 << 112) | word1U112;
  }

  /// @notice Decrements the 112-bit words, word0 and/or word1, stored within the provided
  ///         256-bit slot, _slot, by the values provided in _decrement0U112 and
  ///         _decrement1U112 respectively. Errors on underflow.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 112-bit words, word0 and word1.
  /// @param _decrement0U112 The amount to decrement word0 by.
  ///                        Min. = 0, Max. = (2**112)-1.
  /// @param _decrement1U112 The amount to decrement word1 by.
  ///                        Min. = 0, Max. = (2**112)-1.
  /// @return slot The modified slot containing decremented values of word0 and/or word1.
  ///
  function decrementPairU112(
    uint256 _slot,
    uint256 _decrement0U112,
    uint256 _decrement1U112
  ) internal pure returns (uint256 slot) {
    uint256 word0U112 = ((_slot >> 112) & C.MAX_U112);
    uint256 word1U112 = (_slot & C.MAX_U112);

    if (_decrement0U112 > 0) {
      requireErrCode(word0U112 >= _decrement0U112, CronErrors.UNDERFLOW);
      // #unchecked
      //            safe from underflow in decrement below because of check above.
      word0U112 -= _decrement0U112;
    }
    if (_decrement1U112 > 0) {
      requireErrCode(word1U112 >= _decrement1U112, CronErrors.UNDERFLOW);
      // #unchecked
      //            safe from underflow in decrement below because of check above.
      word1U112 -= _decrement1U112;
    }

    // NOTE: No need to create set masks as _value*U112 above both at most MAX_U112 (correct by
    //       construction--checked at creation/pack) and operation is subtraction with underflow
    //       checked;
    slot = (_slot & C.CLEAR_MASK_PAIR_U112) | (word0U112 << 112) | word1U112;
  }

  /// @notice Unpacks and returns the specified 128-bit values, word0 or word1, from the provided slot,
  ///         depending on the value of isWord0.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 128-bit words, word0 and word1.
  /// @param _isWord0 Instructs this method to unpack the upper 128-bits corresponding to word0 when true.
  ///                 Otherwise the lower 128-bits, word1 are unpacked.
  /// @return wordU128 The value of word0.
  ///                  Min. = 0, Max. = (2**128)-1.
  ///
  function unpackU128(uint256 _slot, bool _isWord0) internal pure returns (uint256 wordU128) {
    wordU128 = _isWord0 ? _slot >> 128 : _slot & C.MAX_U128;
  }

  /// @notice Packs the two provided 128-bit words, word0 and word1, into a 256-bit slot.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the slot.
  /// @param _word0U128 The value of word0 to pack.
  ///                   Min. = 0, Max. = (2**128)-1.
  /// @param _word1U128 The value of word1 to pack.
  ///                   Min. = 0, Max. = (2**128)-1.
  /// @return slot A slot containing the 128-bit values word0 and word1.
  ///
  function packPairU128(uint256 _word0U128, uint256 _word1U128) internal pure returns (uint256 slot) {
    requireErrCode(_word0U128 <= C.MAX_U128, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);
    requireErrCode(_word1U128 <= C.MAX_U128, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    slot = (_word0U128 << 128) | _word1U128;
  }

  /// @notice Unpacks and returns the two 128-bit values, word0 and word1, from the provided slot.
  /// @dev    See the section on Pairs in the notes on Conventions to understand how the two
  ///         words are stored within the provided slot.
  /// @param _slot A 256-bit container holding two 128-bit words, word0 and word1.
  /// @return word0U128 The value of word0.
  ///                   Min. = 0, Max. = (2**128)-1.
  /// @return word1U128 The value of word1.
  ///                   Min. = 0, Max. = (2**128)-1.
  ///
  function unpackPairU128(uint256 _slot) internal pure returns (uint256 word0U128, uint256 word1U128) {
    word0U128 = _slot >> 128;
    word1U128 = _slot & C.MAX_U128;
  }

  //
  // Slot 2 Specific Packing Functions
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Packs the 32-bit oracle time stamp, _oracleTimeStampU32, into the provided 256-bit slot.
  /// @param _slot A 256-bit container to pack the oracle time stamp within.
  /// @param _oracleTimeStampU32 The 32-bit oracle time stamp.
  ///                            Min. = 0, Max. = (2**32)-1.
  /// @return slot The modified slot containing the oracle time stamp.
  ///
  function packOracleTimeStampS2(uint256 _slot, uint256 _oracleTimeStampU32) internal pure returns (uint256 slot) {
    requireErrCode(_oracleTimeStampU32 <= C.MAX_U32, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    uint256 setMask = _oracleTimeStampU32 << C.S2_OFFSET_ORACLE_TIMESTAMP;
    slot = (_slot & C.CLEAR_MASK_ORACLE_TIMESTAMP) | setMask;
  }

  /// @notice Unpacks the 32-bit oracle time stamp, oracleTimeStampU32, from the provided 256-bit slot,
  /// @param _slot A 256-bit container to unpack the oracle time stamp from.
  /// @return oracleTimeStampU32 The 32-bit oracle time stamp.
  ///                            Min. = 0, Max. = (2**32)-1.
  function unpackOracleTimeStampS2(uint256 _slot) internal pure returns (uint256 oracleTimeStampU32) {
    oracleTimeStampU32 = (_slot >> C.S2_OFFSET_ORACLE_TIMESTAMP) & C.MAX_U32;
  }

  //
  // Slot 3 Specific Packing Functions
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Packs the 3-bit fee shift, _feeShiftU3, into the provided 256-bit slot.
  /// @param _slot A 256-bit container to pack the fee shift into.
  /// @param _feeShiftU3 The 3-bit fee shift.
  ///                    Min. = 0, Max. = 7.
  /// @return slot The modified slot containing the new fee shift value.
  ///
  function packFeeShiftS3(uint256 _slot, uint256 _feeShiftU3) internal pure returns (uint256 slot) {
    requireErrCode(_feeShiftU3 <= C.MAX_U3, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    uint256 setMask = _feeShiftU3 << C.S3_OFFSET_FEE_SHIFT_U3;
    slot = (_slot & C.CLEAR_MASK_FEE_SHIFT) | setMask;
  }

  /// @notice Unpacks the 3-bit fee shift, feeShiftU3, from the provided 256-bit slot,
  /// @param _slot A 256-bit container to unpack the fee shift from.
  /// @return feeShiftU3 The 3-bit fee shift.
  ///                    Min. = 0, Max. = 7.
  function unpackFeeShiftS3(uint256 _slot) internal pure returns (uint256 feeShiftU3) {
    feeShiftU3 = (_slot >> C.S3_OFFSET_FEE_SHIFT_U3) & C.MAX_U3;
  }

  //
  // Slot 4 Specific Packing Functions
  ////////////////////////////////////////////////////////////////////////////////

  /// @notice Packs the balancer fee, _balancerFeeDU1F18, into the provided 256-bit slot.
  /// @param _slot A 256-bit container to pack the balancer fee into.
  /// @param _balancerFeeDU1F18 The balancer fee representing a 19 decimal digit
  ///                           value with 18 fractional digits, NOT TO EXCEED 10**19.
  ///                           Min. = 0, Max. = 10**19.
  /// @return slot The modified slot containing the new balancer fee value.
  ///
  function packBalancerFeeS4(uint256 _slot, uint256 _balancerFeeDU1F18) internal pure returns (uint256 slot) {
    requireErrCode(_balancerFeeDU1F18 <= C.MAX_U60, CronErrors.VALUE_EXCEEDS_CONTAINER_SZ);

    uint256 setMask = _balancerFeeDU1F18 << C.S4_OFFSET_BALANCER_FEE;
    slot = (_slot & C.CLEAR_MASK_BALANCER_FEE) | setMask;
  }

  /// @notice Unpacks the 60-bit balancer fee representation, balancerFeeDU1F18, from the
  ///         provided 256-bit slot,
  /// @param _slot A 256-bit container to unpack the balancer fee from.
  /// @return balancerFeeDU1F18 The 60-bit balancer fee representing a 19 decimal digit value
  ///                           with 18 fractional digits.
  ///                           Min. = 0, Max. = (2**60)-1.
  function unpackBalancerFeeS4(uint256 _slot) internal pure returns (uint256 balancerFeeDU1F18) {
    balancerFeeDU1F18 = (_slot >> C.S4_OFFSET_BALANCER_FEE) & C.MAX_U60;
  }
}

// File: contracts/twault/miscellany/Constants.sol



// (c) Copyright 2023, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.7.6;

/// @notice Library of constants used throughout the implementation.
///
/// @dev Conventions in the methods, variables and constants are as follows:
///
///      Prefixes:
///
///      - In constants, the prefix "Sn", where 1 <= n <= 4, denotes which slot the constant
///        pertains too. There are four storage slots that are bitpacked. For example,
///        "S2_OFFSET_ORACLE_TIMESTAMP" refers to the offset of the oracle timestamp in bit-
///        packed storage slot 2.
///
///      Suffixes:
///
///      - The suffix of a variable name denotes the type contained within the variable.
///        For instance "uint256 _incrementU96" is a 256-bit unsigned container representing
///        the 96-bit value "_increment".
///        In the case of "uint256 _balancerFeeDU1F18", the 256-bit unsigned container is
///        representing a 19 digit decimal value with 18 fractional digits. In this scenario,
///        the D=Decimal, U=Unsigned, F=Fractional.
///        Finally, "uint128 valueU128F64" is a 128-bit container representing a 128-bit value
///        with 64 fractional bits.
///
///      - The suffix of a function name denotes what slot it is proprietary too as a
///        matter of convention. While unchecked at run-time or by the compiler, the naming
///        convention easily aids in understanding what slot a packed value is stored within.
///        For instance the function "unpackFeeShiftS3" unpacks the fee shift from slot 3. If
///        the value of slot 2 were passed to this method, the unpacked value would be
///        incorrect.
///
library C {
  //
  // Factory owner and default pool admin address
  ////////////////////////////////////////////////////////////////////////////////
  address internal constant CRON_DEPLOYER_ADMIN = 0xe122Eff60083bC550ACbf31E7d8197A58d436b39;

  //
  // General constants
  ////////////////////////////////////////////////////////////////////////////////
  address internal constant NULL_ADDR = address(0);

  uint256 internal constant FALSE = 0;

  uint256 internal constant MAX_U256 = type(uint256).max;
  uint256 internal constant MAX_U128 = type(uint128).max;
  uint256 internal constant MAX_U112 = type(uint112).max;
  uint256 internal constant MAX_U96 = type(uint96).max;
  uint256 internal constant MAX_U64 = type(uint64).max;
  uint256 internal constant MAX_U60 = 2**60 - 1;
  uint256 internal constant MAX_U32 = type(uint32).max;
  uint256 internal constant MAX_U24 = type(uint24).max;
  uint256 internal constant MAX_U20 = 0xFFFFF;
  uint256 internal constant MAX_U16 = type(uint16).max;
  uint256 internal constant MAX_U10 = 0x3FF;
  uint256 internal constant MAX_U8 = type(uint8).max;
  uint256 internal constant MAX_U3 = 0x7;
  uint256 internal constant MAX_U1 = 0x1;

  uint256 internal constant ONE_DU1_18 = 10**18;
  uint256 internal constant DENOMINATOR_DU1_18 = 10**18;

  uint256 internal constant SECONDS_PER_BLOCK = 12;

  //
  // Array Index constants
  ////////////////////////////////////////////////////////////////////////////////
  uint256 internal constant INDEX_TOKEN0 = 0;
  uint256 internal constant INDEX_TOKEN1 = 1;

  //
  // Bit-Packing constants
  //
  // Dev: Bit-offsets below are the offset from the first bit. For example to get
  //      to bit 250, the offset 249 is used. (The first bit is counted as bit 1).
  ////////////////////////////////////////////////////////////////////////////////

  // Masks:
  uint256 internal constant CLEAR_MASK_PAIR_U96 = ~((MAX_U96 << 96) | MAX_U96);
  uint256 internal constant CLEAR_MASK_PAIR_U112 = ~((MAX_U112 << 112) | MAX_U112);
  uint256 internal constant CLEAR_MASK_ORACLE_TIMESTAMP = ~(MAX_U32 << S2_OFFSET_ORACLE_TIMESTAMP);
  uint256 internal constant CLEAR_MASK_FEE_SHIFT = ~(MAX_U3 << S3_OFFSET_FEE_SHIFT_U3);
  uint256 internal constant CLEAR_MASK_BALANCER_FEE = ~(MAX_U60 << S4_OFFSET_BALANCER_FEE);

  // Slot 1 Offsets:
  uint256 internal constant S1_OFFSET_SHORT_TERM_FEE_FP = 244; // Bits 254-245;
  uint256 internal constant S1_OFFSET_PARTNER_FEE_FP = 234; // Bits 244-235;
  uint256 internal constant S1_OFFSET_LONG_TERM_FEE_FP = 224; // Bits 234-225;

  // Slot 2 Offsets:
  uint256 internal constant S2_OFFSET_ORACLE_TIMESTAMP = 224; // Bits 256-225;

  // Slot 3 Offsets:
  uint256 internal constant S3_OFFSET_FEE_SHIFT_U3 = 222; // Bits 225-223

  // Slot 4 Offsets:
  uint256 internal constant S4_OFFSET_PAUSE = 255; // Bit 256
  uint256 internal constant S4_OFFSET_CRON_FEE_ENABLED = 254; // Bit 255
  uint256 internal constant S4_OFFSET_COLLECT_BALANCER_FEES = 253; // Bit 254
  uint256 internal constant S4_OFFSET_ZERO_CRONFI_FEES = 252; // Bit 253
  uint256 internal constant S4_OFFSET_BALANCER_FEE = 192; // Bits 252-193;

  //
  // Scaling constants
  ////////////////////////////////////////////////////////////////////////////////
  //
  uint256 internal constant MAX_DECIMALS = 22;
  uint256 internal constant MIN_DECIMALS = 2;

  //
  // Pool Specific constants
  ////////////////////////////////////////////////////////////////////////////////
  uint256 internal constant MINIMUM_LIQUIDITY = 10**3;

  uint16 internal constant STABLE_OBI = 75; // ~15m @ 12s/block
  uint16 internal constant LIQUID_OBI = 300; // ~60m @ 12s/block
  uint16 internal constant VOLATILE_OBI = 1200; // ~240m @ 12s/block

  // Maximum long-term swap (5 years, 13149000 blocks @ 12s/block).
  // - Numbers below are 13149000 / OBI (rounded down where noted):
  uint24 internal constant STABLE_MAX_INTERVALS = 175320;
  uint24 internal constant LIQUID_MAX_INTERVALS = 43830;
  uint24 internal constant VOLATILE_MAX_INTERVALS = 10957; // Rounded down from 10957.5

  //
  // Fees constants
  ////////////////////////////////////////////////////////////////////////////////
  //       FP = Total Fee Points
  //       ST = Short-Term Swap
  //       LT = Long-Term Swap
  //       LP = Liquidity Provider
  //       CF = Cron Fi
  //
  // NOTE: Mult-by these constants requires Max. 14-bits (~13.3 bits) headroom to prevent overflow.
  //
  uint256 internal constant TOTAL_FP = 100000;
  uint256 internal constant MAX_FEE_FP = 1000; // 1.000%

  // Short Term Swap Payouts:
  // ----------------------------------------
  uint16 internal constant STABLE_ST_FEE_FP = 10; // 0.010%
  uint16 internal constant LIQUID_ST_FEE_FP = 50; // 0.050%
  uint16 internal constant VOLATILE_ST_FEE_FP = 100; // 0.100%

  // Partner Swap Payouts:
  // ----------------------------------------
  uint16 internal constant STABLE_ST_PARTNER_FEE_FP = 5; // 0.005%
  uint16 internal constant LIQUID_ST_PARTNER_FEE_FP = 25; // 0.025%
  uint16 internal constant VOLATILE_ST_PARTNER_FEE_FP = 50; // 0.050%

  // Long Term Swap Payouts
  // ----------------------------------------
  uint16 internal constant STABLE_LT_FEE_FP = 30; // 0.030%
  uint16 internal constant LIQUID_LT_FEE_FP = 150; // 0.150%
  uint16 internal constant VOLATILE_LT_FEE_FP = 300; // 0.300%

  uint8 internal constant DEFAULT_FEE_SHIFT = 1; // 66% LP to 33% CronFi
}

// File: contracts/twault/miscellany/Errors.sol



// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// NOTE: Adapted from Balancer's BalancerErrors.sol code.

pragma solidity ^0.7.6;

/// @dev Conventions in the methods below are as follows:
///
///      Suffixes:
///
///      - The suffix of a variable name denotes the type contained within the variable.
///        For instance "uint256 _incrementU96" is a 256-bit unsigned container representing
///        a 96-bit value, _increment.
///        In the case of "uint256 _balancerFeeDU1F18", the 256-bit unsigned container is
///        representing a 19 digit decimal value with 18 fractional digits. In this scenario,
///        the D=Decimal, U=Unsigned, F=Fractional.
///
///      - The suffix of a function name denotes what slot it is proprietary too as a
///        matter of convention. While unchecked at run-time or by the compiler, the naming
///        convention easily aids in understanding what slot a packed value is stored within.
///        For instance the function "unpackFeeShiftS3" unpacks the fee shift from slot 3. If
///        the value of slot 2 were passed to this method, the unpacked value would be
///        incorrect.

/// @notice Reverts if the specified condition is not true with the provided error code.
/// @param _condition A condition to test; must resolve to true to not revert.
/// @param _errorCodeD3 An 3 digit decimal error code to present if the condition
///                     resolves to false.
///                     Min. = 0, Max. = 999.
/// @dev WARNING: No checks of _errorCodeD3 are performed for efficiency!
///
// solhint-disable-next-line func-visibility
function requireErrCode(bool _condition, uint256 _errorCodeD3) pure {
  if (!_condition) {
    // We're going to dynamically create a revert string based on the error code, with the following format:
    // 'CFI#{errorCode}'
    // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).
    //
    // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a
    // number (8 to 16 bits) than the individual string characters.
    //
    // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a
    // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a
    // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.
    // solhint-disable-next-line no-inline-assembly
    assembly {
      // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999
      // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for
      // the '0' character.

      let units := add(mod(_errorCodeD3, 10), 0x30)

      _errorCodeD3 := div(_errorCodeD3, 10)
      let tenths := add(mod(_errorCodeD3, 10), 0x30)

      _errorCodeD3 := div(_errorCodeD3, 10)
      let hundreds := add(mod(_errorCodeD3, 10), 0x30)

      // With the individual characters, we can now construct the full string. The "CFI#" part is a known constant
      // (0x43464923): we simply shift this by 24 (to provide space for the 3 bytes of the error code), and add the
      // characters to it, each shifted by a multiple of 8.
      // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits
      // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte
      // array).

      let revertReason := shl(200, add(0x43464923000000, add(add(units, shl(8, tenths)), shl(16, hundreds))))

      // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded
      // message will have the following layout:
      // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]

      // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We
      // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.
      mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
      // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).
      mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
      // The string length is fixed: 7 characters.
      mstore(0x24, 7)
      // Finally, the string itself is stored.
      mstore(0x44, revertReason)

      // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of
      // the encoded message is therefore 4 + 32 + 32 + 32 = 100.
      revert(0, 100)
    }
  }
}

library CronErrors {
  //
  // Permissions
  ////////////////////////////////////////////////////////////////////////////////

  uint256 internal constant SENDER_NOT_FACTORY = 0;
  uint256 internal constant SENDER_NOT_FACTORY_OWNER = 1;
  uint256 internal constant SENDER_NOT_ADMIN = 2;
  uint256 internal constant SENDER_NOT_ARBITRAGE_PARTNER = 3;
  uint256 internal constant NON_VAULT_CALLER = 4;
  uint256 internal constant SENDER_NOT_PARTNER = 5;
  uint256 internal constant SENDER_NOT_FEE_ADDRESS = 7;
  uint256 internal constant SENDER_NOT_ORDER_OWNER_OR_DELEGATE = 8;
  uint256 internal constant CANNOT_TRANSFER_TO_SELF_OR_NULL = 9;
  uint256 internal constant RECIPIENT_NOT_OWNER = 10;
  // A cleared order can be one that:
  //   - was cancelled
  //   - was withdrawn after expiry
  //   - never existed (i.e. empty blockchain state in the future)
  uint256 internal constant CLEARED_ORDER = 11;

  //
  // Modifiers
  ////////////////////////////////////////////////////////////////////////////////

  uint256 internal constant POOL_PAUSED = 100;

  //
  // Configuration & Parameterization
  ////////////////////////////////////////////////////////////////////////////////

  uint256 internal constant UNSUPPORTED_SWAP_KIND = 201;
  uint256 internal constant INSUFFICIENT_LIQUIDITY = 204;
  uint256 internal constant INCORRECT_POOL_ID = 206;
  uint256 internal constant ZERO_SALES_RATE = 208;
  uint256 internal constant NO_FUNDS_AVAILABLE = 212;
  uint256 internal constant MAX_ORDER_LENGTH_EXCEEDED = 223;
  uint256 internal constant NO_FEES_AVAILABLE = 224;
  uint256 internal constant UNSUPPORTED_TOKEN_DECIMALS = 225;
  uint256 internal constant NULL_RECIPIENT_ON_JOIN = 226;
  uint256 internal constant CANT_CANCEL_COMPLETED_ORDER = 227;
  uint256 internal constant MINIMUM_NOT_SATISFIED = 228;

  //
  // General
  ////////////////////////////////////////////////////////////////////////////////

  uint256 internal constant VALUE_EXCEEDS_CONTAINER_SZ = 400;
  uint256 internal constant OVERFLOW = 401;
  uint256 internal constant UNDERFLOW = 402;
  uint256 internal constant PARAM_ERROR = 403;

  //
  // Factory
  ////////////////////////////////////////////////////////////////////////////////

  uint256 internal constant ZERO_TOKEN_ADDRESSES = 500;
  uint256 internal constant IDENTICAL_TOKEN_ADDRESSES = 501;
  uint256 internal constant EXISTING_POOL = 502;
  uint256 internal constant INVALID_FACTORY_OWNER = 503;
  uint256 internal constant INVALID_PENDING_OWNER = 504;
  uint256 internal constant NON_EXISTING_POOL = 505;

  //
  // Periphery Relayer
  ////////////////////////////////////////////////////////////////////////////////
  uint256 internal constant P_ETH_TRANSFER = 600;
  uint256 internal constant P_NULL_USER_ADDRESS = 602;
  uint256 internal constant P_INSUFFICIENT_LIQUIDITY = 603;
  uint256 internal constant P_INSUFFICIENT_TOKEN_A_USER_BALANCE = 604;
  uint256 internal constant P_INSUFFICIENT_TOKEN_B_USER_BALANCE = 605;
  uint256 internal constant P_INVALID_POOL_TOKEN_AMOUNT = 606;
  uint256 internal constant P_INSUFFICIENT_POOL_TOKEN_USER_BALANCE = 607;
  uint256 internal constant P_INVALID_INTERVAL_AMOUNT = 608;
  uint256 internal constant P_DELEGATE_WITHDRAW_RECIPIENT_NOT_OWNER = 609;
  uint256 internal constant P_INVALID_OR_EXPIRED_ORDER_ID = 610;
  uint256 internal constant P_WITHDRAW_BY_ORDER_OR_DELEGATE_ONLY = 611;
  uint256 internal constant P_DELEGATE_CANCEL_RECIPIENT_NOT_OWNER = 612;
  uint256 internal constant P_CANCEL_BY_ORDER_OR_DELEGATE_ONLY = 613;
  uint256 internal constant P_INVALID_TOKEN_IN_ADDRESS = 614;
  uint256 internal constant P_INVALID_TOKEN_OUT_ADDRESS = 615;
  uint256 internal constant P_INVALID_POOL_TYPE = 616;
  uint256 internal constant P_NON_EXISTING_POOL = 617;
  uint256 internal constant P_INVALID_POOL_ADDRESS = 618;
  uint256 internal constant P_INVALID_AMOUNT_IN = 619;
  uint256 internal constant P_INSUFFICIENT_TOKEN_IN_USER_BALANCE = 620;
  uint256 internal constant P_POOL_HAS_NO_LIQUIDITY = 621;
  uint256 internal constant P_MAX_ORDER_LENGTH_EXCEEDED = 622;
  uint256 internal constant P_NOT_IMPLEMENTED = 624;
  uint256 internal constant P_MULTICALL_NOT_SUPPORTED = 625;
}

// File: contracts/twault/miscellany/Misc.sol



// (c) Copyright 2022, Bad Pumpkin Inc. All Rights Reserved
//
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.7.6;

/// @notice Square-root function for providing initial liquidity.
/// @dev    Sourced from Uniswap V2 library:
///           - https://github.com/Uniswap/v2-core/blob/master/contracts/libraries/Math.sol
/// @dev    Based on Babylonian Method:
///           - https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method
/// @param _value Is a number to approximate the square root of.
/// @return root The approximate square root of _value using the Babylonian Method.
///
// solhint-disable-next-line func-visibility
function sqrt(uint256 _value) pure returns (uint256 root) {
  if (_value > 3) {
    root = _value;
    uint256 iteration = _value / 2 + 1;
    while (iteration < root) {
      root = iteration;
      iteration = (_value / iteration + iteration) / 2;
    }
  } else if (_value != 0) {
    root = 1;
  }
}