ETH Price: $2,564.90 (-2.02%)

Transaction Decoder

Block:
22716289 at Jun-16-2025 09:49:11 AM +UTC
Transaction Fee:
0.000348844127900928 ETH $0.89
Gas Used:
225,462 Gas / 1.547241344 Gwei

Emitted Events:

209 WstETH.Transfer( from=[Sender] 0x1e0cf58b2789a7434b92df12d7b128324d44f642, to=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, value=166225910839047958 )
210 WstETH.Approval( owner=[Sender] 0x1e0cf58b2789a7434b92df12d7b128324d44f642, spender=[Receiver] RedSnwapper, value=0 )
211 WstETH.Approval( owner=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, spender=FluidDexT1, value=166225910839047958 )
212 WstETH.Transfer( from=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, to=FluidLiquidityProxy, value=166225910839047958 )
213 WstETH.Approval( owner=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, spender=FluidDexT1, value=0 )
214 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x0000000000000000000000000b1a513ee24972daef112bc777a5610d4325c9e7, 0x0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0, 00000000000000000000000000000000000000000000000001379d5a771caf00, fffffffffffffffffffffffffffffffffffffffffffffffffee910998446cc40, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000fa4140c5a3d692130000000000000000b227741541f8e315, 0200000000000007c2ae9252100000078d69a7b021a13db11c01e38443e80024 )
215 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x0000000000000000000000000b1a513ee24972daef112bc777a5610d4325c9e7, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, fffffffffffffffffffffffffffffffffffffffffffffffffe885cd7894c5b00, 00000000000000000000000000000000000000000000000001503e6198421d80, 0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 0000000000000000921d99aa3e3047150000000000000000b11eda9b7bba3e15, 0000000000000007ca816433d0000007ab9e399541a13f8d8c01e82f43e80124 )
216 FluidDexT1.Swap( swap0to1=True, amountIn=166225910839047958, amountOut=200376691514000000, to=0x3Ced11c610556e5292fBC2e75D68c3899098C14C )
217 0x3ced11c610556e5292fbc2e75d68c3899098c14c.0xbbb02a24579dc2e59c1609253b6ddab5457ba00895b3eda80dd41e03e2cd7e55( 0xbbb02a24579dc2e59c1609253b6ddab5457ba00895b3eda80dd41e03e2cd7e55, 0x000000000000000000000000ad27827c312cd5e71311d68e180a9872d42de23d, 0x0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0, 0x0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000001e0cf58b2789a7434b92df12d7b128324d44f642, 000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 000000000000000000000000000000000000000000000000024e8d95d006f716, 00000000000000000000000000000000000000000000000002c7e189c5f051c0, 00000000000000000000000000000000000000000000000000000000490570c0 )

Account State Difference:

  Address   Before After State Difference Code
0x0B1a513e...d4325C9e7
(Fluid: Dex wstETH - ETH)
0x1E0CF58b...24D44f642
0.007513870219640845 Eth
Nonce: 19
0.207541716380646605 Eth
Nonce: 20
0.20002784616100576
0x3Ced11c6...99098C14C 0 Eth0.000000000000000001 Eth0.000000000000000001
(Titan Builder)
8.373773619799601087 Eth8.373886350799601087 Eth0.000112731
0x52Aa8994...360F4e497
(Fluid: Liquidity)
17,902.713484287827821886 Eth17,902.513107596313821886 Eth0.200376691514
0x5C2e1127...3e592559C 0.004054093477584743 Eth0.004054094702678054 Eth0.000000001225093311
0x7f39C581...c935E2Ca0

Execution Trace

RedSnwapper.snwap( tokenIn=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, amountIn=166225910839047958, recipient=0x1E0CF58b2789a7434B92df12D7B128324D44f642, tokenOut=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, amountOutMin=199374806837462154, executor=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, executorData=0x6BE92B890000000000000000000000007F39C581F595B53C5CB19BD0B3F8DA6C935E2CA0000000000000000000000000000000000000000000000000024E8D95D006F716000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE00000000000000000000000000000000000000000000000002C7E189C5F051C00000000000000000000000001E0CF58B2789A7434B92DF12D7B128324D44F6420000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000042017F39C581F595B53C5CB19BD0B3F8DA6C935E2CA001FFFF0B0B1A513EE24972DAEF112BC777A5610D4325C9E7013CED11C610556E5292FBC2E75D68C3899098C14C000000000000000000000000000000000000000000000000000000000000 ) => ( amountOut=200376690288906688 )
  • WstETH.transferFrom( sender=0x1E0CF58b2789a7434B92df12D7B128324D44f642, recipient=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, amount=166225910839047958 ) => ( True )
  • 0xad27827c312cd5e71311d68e180a9872d42de23d.1cff79cd( )
    • 0x3ced11c610556e5292fbc2e75d68c3899098c14c.6be92b89( )
      • WstETH.balanceOf( account=0x3Ced11c610556e5292fBC2e75D68c3899098C14C ) => ( 166225910839047959 )
      • WstETH.approve( spender=0x0B1a513ee24972DAEf112bC777a5610d4325C9e7, amount=166225910839047958 ) => ( True )
      • FluidDexT1.swapIn( swap0to1_=True, amountIn_=166225910839047958, amountOutMin_=0, to_=0x3Ced11c610556e5292fBC2e75D68c3899098C14C ) => ( amountOut_=200376691514000000 )
        • WstETHContractRate.CALL( )
        • FluidLiquidityProxy.readFromStorage( slot_=C24EACEFF5753C99066A839532D708A8661AF7A9B01D44D0CD915C53969EB725 ) => ( result_=904625697166532825459770292204034545789313605009416454898498935824874733604 )
        • FluidLiquidityProxy.readFromStorage( slot_=A1829A9003092132F585B6CCDD167C19FE9774DBDEA4260287E8A8E8CA8185D7 ) => ( result_=48905070655031274051387399887010072932845599607332465541412 )
        • FluidLiquidityProxy.readFromStorage( slot_=A893C3AB5C5189A9BD276B29D25998250798D4F72DBB029D43E23884B0119A5A ) => ( result_=291355544087482513767394396551803875498524239145451863492591948829 )
        • FluidLiquidityProxy.readFromStorage( slot_=236696EFD8534CE144B358082D303BA190CAD0C8D37E9F4802B2A5198019379B ) => ( result_=353061964987027740348205741447647113032981349141800316578589693989 )
        • FluidLiquidityProxy.readFromStorage( slot_=2CD14670F8A9E59D7C072449B534CC4EC6D89953CF20C518BA36D7FBDD468BAF ) => ( result_=94710661335958479177862972678648678086926670647278721599217106934408229 )
        • FluidLiquidityProxy.readFromStorage( slot_=D943CEC1DFC617BF9515058376ABFAB0217F98CCE018735F02EFD4ABD3453AD8 ) => ( result_=58153252158555476274676125224725950159383527122220084138564205941848093 )
        • FluidLiquidityProxy.ad967e15( )
          • FluidLiquidityUserModule.operate( token_=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, supplyAmount_=87711729628000000, borrowAmount_=-78513266967000000, withdrawTo_=0x0000000000000000000000000000000000000000, borrowTo_=0x0000000000000000000000000000000000000000, callbackData_=0x000000000000000000000000000000000000000000000000024E8D95D006F71600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003CED11C610556E5292FBC2E75D68C3899098C14C ) => ( memVar3_=1037993047556, memVar4_=1066591734338 )
            • WstETH.balanceOf( account=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497 ) => ( 69779735742461097999919 )
            • FluidDexT1.liquidityCallback( token_=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, amount_=166224996595000000, data_=0x000000000000000000000000000000000000000000000000024E8D95D006F71600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003CED11C610556E5292FBC2E75D68C3899098C14C )
            • WstETH.balanceOf( account=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497 ) => ( 69779901968371937047877 )
            • FluidLiquidityProxy.ad967e15( )
              • FluidLiquidityUserModule.operate( token_=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, supplyAmount_=-105732510452000000, borrowAmount_=94644181062000000, withdrawTo_=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, borrowTo_=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, callbackData_=0x ) => ( memVar3_=1054209421992, memVar4_=1070791951994 )
                • ETH 0.200376691514 0x3ced11c610556e5292fbc2e75d68c3899098c14c.CALL( )
                • FluidLiquidityProxy.readFromStorage( slot_=A1829A9003092132F585B6CCDD167C19FE9774DBDEA4260287E8A8E8CA8185D7 ) => ( result_=48905070655031274051387399887010072932845599607332465541412 )
                • ETH 0.000000001225093311 TokenChwomper.CALL( )
                • ETH 0.200376690288906688 0x1e0cf58b2789a7434b92df12d7b128324d44f642.CALL( )
                  File 1 of 7: RedSnwapper
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
                  pragma solidity ^0.8.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);
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20 {
                      /**
                       * @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);
                      /**
                       * @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 `to`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address to, 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 `from` to `to` 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 from,
                          address to,
                          uint256 amount
                      ) external returns (bool);
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
                  pragma solidity ^0.8.0;
                  import "../IERC20.sol";
                  import "../extensions/draft-IERC20Permit.sol";
                  import "../../../utils/Address.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 {
                      using Address for address;
                      function safeTransfer(
                          IERC20 token,
                          address to,
                          uint256 value
                      ) internal {
                          _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                      }
                      function safeTransferFrom(
                          IERC20 token,
                          address from,
                          address to,
                          uint256 value
                      ) internal {
                          _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                      }
                      /**
                       * @dev Deprecated. This function has issues similar to the ones found in
                       * {IERC20-approve}, and its usage is discouraged.
                       *
                       * Whenever possible, use {safeIncreaseAllowance} and
                       * {safeDecreaseAllowance} instead.
                       */
                      function safeApprove(
                          IERC20 token,
                          address spender,
                          uint256 value
                      ) internal {
                          // safeApprove should only be called when setting an initial allowance,
                          // or when resetting it to zero. To increase and decrease it, use
                          // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                          require(
                              (value == 0) || (token.allowance(address(this), spender) == 0),
                              "SafeERC20: approve from non-zero to non-zero allowance"
                          );
                          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                      }
                      function safeIncreaseAllowance(
                          IERC20 token,
                          address spender,
                          uint256 value
                      ) internal {
                          uint256 newAllowance = token.allowance(address(this), spender) + value;
                          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                      }
                      function safeDecreaseAllowance(
                          IERC20 token,
                          address spender,
                          uint256 value
                      ) internal {
                          unchecked {
                              uint256 oldAllowance = token.allowance(address(this), spender);
                              require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                              uint256 newAllowance = oldAllowance - value;
                              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                          }
                      }
                      function safePermit(
                          IERC20Permit token,
                          address owner,
                          address spender,
                          uint256 value,
                          uint256 deadline,
                          uint8 v,
                          bytes32 r,
                          bytes32 s
                      ) internal {
                          uint256 nonceBefore = token.nonces(owner);
                          token.permit(owner, spender, value, deadline, v, r, s);
                          uint256 nonceAfter = token.nonces(owner);
                          require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
                      }
                      /**
                       * @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).
                       * @param token The token targeted by the call.
                       * @param data The call data (encoded using abi.encode or one of its variants).
                       */
                      function _callOptionalReturn(IERC20 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. We use {Address-functionCall} to perform this call, which verifies that
                          // the target address contains contract code and also asserts for success in the low-level call.
                          bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                          if (returndata.length > 0) {
                              // Return data is optional
                              require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
                  pragma solidity ^0.8.1;
                  /**
                   * @dev Collection of functions related to the address type
                   */
                  library Address {
                      /**
                       * @dev Returns true if `account` is a contract.
                       *
                       * [IMPORTANT]
                       * ====
                       * It is unsafe to assume that an address for which this function returns
                       * false is an externally-owned account (EOA) and not a contract.
                       *
                       * Among others, `isContract` will return false for the following
                       * types of addresses:
                       *
                       *  - an externally-owned account
                       *  - a contract in construction
                       *  - an address where a contract will be created
                       *  - an address where a contract lived, but was destroyed
                       * ====
                       *
                       * [IMPORTANT]
                       * ====
                       * You shouldn't rely on `isContract` to protect against flash loan attacks!
                       *
                       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                       * constructor.
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize/address.code.length, which returns 0
                          // for contracts in construction, since the code is only stored at the end
                          // of the constructor execution.
                          return account.code.length > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (bool success, ) = recipient.call{value: amount}("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain `call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, "Address: low-level call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          (bool success, bytes memory returndata) = target.call{value: value}(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
                       * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
                       *
                       * _Available since v4.8._
                       */
                      function verifyCallResultFromTarget(
                          address target,
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          if (success) {
                              if (returndata.length == 0) {
                                  // only check isContract if the call was successful and the return data is empty
                                  // otherwise we already know that it was a contract
                                  require(isContract(target), "Address: call to non-contract");
                              }
                              return returndata;
                          } else {
                              _revert(returndata, errorMessage);
                          }
                      }
                      /**
                       * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
                       * revert reason or using the provided one.
                       *
                       * _Available since v4.3._
                       */
                      function verifyCallResult(
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal pure returns (bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              _revert(returndata, errorMessage);
                          }
                      }
                      function _revert(bytes memory returndata, string memory errorMessage) private pure {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              /// @solidity memory-safe-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity 0.8.24;
                  import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
                  contract RedSnwapper {
                    using SafeERC20 for IERC20;
                    using Utils for IERC20;
                    SafeExecutor public immutable safeExecutor;
                    constructor() {
                      safeExecutor = new SafeExecutor();
                    }
                    // @notice Swaps tokens
                    // @notice 1. Transfers amountIn of tokens tokenIn to executor
                    // @notice 2. launches executor with executorData and value = msg.value
                    // @notice 3. Checks that recipient's tokenOut balance was increased at least amountOutMin
                    function snwap(
                      IERC20 tokenIn,
                      uint amountIn, // if amountIn == 0 then amountIn = tokenIn.balance(this) - 1
                      address recipient,
                      IERC20 tokenOut,
                      uint amountOutMin,
                      address executor,
                      bytes calldata executorData
                    ) external payable returns (uint amountOut) {
                      uint initialOutputBalance = tokenOut.universalBalanceOf(recipient);
                      if (address(tokenIn) != NATIVE_ADDRESS) {
                        if (amountIn > 0) tokenIn.safeTransferFrom(msg.sender, executor, amountIn);
                        else tokenIn.safeTransfer(executor, tokenIn.balanceOf(address(this)) - 1); // -1 is slot undrain protection
                      }
                      safeExecutor.execute{value: msg.value}(executor, executorData);
                      amountOut = tokenOut.universalBalanceOf(recipient) - initialOutputBalance;
                      if (amountOut < amountOutMin)
                        revert MinimalOutputBalanceViolation(address(tokenOut), amountOut);
                    }
                    // @notice Swaps multiple tokens
                    // @notice 1. Transfers inputTokens to inputTokens[i].transferTo
                    // @notice 2. launches executors
                    // @notice 3. Checks that recipient's tokenOut balance was increased at least amountOutMin
                    function snwapMultiple(
                      InputToken[] calldata inputTokens,
                      OutputToken[] calldata outputTokens,
                      Executor[] calldata executors
                    ) external payable returns (uint[] memory amountOut) {
                      uint[] memory initialOutputBalance = new uint[](outputTokens.length);
                      for (uint i = 0; i < outputTokens.length; i++) {
                        initialOutputBalance[i] = outputTokens[i].token.universalBalanceOf(outputTokens[i].recipient);
                      }
                      for (uint i = 0; i < inputTokens.length; i++) {
                        IERC20 tokenIn = inputTokens[i].token;
                        if (address(tokenIn) != NATIVE_ADDRESS) {
                          if (inputTokens[i].amountIn > 0) 
                            tokenIn.safeTransferFrom(msg.sender, inputTokens[i].transferTo, inputTokens[i].amountIn);
                          else tokenIn.safeTransfer(inputTokens[i].transferTo, tokenIn.balanceOf(address(this)) - 1); // -1 is slot undrain protection
                        }
                      }
                      safeExecutor.executeMultiple{value: msg.value}(executors);
                      amountOut = new uint[](outputTokens.length);
                      for (uint i = 0; i < outputTokens.length; i++) {
                        amountOut[i] = outputTokens[i].token.universalBalanceOf(outputTokens[i].recipient) - initialOutputBalance[i];
                        if (amountOut[i] < outputTokens[i].amountOutMin)
                          revert MinimalOutputBalanceViolation(address(outputTokens[i].token), amountOut[i]);
                      }
                    }
                  }
                  // This contract doesn't have token approves, so can safely call other contracts
                  contract SafeExecutor {  
                    using Utils for address;
                    function execute(address executor, bytes calldata executorData) external payable {
                      executor.callRevertBubbleUp(msg.value, executorData);
                    }
                    function executeMultiple(Executor[] calldata executors) external payable {
                      for (uint i = 0; i < executors.length; i++) {
                        executors[i].executor.callRevertBubbleUp(executors[i].value, executors[i].data);
                      }
                    }
                  }
                  error MinimalOutputBalanceViolation(address tokenOut, uint256 amountOut);
                  address constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                  struct InputToken {
                    IERC20 token;
                    uint amountIn;
                    address transferTo;
                  }
                  struct OutputToken {
                    IERC20 token;
                    address recipient;
                    uint amountOutMin;
                  }
                  struct Executor {
                    address executor;
                    uint value;
                    bytes data;
                  }
                  library Utils {
                    using SafeERC20 for IERC20;
                    
                    function universalBalanceOf(IERC20 token, address user) internal view returns (uint256) {
                      if (address(token) == NATIVE_ADDRESS) return address(user).balance;
                      else return token.balanceOf(user);
                    }
                    function callRevertBubbleUp(address contr, uint256 value, bytes memory data) internal {
                      (bool success, bytes memory returnBytes) = contr.call{value: value}(data);
                      if (!success) {
                        assembly {
                          revert(add(32, returnBytes), mload(returnBytes))
                        }
                      }
                    }
                  }

                  File 2 of 7: WstETH
                  // SPDX-License-Identifier: MIT AND GPL-3.0
                  // File: @openzeppelin/contracts/utils/Context.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  /*
                   * @dev Provides information about the current execution context, including the
                   * sender of the transaction and its data. While these are generally available
                   * via msg.sender and msg.data, they should not be accessed in such a direct
                   * manner, since when dealing with GSN meta-transactions the account sending and
                   * paying for execution may not be the actual sender (as far as an application
                   * is concerned).
                   *
                   * This contract is only required for intermediate, library-like contracts.
                   */
                  abstract contract Context {
                      function _msgSender() internal view virtual returns (address payable) {
                          return msg.sender;
                      }
                  
                      function _msgData() internal view virtual returns (bytes memory) {
                          this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                          return msg.data;
                      }
                  }
                  
                  // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.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: @openzeppelin/contracts/math/SafeMath.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  /**
                   * @dev Wrappers over Solidity's arithmetic operations with added overflow
                   * checks.
                   *
                   * Arithmetic operations in Solidity wrap on overflow. This can easily result
                   * in bugs, because programmers usually assume that an overflow raises an
                   * error, which is the standard behavior in high level programming languages.
                   * `SafeMath` restores this intuition by reverting the transaction when an
                   * operation overflows.
                   *
                   * Using this library instead of the unchecked operations eliminates an entire
                   * class of bugs, so it's recommended to use it always.
                   */
                  library SafeMath {
                      /**
                       * @dev Returns the addition of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          uint256 c = a + b;
                          if (c < a) return (false, 0);
                          return (true, c);
                      }
                  
                      /**
                       * @dev Returns the substraction of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b > a) return (false, 0);
                          return (true, a - b);
                      }
                  
                      /**
                       * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                          // benefit is lost if 'b' is also tested.
                          // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                          if (a == 0) return (true, 0);
                          uint256 c = a * b;
                          if (c / a != b) return (false, 0);
                          return (true, c);
                      }
                  
                      /**
                       * @dev Returns the division of two unsigned integers, with a division by zero flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b == 0) return (false, 0);
                          return (true, a / b);
                      }
                  
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b == 0) return (false, 0);
                          return (true, a % b);
                      }
                  
                      /**
                       * @dev Returns the addition of two unsigned integers, reverting on
                       * overflow.
                       *
                       * Counterpart to Solidity's `+` operator.
                       *
                       * Requirements:
                       *
                       * - Addition cannot overflow.
                       */
                      function add(uint256 a, uint256 b) internal pure returns (uint256) {
                          uint256 c = a + b;
                          require(c >= a, "SafeMath: addition overflow");
                          return c;
                      }
                  
                      /**
                       * @dev Returns the subtraction of two unsigned integers, reverting on
                       * overflow (when the result is negative).
                       *
                       * Counterpart to Solidity's `-` operator.
                       *
                       * Requirements:
                       *
                       * - Subtraction cannot overflow.
                       */
                      function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                          require(b <= a, "SafeMath: subtraction overflow");
                          return a - b;
                      }
                  
                      /**
                       * @dev Returns the multiplication of two unsigned integers, reverting on
                       * overflow.
                       *
                       * Counterpart to Solidity's `*` operator.
                       *
                       * Requirements:
                       *
                       * - Multiplication cannot overflow.
                       */
                      function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                          if (a == 0) return 0;
                          uint256 c = a * b;
                          require(c / a == b, "SafeMath: multiplication overflow");
                          return c;
                      }
                  
                      /**
                       * @dev Returns the integer division of two unsigned integers, reverting on
                       * division by zero. The result is rounded towards zero.
                       *
                       * Counterpart to Solidity's `/` operator. Note: this function uses a
                       * `revert` opcode (which leaves remaining gas untouched) while Solidity
                       * uses an invalid opcode to revert (consuming all remaining gas).
                       *
                       * Requirements:
                       *
                       * - The divisor cannot be zero.
                       */
                      function div(uint256 a, uint256 b) internal pure returns (uint256) {
                          require(b > 0, "SafeMath: division by zero");
                          return a / b;
                      }
                  
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                       * reverting when dividing by zero.
                       *
                       * Counterpart to Solidity's `%` operator. This function uses a `revert`
                       * opcode (which leaves remaining gas untouched) while Solidity uses an
                       * invalid opcode to revert (consuming all remaining gas).
                       *
                       * Requirements:
                       *
                       * - The divisor cannot be zero.
                       */
                      function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                          require(b > 0, "SafeMath: modulo by zero");
                          return a % b;
                      }
                  
                      /**
                       * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                       * overflow (when the result is negative).
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {trySub}.
                       *
                       * Counterpart to Solidity's `-` operator.
                       *
                       * Requirements:
                       *
                       * - Subtraction cannot overflow.
                       */
                      function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                          require(b <= a, errorMessage);
                          return a - b;
                      }
                  
                      /**
                       * @dev Returns the integer division of two unsigned integers, reverting with custom message on
                       * division by zero. The result is rounded towards zero.
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {tryDiv}.
                       *
                       * Counterpart to Solidity's `/` operator. Note: this function uses a
                       * `revert` opcode (which leaves remaining gas untouched) while Solidity
                       * uses an invalid opcode to revert (consuming all remaining gas).
                       *
                       * Requirements:
                       *
                       * - The divisor cannot be zero.
                       */
                      function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                          require(b > 0, errorMessage);
                          return a / b;
                      }
                  
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                       * reverting with custom message when dividing by zero.
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {tryMod}.
                       *
                       * Counterpart to Solidity's `%` operator. This function uses a `revert`
                       * opcode (which leaves remaining gas untouched) while Solidity uses an
                       * invalid opcode to revert (consuming all remaining gas).
                       *
                       * Requirements:
                       *
                       * - The divisor cannot be zero.
                       */
                      function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                          require(b > 0, errorMessage);
                          return a % b;
                      }
                  }
                  
                  // File: @openzeppelin/contracts/token/ERC20/ERC20.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  
                  
                  
                  /**
                   * @dev Implementation of the {IERC20} interface.
                   *
                   * This implementation is agnostic to the way tokens are created. This means
                   * that a supply mechanism has to be added in a derived contract using {_mint}.
                   * For a generic mechanism see {ERC20PresetMinterPauser}.
                   *
                   * TIP: For a detailed writeup see our guide
                   * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
                   * to implement supply mechanisms].
                   *
                   * We have followed general OpenZeppelin guidelines: functions revert instead
                   * of returning `false` on failure. This behavior is nonetheless conventional
                   * and does not conflict with the expectations of ERC20 applications.
                   *
                   * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
                   * This allows applications to reconstruct the allowance for all accounts just
                   * by listening to said events. Other implementations of the EIP may not emit
                   * these events, as it isn't required by the specification.
                   *
                   * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
                   * functions have been added to mitigate the well-known issues around setting
                   * allowances. See {IERC20-approve}.
                   */
                  contract ERC20 is Context, IERC20 {
                      using SafeMath for uint256;
                  
                      mapping (address => uint256) private _balances;
                  
                      mapping (address => mapping (address => uint256)) private _allowances;
                  
                      uint256 private _totalSupply;
                  
                      string private _name;
                      string private _symbol;
                      uint8 private _decimals;
                  
                      /**
                       * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
                       * a default value of 18.
                       *
                       * To select a different value for {decimals}, use {_setupDecimals}.
                       *
                       * All three of these values are immutable: they can only be set once during
                       * construction.
                       */
                      constructor (string memory name_, string memory symbol_) public {
                          _name = name_;
                          _symbol = symbol_;
                          _decimals = 18;
                      }
                  
                      /**
                       * @dev Returns the name of the token.
                       */
                      function name() public view virtual returns (string memory) {
                          return _name;
                      }
                  
                      /**
                       * @dev Returns the symbol of the token, usually a shorter version of the
                       * name.
                       */
                      function symbol() public view virtual returns (string memory) {
                          return _symbol;
                      }
                  
                      /**
                       * @dev Returns the number of decimals used to get its user representation.
                       * For example, if `decimals` equals `2`, a balance of `505` tokens should
                       * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                       *
                       * Tokens usually opt for a value of 18, imitating the relationship between
                       * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
                       * called.
                       *
                       * NOTE: This information is only used for _display_ purposes: it in
                       * no way affects any of the arithmetic of the contract, including
                       * {IERC20-balanceOf} and {IERC20-transfer}.
                       */
                      function decimals() public view virtual returns (uint8) {
                          return _decimals;
                      }
                  
                      /**
                       * @dev See {IERC20-totalSupply}.
                       */
                      function totalSupply() public view virtual override returns (uint256) {
                          return _totalSupply;
                      }
                  
                      /**
                       * @dev See {IERC20-balanceOf}.
                       */
                      function balanceOf(address account) public view virtual override returns (uint256) {
                          return _balances[account];
                      }
                  
                      /**
                       * @dev See {IERC20-transfer}.
                       *
                       * Requirements:
                       *
                       * - `recipient` cannot be the zero address.
                       * - the caller must have a balance of at least `amount`.
                       */
                      function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                          _transfer(_msgSender(), recipient, amount);
                          return true;
                      }
                  
                      /**
                       * @dev See {IERC20-allowance}.
                       */
                      function allowance(address owner, address spender) public view virtual override returns (uint256) {
                          return _allowances[owner][spender];
                      }
                  
                      /**
                       * @dev See {IERC20-approve}.
                       *
                       * Requirements:
                       *
                       * - `spender` cannot be the zero address.
                       */
                      function approve(address spender, uint256 amount) public virtual override returns (bool) {
                          _approve(_msgSender(), spender, amount);
                          return true;
                      }
                  
                      /**
                       * @dev See {IERC20-transferFrom}.
                       *
                       * Emits an {Approval} event indicating the updated allowance. This is not
                       * required by the EIP. See the note at the beginning of {ERC20}.
                       *
                       * Requirements:
                       *
                       * - `sender` and `recipient` cannot be the zero address.
                       * - `sender` must have a balance of at least `amount`.
                       * - the caller must have allowance for ``sender``'s tokens of at least
                       * `amount`.
                       */
                      function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
                          _transfer(sender, recipient, amount);
                          _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                          return true;
                      }
                  
                      /**
                       * @dev Atomically increases the allowance granted to `spender` by the caller.
                       *
                       * This is an alternative to {approve} that can be used as a mitigation for
                       * problems described in {IERC20-approve}.
                       *
                       * Emits an {Approval} event indicating the updated allowance.
                       *
                       * Requirements:
                       *
                       * - `spender` cannot be the zero address.
                       */
                      function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                          _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                          return true;
                      }
                  
                      /**
                       * @dev Atomically decreases the allowance granted to `spender` by the caller.
                       *
                       * This is an alternative to {approve} that can be used as a mitigation for
                       * problems described in {IERC20-approve}.
                       *
                       * Emits an {Approval} event indicating the updated allowance.
                       *
                       * Requirements:
                       *
                       * - `spender` cannot be the zero address.
                       * - `spender` must have allowance for the caller of at least
                       * `subtractedValue`.
                       */
                      function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                          _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                          return true;
                      }
                  
                      /**
                       * @dev Moves tokens `amount` from `sender` to `recipient`.
                       *
                       * This is internal function is equivalent to {transfer}, and can be used to
                       * e.g. implement automatic token fees, slashing mechanisms, etc.
                       *
                       * Emits a {Transfer} event.
                       *
                       * Requirements:
                       *
                       * - `sender` cannot be the zero address.
                       * - `recipient` cannot be the zero address.
                       * - `sender` must have a balance of at least `amount`.
                       */
                      function _transfer(address sender, address recipient, uint256 amount) internal virtual {
                          require(sender != address(0), "ERC20: transfer from the zero address");
                          require(recipient != address(0), "ERC20: transfer to the zero address");
                  
                          _beforeTokenTransfer(sender, recipient, amount);
                  
                          _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                          _balances[recipient] = _balances[recipient].add(amount);
                          emit Transfer(sender, recipient, amount);
                      }
                  
                      /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                       * the total supply.
                       *
                       * Emits a {Transfer} event with `from` set to the zero address.
                       *
                       * Requirements:
                       *
                       * - `to` cannot be the zero address.
                       */
                      function _mint(address account, uint256 amount) internal virtual {
                          require(account != address(0), "ERC20: mint to the zero address");
                  
                          _beforeTokenTransfer(address(0), account, amount);
                  
                          _totalSupply = _totalSupply.add(amount);
                          _balances[account] = _balances[account].add(amount);
                          emit Transfer(address(0), account, amount);
                      }
                  
                      /**
                       * @dev Destroys `amount` tokens from `account`, reducing the
                       * total supply.
                       *
                       * Emits a {Transfer} event with `to` set to the zero address.
                       *
                       * Requirements:
                       *
                       * - `account` cannot be the zero address.
                       * - `account` must have at least `amount` tokens.
                       */
                      function _burn(address account, uint256 amount) internal virtual {
                          require(account != address(0), "ERC20: burn from the zero address");
                  
                          _beforeTokenTransfer(account, address(0), amount);
                  
                          _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                          _totalSupply = _totalSupply.sub(amount);
                          emit Transfer(account, address(0), amount);
                      }
                  
                      /**
                       * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
                       *
                       * This internal function is equivalent to `approve`, and can be used to
                       * e.g. set automatic allowances for certain subsystems, etc.
                       *
                       * Emits an {Approval} event.
                       *
                       * Requirements:
                       *
                       * - `owner` cannot be the zero address.
                       * - `spender` cannot be the zero address.
                       */
                      function _approve(address owner, address spender, uint256 amount) internal virtual {
                          require(owner != address(0), "ERC20: approve from the zero address");
                          require(spender != address(0), "ERC20: approve to the zero address");
                  
                          _allowances[owner][spender] = amount;
                          emit Approval(owner, spender, amount);
                      }
                  
                      /**
                       * @dev Sets {decimals} to a value other than the default one of 18.
                       *
                       * WARNING: This function should only be called from the constructor. Most
                       * applications that interact with token contracts will not expect
                       * {decimals} to ever change, and may work incorrectly if it does.
                       */
                      function _setupDecimals(uint8 decimals_) internal virtual {
                          _decimals = decimals_;
                      }
                  
                      /**
                       * @dev Hook that is called before any transfer of tokens. This includes
                       * minting and burning.
                       *
                       * Calling conditions:
                       *
                       * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                       * will be to transferred to `to`.
                       * - when `from` is zero, `amount` tokens will be minted for `to`.
                       * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                       * - `from` and `to` are never both zero.
                       *
                       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                       */
                      function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
                  }
                  
                  // File: @openzeppelin/contracts/drafts/IERC20Permit.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.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: @openzeppelin/contracts/cryptography/ECDSA.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  /**
                   * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
                   *
                   * These functions can be used to verify that a message was signed by the holder
                   * of the private keys of a given address.
                   */
                  library ECDSA {
                      /**
                       * @dev Returns the address that signed a hashed message (`hash`) with
                       * `signature`. This address can then be used for verification purposes.
                       *
                       * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
                       * this function rejects them by requiring the `s` value to be in the lower
                       * half order, and the `v` value to be either 27 or 28.
                       *
                       * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                       * verification to be secure: it is possible to craft signatures that
                       * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                       * this is by receiving a hash of the original message (which may otherwise
                       * be too long), and then calling {toEthSignedMessageHash} on it.
                       */
                      function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                          // Check the signature length
                          if (signature.length != 65) {
                              revert("ECDSA: invalid signature length");
                          }
                  
                          // Divide the signature in r, s and v variables
                          bytes32 r;
                          bytes32 s;
                          uint8 v;
                  
                          // ecrecover takes the signature parameters, and the only way to get them
                          // currently is to use assembly.
                          // solhint-disable-next-line no-inline-assembly
                          assembly {
                              r := mload(add(signature, 0x20))
                              s := mload(add(signature, 0x40))
                              v := byte(0, mload(add(signature, 0x60)))
                          }
                  
                          return recover(hash, v, r, s);
                      }
                  
                      /**
                       * @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`,
                       * `r` and `s` signature fields separately.
                       */
                      function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
                          // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                          // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                          // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                          // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                          //
                          // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                          // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                          // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                          // these malleable signatures as well.
                          require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
                          require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
                  
                          // If the signature is valid (and not malleable), return the signer address
                          address signer = ecrecover(hash, v, r, s);
                          require(signer != address(0), "ECDSA: invalid signature");
                  
                          return signer;
                      }
                  
                      /**
                       * @dev Returns an Ethereum Signed Message, created from a `hash`. This
                       * replicates the behavior of the
                       * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
                       * JSON-RPC method.
                       *
                       * See {recover}.
                       */
                      function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                          // 32 is the length in bytes of hash,
                          // enforced by the type signature above
                          return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
                      }
                  }
                  
                  // File: @openzeppelin/contracts/utils/Counters.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  
                  /**
                   * @title Counters
                   * @author Matt Condon (@shrugs)
                   * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
                   * of elements in a mapping, issuing ERC721 ids, or counting request ids.
                   *
                   * Include with `using Counters for Counters.Counter;`
                   * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the {SafeMath}
                   * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
                   * directly accessed.
                   */
                  library Counters {
                      using SafeMath for uint256;
                  
                      struct Counter {
                          // This variable should never be directly accessed by users of the library: interactions must be restricted to
                          // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
                          // this feature: see https://github.com/ethereum/solidity/issues/4637
                          uint256 _value; // default: 0
                      }
                  
                      function current(Counter storage counter) internal view returns (uint256) {
                          return counter._value;
                      }
                  
                      function increment(Counter storage counter) internal {
                          // The {SafeMath} overflow check can be skipped here, see the comment at the top
                          counter._value += 1;
                      }
                  
                      function decrement(Counter storage counter) internal {
                          counter._value = counter._value.sub(1);
                      }
                  }
                  
                  // File: @openzeppelin/contracts/drafts/EIP712.sol
                  
                  
                  pragma solidity >=0.6.0 <0.8.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 */
                      // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
                      // invalidate the cached domain separator if the chain id changes.
                      bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
                      uint256 private immutable _CACHED_CHAIN_ID;
                  
                      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) internal {
                          bytes32 hashedName = keccak256(bytes(name));
                          bytes32 hashedVersion = keccak256(bytes(version));
                          bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
                          _HASHED_NAME = hashedName;
                          _HASHED_VERSION = hashedVersion;
                          _CACHED_CHAIN_ID = _getChainId();
                          _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
                          _TYPE_HASH = typeHash;
                      }
                  
                      /**
                       * @dev Returns the domain separator for the current chain.
                       */
                      function _domainSeparatorV4() internal view virtual returns (bytes32) {
                          if (_getChainId() == _CACHED_CHAIN_ID) {
                              return _CACHED_DOMAIN_SEPARATOR;
                          } else {
                              return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
                          }
                      }
                  
                      function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) {
                          return keccak256(
                              abi.encode(
                                  typeHash,
                                  name,
                                  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) {
                          this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                          // solhint-disable-next-line no-inline-assembly
                          assembly {
                              chainId := chainid()
                          }
                      }
                  }
                  
                  // File: @openzeppelin/contracts/drafts/ERC20Permit.sol
                  
                  
                  pragma solidity >=0.6.5 <0.8.0;
                  
                  
                  
                  
                  
                  
                  /**
                   * @dev Implementation 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.
                   *
                   * _Available since v3.4._
                   */
                  abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
                      using Counters for Counters.Counter;
                  
                      mapping (address => Counters.Counter) private _nonces;
                  
                      // solhint-disable-next-line var-name-mixedcase
                      bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
                  
                      /**
                       * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
                       *
                       * It's a good idea to use the same `name` that is defined as the ERC20 token name.
                       */
                      constructor(string memory name) internal EIP712(name, "1") {
                      }
                  
                      /**
                       * @dev See {IERC20Permit-permit}.
                       */
                      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, "ERC20Permit: expired deadline");
                  
                          bytes32 structHash = keccak256(
                              abi.encode(
                                  _PERMIT_TYPEHASH,
                                  owner,
                                  spender,
                                  value,
                                  _nonces[owner].current(),
                                  deadline
                              )
                          );
                  
                          bytes32 hash = _hashTypedDataV4(structHash);
                  
                          address signer = ECDSA.recover(hash, v, r, s);
                          require(signer == owner, "ERC20Permit: invalid signature");
                  
                          _nonces[owner].increment();
                          _approve(owner, spender, value);
                      }
                  
                      /**
                       * @dev See {IERC20Permit-nonces}.
                       */
                      function nonces(address owner) public view override returns (uint256) {
                          return _nonces[owner].current();
                      }
                  
                      /**
                       * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
                       */
                      // solhint-disable-next-line func-name-mixedcase
                      function DOMAIN_SEPARATOR() external view override returns (bytes32) {
                          return _domainSeparatorV4();
                      }
                  }
                  
                  // File: contracts/0.6.12/interfaces/IStETH.sol
                  
                  // SPDX-FileCopyrightText: 2021 Lido <[email protected]>
                  
                  
                  pragma solidity 0.6.12; // latest available for using OZ
                  
                  
                  
                  interface IStETH is IERC20 {
                      function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);
                  
                      function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256);
                  
                      function submit(address _referral) external payable returns (uint256);
                  }
                  
                  // File: contracts/0.6.12/WstETH.sol
                  
                  // SPDX-FileCopyrightText: 2021 Lido <[email protected]>
                  
                  
                  /* See contracts/COMPILERS.md */
                  pragma solidity 0.6.12;
                  
                  
                  
                  /**
                   * @title StETH token wrapper with static balances.
                   * @dev It's an ERC20 token that represents the account's share of the total
                   * supply of stETH tokens. WstETH token's balance only changes on transfers,
                   * unlike StETH that is also changed when oracles report staking rewards and
                   * penalties. It's a "power user" token for DeFi protocols which don't
                   * support rebasable tokens.
                   *
                   * The contract is also a trustless wrapper that accepts stETH tokens and mints
                   * wstETH in return. Then the user unwraps, the contract burns user's wstETH
                   * and sends user locked stETH in return.
                   *
                   * The contract provides the staking shortcut: user can send ETH with regular
                   * transfer and get wstETH in return. The contract will send ETH to Lido submit
                   * method, staking it and wrapping the received stETH.
                   *
                   */
                  contract WstETH is ERC20Permit {
                      IStETH public stETH;
                  
                      /**
                       * @param _stETH address of the StETH token to wrap
                       */
                      constructor(IStETH _stETH)
                          public
                          ERC20Permit("Wrapped liquid staked Ether 2.0")
                          ERC20("Wrapped liquid staked Ether 2.0", "wstETH")
                      {
                          stETH = _stETH;
                      }
                  
                      /**
                       * @notice Exchanges stETH to wstETH
                       * @param _stETHAmount amount of stETH to wrap in exchange for wstETH
                       * @dev Requirements:
                       *  - `_stETHAmount` must be non-zero
                       *  - msg.sender must approve at least `_stETHAmount` stETH to this
                       *    contract.
                       *  - msg.sender must have at least `_stETHAmount` of stETH.
                       * User should first approve _stETHAmount to the WstETH contract
                       * @return Amount of wstETH user receives after wrap
                       */
                      function wrap(uint256 _stETHAmount) external returns (uint256) {
                          require(_stETHAmount > 0, "wstETH: can't wrap zero stETH");
                          uint256 wstETHAmount = stETH.getSharesByPooledEth(_stETHAmount);
                          _mint(msg.sender, wstETHAmount);
                          stETH.transferFrom(msg.sender, address(this), _stETHAmount);
                          return wstETHAmount;
                      }
                  
                      /**
                       * @notice Exchanges wstETH to stETH
                       * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH
                       * @dev Requirements:
                       *  - `_wstETHAmount` must be non-zero
                       *  - msg.sender must have at least `_wstETHAmount` wstETH.
                       * @return Amount of stETH user receives after unwrap
                       */
                      function unwrap(uint256 _wstETHAmount) external returns (uint256) {
                          require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed");
                          uint256 stETHAmount = stETH.getPooledEthByShares(_wstETHAmount);
                          _burn(msg.sender, _wstETHAmount);
                          stETH.transfer(msg.sender, stETHAmount);
                          return stETHAmount;
                      }
                  
                      /**
                      * @notice Shortcut to stake ETH and auto-wrap returned stETH
                      */
                      receive() external payable {
                          uint256 shares = stETH.submit{value: msg.value}(address(0));
                          _mint(msg.sender, shares);
                      }
                  
                      /**
                       * @notice Get amount of wstETH for a given amount of stETH
                       * @param _stETHAmount amount of stETH
                       * @return Amount of wstETH for a given stETH amount
                       */
                      function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256) {
                          return stETH.getSharesByPooledEth(_stETHAmount);
                      }
                  
                      /**
                       * @notice Get amount of stETH for a given amount of wstETH
                       * @param _wstETHAmount amount of wstETH
                       * @return Amount of stETH for a given wstETH amount
                       */
                      function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) {
                          return stETH.getPooledEthByShares(_wstETHAmount);
                      }
                  
                      /**
                       * @notice Get amount of stETH for a one wstETH
                       * @return Amount of stETH for 1 wstETH
                       */
                      function stEthPerToken() external view returns (uint256) {
                          return stETH.getPooledEthByShares(1 ether);
                      }
                  
                      /**
                       * @notice Get amount of wstETH for a one stETH
                       * @return Amount of wstETH for a 1 stETH
                       */
                      function tokensPerStEth() external view returns (uint256) {
                          return stETH.getSharesByPooledEth(1 ether);
                      }
                  }

                  File 3 of 7: FluidLiquidityProxy
                  //SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  contract Error {
                      error FluidInfiniteProxyError(uint256 errorId_);
                  }
                  //SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  library ErrorTypes {
                      /***********************************|
                      |         Infinite proxy            | 
                      |__________________________________*/
                      /// @notice thrown when an implementation does not exist
                      uint256 internal constant InfiniteProxy__ImplementationNotExist = 50001;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  contract Events {
                      /// @notice emitted when a new admin is set
                      event LogSetAdmin(address indexed oldAdmin, address indexed newAdmin);
                      /// @notice emitted when a new dummy implementation is set
                      event LogSetDummyImplementation(address indexed oldDummyImplementation, address indexed newDummyImplementation);
                      /// @notice emitted when a new implementation is set with certain sigs
                      event LogSetImplementation(address indexed implementation, bytes4[] sigs);
                      /// @notice emitted when an implementation is removed
                      event LogRemoveImplementation(address indexed implementation);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  import { Events } from "./events.sol";
                  import { ErrorTypes } from "./errorTypes.sol";
                  import { Error } from "./error.sol";
                  import { StorageRead } from "../libraries/storageRead.sol";
                  contract CoreInternals is StorageRead, Events, Error {
                      struct SigsSlot {
                          bytes4[] value;
                      }
                      /// @dev Storage slot with the admin of the contract.
                      /// This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                      /// validated in the constructor.
                      bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                      /// @dev Storage slot with the address of the current dummy-implementation.
                      /// This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                      /// validated in the constructor.
                      bytes32 internal constant _DUMMY_IMPLEMENTATION_SLOT =
                          0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                      /// @dev use EIP1967 proxy slot (see _DUMMY_IMPLEMENTATION_SLOT) except for first 4 bytes,
                      // which are set to 0. This is combined with a sig which will be set in those first 4 bytes
                      bytes32 internal constant _SIG_SLOT_BASE = 0x000000003ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                      /// @dev Returns the storage slot which stores the sigs array set for the implementation.
                      function _getSlotImplSigsSlot(address implementation_) internal pure returns (bytes32) {
                          return keccak256(abi.encode("eip1967.proxy.implementation", implementation_));
                      }
                      /// @dev Returns the storage slot which stores the implementation address for the function sig.
                      function _getSlotSigsImplSlot(bytes4 sig_) internal pure returns (bytes32 result_) {
                          assembly {
                              // or operator sets sig_ in first 4 bytes with rest of bytes32 having default value of _SIG_SLOT_BASE
                              result_ := or(_SIG_SLOT_BASE, sig_)
                          }
                      }
                      /// @dev Returns an address `data_` located at `slot_`.
                      function _getAddressSlot(bytes32 slot_) internal view returns (address data_) {
                          assembly {
                              data_ := sload(slot_)
                          }
                      }
                      /// @dev Sets an address `data_` located at `slot_`.
                      function _setAddressSlot(bytes32 slot_, address data_) internal {
                          assembly {
                              sstore(slot_, data_)
                          }
                      }
                      /// @dev Returns an `SigsSlot` with member `value` located at `slot`.
                      function _getSigsSlot(bytes32 slot_) internal pure returns (SigsSlot storage _r) {
                          assembly {
                              _r.slot := slot_
                          }
                      }
                      /// @dev Sets new implementation and adds mapping from implementation to sigs and sig to implementation.
                      function _setImplementationSigs(address implementation_, bytes4[] memory sigs_) internal {
                          require(sigs_.length != 0, "no-sigs");
                          bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
                          bytes4[] memory sigsCheck_ = _getSigsSlot(slot_).value;
                          require(sigsCheck_.length == 0, "implementation-already-exist");
                          for (uint256 i; i < sigs_.length; i++) {
                              bytes32 sigSlot_ = _getSlotSigsImplSlot(sigs_[i]);
                              require(_getAddressSlot(sigSlot_) == address(0), "sig-already-exist");
                              _setAddressSlot(sigSlot_, implementation_);
                          }
                          _getSigsSlot(slot_).value = sigs_;
                          emit LogSetImplementation(implementation_, sigs_);
                      }
                      /// @dev Removes implementation and the mappings corresponding to it.
                      function _removeImplementationSigs(address implementation_) internal {
                          bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
                          bytes4[] memory sigs_ = _getSigsSlot(slot_).value;
                          require(sigs_.length != 0, "implementation-not-exist");
                          for (uint256 i; i < sigs_.length; i++) {
                              bytes32 sigSlot_ = _getSlotSigsImplSlot(sigs_[i]);
                              _setAddressSlot(sigSlot_, address(0));
                          }
                          delete _getSigsSlot(slot_).value;
                          emit LogRemoveImplementation(implementation_);
                      }
                      /// @dev Returns bytes4[] sigs from implementation address. If implemenatation is not registered then returns empty array.
                      function _getImplementationSigs(address implementation_) internal view returns (bytes4[] memory) {
                          bytes32 slot_ = _getSlotImplSigsSlot(implementation_);
                          return _getSigsSlot(slot_).value;
                      }
                      /// @dev Returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
                      function _getSigImplementation(bytes4 sig_) internal view returns (address implementation_) {
                          bytes32 slot_ = _getSlotSigsImplSlot(sig_);
                          return _getAddressSlot(slot_);
                      }
                      /// @dev Returns the current admin.
                      function _getAdmin() internal view returns (address) {
                          return _getAddressSlot(_ADMIN_SLOT);
                      }
                      /// @dev Returns the current dummy-implementation.
                      function _getDummyImplementation() internal view returns (address) {
                          return _getAddressSlot(_DUMMY_IMPLEMENTATION_SLOT);
                      }
                      /// @dev Stores a new address in the EIP1967 admin slot.
                      function _setAdmin(address newAdmin_) internal {
                          address oldAdmin_ = _getAdmin();
                          require(newAdmin_ != address(0), "ERC1967: new admin is the zero address");
                          _setAddressSlot(_ADMIN_SLOT, newAdmin_);
                          emit LogSetAdmin(oldAdmin_, newAdmin_);
                      }
                      /// @dev Stores a new address in the EIP1967 implementation slot.
                      function _setDummyImplementation(address newDummyImplementation_) internal {
                          address oldDummyImplementation_ = _getDummyImplementation();
                          _setAddressSlot(_DUMMY_IMPLEMENTATION_SLOT, newDummyImplementation_);
                          emit LogSetDummyImplementation(oldDummyImplementation_, newDummyImplementation_);
                      }
                  }
                  contract AdminInternals is CoreInternals {
                      /// @dev Only admin guard
                      modifier onlyAdmin() {
                          require(msg.sender == _getAdmin(), "only-admin");
                          _;
                      }
                      constructor(address admin_, address dummyImplementation_) {
                          _setAdmin(admin_);
                          _setDummyImplementation(dummyImplementation_);
                      }
                      /// @dev Sets new admin.
                      function setAdmin(address newAdmin_) external onlyAdmin {
                          _setAdmin(newAdmin_);
                      }
                      /// @dev Sets new dummy-implementation.
                      function setDummyImplementation(address newDummyImplementation_) external onlyAdmin {
                          _setDummyImplementation(newDummyImplementation_);
                      }
                      /// @dev Adds new implementation address.
                      function addImplementation(address implementation_, bytes4[] calldata sigs_) external onlyAdmin {
                          _setImplementationSigs(implementation_, sigs_);
                      }
                      /// @dev Removes an existing implementation address.
                      function removeImplementation(address implementation_) external onlyAdmin {
                          _removeImplementationSigs(implementation_);
                      }
                  }
                  /// @title Proxy
                  /// @notice This abstract contract provides a fallback function that delegates all calls to another contract using the EVM.
                  /// It implements the Instadapp infinite-proxy: https://github.com/Instadapp/infinite-proxy
                  abstract contract Proxy is AdminInternals {
                      constructor(address admin_, address dummyImplementation_) AdminInternals(admin_, dummyImplementation_) {}
                      /// @dev Returns admin's address.
                      function getAdmin() external view returns (address) {
                          return _getAdmin();
                      }
                      /// @dev Returns dummy-implementations's address.
                      function getDummyImplementation() external view returns (address) {
                          return _getDummyImplementation();
                      }
                      /// @dev Returns bytes4[] sigs from implementation address If not registered then returns empty array.
                      function getImplementationSigs(address impl_) external view returns (bytes4[] memory) {
                          return _getImplementationSigs(impl_);
                      }
                      /// @dev Returns implementation address from bytes4 sig. If sig is not registered then returns address(0).
                      function getSigsImplementation(bytes4 sig_) external view returns (address) {
                          return _getSigImplementation(sig_);
                      }
                      /// @dev Fallback function that delegates calls to the address returned by Implementations registry.
                      fallback() external payable {
                          address implementation_;
                          assembly {
                              // get slot for sig and directly SLOAD implementation address from storage at that slot
                              implementation_ := sload(
                                  // same as in `_getSlotSigsImplSlot()` but we must also load msg.sig from calldata.
                                  // msg.sig is first 4 bytes of calldata, so we can use calldataload(0) with a mask
                                  or(
                                      // or operator sets sig_ in first 4 bytes with rest of bytes32 having default value of _SIG_SLOT_BASE
                                      _SIG_SLOT_BASE,
                                      and(calldataload(0), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
                                  )
                              )
                          }
                          if (implementation_ == address(0)) {
                              revert FluidInfiniteProxyError(ErrorTypes.InfiniteProxy__ImplementationNotExist);
                          }
                          // Delegate the current call to `implementation`.
                          // This does not return to its internall call site, it will return directly to the external caller.
                          // solhint-disable-next-line no-inline-assembly
                          assembly {
                              // Copy msg.data. We take full control of memory in this inline assembly
                              // block because it will not return to Solidity code. We overwrite the
                              // Solidity scratch pad at memory position 0.
                              calldatacopy(0, 0, calldatasize())
                              // Call the implementation.
                              // out and outsize are 0 because we don't know the size yet.
                              let result := delegatecall(gas(), implementation_, 0, calldatasize(), 0, 0)
                              // Copy the returned data.
                              returndatacopy(0, 0, returndatasize())
                              if eq(result, 0) {
                                  // delegatecall returns 0 on error.
                                  revert(0, returndatasize())
                              }
                              return(0, returndatasize())
                          }
                      }
                      receive() external payable {
                          // receive method can never have calldata in EVM so no need for any logic here
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @notice implements a method to read uint256 data from storage at a bytes32 storage slot key.
                  contract StorageRead {
                      function readFromStorage(bytes32 slot_) public view returns (uint256 result_) {
                          assembly {
                              result_ := sload(slot_) // read value from the storage slot
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { Proxy } from "../infiniteProxy/proxy.sol";
                  /// @notice Fluid Liquidity infinte proxy.
                  /// Liquidity is the central point of the Instadapp Fluid architecture, it is the core interaction point
                  /// for all allow-listed protocols, such as fTokens, Vault, Flashloan, StETH protocol, DEX protocol etc.
                  contract FluidLiquidityProxy is Proxy {
                      constructor(address admin_, address dummyImplementation_) Proxy(admin_, dummyImplementation_) {}
                  }
                  

                  File 4 of 7: FluidDexT1
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  interface IProxy {
                      function setAdmin(address newAdmin_) external;
                      function setDummyImplementation(address newDummyImplementation_) external;
                      function addImplementation(address implementation_, bytes4[] calldata sigs_) external;
                      function removeImplementation(address implementation_) external;
                      function getAdmin() external view returns (address);
                      function getDummyImplementation() external view returns (address);
                      function getImplementationSigs(address impl_) external view returns (bytes4[] memory);
                      function getSigsImplementation(bytes4 sig_) external view returns (address);
                      function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @notice implements calculation of address for contracts deployed through CREATE.
                  /// Accepts contract deployed from which address & nonce
                  library AddressCalcs {
                      /// @notice                         Computes the address of a contract based
                      /// @param deployedFrom_            Address from which the contract was deployed
                      /// @param nonce_                   Nonce at which the contract was deployed
                      /// @return contract_               Address of deployed contract
                      function addressCalc(address deployedFrom_, uint nonce_) internal pure returns (address contract_) {
                          // @dev based on https://ethereum.stackexchange.com/a/61413
                          // nonce of smart contract always starts with 1. so, with nonce 0 there won't be any deployment
                          // hence, nonce of vault deployment starts with 1.
                          bytes memory data;
                          if (nonce_ == 0x00) {
                              return address(0);
                          } else if (nonce_ <= 0x7f) {
                              data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployedFrom_, uint8(nonce_));
                          } else if (nonce_ <= 0xff) {
                              data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployedFrom_, bytes1(0x81), uint8(nonce_));
                          } else if (nonce_ <= 0xffff) {
                              data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployedFrom_, bytes1(0x82), uint16(nonce_));
                          } else if (nonce_ <= 0xffffff) {
                              data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployedFrom_, bytes1(0x83), uint24(nonce_));
                          } else {
                              data = abi.encodePacked(bytes1(0xda), bytes1(0x94), deployedFrom_, bytes1(0x84), uint32(nonce_));
                          }
                          return address(uint160(uint256(keccak256(data))));
                      }
                  }// SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @title library that represents a number in BigNumber(coefficient and exponent) format to store in smaller bits.
                  /// @notice the number is divided into two parts: a coefficient and an exponent. This comes at a cost of losing some precision
                  /// at the end of the number because the exponent simply fills it with zeroes. This precision is oftentimes negligible and can
                  /// result in significant gas cost reduction due to storage space reduction.
                  /// Also note, a valid big number is as follows: if the exponent is > 0, then coefficient last bits should be occupied to have max precision.
                  /// @dev roundUp is more like a increase 1, which happens everytime for the same number.
                  /// roundDown simply sets trailing digits after coefficientSize to zero (floor), only once for the same number.
                  library BigMathMinified {
                      /// @dev constants to use for `roundUp` input param to increase readability
                      bool internal constant ROUND_DOWN = false;
                      bool internal constant ROUND_UP = true;
                      /// @dev converts `normal` number to BigNumber with `exponent` and `coefficient` (or precision).
                      /// e.g.:
                      /// 5035703444687813576399599 (normal) = (coefficient[32bits], exponent[8bits])[40bits]
                      /// 5035703444687813576399599 (decimal) => 10000101010010110100000011111011110010100110100000000011100101001101001101011101111 (binary)
                      ///                                     => 10000101010010110100000011111011000000000000000000000000000000000000000000000000000
                      ///                                                                        ^-------------------- 51(exponent) -------------- ^
                      /// coefficient = 1000,0101,0100,1011,0100,0000,1111,1011               (2236301563)
                      /// exponent =                                            0011,0011     (51)
                      /// bigNumber =   1000,0101,0100,1011,0100,0000,1111,1011,0011,0011     (572493200179)
                      ///
                      /// @param normal number which needs to be converted into Big Number
                      /// @param coefficientSize at max how many bits of precision there should be (64 = uint64 (64 bits precision))
                      /// @param exponentSize at max how many bits of exponent there should be (8 = uint8 (8 bits exponent))
                      /// @param roundUp signals if result should be rounded down or up
                      /// @return bigNumber converted bigNumber (coefficient << exponent)
                      function toBigNumber(
                          uint256 normal,
                          uint256 coefficientSize,
                          uint256 exponentSize,
                          bool roundUp
                      ) internal pure returns (uint256 bigNumber) {
                          assembly {
                              let lastBit_
                              let number_ := normal
                              if gt(number_, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x80, number_)
                                  lastBit_ := 0x80
                              }
                              if gt(number_, 0xFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x40, number_)
                                  lastBit_ := add(lastBit_, 0x40)
                              }
                              if gt(number_, 0xFFFFFFFF) {
                                  number_ := shr(0x20, number_)
                                  lastBit_ := add(lastBit_, 0x20)
                              }
                              if gt(number_, 0xFFFF) {
                                  number_ := shr(0x10, number_)
                                  lastBit_ := add(lastBit_, 0x10)
                              }
                              if gt(number_, 0xFF) {
                                  number_ := shr(0x8, number_)
                                  lastBit_ := add(lastBit_, 0x8)
                              }
                              if gt(number_, 0xF) {
                                  number_ := shr(0x4, number_)
                                  lastBit_ := add(lastBit_, 0x4)
                              }
                              if gt(number_, 0x3) {
                                  number_ := shr(0x2, number_)
                                  lastBit_ := add(lastBit_, 0x2)
                              }
                              if gt(number_, 0x1) {
                                  lastBit_ := add(lastBit_, 1)
                              }
                              if gt(number_, 0) {
                                  lastBit_ := add(lastBit_, 1)
                              }
                              if lt(lastBit_, coefficientSize) {
                                  // for throw exception
                                  lastBit_ := coefficientSize
                              }
                              let exponent := sub(lastBit_, coefficientSize)
                              let coefficient := shr(exponent, normal)
                              if and(roundUp, gt(exponent, 0)) {
                                  // rounding up is only needed if exponent is > 0, as otherwise the coefficient fully holds the original number
                                  coefficient := add(coefficient, 1)
                                  if eq(shl(coefficientSize, 1), coefficient) {
                                      // case were coefficient was e.g. 111, with adding 1 it became 1000 (in binary) and coefficientSize 3 bits
                                      // final coefficient would exceed it's size. -> reduce coefficent to 100 and increase exponent by 1.
                                      coefficient := shl(sub(coefficientSize, 1), 1)
                                      exponent := add(exponent, 1)
                                  }
                              }
                              if iszero(lt(exponent, shl(exponentSize, 1))) {
                                  // if exponent is >= exponentSize, the normal number is too big to fit within
                                  // BigNumber with too small sizes for coefficient and exponent
                                  revert(0, 0)
                              }
                              bigNumber := shl(exponentSize, coefficient)
                              bigNumber := add(bigNumber, exponent)
                          }
                      }
                      /// @dev get `normal` number from `bigNumber`, `exponentSize` and `exponentMask`
                      function fromBigNumber(
                          uint256 bigNumber,
                          uint256 exponentSize,
                          uint256 exponentMask
                      ) internal pure returns (uint256 normal) {
                          assembly {
                              let coefficient := shr(exponentSize, bigNumber)
                              let exponent := and(bigNumber, exponentMask)
                              normal := shl(exponent, coefficient)
                          }
                      }
                      /// @dev gets the most significant bit `lastBit` of a `normal` number (length of given number of binary format).
                      /// e.g.
                      /// 5035703444687813576399599 = 10000101010010110100000011111011110010100110100000000011100101001101001101011101111
                      /// lastBit =                   ^---------------------------------   83   ----------------------------------------^
                      function mostSignificantBit(uint256 normal) internal pure returns (uint lastBit) {
                          assembly {
                              let number_ := normal
                              if gt(normal, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x80, number_)
                                  lastBit := 0x80
                              }
                              if gt(number_, 0xFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x40, number_)
                                  lastBit := add(lastBit, 0x40)
                              }
                              if gt(number_, 0xFFFFFFFF) {
                                  number_ := shr(0x20, number_)
                                  lastBit := add(lastBit, 0x20)
                              }
                              if gt(number_, 0xFFFF) {
                                  number_ := shr(0x10, number_)
                                  lastBit := add(lastBit, 0x10)
                              }
                              if gt(number_, 0xFF) {
                                  number_ := shr(0x8, number_)
                                  lastBit := add(lastBit, 0x8)
                              }
                              if gt(number_, 0xF) {
                                  number_ := shr(0x4, number_)
                                  lastBit := add(lastBit, 0x4)
                              }
                              if gt(number_, 0x3) {
                                  number_ := shr(0x2, number_)
                                  lastBit := add(lastBit, 0x2)
                              }
                              if gt(number_, 0x1) {
                                  lastBit := add(lastBit, 1)
                              }
                              if gt(number_, 0) {
                                  lastBit := add(lastBit, 1)
                              }
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { BigMathMinified } from "./bigMathMinified.sol";
                  import { DexSlotsLink } from "./dexSlotsLink.sol";
                  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                  // @DEV ATTENTION: ON ANY CHANGES HERE, MAKE SURE THAT LOGIC IN VAULTS WILL STILL BE VALID.
                  // SOME CODE THERE ASSUMES DEXCALCS == LIQUIDITYCALCS.
                  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                  /// @notice implements calculation methods used for Fluid Dex such as updated withdrawal / borrow limits.
                  library DexCalcs {
                      // constants used for BigMath conversion from and to storage
                      uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
                      uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
                      uint256 internal constant FOUR_DECIMALS = 1e4;
                      uint256 internal constant X14 = 0x3fff;
                      uint256 internal constant X18 = 0x3ffff;
                      uint256 internal constant X24 = 0xffffff;
                      uint256 internal constant X33 = 0x1ffffffff;
                      uint256 internal constant X64 = 0xffffffffffffffff;
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                      CALC LIMITS                       /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev calculates withdrawal limit before an operate execution:
                      /// amount of user supply that must stay supplied (not amount that can be withdrawn).
                      /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
                      /// @param userSupplyData_ user supply data packed uint256 from storage
                      /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and converted from BigMath
                      /// @return currentWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction.
                      ///         returned value is in raw for with interest mode, normal amount for interest free mode!
                      function calcWithdrawalLimitBeforeOperate(
                          uint256 userSupplyData_,
                          uint256 userSupply_
                      ) internal view returns (uint256 currentWithdrawalLimit_) {
                          // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet).
                          // first tx where timestamp is 0 will enter `if (lastWithdrawalLimit_ == 0)` because lastWithdrawalLimit_ is not set yet.
                          // returning max withdrawal allowed, which is not exactly right but doesn't matter because the first interaction must be
                          // a deposit anyway. Important is that it would not revert.
                          // Note the first time a deposit brings the user supply amount to above the base withdrawal limit, the active limit
                          // is the fully expanded limit immediately.
                          // extract last set withdrawal limit
                          uint256 lastWithdrawalLimit_ = (userSupplyData_ >> DexSlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) &
                              X64;
                          lastWithdrawalLimit_ =
                              (lastWithdrawalLimit_ >> DEFAULT_EXPONENT_SIZE) <<
                              (lastWithdrawalLimit_ & DEFAULT_EXPONENT_MASK);
                          if (lastWithdrawalLimit_ == 0) {
                              // withdrawal limit is not activated. Max withdrawal allowed
                              return 0;
                          }
                          uint256 maxWithdrawableLimit_;
                          uint256 temp_;
                          unchecked {
                              // extract max withdrawable percent of user supply and
                              // calculate maximum withdrawable amount expandPercentage of user supply at full expansion duration elapsed
                              // e.g.: if 10% expandPercentage, meaning 10% is withdrawable after full expandDuration has elapsed.
                              // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              maxWithdrawableLimit_ =
                                  (((userSupplyData_ >> DexSlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14) * userSupply_) /
                                  FOUR_DECIMALS;
                              // time elapsed since last withdrawal limit was set (in seconds)
                              // @dev last process timestamp is guaranteed to exist for withdrawal, as a supply must have happened before.
                              // last timestamp can not be > current timestamp
                              temp_ = block.timestamp - ((userSupplyData_ >> DexSlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) & X33);
                          }
                          // calculate withdrawable amount of expandPercent that is elapsed of expandDuration.
                          // e.g. if 60% of expandDuration has elapsed, then user should be able to withdraw 6% of user supply, down to 94%.
                          // Note: no explicit check for this needed, it is covered by setting minWithdrawalLimit_ if needed.
                          temp_ =
                              (maxWithdrawableLimit_ * temp_) /
                              // extract expand duration: After this, decrement won't happen (user can withdraw 100% of withdraw limit)
                              ((userSupplyData_ >> DexSlotsLink.BITS_USER_SUPPLY_EXPAND_DURATION) & X24); // expand duration can never be 0
                          // calculate expanded withdrawal limit: last withdrawal limit - withdrawable amount.
                          // Note: withdrawable amount here can grow bigger than userSupply if timeElapsed is a lot bigger than expandDuration,
                          // which would cause the subtraction `lastWithdrawalLimit_ - withdrawableAmount_` to revert. In that case, set 0
                          // which will cause minimum (fully expanded) withdrawal limit to be set in lines below.
                          unchecked {
                              // underflow explicitly checked & handled
                              currentWithdrawalLimit_ = lastWithdrawalLimit_ > temp_ ? lastWithdrawalLimit_ - temp_ : 0;
                              // calculate minimum withdrawal limit: minimum amount of user supply that must stay supplied at full expansion.
                              // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
                              temp_ = userSupply_ - maxWithdrawableLimit_;
                          }
                          // if withdrawal limit is decreased below minimum then set minimum
                          // (e.g. when more than expandDuration time has elapsed)
                          if (temp_ > currentWithdrawalLimit_) {
                              currentWithdrawalLimit_ = temp_;
                          }
                      }
                      /// @dev calculates withdrawal limit after an operate execution:
                      /// amount of user supply that must stay supplied (not amount that can be withdrawn).
                      /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
                      /// @param userSupplyData_ user supply data packed uint256 from storage
                      /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and added / subtracted with the executed operate amount
                      /// @param newWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction, result from `calcWithdrawalLimitBeforeOperate`
                      /// @return withdrawalLimit_ updated withdrawal limit that should be written to storage. returned value is in
                      ///                          raw for with interest mode, normal amount for interest free mode!
                      function calcWithdrawalLimitAfterOperate(
                          uint256 userSupplyData_,
                          uint256 userSupply_,
                          uint256 newWithdrawalLimit_
                      ) internal pure returns (uint256) {
                          // temp_ => base withdrawal limit. below this, maximum withdrawals are allowed
                          uint256 temp_ = (userSupplyData_ >> DexSlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          // if user supply is below base limit then max withdrawals are allowed
                          if (userSupply_ < temp_) {
                              return 0;
                          }
                          // temp_ => withdrawal limit expandPercent (is in 1e2 decimals)
                          temp_ = (userSupplyData_ >> DexSlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14;
                          unchecked {
                              // temp_ => minimum withdrawal limit: userSupply - max withdrawable limit (userSupply * expandPercent))
                              // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
                              temp_ = userSupply_ - ((userSupply_ * temp_) / FOUR_DECIMALS);
                          }
                          // if new (before operation) withdrawal limit is less than minimum limit then set minimum limit.
                          // e.g. can happen on new deposits. withdrawal limit is instantly fully expanded in a scenario where
                          // increased deposit amount outpaces withrawals.
                          if (temp_ > newWithdrawalLimit_) {
                              return temp_;
                          }
                          return newWithdrawalLimit_;
                      }
                      /// @dev calculates borrow limit before an operate execution:
                      /// total amount user borrow can reach (not borrowable amount in current operation).
                      /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
                      /// @param userBorrowData_ user borrow data packed uint256 from storage
                      /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_`
                      /// @return currentBorrowLimit_ current borrow limit updated for expansion since last interaction. returned value is in
                      ///                             raw for with interest mode, normal amount for interest free mode!
                      function calcBorrowLimitBeforeOperate(
                          uint256 userBorrowData_,
                          uint256 userBorrow_
                      ) internal view returns (uint256 currentBorrowLimit_) {
                          // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet) -> base limit.
                          // first tx where timestamp is 0 will enter `if (maxExpandedBorrowLimit_ < baseBorrowLimit_)` because `userBorrow_` and thus
                          // `maxExpansionLimit_` and thus `maxExpandedBorrowLimit_` is 0 and `baseBorrowLimit_` can not be 0.
                          // temp_ = extract borrow expand percent (is in 1e2 decimals)
                          uint256 temp_ = (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14;
                          uint256 maxExpansionLimit_;
                          uint256 maxExpandedBorrowLimit_;
                          unchecked {
                              // calculate max expansion limit: Max amount limit can expand to since last interaction
                              // userBorrow_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              maxExpansionLimit_ = ((userBorrow_ * temp_) / FOUR_DECIMALS);
                              // calculate max borrow limit: Max point limit can increase to since last interaction
                              maxExpandedBorrowLimit_ = userBorrow_ + maxExpansionLimit_;
                          }
                          // currentBorrowLimit_ = extract base borrow limit
                          currentBorrowLimit_ = (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
                          currentBorrowLimit_ =
                              (currentBorrowLimit_ >> DEFAULT_EXPONENT_SIZE) <<
                              (currentBorrowLimit_ & DEFAULT_EXPONENT_MASK);
                          if (maxExpandedBorrowLimit_ < currentBorrowLimit_) {
                              return currentBorrowLimit_;
                          }
                          // time elapsed since last borrow limit was set (in seconds)
                          unchecked {
                              // temp_ = timeElapsed_ (last timestamp can not be > current timestamp)
                              temp_ = block.timestamp - ((userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP) & X33); // extract last update timestamp
                          }
                          // currentBorrowLimit_ = expandedBorrowableAmount + extract last set borrow limit
                          currentBorrowLimit_ =
                              // calculate borrow limit expansion since last interaction for `expandPercent` that is elapsed of `expandDuration`.
                              // divisor is extract expand duration (after this, full expansion to expandPercentage happened).
                              ((maxExpansionLimit_ * temp_) /
                                  ((userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24)) + // expand duration can never be 0
                              //  extract last set borrow limit
                              BigMathMinified.fromBigNumber(
                                  (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) & X64,
                                  DEFAULT_EXPONENT_SIZE,
                                  DEFAULT_EXPONENT_MASK
                              );
                          // if timeElapsed is bigger than expandDuration, new borrow limit would be > max expansion,
                          // so set to `maxExpandedBorrowLimit_` in that case.
                          // also covers the case where last process timestamp = 0 (timeElapsed would simply be very big)
                          if (currentBorrowLimit_ > maxExpandedBorrowLimit_) {
                              currentBorrowLimit_ = maxExpandedBorrowLimit_;
                          }
                          // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
                          temp_ = (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (currentBorrowLimit_ > temp_) {
                              currentBorrowLimit_ = temp_;
                          }
                      }
                      /// @dev calculates borrow limit after an operate execution:
                      /// total amount user borrow can reach (not borrowable amount in current operation).
                      /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
                      /// @param userBorrowData_ user borrow data packed uint256 from storage
                      /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_` and added / subtracted with the executed operate amount
                      /// @param newBorrowLimit_ current borrow limit updated for expansion since last interaction, result from `calcBorrowLimitBeforeOperate`
                      /// @return borrowLimit_ updated borrow limit that should be written to storage.
                      ///                      returned value is in raw for with interest mode, normal amount for interest free mode!
                      function calcBorrowLimitAfterOperate(
                          uint256 userBorrowData_,
                          uint256 userBorrow_,
                          uint256 newBorrowLimit_
                      ) internal pure returns (uint256 borrowLimit_) {
                          // temp_ = extract borrow expand percent
                          uint256 temp_ = (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14; // (is in 1e2 decimals)
                          unchecked {
                              // borrowLimit_ = calculate maximum borrow limit at full expansion.
                              // userBorrow_ needs to be at least 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              borrowLimit_ = userBorrow_ + ((userBorrow_ * temp_) / FOUR_DECIMALS);
                          }
                          // temp_ = extract base borrow limit
                          temp_ = (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (borrowLimit_ < temp_) {
                              // below base limit, borrow limit is always base limit
                              return temp_;
                          }
                          // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
                          temp_ = (userBorrowData_ >> DexSlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          // make sure fully expanded borrow limit is not above hard max borrow limit
                          if (borrowLimit_ > temp_) {
                              borrowLimit_ = temp_;
                          }
                          // if new borrow limit (from before operate) is > max borrow limit, set max borrow limit.
                          // (e.g. on a repay shrinking instantly to fully expanded borrow limit from new borrow amount. shrinking is instant)
                          if (newBorrowLimit_ > borrowLimit_) {
                              return borrowLimit_;
                          }
                          return newBorrowLimit_;
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @notice library that helps in reading / working with storage slot data of Fluid Dex.
                  /// @dev as all data for Fluid Dex is internal, any data must be fetched directly through manual
                  /// slot reading through this library or, if gas usage is less important, through the FluidDexResolver.
                  library DexSlotsLink {
                      /// @dev storage slot for variables at Dex
                      uint256 internal constant DEX_VARIABLES_SLOT = 0;
                      /// @dev storage slot for variables2 at Dex
                      uint256 internal constant DEX_VARIABLES2_SLOT = 1;
                      /// @dev storage slot for total supply shares at Dex
                      uint256 internal constant DEX_TOTAL_SUPPLY_SHARES_SLOT = 2;
                      /// @dev storage slot for user supply mapping at Dex
                      uint256 internal constant DEX_USER_SUPPLY_MAPPING_SLOT = 3;
                      /// @dev storage slot for total borrow shares at Dex
                      uint256 internal constant DEX_TOTAL_BORROW_SHARES_SLOT = 4;
                      /// @dev storage slot for user borrow mapping at Dex
                      uint256 internal constant DEX_USER_BORROW_MAPPING_SLOT = 5;
                      /// @dev storage slot for oracle mapping at Dex
                      uint256 internal constant DEX_ORACLE_MAPPING_SLOT = 6;
                      /// @dev storage slot for range and threshold shifts at Dex
                      uint256 internal constant DEX_RANGE_THRESHOLD_SHIFTS_SLOT = 7;
                      /// @dev storage slot for center price shift at Dex
                      uint256 internal constant DEX_CENTER_PRICE_SHIFT_SLOT = 8;
                      // --------------------------------
                      // @dev stacked uint256 storage slots bits position data for each:
                      // UserSupplyData
                      uint256 internal constant BITS_USER_SUPPLY_ALLOWED = 0;
                      uint256 internal constant BITS_USER_SUPPLY_AMOUNT = 1;
                      uint256 internal constant BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT = 65;
                      uint256 internal constant BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP = 129;
                      uint256 internal constant BITS_USER_SUPPLY_EXPAND_PERCENT = 162;
                      uint256 internal constant BITS_USER_SUPPLY_EXPAND_DURATION = 176;
                      uint256 internal constant BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT = 200;
                      // UserBorrowData
                      uint256 internal constant BITS_USER_BORROW_ALLOWED = 0;
                      uint256 internal constant BITS_USER_BORROW_AMOUNT = 1;
                      uint256 internal constant BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT = 65;
                      uint256 internal constant BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP = 129;
                      uint256 internal constant BITS_USER_BORROW_EXPAND_PERCENT = 162;
                      uint256 internal constant BITS_USER_BORROW_EXPAND_DURATION = 176;
                      uint256 internal constant BITS_USER_BORROW_BASE_BORROW_LIMIT = 200;
                      uint256 internal constant BITS_USER_BORROW_MAX_BORROW_LIMIT = 218;
                      // --------------------------------
                      /// @notice Calculating the slot ID for Dex contract for single mapping at `slot_` for `key_`
                      function calculateMappingStorageSlot(uint256 slot_, address key_) internal pure returns (bytes32) {
                          return keccak256(abi.encode(key_, slot_));
                      }
                      /// @notice Calculating the slot ID for Dex contract for double mapping at `slot_` for `key1_` and `key2_`
                      function calculateDoubleMappingStorageSlot(
                          uint256 slot_,
                          address key1_,
                          address key2_
                      ) internal pure returns (bytes32) {
                          bytes32 intermediateSlot_ = keccak256(abi.encode(key1_, slot_));
                          return keccak256(abi.encode(key2_, intermediateSlot_));
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  library LibsErrorTypes {
                      /***********************************|
                      |         LiquidityCalcs            | 
                      |__________________________________*/
                      /// @notice thrown when supply or borrow exchange price is zero at calc token data (token not configured yet)
                      uint256 internal constant LiquidityCalcs__ExchangePriceZero = 70001;
                      /// @notice thrown when rate data is set to a version that is not implemented
                      uint256 internal constant LiquidityCalcs__UnsupportedRateVersion = 70002;
                      /// @notice thrown when the calculated borrow rate turns negative. This should never happen.
                      uint256 internal constant LiquidityCalcs__BorrowRateNegative = 70003;
                      /***********************************|
                      |           SafeTransfer            | 
                      |__________________________________*/
                      /// @notice thrown when safe transfer from for an ERC20 fails
                      uint256 internal constant SafeTransfer__TransferFromFailed = 71001;
                      /// @notice thrown when safe transfer for an ERC20 fails
                      uint256 internal constant SafeTransfer__TransferFailed = 71002;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
                  import { LiquiditySlotsLink } from "./liquiditySlotsLink.sol";
                  import { BigMathMinified } from "./bigMathMinified.sol";
                  /// @notice implements calculation methods used for Fluid liquidity such as updated exchange prices,
                  /// borrow rate, withdrawal / borrow limits, revenue amount.
                  library LiquidityCalcs {
                      error FluidLiquidityCalcsError(uint256 errorId_);
                      /// @notice emitted if the calculated borrow rate surpassed max borrow rate (16 bits) and was capped at maximum value 65535
                      event BorrowRateMaxCap();
                      /// @dev constants as from Liquidity variables.sol
                      uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
                      /// @dev Ignoring leap years
                      uint256 internal constant SECONDS_PER_YEAR = 365 days;
                      // constants used for BigMath conversion from and to storage
                      uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
                      uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
                      uint256 internal constant FOUR_DECIMALS = 1e4;
                      uint256 internal constant TWELVE_DECIMALS = 1e12;
                      uint256 internal constant X14 = 0x3fff;
                      uint256 internal constant X15 = 0x7fff;
                      uint256 internal constant X16 = 0xffff;
                      uint256 internal constant X18 = 0x3ffff;
                      uint256 internal constant X24 = 0xffffff;
                      uint256 internal constant X33 = 0x1ffffffff;
                      uint256 internal constant X64 = 0xffffffffffffffff;
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                  CALC EXCHANGE PRICES                  /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev calculates interest (exchange prices) for a token given its' exchangePricesAndConfig from storage.
                      /// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
                      /// @return supplyExchangePrice_ updated supplyExchangePrice
                      /// @return borrowExchangePrice_ updated borrowExchangePrice
                      function calcExchangePrices(
                          uint256 exchangePricesAndConfig_
                      ) internal view returns (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) {
                          // Extracting exchange prices
                          supplyExchangePrice_ =
                              (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) &
                              X64;
                          borrowExchangePrice_ =
                              (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) &
                              X64;
                          if (supplyExchangePrice_ == 0 || borrowExchangePrice_ == 0) {
                              revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__ExchangePriceZero);
                          }
                          uint256 temp_ = exchangePricesAndConfig_ & X16; // temp_ = borrowRate
                          unchecked {
                              // last timestamp can not be > current timestamp
                              uint256 secondsSinceLastUpdate_ = block.timestamp -
                                  ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) & X33);
                              uint256 borrowRatio_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO) &
                                  X15;
                              if (secondsSinceLastUpdate_ == 0 || temp_ == 0 || borrowRatio_ == 1) {
                                  // if no time passed, borrow rate is 0, or no raw borrowings: no exchange price update needed
                                  // (if borrowRatio_ == 1 means there is only borrowInterestFree, as first bit is 1 and rest is 0)
                                  return (supplyExchangePrice_, borrowExchangePrice_);
                              }
                              // calculate new borrow exchange price.
                              // formula borrowExchangePriceIncrease: previous price * borrow rate * secondsSinceLastUpdate_.
                              // nominator is max uint112 (uint64 * uint16 * uint32). Divisor can not be 0.
                              borrowExchangePrice_ +=
                                  (borrowExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
                                  (SECONDS_PER_YEAR * FOUR_DECIMALS);
                              // FOR SUPPLY EXCHANGE PRICE:
                              // all yield paid by borrowers (in mode with interest) goes to suppliers in mode with interest.
                              // formula: previous price * supply rate * secondsSinceLastUpdate_.
                              // where supply rate = (borrow rate  - revenueFee%) * ratioSupplyYield. And
                              // ratioSupplyYield = utilization * supplyRatio * borrowRatio
                              //
                              // Example:
                              // supplyRawInterest is 80, supplyInterestFree is 20. totalSupply is 100. BorrowedRawInterest is 50.
                              // BorrowInterestFree is 10. TotalBorrow is 60. borrow rate 40%, revenueFee 10%.
                              // yield is 10 (so half a year must have passed).
                              // supplyRawInterest must become worth 89. totalSupply must become 109. BorrowedRawInterest must become 60.
                              // borrowInterestFree must still be 10. supplyInterestFree still 20. totalBorrow 70.
                              // supplyExchangePrice would have to go from 1 to 1,125 (+ 0.125). borrowExchangePrice from 1 to 1,2 (+0.2).
                              // utilization is 60%. supplyRatio = 20 / 80 = 25% (only 80% of lenders receiving yield).
                              // borrowRatio = 10 / 50 = 20% (only 83,333% of borrowers paying yield):
                              // x of borrowers paying yield = 100% - (20 / (100 + 20)) = 100% - 16.6666666% = 83,333%.
                              // ratioSupplyYield = 60% * 83,33333% * (100% + 20%) = 62,5%
                              // supplyRate = (40% * (100% - 10%)) * = 36% * 62,5% = 22.5%
                              // increase in supplyExchangePrice, assuming 100 as previous price.
                              // 100 * 22,5% * 1/2 (half a year) = 0,1125.
                              // cross-check supplyRawInterest worth = 80 * 1.1125 = 89. totalSupply worth = 89 + 20.
                              // -------------- 1. calculate ratioSupplyYield --------------------------------
                              // step1: utilization * supplyRatio (or actually part of lenders receiving yield)
                              // temp_ => supplyRatio (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                              // if first bit 0 then ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
                              // else ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
                              temp_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) & X15;
                              if (temp_ == 1) {
                                  // if no raw supply: no exchange price update needed
                                  // (if supplyRatio_ == 1 means there is only supplyInterestFree, as first bit is 1 and rest is 0)
                                  return (supplyExchangePrice_, borrowExchangePrice_);
                              }
                              // ratioSupplyYield precision is 1e27 as 100% for increased precision when supplyInterestFree > supplyWithInterest
                              if (temp_ & 1 == 1) {
                                  // ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
                                  temp_ = temp_ >> 1;
                                  // Note: case where temp_ == 0 (only supplyInterestFree, no yield) already covered by early return
                                  // in the if statement a little above.
                                  // based on above example but supplyRawInterest is 20, supplyInterestFree is 80. no fee.
                                  // supplyRawInterest must become worth 30. totalSupply must become 110.
                                  // supplyExchangePrice would have to go from 1 to 1,5. borrowExchangePrice from 1 to 1,2.
                                  // so ratioSupplyYield must come out as 2.5 (250%).
                                  // supplyRatio would be (20 * 10_000 / 80) = 2500. but must be inverted.
                                  temp_ = (1e27 * FOUR_DECIMALS) / temp_; // e.g. 1e31 / 2500 = 4e27. (* 1e27 for precision)
                                  // e.g. 5_000 * (1e27 + 4e27) / 1e27 = 25_000 (=250%).
                                  temp_ =
                                      // utilization * (100% + 100% / supplyRatio)
                                      (((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) *
                                          (1e27 + temp_)) / // extract utilization (max 16_383 so there is no way this can overflow).
                                      (FOUR_DECIMALS);
                                  // max possible value of temp_ here is 16383 * (1e27 + 1e31) / 1e4 = ~1.64e31
                              } else {
                                  // ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
                                  temp_ = temp_ >> 1;
                                  // if temp_ == 0 then only supplyWithInterest => full yield. temp_ is already 0
                                  // e.g. 5_000 * 10_000 + (20 * 10_000 / 80) / 10_000 = 5000 * 12500 / 10000 = 6250 (=62.5%).
                                  temp_ =
                                      // 1e27 * utilization * (100% + supplyRatio) / 100%
                                      (1e27 *
                                          ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) * // extract utilization (max 16_383 so there is no way this can overflow).
                                          (FOUR_DECIMALS + temp_)) /
                                      (FOUR_DECIMALS * FOUR_DECIMALS);
                                  // max possible temp_ value: 1e27 * 16383 * 2e4 / 1e8 = 3.2766e27
                              }
                              // from here temp_ => ratioSupplyYield (utilization * supplyRatio part) scaled by 1e27. max possible value ~1.64e31
                              // step2 of ratioSupplyYield: add borrowRatio (only x% of borrowers paying yield)
                              if (borrowRatio_ & 1 == 1) {
                                  // ratio is borrowWithInterest / borrowInterestFree (borrowInterestFree is bigger)
                                  borrowRatio_ = borrowRatio_ >> 1;
                                  // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                                  // Note: case where borrowRatio_ == 0 (only borrowInterestFree, no yield) already covered
                                  // at the beginning of the method by early return if `borrowRatio_ == 1`.
                                  // based on above example but borrowRawInterest is 10, borrowInterestFree is 50. no fee. borrowRatio = 20%.
                                  // so only 16.66% of borrowers are paying yield. so the 100% - part of the formula is not needed.
                                  // x of borrowers paying yield = (borrowRatio / (100 + borrowRatio)) = 16.6666666%
                                  // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                                  borrowRatio_ = (borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_);
                                  // max value here for borrowRatio_ is (1e31 / (1e4 + 1e4))= 5e26 (= 50% of borrowers paying yield).
                              } else {
                                  // ratio is borrowInterestFree / borrowWithInterest (borrowWithInterest is bigger)
                                  borrowRatio_ = borrowRatio_ >> 1;
                                  // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                                  // x of borrowers paying yield = 100% - (borrowRatio / (100 + borrowRatio)) = 100% - 16.6666666% = 83,333%.
                                  borrowRatio_ = (1e27 - ((borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_)));
                                  // borrowRatio can never be > 100%. so max subtraction can be 100% - 100% / 200%.
                                  // or if borrowRatio_ is 0 -> 100% - 0. or if borrowRatio_ is 1 -> 100% - 1 / 101.
                                  // max value here for borrowRatio_ is 1e27 - 0 = 1e27 (= 100% of borrowers paying yield).
                              }
                              // temp_ => ratioSupplyYield. scaled down from 1e25 = 1% each to normal percent precision 1e2 = 1%.
                              // max nominator value is ~1.64e31 * 1e27 = 1.64e58. max result = 1.64e8
                              temp_ = (FOUR_DECIMALS * temp_ * borrowRatio_) / 1e54;
                              // 2. calculate supply rate
                              // temp_ => supply rate (borrow rate  - revenueFee%) * ratioSupplyYield.
                              // division part is done in next step to increase precision. (divided by 2x FOUR_DECIMALS, fee + borrowRate)
                              // Note that all calculation divisions for supplyExchangePrice are rounded down.
                              // Note supply rate can be bigger than the borrowRate, e.g. if there are only few lenders with interest
                              // but more suppliers not earning interest.
                              temp_ = ((exchangePricesAndConfig_ & X16) * // borrow rate
                                  temp_ * // ratioSupplyYield
                                  (FOUR_DECIMALS - ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_FEE) & X14))); // revenueFee
                              // fee can not be > 100%. max possible = 65535 * ~1.64e8 * 1e4 =~1.074774e17.
                              // 3. calculate increase in supply exchange price
                              supplyExchangePrice_ += ((supplyExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
                                  (SECONDS_PER_YEAR * FOUR_DECIMALS * FOUR_DECIMALS * FOUR_DECIMALS));
                              // max possible nominator = max uint 64 * 1.074774e17 * max uint32 = ~8.52e45. Denominator can not be 0.
                          }
                      }
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                     CALC REVENUE                       /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev gets the `revenueAmount_` for a token given its' totalAmounts and exchangePricesAndConfig from storage
                      /// and the current balance of the Fluid liquidity contract for the token.
                      /// @param totalAmounts_ total amounts packed uint256 read from storage
                      /// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
                      /// @param liquidityTokenBalance_   current balance of Liquidity contract (IERC20(token_).balanceOf(address(this)))
                      /// @return revenueAmount_ collectable revenue amount
                      function calcRevenue(
                          uint256 totalAmounts_,
                          uint256 exchangePricesAndConfig_,
                          uint256 liquidityTokenBalance_
                      ) internal view returns (uint256 revenueAmount_) {
                          // @dev no need to super-optimize this method as it is only used by admin
                          // calculate the new exchange prices based on earned interest
                          (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) = calcExchangePrices(exchangePricesAndConfig_);
                          // total supply = interest free + with interest converted from raw
                          uint256 totalSupply_ = getTotalSupply(totalAmounts_, supplyExchangePrice_);
                          if (totalSupply_ > 0) {
                              // available revenue: balanceOf(token) + totalBorrowings - totalLendings.
                              revenueAmount_ = liquidityTokenBalance_ + getTotalBorrow(totalAmounts_, borrowExchangePrice_);
                              // ensure there is no possible case because of rounding etc. where this would revert,
                              // explicitly check if >
                              revenueAmount_ = revenueAmount_ > totalSupply_ ? revenueAmount_ - totalSupply_ : 0;
                              // Note: if utilization > 100% (totalSupply < totalBorrow), then all the amount above 100% utilization
                              // can only be revenue.
                          } else {
                              // if supply is 0, then rest of balance can be withdrawn as revenue so that no amounts get stuck
                              revenueAmount_ = liquidityTokenBalance_;
                          }
                      }
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                      CALC LIMITS                       /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev calculates withdrawal limit before an operate execution:
                      /// amount of user supply that must stay supplied (not amount that can be withdrawn).
                      /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
                      /// @param userSupplyData_ user supply data packed uint256 from storage
                      /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and converted from BigMath
                      /// @return currentWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction.
                      ///         returned value is in raw for with interest mode, normal amount for interest free mode!
                      function calcWithdrawalLimitBeforeOperate(
                          uint256 userSupplyData_,
                          uint256 userSupply_
                      ) internal view returns (uint256 currentWithdrawalLimit_) {
                          // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet).
                          // first tx where timestamp is 0 will enter `if (lastWithdrawalLimit_ == 0)` because lastWithdrawalLimit_ is not set yet.
                          // returning max withdrawal allowed, which is not exactly right but doesn't matter because the first interaction must be
                          // a deposit anyway. Important is that it would not revert.
                          // Note the first time a deposit brings the user supply amount to above the base withdrawal limit, the active limit
                          // is the fully expanded limit immediately.
                          // extract last set withdrawal limit
                          uint256 lastWithdrawalLimit_ = (userSupplyData_ >>
                              LiquiditySlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) & X64;
                          lastWithdrawalLimit_ =
                              (lastWithdrawalLimit_ >> DEFAULT_EXPONENT_SIZE) <<
                              (lastWithdrawalLimit_ & DEFAULT_EXPONENT_MASK);
                          if (lastWithdrawalLimit_ == 0) {
                              // withdrawal limit is not activated. Max withdrawal allowed
                              return 0;
                          }
                          uint256 maxWithdrawableLimit_;
                          uint256 temp_;
                          unchecked {
                              // extract max withdrawable percent of user supply and
                              // calculate maximum withdrawable amount expandPercentage of user supply at full expansion duration elapsed
                              // e.g.: if 10% expandPercentage, meaning 10% is withdrawable after full expandDuration has elapsed.
                              // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              maxWithdrawableLimit_ =
                                  (((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14) * userSupply_) /
                                  FOUR_DECIMALS;
                              // time elapsed since last withdrawal limit was set (in seconds)
                              // @dev last process timestamp is guaranteed to exist for withdrawal, as a supply must have happened before.
                              // last timestamp can not be > current timestamp
                              temp_ =
                                  block.timestamp -
                                  ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) & X33);
                          }
                          // calculate withdrawable amount of expandPercent that is elapsed of expandDuration.
                          // e.g. if 60% of expandDuration has elapsed, then user should be able to withdraw 6% of user supply, down to 94%.
                          // Note: no explicit check for this needed, it is covered by setting minWithdrawalLimit_ if needed.
                          temp_ =
                              (maxWithdrawableLimit_ * temp_) /
                              // extract expand duration: After this, decrement won't happen (user can withdraw 100% of withdraw limit)
                              ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_DURATION) & X24); // expand duration can never be 0
                          // calculate expanded withdrawal limit: last withdrawal limit - withdrawable amount.
                          // Note: withdrawable amount here can grow bigger than userSupply if timeElapsed is a lot bigger than expandDuration,
                          // which would cause the subtraction `lastWithdrawalLimit_ - withdrawableAmount_` to revert. In that case, set 0
                          // which will cause minimum (fully expanded) withdrawal limit to be set in lines below.
                          unchecked {
                              // underflow explicitly checked & handled
                              currentWithdrawalLimit_ = lastWithdrawalLimit_ > temp_ ? lastWithdrawalLimit_ - temp_ : 0;
                              // calculate minimum withdrawal limit: minimum amount of user supply that must stay supplied at full expansion.
                              // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
                              temp_ = userSupply_ - maxWithdrawableLimit_;
                          }
                          // if withdrawal limit is decreased below minimum then set minimum
                          // (e.g. when more than expandDuration time has elapsed)
                          if (temp_ > currentWithdrawalLimit_) {
                              currentWithdrawalLimit_ = temp_;
                          }
                      }
                      /// @dev calculates withdrawal limit after an operate execution:
                      /// amount of user supply that must stay supplied (not amount that can be withdrawn).
                      /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
                      /// @param userSupplyData_ user supply data packed uint256 from storage
                      /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and added / subtracted with the executed operate amount
                      /// @param newWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction, result from `calcWithdrawalLimitBeforeOperate`
                      /// @return withdrawalLimit_ updated withdrawal limit that should be written to storage. returned value is in
                      ///                          raw for with interest mode, normal amount for interest free mode!
                      function calcWithdrawalLimitAfterOperate(
                          uint256 userSupplyData_,
                          uint256 userSupply_,
                          uint256 newWithdrawalLimit_
                      ) internal pure returns (uint256) {
                          // temp_ => base withdrawal limit. below this, maximum withdrawals are allowed
                          uint256 temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          // if user supply is below base limit then max withdrawals are allowed
                          if (userSupply_ < temp_) {
                              return 0;
                          }
                          // temp_ => withdrawal limit expandPercent (is in 1e2 decimals)
                          temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14;
                          unchecked {
                              // temp_ => minimum withdrawal limit: userSupply - max withdrawable limit (userSupply * expandPercent))
                              // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
                              temp_ = userSupply_ - ((userSupply_ * temp_) / FOUR_DECIMALS);
                          }
                          // if new (before operation) withdrawal limit is less than minimum limit then set minimum limit.
                          // e.g. can happen on new deposits. withdrawal limit is instantly fully expanded in a scenario where
                          // increased deposit amount outpaces withrawals.
                          if (temp_ > newWithdrawalLimit_) {
                              return temp_;
                          }
                          return newWithdrawalLimit_;
                      }
                      /// @dev calculates borrow limit before an operate execution:
                      /// total amount user borrow can reach (not borrowable amount in current operation).
                      /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
                      /// @param userBorrowData_ user borrow data packed uint256 from storage
                      /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_`
                      /// @return currentBorrowLimit_ current borrow limit updated for expansion since last interaction. returned value is in
                      ///                             raw for with interest mode, normal amount for interest free mode!
                      function calcBorrowLimitBeforeOperate(
                          uint256 userBorrowData_,
                          uint256 userBorrow_
                      ) internal view returns (uint256 currentBorrowLimit_) {
                          // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet) -> base limit.
                          // first tx where timestamp is 0 will enter `if (maxExpandedBorrowLimit_ < baseBorrowLimit_)` because `userBorrow_` and thus
                          // `maxExpansionLimit_` and thus `maxExpandedBorrowLimit_` is 0 and `baseBorrowLimit_` can not be 0.
                          // temp_ = extract borrow expand percent (is in 1e2 decimals)
                          uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14;
                          uint256 maxExpansionLimit_;
                          uint256 maxExpandedBorrowLimit_;
                          unchecked {
                              // calculate max expansion limit: Max amount limit can expand to since last interaction
                              // userBorrow_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              maxExpansionLimit_ = ((userBorrow_ * temp_) / FOUR_DECIMALS);
                              // calculate max borrow limit: Max point limit can increase to since last interaction
                              maxExpandedBorrowLimit_ = userBorrow_ + maxExpansionLimit_;
                          }
                          // currentBorrowLimit_ = extract base borrow limit
                          currentBorrowLimit_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
                          currentBorrowLimit_ =
                              (currentBorrowLimit_ >> DEFAULT_EXPONENT_SIZE) <<
                              (currentBorrowLimit_ & DEFAULT_EXPONENT_MASK);
                          if (maxExpandedBorrowLimit_ < currentBorrowLimit_) {
                              return currentBorrowLimit_;
                          }
                          // time elapsed since last borrow limit was set (in seconds)
                          unchecked {
                              // temp_ = timeElapsed_ (last timestamp can not be > current timestamp)
                              temp_ =
                                  block.timestamp -
                                  ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP) & X33); // extract last update timestamp
                          }
                          // currentBorrowLimit_ = expandedBorrowableAmount + extract last set borrow limit
                          currentBorrowLimit_ =
                              // calculate borrow limit expansion since last interaction for `expandPercent` that is elapsed of `expandDuration`.
                              // divisor is extract expand duration (after this, full expansion to expandPercentage happened).
                              ((maxExpansionLimit_ * temp_) /
                                  ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24)) + // expand duration can never be 0
                              //  extract last set borrow limit
                              BigMathMinified.fromBigNumber(
                                  (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) & X64,
                                  DEFAULT_EXPONENT_SIZE,
                                  DEFAULT_EXPONENT_MASK
                              );
                          // if timeElapsed is bigger than expandDuration, new borrow limit would be > max expansion,
                          // so set to `maxExpandedBorrowLimit_` in that case.
                          // also covers the case where last process timestamp = 0 (timeElapsed would simply be very big)
                          if (currentBorrowLimit_ > maxExpandedBorrowLimit_) {
                              currentBorrowLimit_ = maxExpandedBorrowLimit_;
                          }
                          // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
                          temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (currentBorrowLimit_ > temp_) {
                              currentBorrowLimit_ = temp_;
                          }
                      }
                      /// @dev calculates borrow limit after an operate execution:
                      /// total amount user borrow can reach (not borrowable amount in current operation).
                      /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
                      /// @param userBorrowData_ user borrow data packed uint256 from storage
                      /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_` and added / subtracted with the executed operate amount
                      /// @param newBorrowLimit_ current borrow limit updated for expansion since last interaction, result from `calcBorrowLimitBeforeOperate`
                      /// @return borrowLimit_ updated borrow limit that should be written to storage.
                      ///                      returned value is in raw for with interest mode, normal amount for interest free mode!
                      function calcBorrowLimitAfterOperate(
                          uint256 userBorrowData_,
                          uint256 userBorrow_,
                          uint256 newBorrowLimit_
                      ) internal pure returns (uint256 borrowLimit_) {
                          // temp_ = extract borrow expand percent
                          uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14; // (is in 1e2 decimals)
                          unchecked {
                              // borrowLimit_ = calculate maximum borrow limit at full expansion.
                              // userBorrow_ needs to be at least 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              borrowLimit_ = userBorrow_ + ((userBorrow_ * temp_) / FOUR_DECIMALS);
                          }
                          // temp_ = extract base borrow limit
                          temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (borrowLimit_ < temp_) {
                              // below base limit, borrow limit is always base limit
                              return temp_;
                          }
                          // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
                          temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          // make sure fully expanded borrow limit is not above hard max borrow limit
                          if (borrowLimit_ > temp_) {
                              borrowLimit_ = temp_;
                          }
                          // if new borrow limit (from before operate) is > max borrow limit, set max borrow limit.
                          // (e.g. on a repay shrinking instantly to fully expanded borrow limit from new borrow amount. shrinking is instant)
                          if (newBorrowLimit_ > borrowLimit_) {
                              return borrowLimit_;
                          }
                          return newBorrowLimit_;
                      }
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                      CALC RATES                        /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev Calculates new borrow rate from utilization for a token
                      /// @param rateData_ rate data packed uint256 from storage for the token
                      /// @param utilization_ totalBorrow / totalSupply. 1e4 = 100% utilization
                      /// @return rate_ rate for that particular token in 1e2 precision (e.g. 5% rate = 500)
                      function calcBorrowRateFromUtilization(uint256 rateData_, uint256 utilization_) internal returns (uint256 rate_) {
                          // extract rate version: 4 bits (0xF) starting from bit 0
                          uint256 rateVersion_ = (rateData_ & 0xF);
                          if (rateVersion_ == 1) {
                              rate_ = calcRateV1(rateData_, utilization_);
                          } else if (rateVersion_ == 2) {
                              rate_ = calcRateV2(rateData_, utilization_);
                          } else {
                              revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__UnsupportedRateVersion);
                          }
                          if (rate_ > X16) {
                              // hard cap for borrow rate at maximum value 16 bits (65535) to make sure it does not overflow storage space.
                              // this is unlikely to ever happen if configs stay within expected levels.
                              rate_ = X16;
                              // emit event to more easily become aware
                              emit BorrowRateMaxCap();
                          }
                      }
                      /// @dev calculates the borrow rate based on utilization for rate data version 1 (with one kink) in 1e2 precision
                      /// @param rateData_ rate data packed uint256 from storage for the token
                      /// @param utilization_  in 1e2 (100% = 1e4)
                      /// @return rate_ rate in 1e2 precision
                      function calcRateV1(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
                          /// For rate v1 (one kink) ------------------------------------------------------
                          /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  52- 67 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Last 188 bits =>  68-255 => blank, might come in use in future
                          // y = mx + c.
                          // y is borrow rate
                          // x is utilization
                          // m = slope (m can also be negative for declining rates)
                          // c is constant (c can be negative)
                          uint256 y1_;
                          uint256 y2_;
                          uint256 x1_;
                          uint256 x2_;
                          // extract kink1: 16 bits (0xFFFF) starting from bit 20
                          // kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
                          uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_UTILIZATION_AT_KINK) & X16;
                          if (utilization_ < kink1_) {
                              // if utilization is less than kink
                              y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO) & X16;
                              y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
                              x1_ = 0; // 0%
                              x2_ = kink1_;
                          } else {
                              // else utilization is greater than kink
                              y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
                              y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) & X16;
                              x1_ = kink1_;
                              x2_ = FOUR_DECIMALS; // 100%
                          }
                          int256 constant_;
                          int256 slope_;
                          unchecked {
                              // calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
                              // utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
                              // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
                              slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));
                              // calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
                              // maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
                              // maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
                              // maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
                              // subtraction most extreme case would be  0 - max value slope_ * x1_ => can not underflow int256
                              constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));
                              // calculating new borrow rate
                              // - slope_ max value is 65535 * 1e12,
                              // - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
                              // - constant max value is 65535 * 1e12
                              // so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
                              // divisor TWELVE_DECIMALS can not be 0
                              slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
                              if (slope_ < 0) {
                                  revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
                              }
                              rate_ = uint256(slope_) / TWELVE_DECIMALS;
                          }
                      }
                      /// @dev calculates the borrow rate based on utilization for rate data version 2 (with two kinks) in 1e4 precision
                      /// @param rateData_ rate data packed uint256 from storage for the token
                      /// @param utilization_  in 1e2 (100% = 1e4)
                      /// @return rate_ rate in 1e4 precision
                      function calcRateV2(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
                          /// For rate v2 (two kinks) -----------------------------------------------------
                          /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  52- 67 => Utilization at kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  68- 83 => Rate at utilization kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  84- 99 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Last 156 bits => 100-255 => blank, might come in use in future
                          // y = mx + c.
                          // y is borrow rate
                          // x is utilization
                          // m = slope (m can also be negative for declining rates)
                          // c is constant (c can be negative)
                          uint256 y1_;
                          uint256 y2_;
                          uint256 x1_;
                          uint256 x2_;
                          // extract kink1: 16 bits (0xFFFF) starting from bit 20
                          // kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
                          uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1) & X16;
                          if (utilization_ < kink1_) {
                              // if utilization is less than kink1
                              y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO) & X16;
                              y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
                              x1_ = 0; // 0%
                              x2_ = kink1_;
                          } else {
                              // extract kink2: 16 bits (0xFFFF) starting from bit 52
                              uint256 kink2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2) & X16;
                              if (utilization_ < kink2_) {
                                  // if utilization is less than kink2
                                  y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
                                  y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
                                  x1_ = kink1_;
                                  x2_ = kink2_;
                              } else {
                                  // else utilization is greater than kink2
                                  y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
                                  y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) & X16;
                                  x1_ = kink2_;
                                  x2_ = FOUR_DECIMALS;
                              }
                          }
                          int256 constant_;
                          int256 slope_;
                          unchecked {
                              // calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
                              // utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
                              // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
                              slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));
                              // calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
                              // maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
                              // maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
                              // maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
                              // subtraction most extreme case would be  0 - max value slope_ * x1_ => can not underflow int256
                              constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));
                              // calculating new borrow rate
                              // - slope_ max value is 65535 * 1e12,
                              // - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
                              // - constant max value is 65535 * 1e12
                              // so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
                              // divisor TWELVE_DECIMALS can not be 0
                              slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
                              if (slope_ < 0) {
                                  revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
                              }
                              rate_ = uint256(slope_) / TWELVE_DECIMALS;
                          }
                      }
                      /// @dev reads the total supply out of Liquidity packed storage `totalAmounts_` for `supplyExchangePrice_`
                      function getTotalSupply(
                          uint256 totalAmounts_,
                          uint256 supplyExchangePrice_
                      ) internal pure returns (uint256 totalSupply_) {
                          // totalSupply_ => supplyInterestFree
                          totalSupply_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
                          totalSupply_ = (totalSupply_ >> DEFAULT_EXPONENT_SIZE) << (totalSupply_ & DEFAULT_EXPONENT_MASK);
                          uint256 totalSupplyRaw_ = totalAmounts_ & X64; // no shifting as supplyRaw is first 64 bits
                          totalSupplyRaw_ = (totalSupplyRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalSupplyRaw_ & DEFAULT_EXPONENT_MASK);
                          // totalSupply = supplyInterestFree + supplyRawInterest normalized from raw
                          totalSupply_ += ((totalSupplyRaw_ * supplyExchangePrice_) / EXCHANGE_PRICES_PRECISION);
                      }
                      /// @dev reads the total borrow out of Liquidity packed storage `totalAmounts_` for `borrowExchangePrice_`
                      function getTotalBorrow(
                          uint256 totalAmounts_,
                          uint256 borrowExchangePrice_
                      ) internal pure returns (uint256 totalBorrow_) {
                          // totalBorrow_ => borrowInterestFree
                          // no & mask needed for borrow interest free as it occupies the last bits in the storage slot
                          totalBorrow_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
                          totalBorrow_ = (totalBorrow_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrow_ & DEFAULT_EXPONENT_MASK);
                          uint256 totalBorrowRaw_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
                          totalBorrowRaw_ = (totalBorrowRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrowRaw_ & DEFAULT_EXPONENT_MASK);
                          // totalBorrow = borrowInterestFree + borrowRawInterest normalized from raw
                          totalBorrow_ += ((totalBorrowRaw_ * borrowExchangePrice_) / EXCHANGE_PRICES_PRECISION);
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @notice library that helps in reading / working with storage slot data of Fluid Liquidity.
                  /// @dev as all data for Fluid Liquidity is internal, any data must be fetched directly through manual
                  /// slot reading through this library or, if gas usage is less important, through the FluidLiquidityResolver.
                  library LiquiditySlotsLink {
                      /// @dev storage slot for status at Liquidity
                      uint256 internal constant LIQUIDITY_STATUS_SLOT = 1;
                      /// @dev storage slot for auths mapping at Liquidity
                      uint256 internal constant LIQUIDITY_AUTHS_MAPPING_SLOT = 2;
                      /// @dev storage slot for guardians mapping at Liquidity
                      uint256 internal constant LIQUIDITY_GUARDIANS_MAPPING_SLOT = 3;
                      /// @dev storage slot for user class mapping at Liquidity
                      uint256 internal constant LIQUIDITY_USER_CLASS_MAPPING_SLOT = 4;
                      /// @dev storage slot for exchangePricesAndConfig mapping at Liquidity
                      uint256 internal constant LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT = 5;
                      /// @dev storage slot for rateData mapping at Liquidity
                      uint256 internal constant LIQUIDITY_RATE_DATA_MAPPING_SLOT = 6;
                      /// @dev storage slot for totalAmounts mapping at Liquidity
                      uint256 internal constant LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT = 7;
                      /// @dev storage slot for user supply double mapping at Liquidity
                      uint256 internal constant LIQUIDITY_USER_SUPPLY_DOUBLE_MAPPING_SLOT = 8;
                      /// @dev storage slot for user borrow double mapping at Liquidity
                      uint256 internal constant LIQUIDITY_USER_BORROW_DOUBLE_MAPPING_SLOT = 9;
                      /// @dev storage slot for listed tokens array at Liquidity
                      uint256 internal constant LIQUIDITY_LISTED_TOKENS_ARRAY_SLOT = 10;
                      /// @dev storage slot for listed tokens array at Liquidity
                      uint256 internal constant LIQUIDITY_CONFIGS2_MAPPING_SLOT = 11;
                      // --------------------------------
                      // @dev stacked uint256 storage slots bits position data for each:
                      // ExchangePricesAndConfig
                      uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATE = 0;
                      uint256 internal constant BITS_EXCHANGE_PRICES_FEE = 16;
                      uint256 internal constant BITS_EXCHANGE_PRICES_UTILIZATION = 30;
                      uint256 internal constant BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD = 44;
                      uint256 internal constant BITS_EXCHANGE_PRICES_LAST_TIMESTAMP = 58;
                      uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE = 91;
                      uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE = 155;
                      uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_RATIO = 219;
                      uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATIO = 234;
                      uint256 internal constant BITS_EXCHANGE_PRICES_USES_CONFIGS2 = 249;
                      // RateData:
                      uint256 internal constant BITS_RATE_DATA_VERSION = 0;
                      // RateData: V1
                      uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO = 4;
                      uint256 internal constant BITS_RATE_DATA_V1_UTILIZATION_AT_KINK = 20;
                      uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK = 36;
                      uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX = 52;
                      // RateData: V2
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO = 4;
                      uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1 = 20;
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1 = 36;
                      uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2 = 52;
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2 = 68;
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX = 84;
                      // TotalAmounts
                      uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_WITH_INTEREST = 0;
                      uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE = 64;
                      uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST = 128;
                      uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE = 192;
                      // UserSupplyData
                      uint256 internal constant BITS_USER_SUPPLY_MODE = 0;
                      uint256 internal constant BITS_USER_SUPPLY_AMOUNT = 1;
                      uint256 internal constant BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT = 65;
                      uint256 internal constant BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP = 129;
                      uint256 internal constant BITS_USER_SUPPLY_EXPAND_PERCENT = 162;
                      uint256 internal constant BITS_USER_SUPPLY_EXPAND_DURATION = 176;
                      uint256 internal constant BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT = 200;
                      uint256 internal constant BITS_USER_SUPPLY_IS_PAUSED = 255;
                      // UserBorrowData
                      uint256 internal constant BITS_USER_BORROW_MODE = 0;
                      uint256 internal constant BITS_USER_BORROW_AMOUNT = 1;
                      uint256 internal constant BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT = 65;
                      uint256 internal constant BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP = 129;
                      uint256 internal constant BITS_USER_BORROW_EXPAND_PERCENT = 162;
                      uint256 internal constant BITS_USER_BORROW_EXPAND_DURATION = 176;
                      uint256 internal constant BITS_USER_BORROW_BASE_BORROW_LIMIT = 200;
                      uint256 internal constant BITS_USER_BORROW_MAX_BORROW_LIMIT = 218;
                      uint256 internal constant BITS_USER_BORROW_IS_PAUSED = 255;
                      // Configs2
                      uint256 internal constant BITS_CONFIGS2_MAX_UTILIZATION = 0;
                      // --------------------------------
                      /// @notice Calculating the slot ID for Liquidity contract for single mapping at `slot_` for `key_`
                      function calculateMappingStorageSlot(uint256 slot_, address key_) internal pure returns (bytes32) {
                          return keccak256(abi.encode(key_, slot_));
                      }
                      /// @notice Calculating the slot ID for Liquidity contract for double mapping at `slot_` for `key1_` and `key2_`
                      function calculateDoubleMappingStorageSlot(
                          uint256 slot_,
                          address key1_,
                          address key2_
                      ) internal pure returns (bytes32) {
                          bytes32 intermediateSlot_ = keccak256(abi.encode(key1_, slot_));
                          return keccak256(abi.encode(key2_, intermediateSlot_));
                      }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity 0.8.21;
                  import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
                  /// @notice provides minimalistic methods for safe transfers, e.g. ERC20 safeTransferFrom
                  library SafeTransfer {
                      uint256 internal constant MAX_NATIVE_TRANSFER_GAS = 20000; // pass max. 20k gas for native transfers
                      error FluidSafeTransferError(uint256 errorId_);
                      /// @dev Transfer `amount_` of `token_` from `from_` to `to_`, spending the approval given by `from_` to the
                      /// calling contract. If `token_` returns no value, non-reverting calls are assumed to be successful.
                      /// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
                      /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L31-L63
                      function safeTransferFrom(address token_, address from_, address to_, uint256 amount_) internal {
                          bool success_;
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Get a pointer to some free memory.
                              let freeMemoryPointer := mload(0x40)
                              // Write the abi-encoded calldata into memory, beginning with the function selector.
                              mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
                              mstore(add(freeMemoryPointer, 4), and(from_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from_" argument.
                              mstore(add(freeMemoryPointer, 36), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
                              mstore(add(freeMemoryPointer, 68), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.
                              success_ := and(
                                  // Set success to whether the call reverted, if not we check it either
                                  // returned exactly 1 (can't just be non-zero data), or had no return data.
                                  or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                                  // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                                  // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                                  // Counterintuitively, this call must be positioned second to the or() call in the
                                  // surrounding and() call or else returndatasize() will be zero during the computation.
                                  call(gas(), token_, 0, freeMemoryPointer, 100, 0, 32)
                              )
                          }
                          if (!success_) {
                              revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFromFailed);
                          }
                      }
                      /// @dev Transfer `amount_` of `token_` to `to_`.
                      /// If `token_` returns no value, non-reverting calls are assumed to be successful.
                      /// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
                      /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L65-L95
                      function safeTransfer(address token_, address to_, uint256 amount_) internal {
                          bool success_;
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Get a pointer to some free memory.
                              let freeMemoryPointer := mload(0x40)
                              // Write the abi-encoded calldata into memory, beginning with the function selector.
                              mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                              mstore(add(freeMemoryPointer, 4), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
                              mstore(add(freeMemoryPointer, 36), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.
                              success_ := and(
                                  // Set success to whether the call reverted, if not we check it either
                                  // returned exactly 1 (can't just be non-zero data), or had no return data.
                                  or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                                  // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                                  // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                                  // Counterintuitively, this call must be positioned second to the or() call in the
                                  // surrounding and() call or else returndatasize() will be zero during the computation.
                                  call(gas(), token_, 0, freeMemoryPointer, 68, 0, 32)
                              )
                          }
                          if (!success_) {
                              revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
                          }
                      }
                      /// @dev Transfer `amount_` of ` native token to `to_`.
                      /// Minimally modified from Solmate SafeTransferLib (Custom Error):
                      /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L15-L25
                      function safeTransferNative(address to_, uint256 amount_) internal {
                          bool success_;
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Transfer the ETH and store if it succeeded or not. Pass limited gas
                              success_ := call(MAX_NATIVE_TRANSFER_GAS, to_, amount_, 0, 0, 0, 0)
                          }
                          if (!success_) {
                              revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @notice implements a method to read uint256 data from storage at a bytes32 storage slot key.
                  contract StorageRead {
                      function readFromStorage(bytes32 slot_) public view returns (uint256 result_) {
                          assembly {
                              result_ := sload(slot_) // read value from the storage slot
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  abstract contract Structs {
                      struct AddressBool {
                          address addr;
                          bool value;
                      }
                      struct AddressUint256 {
                          address addr;
                          uint256 value;
                      }
                      /// @notice struct to set borrow rate data for version 1
                      struct RateDataV1Params {
                          ///
                          /// @param token for rate data
                          address token;
                          ///
                          /// @param kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
                          /// utilization below kink usually means slow increase in rate, once utilization is above kink borrow rate increases fast
                          uint256 kink;
                          ///
                          /// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
                          /// i.e. constant minimum borrow rate
                          /// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
                          uint256 rateAtUtilizationZero;
                          ///
                          /// @param rateAtUtilizationKink borrow rate when utilization is at kink. in 1e2: 100% = 10_000; 1% = 100
                          /// e.g. when rate should be 7% at kink then rateAtUtilizationKink would be 700
                          uint256 rateAtUtilizationKink;
                          ///
                          /// @param rateAtUtilizationMax borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
                          /// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
                          uint256 rateAtUtilizationMax;
                      }
                      /// @notice struct to set borrow rate data for version 2
                      struct RateDataV2Params {
                          ///
                          /// @param token for rate data
                          address token;
                          ///
                          /// @param kink1 first kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
                          /// utilization below kink 1 usually means slow increase in rate, once utilization is above kink 1 borrow rate increases faster
                          uint256 kink1;
                          ///
                          /// @param kink2 second kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
                          /// utilization below kink 2 usually means slow / medium increase in rate, once utilization is above kink 2 borrow rate increases fast
                          uint256 kink2;
                          ///
                          /// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
                          /// i.e. constant minimum borrow rate
                          /// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
                          uint256 rateAtUtilizationZero;
                          ///
                          /// @param rateAtUtilizationKink1 desired borrow rate when utilization is at first kink. in 1e2: 100% = 10_000; 1% = 100
                          /// e.g. when rate should be 7% at first kink then rateAtUtilizationKink would be 700
                          uint256 rateAtUtilizationKink1;
                          ///
                          /// @param rateAtUtilizationKink2 desired borrow rate when utilization is at second kink. in 1e2: 100% = 10_000; 1% = 100
                          /// e.g. when rate should be 7% at second kink then rateAtUtilizationKink would be 1_200
                          uint256 rateAtUtilizationKink2;
                          ///
                          /// @param rateAtUtilizationMax desired borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
                          /// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
                          uint256 rateAtUtilizationMax;
                      }
                      /// @notice struct to set token config
                      struct TokenConfig {
                          ///
                          /// @param token address
                          address token;
                          ///
                          /// @param fee charges on borrower's interest. in 1e2: 100% = 10_000; 1% = 100
                          uint256 fee;
                          ///
                          /// @param threshold on when to update the storage slot. in 1e2: 100% = 10_000; 1% = 100
                          uint256 threshold;
                          ///
                          /// @param maxUtilization maximum allowed utilization. in 1e2: 100% = 10_000; 1% = 100
                          ///                       set to 100% to disable and have default limit of 100% (avoiding SLOAD).
                          uint256 maxUtilization;
                      }
                      /// @notice struct to set user supply & withdrawal config
                      struct UserSupplyConfig {
                          ///
                          /// @param user address
                          address user;
                          ///
                          /// @param token address
                          address token;
                          ///
                          /// @param mode: 0 = without interest. 1 = with interest
                          uint8 mode;
                          ///
                          /// @param expandPercent withdrawal limit expand percent. in 1e2: 100% = 10_000; 1% = 100
                          /// Also used to calculate rate at which withdrawal limit should decrease (instant).
                          uint256 expandPercent;
                          ///
                          /// @param expandDuration withdrawal limit expand duration in seconds.
                          /// used to calculate rate together with expandPercent
                          uint256 expandDuration;
                          ///
                          /// @param baseWithdrawalLimit base limit, below this, user can withdraw the entire amount.
                          /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
                          /// with interest -> raw, without interest -> normal
                          uint256 baseWithdrawalLimit;
                      }
                      /// @notice struct to set user borrow & payback config
                      struct UserBorrowConfig {
                          ///
                          /// @param user address
                          address user;
                          ///
                          /// @param token address
                          address token;
                          ///
                          /// @param mode: 0 = without interest. 1 = with interest
                          uint8 mode;
                          ///
                          /// @param expandPercent debt limit expand percent. in 1e2: 100% = 10_000; 1% = 100
                          /// Also used to calculate rate at which debt limit should decrease (instant).
                          uint256 expandPercent;
                          ///
                          /// @param expandDuration debt limit expand duration in seconds.
                          /// used to calculate rate together with expandPercent
                          uint256 expandDuration;
                          ///
                          /// @param baseDebtCeiling base borrow limit. until here, borrow limit remains as baseDebtCeiling
                          /// (user can borrow until this point at once without stepped expansion). Above this, automated limit comes in place.
                          /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
                          /// with interest -> raw, without interest -> normal
                          uint256 baseDebtCeiling;
                          ///
                          /// @param maxDebtCeiling max borrow ceiling, maximum amount the user can borrow.
                          /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
                          /// with interest -> raw, without interest -> normal
                          uint256 maxDebtCeiling;
                      }
                  }
                  //SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  import { IProxy } from "../../infiniteProxy/interfaces/iProxy.sol";
                  import { Structs as AdminModuleStructs } from "../adminModule/structs.sol";
                  interface IFluidLiquidityAdmin {
                      /// @notice adds/removes auths. Auths generally could be contracts which can have restricted actions defined on contract.
                      ///         auths can be helpful in reducing governance overhead where it's not needed.
                      /// @param authsStatus_ array of structs setting allowed status for an address.
                      ///                     status true => add auth, false => remove auth
                      function updateAuths(AdminModuleStructs.AddressBool[] calldata authsStatus_) external;
                      /// @notice adds/removes guardians. Only callable by Governance.
                      /// @param guardiansStatus_ array of structs setting allowed status for an address.
                      ///                         status true => add guardian, false => remove guardian
                      function updateGuardians(AdminModuleStructs.AddressBool[] calldata guardiansStatus_) external;
                      /// @notice changes the revenue collector address (contract that is sent revenue). Only callable by Governance.
                      /// @param revenueCollector_  new revenue collector address
                      function updateRevenueCollector(address revenueCollector_) external;
                      /// @notice changes current status, e.g. for pausing or unpausing all user operations. Only callable by Auths.
                      /// @param newStatus_ new status
                      ///        status = 2 -> pause, status = 1 -> resume.
                      function changeStatus(uint256 newStatus_) external;
                      /// @notice                  update tokens rate data version 1. Only callable by Auths.
                      /// @param tokensRateData_   array of RateDataV1Params with rate data to set for each token
                      function updateRateDataV1s(AdminModuleStructs.RateDataV1Params[] calldata tokensRateData_) external;
                      /// @notice                  update tokens rate data version 2. Only callable by Auths.
                      /// @param tokensRateData_   array of RateDataV2Params with rate data to set for each token
                      function updateRateDataV2s(AdminModuleStructs.RateDataV2Params[] calldata tokensRateData_) external;
                      /// @notice updates token configs: fee charge on borrowers interest & storage update utilization threshold.
                      ///         Only callable by Auths.
                      /// @param tokenConfigs_ contains token address, fee & utilization threshold
                      function updateTokenConfigs(AdminModuleStructs.TokenConfig[] calldata tokenConfigs_) external;
                      /// @notice updates user classes: 0 is for new protocols, 1 is for established protocols.
                      ///         Only callable by Auths.
                      /// @param userClasses_ struct array of uint256 value to assign for each user address
                      function updateUserClasses(AdminModuleStructs.AddressUint256[] calldata userClasses_) external;
                      /// @notice sets user supply configs per token basis. Eg: with interest or interest-free and automated limits.
                      ///         Only callable by Auths.
                      /// @param userSupplyConfigs_ struct array containing user supply config, see `UserSupplyConfig` struct for more info
                      function updateUserSupplyConfigs(AdminModuleStructs.UserSupplyConfig[] memory userSupplyConfigs_) external;
                      /// @notice sets a new withdrawal limit as the current limit for a certain user
                      /// @param user_ user address for which to update the withdrawal limit
                      /// @param token_ token address for which to update the withdrawal limit
                      /// @param newLimit_ new limit until which user supply can decrease to.
                      ///                  Important: input in raw. Must account for exchange price in input param calculation.
                      ///                  Note any limit that is < max expansion or > current user supply will set max expansion limit or
                      ///                  current user supply as limit respectively.
                      ///                  - set 0 to make maximum possible withdrawable: instant full expansion, and if that goes
                      ///                  below base limit then fully down to 0.
                      ///                  - set type(uint256).max to make current withdrawable 0 (sets current user supply as limit).
                      function updateUserWithdrawalLimit(address user_, address token_, uint256 newLimit_) external;
                      /// @notice setting user borrow configs per token basis. Eg: with interest or interest-free and automated limits.
                      ///         Only callable by Auths.
                      /// @param userBorrowConfigs_ struct array containing user borrow config, see `UserBorrowConfig` struct for more info
                      function updateUserBorrowConfigs(AdminModuleStructs.UserBorrowConfig[] memory userBorrowConfigs_) external;
                      /// @notice pause operations for a particular user in class 0 (class 1 users can't be paused by guardians).
                      /// Only callable by Guardians.
                      /// @param user_          address of user to pause operations for
                      /// @param supplyTokens_  token addresses to pause withdrawals for
                      /// @param borrowTokens_  token addresses to pause borrowings for
                      function pauseUser(address user_, address[] calldata supplyTokens_, address[] calldata borrowTokens_) external;
                      /// @notice unpause operations for a particular user in class 0 (class 1 users can't be paused by guardians).
                      /// Only callable by Guardians.
                      /// @param user_          address of user to unpause operations for
                      /// @param supplyTokens_  token addresses to unpause withdrawals for
                      /// @param borrowTokens_  token addresses to unpause borrowings for
                      function unpauseUser(address user_, address[] calldata supplyTokens_, address[] calldata borrowTokens_) external;
                      /// @notice         collects revenue for tokens to configured revenueCollector address.
                      /// @param tokens_  array of tokens to collect revenue for
                      /// @dev            Note that this can revert if token balance is < revenueAmount (utilization > 100%)
                      function collectRevenue(address[] calldata tokens_) external;
                      /// @notice gets the current updated exchange prices for n tokens and updates all prices, rates related data in storage.
                      /// @param tokens_ tokens to update exchange prices for
                      /// @return supplyExchangePrices_ new supply rates of overall system for each token
                      /// @return borrowExchangePrices_ new borrow rates of overall system for each token
                      function updateExchangePrices(
                          address[] calldata tokens_
                      ) external returns (uint256[] memory supplyExchangePrices_, uint256[] memory borrowExchangePrices_);
                  }
                  interface IFluidLiquidityLogic is IFluidLiquidityAdmin {
                      /// @notice Single function which handles supply, withdraw, borrow & payback
                      /// @param token_ address of token (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native)
                      /// @param supplyAmount_ if +ve then supply, if -ve then withdraw, if 0 then nothing
                      /// @param borrowAmount_ if +ve then borrow, if -ve then payback, if 0 then nothing
                      /// @param withdrawTo_ if withdrawal then to which address
                      /// @param borrowTo_ if borrow then to which address
                      /// @param callbackData_ callback data passed to `liquidityCallback` method of protocol
                      /// @return memVar3_ updated supplyExchangePrice
                      /// @return memVar4_ updated borrowExchangePrice
                      /// @dev to trigger skipping in / out transfers (gas optimization):
                      /// -  ` callbackData_` MUST be encoded so that "from" address is the last 20 bytes in the last 32 bytes slot,
                      ///     also for native token operations where liquidityCallback is not triggered!
                      ///     from address must come at last position if there is more data. I.e. encode like:
                      ///     abi.encode(otherVar1, otherVar2, FROM_ADDRESS). Note dynamic types used with abi.encode come at the end
                      ///     so if dynamic types are needed, you must use abi.encodePacked to ensure the from address is at the end.
                      /// -   this "from" address must match withdrawTo_ or borrowTo_ and must be == `msg.sender`
                      /// -   `callbackData_` must in addition to the from address as described above include bytes32 SKIP_TRANSFERS
                      ///     in the slot before (bytes 32 to 63)
                      /// -   `msg.value` must be 0.
                      /// -   Amounts must be either:
                      ///     -  supply(+) == borrow(+), withdraw(-) == payback(-).
                      ///     -  Liquidity must be on the winning side (deposit < borrow OR payback < withdraw).
                      function operate(
                          address token_,
                          int256 supplyAmount_,
                          int256 borrowAmount_,
                          address withdrawTo_,
                          address borrowTo_,
                          bytes calldata callbackData_
                      ) external payable returns (uint256 memVar3_, uint256 memVar4_);
                  }
                  interface IFluidLiquidity is IProxy, IFluidLiquidityLogic {}
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { Structs } from "./poolT1/coreModule/structs.sol";
                  abstract contract Error {
                      error FluidDexError(uint256 errorId_);
                      error FluidDexFactoryError(uint256 errorId);
                      /// @notice used to simulate swap to find the output amount
                      error FluidDexSwapResult(uint256 amountOut);
                      error FluidDexPerfectLiquidityOutput(uint256 token0Amt, uint token1Amt);
                      error FluidDexSingleTokenOutput(uint256 tokenAmt);
                      error FluidDexLiquidityOutput(uint256 shares_);
                      error FluidDexPricesAndExchangeRates(Structs.PricesAndExchangePrice pex_);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  library ErrorTypes {
                      /***********************************|
                      |             DexT1                 | 
                      |__________________________________*/
                      /// @notice thrown at reentrancy
                      uint256 internal constant DexT1__AlreadyEntered = 51001;
                      uint256 internal constant DexT1__NotAnAuth = 51002;
                      uint256 internal constant DexT1__SmartColNotEnabled = 51003;
                      uint256 internal constant DexT1__SmartDebtNotEnabled = 51004;
                      uint256 internal constant DexT1__PoolNotInitialized = 51005;
                      uint256 internal constant DexT1__TokenReservesTooLow = 51006;
                      uint256 internal constant DexT1__EthAndAmountInMisMatch = 51007;
                      uint256 internal constant DexT1__EthSentForNonNativeSwap = 51008;
                      uint256 internal constant DexT1__NoSwapRoute = 51009;
                      uint256 internal constant DexT1__NotEnoughAmountOut = 51010;
                      uint256 internal constant DexT1__LiquidityLayerTokenUtilizationCapReached = 51011;
                      uint256 internal constant DexT1__HookReturnedFalse = 51012;
                      // Either user's config are not set or user is paused
                      uint256 internal constant DexT1__UserSupplyInNotOn = 51013;
                      // Either user's config are not set or user is paused
                      uint256 internal constant DexT1__UserDebtInNotOn = 51014;
                      // Thrown when contract asks for more token0 or token1 than what user's wants to give on deposit
                      uint256 internal constant DexT1__AboveDepositMax = 51015;
                      uint256 internal constant DexT1__MsgValueLowOnDepositOrPayback = 51016;
                      uint256 internal constant DexT1__WithdrawLimitReached = 51017;
                      // Thrown when contract gives less token0 or token1 than what user's wants on withdraw
                      uint256 internal constant DexT1__BelowWithdrawMin = 51018;
                      uint256 internal constant DexT1__DebtLimitReached = 51019;
                      // Thrown when contract gives less token0 or token1 than what user's wants on borrow
                      uint256 internal constant DexT1__BelowBorrowMin = 51020;
                      // Thrown when contract asks for more token0 or token1 than what user's wants on payback
                      uint256 internal constant DexT1__AbovePaybackMax = 51021;
                      uint256 internal constant DexT1__InvalidDepositAmts = 51022;
                      uint256 internal constant DexT1__DepositAmtsZero = 51023;
                      uint256 internal constant DexT1__SharesMintedLess = 51024;
                      uint256 internal constant DexT1__WithdrawalNotEnough = 51025;
                      uint256 internal constant DexT1__InvalidWithdrawAmts = 51026;
                      uint256 internal constant DexT1__WithdrawAmtsZero = 51027;
                      uint256 internal constant DexT1__WithdrawExcessSharesBurn = 51028;
                      uint256 internal constant DexT1__InvalidBorrowAmts = 51029;
                      uint256 internal constant DexT1__BorrowAmtsZero = 51030;
                      uint256 internal constant DexT1__BorrowExcessSharesMinted = 51031;
                      uint256 internal constant DexT1__PaybackAmtTooHigh = 51032;
                      uint256 internal constant DexT1__InvalidPaybackAmts = 51033;
                      uint256 internal constant DexT1__PaybackAmtsZero = 51034;
                      uint256 internal constant DexT1__PaybackSharedBurnedLess = 51035;
                      uint256 internal constant DexT1__NothingToArbitrage = 51036;
                      uint256 internal constant DexT1__MsgSenderNotLiquidity = 51037;
                      // On liquidity callback reentrancy bit should be on
                      uint256 internal constant DexT1__ReentrancyBitShouldBeOn = 51038;
                      // Thrown is reentrancy is already on and someone tries to fetch oracle price. Should not be possible to this
                      uint256 internal constant DexT1__OraclePriceFetchAlreadyEntered = 51039;
                      // Thrown when swap changes the current price by more than 5%
                      uint256 internal constant DexT1__OracleUpdateHugeSwapDiff = 51040;
                      uint256 internal constant DexT1__Token0ShouldBeSmallerThanToken1 = 51041;
                      uint256 internal constant DexT1__OracleMappingOverflow = 51042;
                      /// @notice thrown if governance has paused the swapping & arbitrage so only perfect functions are usable
                      uint256 internal constant DexT1__SwapAndArbitragePaused = 51043;
                      uint256 internal constant DexT1__ExceedsAmountInMax = 51044;
                      /// @notice thrown if amount in is too high or too low
                      uint256 internal constant DexT1__SwapInLimitingAmounts = 51045;
                      /// @notice thrown if amount out is too high or too low
                      uint256 internal constant DexT1__SwapOutLimitingAmounts = 51046;
                      uint256 internal constant DexT1__MintAmtOverflow = 51047;
                      uint256 internal constant DexT1__BurnAmtOverflow = 51048;
                      uint256 internal constant DexT1__LimitingAmountsSwapAndNonPerfectActions = 51049;
                      uint256 internal constant DexT1__InsufficientOracleData = 51050;
                      uint256 internal constant DexT1__SharesAmountInsufficient = 51051;
                      uint256 internal constant DexT1__CenterPriceOutOfRange = 51052;
                      uint256 internal constant DexT1__DebtReservesTooLow = 51053;
                      uint256 internal constant DexT1__SwapAndDepositTooLowOrTooHigh = 51054;
                      uint256 internal constant DexT1__WithdrawAndSwapTooLowOrTooHigh = 51055;
                      uint256 internal constant DexT1__BorrowAndSwapTooLowOrTooHigh = 51056;
                      uint256 internal constant DexT1__SwapAndPaybackTooLowOrTooHigh = 51057;
                      uint256 internal constant DexT1__InvalidImplementation = 51058;
                      uint256 internal constant DexT1__OnlyDelegateCallAllowed = 51059;
                      uint256 internal constant DexT1__IncorrectDataLength = 51060;
                      uint256 internal constant DexT1__AmountToSendLessThanAmount = 51061;
                      uint256 internal constant DexT1__InvalidCollateralReserves = 51062;
                      uint256 internal constant DexT1__InvalidDebtReserves = 51063;
                      uint256 internal constant DexT1__SupplySharesOverflow = 51064;
                      uint256 internal constant DexT1__BorrowSharesOverflow = 51065;
                      uint256 internal constant DexT1__OracleNotActive = 51066;
                      /***********************************|
                      |            DEX Admin              | 
                      |__________________________________*/
                      /// @notice thrown when pool is not initialized
                      uint256 internal constant DexT1Admin__PoolNotInitialized = 52001;
                      uint256 internal constant DexT1Admin__SmartColIsAlreadyOn = 52002;
                      uint256 internal constant DexT1Admin__SmartDebtIsAlreadyOn = 52003;
                      /// @notice thrown when any of the configs value overflow the maximum limit
                      uint256 internal constant DexT1Admin__ConfigOverflow = 52004;
                      uint256 internal constant DexT1Admin__AddressNotAContract = 52005;
                      uint256 internal constant DexT1Admin__InvalidParams = 52006;
                      uint256 internal constant DexT1Admin__UserNotDefined = 52007;
                      uint256 internal constant DexT1Admin__OnlyDelegateCallAllowed = 52008;
                      uint256 internal constant DexT1Admin__UnexpectedPoolState = 52009;
                      /// @notice thrown when trying to pause or unpause but user is already in the target pause state
                      uint256 internal constant DexT1Admin__InvalidPauseToggle = 52009;
                      /***********************************|
                      |            DEX Factory            | 
                      |__________________________________*/
                      uint256 internal constant DexFactory__InvalidOperation = 53001;
                      uint256 internal constant DexFactory__Unauthorized = 53002;
                      uint256 internal constant DexFactory__SameTokenNotAllowed = 53003;
                      uint256 internal constant DexFactory__TokenConfigNotProper = 53004;
                      uint256 internal constant DexFactory__InvalidParams = 53005;
                      uint256 internal constant DexFactory__OnlyDelegateCallAllowed = 53006;
                      uint256 internal constant DexFactory__InvalidDexAddress = 53007;
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  interface IFluidDexFactory {
                      /// @notice Global auth is auth for all dexes
                      function isGlobalAuth(address auth_) external view returns (bool);
                      /// @notice Dex auth is auth for a specific dex
                      function isDexAuth(address vault_, address auth_) external view returns (bool);
                      /// @notice Total dexes deployed.
                      function totalDexes() external view returns (uint256);
                      /// @notice Compute dexAddress
                      function getDexAddress(uint256 dexId_) external view returns (address);
                      /// @notice read uint256 `result_` for a storage `slot_` key
                      function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  interface IFluidDexT1 {
                      error FluidDexError(uint256 errorId);
                      /// @notice used to simulate swap to find the output amount
                      error FluidDexSwapResult(uint256 amountOut);
                      error FluidDexPerfectLiquidityOutput(uint256 token0Amt, uint token1Amt);
                      error FluidDexSingleTokenOutput(uint256 tokenAmt);
                      error FluidDexLiquidityOutput(uint256 shares);
                      error FluidDexPricesAndExchangeRates(PricesAndExchangePrice pex_);
                      /// @notice returns the dex id
                      function DEX_ID() external view returns (uint256);
                      /// @notice reads uint256 data `result_` from storage at a bytes32 storage `slot_` key.
                      function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
                      struct Implementations {
                          address shift;
                          address admin;
                          address colOperations;
                          address debtOperations;
                          address perfectOperationsAndOracle;
                      }
                      struct ConstantViews {
                          uint256 dexId;
                          address liquidity;
                          address factory;
                          Implementations implementations;
                          address deployerContract;
                          address token0;
                          address token1;
                          bytes32 supplyToken0Slot;
                          bytes32 borrowToken0Slot;
                          bytes32 supplyToken1Slot;
                          bytes32 borrowToken1Slot;
                          bytes32 exchangePriceToken0Slot;
                          bytes32 exchangePriceToken1Slot;
                          uint256 oracleMapping;
                      }
                      struct ConstantViews2 {
                          uint token0NumeratorPrecision;
                          uint token0DenominatorPrecision;
                          uint token1NumeratorPrecision;
                          uint token1DenominatorPrecision;
                      }
                      struct PricesAndExchangePrice {
                          uint lastStoredPrice; // last stored price in 1e27 decimals
                          uint centerPrice; // last stored price in 1e27 decimals
                          uint upperRange; // price at upper range in 1e27 decimals
                          uint lowerRange; // price at lower range in 1e27 decimals
                          uint geometricMean; // geometric mean of upper range & lower range in 1e27 decimals
                          uint supplyToken0ExchangePrice;
                          uint borrowToken0ExchangePrice;
                          uint supplyToken1ExchangePrice;
                          uint borrowToken1ExchangePrice;
                      }
                      struct CollateralReserves {
                          uint token0RealReserves;
                          uint token1RealReserves;
                          uint token0ImaginaryReserves;
                          uint token1ImaginaryReserves;
                      }
                      struct DebtReserves {
                          uint token0Debt;
                          uint token1Debt;
                          uint token0RealReserves;
                          uint token1RealReserves;
                          uint token0ImaginaryReserves;
                          uint token1ImaginaryReserves;
                      }
                      function getCollateralReserves(
                          uint geometricMean_,
                          uint upperRange_,
                          uint lowerRange_,
                          uint token0SupplyExchangePrice_,
                          uint token1SupplyExchangePrice_
                      ) external view returns (CollateralReserves memory c_);
                      function getDebtReserves(
                          uint geometricMean_,
                          uint upperRange_,
                          uint lowerRange_,
                          uint token0BorrowExchangePrice_,
                          uint token1BorrowExchangePrice_
                      ) external view returns (DebtReserves memory d_);
                      // reverts with FluidDexPricesAndExchangeRates(pex_);
                      function getPricesAndExchangePrices() external;
                      function constantsView() external view returns (ConstantViews memory constantsView_);
                      function constantsView2() external view returns (ConstantViews2 memory constantsView2_);
                      struct Oracle {
                          uint twap1by0; // TWAP price
                          uint lowestPrice1by0; // lowest price point
                          uint highestPrice1by0; // highest price point
                          uint twap0by1; // TWAP price
                          uint lowestPrice0by1; // lowest price point
                          uint highestPrice0by1; // highest price point
                      }
                      /// @dev This function allows users to swap a specific amount of input tokens for output tokens
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountIn_ The exact amount of input tokens to swap
                      /// @param amountOutMin_ The minimum amount of output tokens the user is willing to accept
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountOut_
                      /// @return amountOut_ The amount of output tokens received from the swap
                      function swapIn(
                          bool swap0to1_,
                          uint256 amountIn_,
                          uint256 amountOutMin_,
                          address to_
                      ) external payable returns (uint256 amountOut_);
                      /// @dev Swap tokens with perfect amount out
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountOut_ The exact amount of tokens to receive after swap
                      /// @param amountInMax_ Maximum amount of tokens to swap in
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountIn_
                      /// @return amountIn_ The amount of input tokens used for the swap
                      function swapOut(
                          bool swap0to1_,
                          uint256 amountOut_,
                          uint256 amountInMax_,
                          address to_
                      ) external payable returns (uint256 amountIn_);
                      /// @dev Deposit tokens in equal proportion to the current pool ratio
                      /// @param shares_ The number of shares to mint
                      /// @param maxToken0Deposit_ Maximum amount of token0 to deposit
                      /// @param maxToken1Deposit_ Maximum amount of token1 to deposit
                      /// @param estimate_ If true, function will revert with estimated deposit amounts without executing the deposit
                      /// @return token0Amt_ Amount of token0 deposited
                      /// @return token1Amt_ Amount of token1 deposited
                      function depositPerfect(
                          uint shares_,
                          uint maxToken0Deposit_,
                          uint maxToken1Deposit_,
                          bool estimate_
                      ) external payable returns (uint token0Amt_, uint token1Amt_);
                      /// @dev This function allows users to withdraw a perfect amount of collateral liquidity
                      /// @param shares_ The number of shares to withdraw
                      /// @param minToken0Withdraw_ The minimum amount of token0 the user is willing to accept
                      /// @param minToken1Withdraw_ The minimum amount of token1 the user is willing to accept
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with token0Amt_ & token1Amt_
                      /// @return token0Amt_ The amount of token0 withdrawn
                      /// @return token1Amt_ The amount of token1 withdrawn
                      function withdrawPerfect(
                          uint shares_,
                          uint minToken0Withdraw_,
                          uint minToken1Withdraw_,
                          address to_
                      ) external returns (uint token0Amt_, uint token1Amt_);
                      /// @dev This function allows users to borrow tokens in equal proportion to the current debt pool ratio
                      /// @param shares_ The number of shares to borrow
                      /// @param minToken0Borrow_ Minimum amount of token0 to borrow
                      /// @param minToken1Borrow_ Minimum amount of token1 to borrow
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with token0Amt_ & token1Amt_
                      /// @return token0Amt_ Amount of token0 borrowed
                      /// @return token1Amt_ Amount of token1 borrowed
                      function borrowPerfect(
                          uint shares_,
                          uint minToken0Borrow_,
                          uint minToken1Borrow_,
                          address to_
                      ) external returns (uint token0Amt_, uint token1Amt_);
                      /// @dev This function allows users to pay back borrowed tokens in equal proportion to the current debt pool ratio
                      /// @param shares_ The number of shares to pay back
                      /// @param maxToken0Payback_ Maximum amount of token0 to pay back
                      /// @param maxToken1Payback_ Maximum amount of token1 to pay back
                      /// @param estimate_ If true, function will revert with estimated payback amounts without executing the payback
                      /// @return token0Amt_ Amount of token0 paid back
                      /// @return token1Amt_ Amount of token1 paid back
                      function paybackPerfect(
                          uint shares_,
                          uint maxToken0Payback_,
                          uint maxToken1Payback_,
                          bool estimate_
                      ) external payable returns (uint token0Amt_, uint token1Amt_);
                      /// @dev This function allows users to deposit tokens in any proportion into the col pool
                      /// @param token0Amt_ The amount of token0 to deposit
                      /// @param token1Amt_ The amount of token1 to deposit
                      /// @param minSharesAmt_ The minimum amount of shares the user expects to receive
                      /// @param estimate_ If true, function will revert with estimated shares without executing the deposit
                      /// @return shares_ The amount of shares minted for the deposit
                      function deposit(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint minSharesAmt_,
                          bool estimate_
                      ) external payable returns (uint shares_);
                      /// @dev This function allows users to withdraw tokens in any proportion from the col pool
                      /// @param token0Amt_ The amount of token0 to withdraw
                      /// @param token1Amt_ The amount of token1 to withdraw
                      /// @param maxSharesAmt_ The maximum number of shares the user is willing to burn
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with shares_
                      /// @return shares_ The number of shares burned for the withdrawal
                      function withdraw(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint maxSharesAmt_,
                          address to_
                      ) external returns (uint shares_);
                      /// @dev This function allows users to borrow tokens in any proportion from the debt pool
                      /// @param token0Amt_ The amount of token0 to borrow
                      /// @param token1Amt_ The amount of token1 to borrow
                      /// @param maxSharesAmt_ The maximum amount of shares the user is willing to receive
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with shares_
                      /// @return shares_ The amount of borrow shares minted to represent the borrowed amount
                      function borrow(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint maxSharesAmt_,
                          address to_
                      ) external returns (uint shares_);
                      /// @dev This function allows users to payback tokens in any proportion to the debt pool
                      /// @param token0Amt_ The amount of token0 to payback
                      /// @param token1Amt_ The amount of token1 to payback
                      /// @param minSharesAmt_ The minimum amount of shares the user expects to burn
                      /// @param estimate_ If true, function will revert with estimated shares without executing the payback
                      /// @return shares_ The amount of borrow shares burned for the payback
                      function payback(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint minSharesAmt_,
                          bool estimate_
                      ) external payable returns (uint shares_);
                      /// @dev This function allows users to withdraw their collateral with perfect shares in one token
                      /// @param shares_ The number of shares to burn for withdrawal
                      /// @param minToken0_ The minimum amount of token0 the user expects to receive (set to 0 if withdrawing in token1)
                      /// @param minToken1_ The minimum amount of token1 the user expects to receive (set to 0 if withdrawing in token0)
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with withdrawAmt_
                      /// @return withdrawAmt_ The amount of tokens withdrawn in the chosen token
                      function withdrawPerfectInOneToken(
                          uint shares_,
                          uint minToken0_,
                          uint minToken1_,
                          address to_
                      ) external returns (
                          uint withdrawAmt_
                      );
                      /// @dev This function allows users to payback their debt with perfect shares in one token
                      /// @param shares_ The number of shares to burn for payback
                      /// @param maxToken0_ The maximum amount of token0 the user is willing to pay (set to 0 if paying back in token1)
                      /// @param maxToken1_ The maximum amount of token1 the user is willing to pay (set to 0 if paying back in token0)
                      /// @param estimate_ If true, the function will revert with the estimated payback amount without executing the payback
                      /// @return paybackAmt_ The amount of tokens paid back in the chosen token
                      function paybackPerfectInOneToken(
                          uint shares_,
                          uint maxToken0_,
                          uint maxToken1_,
                          bool estimate_
                      ) external payable returns (
                          uint paybackAmt_
                      );
                      /// @dev the oracle assumes last set price of pool till the next swap happens.
                      /// There's a possibility that during that time some interest is generated hence the last stored price is not the 100% correct price for the whole duration
                      /// but the difference due to interest will be super low so this difference is ignored
                      /// For example 2 swaps happened 10min (600 seconds) apart and 1 token has 10% higher interest than other.
                      /// then that token will accrue about 10% * 600 / secondsInAYear = ~0.0002%
                      /// @param secondsAgos_ array of seconds ago for which TWAP is needed. If user sends [10, 30, 60] then twaps_ will return [10-0, 30-10, 60-30]
                      /// @return twaps_ twap price, lowest price (aka minima) & highest price (aka maxima) between secondsAgo checkpoints
                      /// @return currentPrice_ price of pool after the most recent swap
                      function oraclePrice(
                          uint[] memory secondsAgos_
                      ) external view returns (
                          Oracle[] memory twaps_,
                          uint currentPrice_
                      );
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { StorageRead } from "../../../../libraries/storageRead.sol";
                  interface ITokenDecimals {
                      function decimals() external view returns (uint8);
                  }
                  abstract contract ConstantVariables is StorageRead {
                      /*//////////////////////////////////////////////////////////////
                                            CONSTANTS / IMMUTABLES
                      //////////////////////////////////////////////////////////////*/
                      address internal constant TEAM_MULTISIG = 0x4F6F977aCDD1177DCD81aB83074855EcB9C2D49e;
                      address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                      uint256 internal constant NATIVE_TOKEN_DECIMALS = 18;
                      address internal constant ADDRESS_DEAD = 0x000000000000000000000000000000000000dEaD;
                      uint256 internal constant TOKENS_DECIMALS_PRECISION = 12;
                      uint256 internal constant TOKENS_DECIMALS = 1e12;
                      uint256 internal constant SMALL_COEFFICIENT_SIZE = 10;
                      uint256 internal constant DEFAULT_COEFFICIENT_SIZE = 56;
                      uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
                      uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
                      uint256 internal constant X2 = 0x3;
                      uint256 internal constant X3 = 0x7;
                      uint256 internal constant X5 = 0x1f;
                      uint256 internal constant X7 = 0x7f;
                      uint256 internal constant X8 = 0xff;
                      uint256 internal constant X9 = 0x1ff;
                      uint256 internal constant X10 = 0x3ff;
                      uint256 internal constant X11 = 0x7ff;
                      uint256 internal constant X14 = 0x3fff;
                      uint256 internal constant X16 = 0xffff;
                      uint256 internal constant X17 = 0x1ffff;
                      uint256 internal constant X18 = 0x3ffff;
                      uint256 internal constant X20 = 0xfffff;
                      uint256 internal constant X22 = 0x3fffff;
                      uint256 internal constant X23 = 0x7fffff;
                      uint256 internal constant X24 = 0xffffff;
                      uint256 internal constant X28 = 0xfffffff;
                      uint256 internal constant X30 = 0x3fffffff;
                      uint256 internal constant X32 = 0xffffffff;
                      uint256 internal constant X33 = 0x1ffffffff;
                      uint256 internal constant X40 = 0xffffffffff;
                      uint256 internal constant X64 = 0xffffffffffffffff;
                      uint256 internal constant X96 = 0xffffffffffffffffffffffff;
                      uint256 internal constant X128 = 0xffffffffffffffffffffffffffffffff;
                      uint256 internal constant TWO_DECIMALS = 1e2;
                      uint256 internal constant THREE_DECIMALS = 1e3;
                      uint256 internal constant FOUR_DECIMALS = 1e4;
                      uint256 internal constant FIVE_DECIMALS = 1e5;
                      uint256 internal constant SIX_DECIMALS = 1e6;
                      uint256 internal constant EIGHT_DECIMALS = 1e8;
                      uint256 internal constant NINE_DECIMALS = 1e9;
                      uint256 internal constant PRICE_PRECISION = 1e27;
                      uint256 internal constant ORACLE_PRECISION = 1e18; // 100%
                      uint256 internal constant ORACLE_LIMIT = 5 * 1e16; // 5%
                      /// after swap token0 reserves should not be less than token1InToken0 / MINIMUM_LIQUIDITY_SWAP
                      /// after swap token1 reserves should not be less than token0InToken1 / MINIMUM_LIQUIDITY_SWAP
                      uint256 internal constant MINIMUM_LIQUIDITY_SWAP = 1e4;
                      /// after user operations (deposit, withdraw, borrow, payback) token0 reserves should not be less than token1InToken0 / MINIMUM_LIQUIDITY_USER_OPERATIONS
                      /// after user operations (deposit, withdraw, borrow, payback) token1 reserves should not be less than token0InToken0 / MINIMUM_LIQUIDITY_USER_OPERATIONS
                      uint256 internal constant MINIMUM_LIQUIDITY_USER_OPERATIONS = 1e6;
                      /// To skip transfers in liquidity layer if token in & out is same and liquidity layer is on the winning side
                      bytes32 internal constant SKIP_TRANSFERS = keccak256(bytes("SKIP_TRANSFERS"));
                      function _decimals(address token_) internal view returns (uint256) {
                          return (token_ == NATIVE_TOKEN) ? NATIVE_TOKEN_DECIMALS : ITokenDecimals(token_).decimals();
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  abstract contract Variables {
                      /*//////////////////////////////////////////////////////////////
                                            STORAGE VARIABLES
                      //////////////////////////////////////////////////////////////*/
                      /// First 1 bit  => 0 => re-entrancy. If 0 then allow transaction to go, else throw.
                      /// Next 40 bits => 1-40 => last to last stored price. BigNumber (32 bits precision, 8 bits exponent)
                      /// Next 40 bits => 41-80 => last stored price of pool. BigNumber (32 bits precision, 8 bits exponent)
                      /// Next 40 bits => 81-120 => center price. Center price from where the ranges will be calculated. BigNumber (32 bits precision, 8 bits exponent)
                      /// Next 33 bits => 121-153 => last interaction time stamp
                      /// Next 22 bits => 154-175 => max 4194303 seconds (~1165 hrs, ~48.5 days), time difference between last to last and last price stored
                      /// Next 3 bits  => 176-178 => oracle checkpoint, if 0 then first slot, if 7 then last slot
                      /// Next 16 bits => 179-194 => current mapping or oracle, after every 8 transaction it will increase by 1. Max capacity is 65535 but it can be lower than that check dexVariables2
                      /// Next 1 bit  => 195 => is oracle active?
                      uint internal dexVariables;
                      /// Next  1 bit  => 0 => is smart collateral enabled?
                      /// Next  1 bit  => 1 => is smart debt enabled?
                      /// Next 17 bits => 2-18 => fee (1% = 10000, max value: 100000 = 10%, fee should not be more than 10%)
                      /// Next  7 bits => 19-25 => revenue cut from fee (1 = 1%, 100 = 100%). If fee is 1000 = 0.1% and revenue cut is 10 = 10% then governance get 0.01% of every swap
                      /// Next  1 bit  => 26 => percent active change going on or not, 0 = false, 1 = true, if true than that means governance has updated the below percents and the update should happen with a specified time.
                      /// Next 20 bits => 27-46 => upperPercent (1% = 10000, max value: 104.8575%) upperRange - upperRange * upperPercent = centerPrice. Hence, upperRange = centerPrice / (1 - upperPercent)
                      /// Next 20 bits => 47-66 => lowerPercent. lowerRange = centerPrice - centerPrice * lowerPercent.
                      /// Next  1 bit  => 67 => threshold percent active change going on or not, 0 = false, 1 = true, if true than that means governance has updated the below percents and the update should happen with a specified time.
                      /// Next 10 bits => 68-77 => upper shift threshold percent, 1 = 0.1%. 1000 = 100%. if currentPrice > (centerPrice + (upperRange - centerPrice) * (1000 - upperShiftThresholdPercent) / 1000) then trigger shift
                      /// Next 10 bits => 78-87 => lower shift threshold percent, 1 = 0.1%. 1000 = 100%. if currentPrice < (centerPrice - (centerPrice - lowerRange) * (1000 - lowerShiftThresholdPercent) / 1000) then trigger shift
                      /// Next 24 bits => 88-111 => Shifting time (~194 days) (rate = (% up + % down) / time ?)
                      /// Next 30 bits => 112-131 => Address of center price if center price should be fetched externally, for example, for wstETH <> ETH pool, fetch wstETH exchange rate into stETH from wstETH contract.
                      /// Why fetch it externally? Because let's say pool width is 0.1% and wstETH temporarily got depeg of 0.5% then pool will start to shift to newer pricing
                      /// but we don't want pool to shift to 0.5% because we know the depeg will recover so to avoid the loss for users.
                      /// Next 30 bits => 142-171 => Hooks bits, calculate hook address by storing deployment nonce from factory.
                      /// Next 28 bits => 172-199 => max center price. BigNumber (20 bits precision, 8 bits exponent)
                      /// Next 28 bits => 200-227 => min center price. BigNumber (20 bits precision, 8 bits exponent)
                      /// Next 10 bits => 228-237 => utilization limit of token0. Max value 1000 = 100%, if 100% then no need to check the utilization.
                      /// Next 10 bits => 238-247 => utilization limit of token1. Max value 1000 = 100%, if 100% then no need to check the utilization.
                      /// Next 1  bit  => 248     => is center price shift active
                      /// Last 1  bit  => 255     => Pause swap & arbitrage (only perfect functions will be usable), if we need to pause entire DEX then that can be done through pausing DEX on Liquidity Layer
                      uint internal dexVariables2;
                      /// first 128 bits => 0-127 => total supply shares
                      /// last 128 bits => 128-255 => max supply shares
                      uint internal _totalSupplyShares;
                      /// @dev user supply data: user -> data
                      /// Aside from 1st bit, entire bits here are same as liquidity layer _userSupplyData. Hence exact same supply & borrow limit library can be used
                      /// First  1 bit  =>       0 => is user allowed to supply? 0 = not allowed, 1 = allowed
                      /// Next  64 bits =>   1- 64 => user supply amount/shares; BigMath: 56 | 8
                      /// Next  64 bits =>  65-128 => previous user withdrawal limit; BigMath: 56 | 8
                      /// Next  33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
                      /// Next  14 bits => 162-175 => expand withdrawal limit percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383).
                      ///                             @dev shrinking is instant
                      /// Next  24 bits => 176-199 => withdrawal limit expand duration in seconds.(Max value 16_777_215; ~4_660 hours, ~194 days)
                      /// Next  18 bits => 200-217 => base withdrawal limit: below this, 100% withdrawals can be done (aka shares can be burned); BigMath: 10 | 8
                      /// Next  38 bits => 218-255 => empty for future use
                      mapping(address => uint) internal _userSupplyData;
                      /// first 128 bits => 0-127 => total borrow shares
                      /// last 128 bits => 128-255 => max borrow shares
                      uint internal _totalBorrowShares;
                      /// @dev user borrow data: user -> data
                      /// Aside from 1st bit, entire bits here are same as liquidity layer _userBorrowData. Hence exact same supply & borrow limit library function can be used
                      /// First  1 bit  =>       0 => is user allowed to borrow? 0 = not allowed, 1 = allowed
                      /// Next  64 bits =>   1- 64 => user debt amount/shares; BigMath: 56 | 8
                      /// Next  64 bits =>  65-128 => previous user debt ceiling; BigMath: 56 | 8
                      /// Next  33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
                      /// Next  14 bits => 162-175 => expand debt ceiling percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      ///                             @dev shrinking is instant
                      /// Next  24 bits => 176-199 => debt ceiling expand duration in seconds (Max value 16_777_215; ~4_660 hours, ~194 days)
                      /// Next  18 bits => 200-217 => base debt ceiling: below this, there's no debt ceiling limits; BigMath: 10 | 8
                      /// Next  18 bits => 218-235 => max debt ceiling: absolute maximum debt ceiling can expand to; BigMath: 10 | 8
                      /// Next  20 bits => 236-255 => empty for future use
                      mapping(address => uint) internal _userBorrowData;
                      /// Price difference between last swap of last block & last swap of new block
                      /// If last swap happened at Block B - 4 and next swap happened after 4 blocks at Block B then it will store that difference
                      /// considering time difference between these 4 blocks is 48 seconds, hence time will be stored as 48
                      /// New oracle update:
                      /// time to 9 bits and precision to 22 bits
                      /// if time exceeds 9 bits which is 511 sec or ~8.5 min then we will use 2 oracle slot to store the data
                      /// we will leave the both time slot as 0 and on first sign + precision slot we will store time and
                      /// on second sign + precision slot we will store sign & precision
                      /// First 9 bits =>   0-  8 => time, 511 seconds
                      /// Next   1 bit  =>  9     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits =>  10- 31 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits =>  32- 40 => time, 511 seconds
                      /// Next   1 bit  =>  41     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits =>  42- 63 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits =>  64- 72 => time, 511 seconds
                      /// Next   1 bit  =>  73     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits =>  74- 95 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits =>  96-104 => time, 511 seconds
                      /// Next   1 bit  => 105     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits => 106-127 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits => 128-136 => time, 511 seconds
                      /// Next   1 bit  => 137     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits => 138-159 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits => 160-168 => time, 511 seconds
                      /// Next   1 bit  => 169     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits => 170-191 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits => 192-200 => time, 511 seconds
                      /// Next   1 bit  => 201     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits => 202-223 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      /// Next  9 bits => 224-232 => time, 511 seconds
                      /// Next   1 bit  => 233     => sign of percent in change, if 1 then 0 or positive, else negative
                      /// Next  22 bits => 234-255 => 4194303, change in price, max change is capped to 5%, so 4194303 = 5%, 1 = 0.0000011920931797249746%
                      mapping(uint => uint) internal _oracle;
                      /// First 20 bits =>  0-19 => old upper shift
                      /// Next  20 bits => 20-39 => old lower shift
                      /// Next  20 bits => 40-59 => in seconds, ~12 days max, shift can last for max ~12 days
                      /// Next  33 bits => 60-92 => timestamp of when the shift has started.
                      uint128 internal _rangeShift;
                      /// First 10 bits =>  0- 9 => old upper shift
                      /// Next  10 bits => 10-19 => empty so we can use same helper function
                      /// Next  10 bits => 20-29 => old lower shift
                      /// Next  10 bits => 30-39 => empty so we can use same helper function
                      /// Next  20 bits => 40-59 => in seconds, ~12 days max, shift can last for max ~12 days
                      /// Next  33 bits => 60-92 => timestamp of when the shift has started.
                      /// Next  24 bits => 93-116 => old threshold time
                      uint128 internal _thresholdShift;
                      /// Shifting is fuzzy and with time it'll keep on getting closer and then eventually get over
                      /// First 33 bits => 0 -32 => starting timestamp
                      /// Next  20 bits => 33-52 => % shift
                      /// Next  20 bits => 53-72 => time to shift that percent
                      uint256 internal _centerPriceShift;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { CoreHelpers } from "../helpers/coreHelpers.sol";
                  import { SafeTransfer } from "../../../../../libraries/safeTransfer.sol";
                  import { DexSlotsLink } from "../../../../../libraries/dexSlotsLink.sol";
                  import { DexCalcs } from "../../../../../libraries/dexCalcs.sol";
                  import { BigMathMinified } from "../../../../../libraries/bigMathMinified.sol";
                  import { ErrorTypes } from "../../../errorTypes.sol";
                  import { IFluidDexT1 } from "../../../interfaces/iDexT1.sol";
                  interface IDexCallback {
                      function dexCallback(address token_, uint256 amount_) external;
                  }
                  /// @title FluidDexT1
                  /// @notice Implements core logics for Fluid Dex protocol.
                  /// Note Token transfers happen directly from user to Liquidity contract and vice-versa.
                  contract FluidDexT1 is CoreHelpers {
                      using BigMathMinified for uint256;
                      constructor(ConstantViews memory constantViews_) CoreHelpers(constantViews_) {
                          // any implementations should not be zero
                          if (
                              constantViews_.implementations.shift == address(0) ||
                              constantViews_.implementations.admin == address(0) ||
                              constantViews_.implementations.colOperations == address(0) ||
                              constantViews_.implementations.debtOperations == address(0) ||
                              constantViews_.implementations.perfectOperationsAndSwapOut == address(0)
                          ) {
                              revert FluidDexError(ErrorTypes.DexT1__InvalidImplementation);
                          }
                      }
                      struct SwapInExtras {
                          address to;
                          uint amountOutMin;
                          bool isCallback;
                      }
                      /// @dev This function allows users to swap a specific amount of input tokens for output tokens
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountIn_ The exact amount of input tokens to swap
                      /// @param extras_ Additional parameters for the swap:
                      ///   - to: Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountOut_
                      ///   - amountOutMin: The minimum amount of output tokens the user expects to receive
                      ///   - isCallback: If true, indicates that the input tokens should be transferred via a callback
                      /// @return amountOut_ The amount of output tokens received from the swap
                      function _swapIn(
                          bool swap0to1_,
                          uint256 amountIn_,
                          SwapInExtras memory extras_
                      ) internal returns (uint256 amountOut_) {
                          uint dexVariables_ = dexVariables;
                          uint dexVariables2_ = dexVariables2;
                          if ((dexVariables2_ >> 255) == 1) revert FluidDexError(ErrorTypes.DexT1__SwapAndArbitragePaused);
                          _check(dexVariables_, dexVariables2_);
                          if (extras_.to == address(0)) extras_.to = msg.sender;
                          SwapInMemory memory s_;
                          if (swap0to1_) {
                              (s_.tokenIn, s_.tokenOut) = (TOKEN_0, TOKEN_1);
                              unchecked {
                                  s_.amtInAdjusted = (amountIn_ * TOKEN_0_NUMERATOR_PRECISION) / TOKEN_0_DENOMINATOR_PRECISION;
                              }
                          } else {
                              (s_.tokenIn, s_.tokenOut) = (TOKEN_1, TOKEN_0);
                              unchecked {
                                  s_.amtInAdjusted = (amountIn_ * TOKEN_1_NUMERATOR_PRECISION) / TOKEN_1_DENOMINATOR_PRECISION;
                              }
                          }
                          _verifySwapAndNonPerfectActions(s_.amtInAdjusted, amountIn_);
                          PricesAndExchangePrice memory pex_ = _getPricesAndExchangePrices(dexVariables_, dexVariables2_);
                          if (msg.value > 0) {
                              if (msg.value != amountIn_) revert FluidDexError(ErrorTypes.DexT1__EthAndAmountInMisMatch);
                              if (s_.tokenIn != NATIVE_TOKEN) revert FluidDexError(ErrorTypes.DexT1__EthSentForNonNativeSwap);
                          }
                          // is smart collateral pool enabled
                          uint temp_ = dexVariables2_ & 1;
                          // is smart debt pool enabled
                          uint temp2_ = (dexVariables2_ >> 1) & 1;
                          uint temp3_;
                          uint temp4_;
                          // extracting fee
                          temp3_ = ((dexVariables2_ >> 2) & X17);
                          unchecked {
                              // revenueCut in 6 decimals, to have proper precision
                              // if fee = 1% and revenue cut = 10% then revenueCut = 1e8 - (10000 * 10) = 99900000
                              s_.revenueCut = EIGHT_DECIMALS - ((((dexVariables2_ >> 19) & X7) * temp3_));
                              // fee in 4 decimals
                              // 1 - fee. If fee is 1% then withoutFee will be 1e6 - 1e4
                              // s_.fee => 1 - withdraw fee
                              s_.fee = SIX_DECIMALS - temp3_;
                          }
                          CollateralReservesSwap memory cs_;
                          DebtReservesSwap memory ds_;
                          if (temp_ == 1) {
                              // smart collateral is enabled
                              {
                                  CollateralReserves memory c_ = _getCollateralReserves(
                                      pex_.geometricMean,
                                      pex_.upperRange,
                                      pex_.lowerRange,
                                      pex_.supplyToken0ExchangePrice,
                                      pex_.supplyToken1ExchangePrice
                                  );
                                  if (swap0to1_) {
                                      (
                                          cs_.tokenInRealReserves,
                                          cs_.tokenOutRealReserves,
                                          cs_.tokenInImaginaryReserves,
                                          cs_.tokenOutImaginaryReserves
                                      ) = (
                                          c_.token0RealReserves,
                                          c_.token1RealReserves,
                                          c_.token0ImaginaryReserves,
                                          c_.token1ImaginaryReserves
                                      );
                                  } else {
                                      (
                                          cs_.tokenInRealReserves,
                                          cs_.tokenOutRealReserves,
                                          cs_.tokenInImaginaryReserves,
                                          cs_.tokenOutImaginaryReserves
                                      ) = (
                                          c_.token1RealReserves,
                                          c_.token0RealReserves,
                                          c_.token1ImaginaryReserves,
                                          c_.token0ImaginaryReserves
                                      );
                                  }
                              }
                          }
                          if (temp2_ == 1) {
                              // smart debt is enabled
                              {
                                  DebtReserves memory d_ = _getDebtReserves(
                                      pex_.geometricMean,
                                      pex_.upperRange,
                                      pex_.lowerRange,
                                      pex_.borrowToken0ExchangePrice,
                                      pex_.borrowToken1ExchangePrice
                                  );
                                  if (swap0to1_) {
                                      (
                                          ds_.tokenInDebt,
                                          ds_.tokenOutDebt,
                                          ds_.tokenInRealReserves,
                                          ds_.tokenOutRealReserves,
                                          ds_.tokenInImaginaryReserves,
                                          ds_.tokenOutImaginaryReserves
                                      ) = (
                                          d_.token0Debt,
                                          d_.token1Debt,
                                          d_.token0RealReserves,
                                          d_.token1RealReserves,
                                          d_.token0ImaginaryReserves,
                                          d_.token1ImaginaryReserves
                                      );
                                  } else {
                                      (
                                          ds_.tokenInDebt,
                                          ds_.tokenOutDebt,
                                          ds_.tokenInRealReserves,
                                          ds_.tokenOutRealReserves,
                                          ds_.tokenInImaginaryReserves,
                                          ds_.tokenOutImaginaryReserves
                                      ) = (
                                          d_.token1Debt,
                                          d_.token0Debt,
                                          d_.token1RealReserves,
                                          d_.token0RealReserves,
                                          d_.token1ImaginaryReserves,
                                          d_.token0ImaginaryReserves
                                      );
                                  }
                              }
                          }
                          // limiting amtInAdjusted to be not more than 50% of both (collateral & debt) imaginary tokenIn reserves combined
                          // basically, if this throws that means user is trying to swap 0.5x tokenIn if current tokenIn imaginary reserves is x
                          // let's take x as token0 here, that means, initially the pool pricing might be:
                          // token1Reserve / x and new pool pricing will become token1Reserve / 1.5x (token1Reserve will decrease after swap but for simplicity ignoring that)
                          // So pool price is decreased by ~33.33% (oracle will throw error in this case as it only allows 5% price difference but better to limit it before hand)
                          unchecked {
                              if (s_.amtInAdjusted > ((cs_.tokenInImaginaryReserves + ds_.tokenInImaginaryReserves) / 2))
                                  revert FluidDexError(ErrorTypes.DexT1__SwapInLimitingAmounts);
                          }
                          if (temp_ == 1 && temp2_ == 1) {
                              // unless both pools are enabled s_.swapRoutingAmt will be 0
                              s_.swapRoutingAmt = _swapRoutingIn(
                                  s_.amtInAdjusted,
                                  cs_.tokenOutImaginaryReserves,
                                  cs_.tokenInImaginaryReserves,
                                  ds_.tokenOutImaginaryReserves,
                                  ds_.tokenInImaginaryReserves
                              );
                          }
                          // In below if else statement temps are:
                          // temp_ => deposit amt
                          // temp2_ => withdraw amt
                          // temp3_ => payback amt
                          // temp4_ => borrow amt
                          if (int(s_.amtInAdjusted) > s_.swapRoutingAmt && s_.swapRoutingAmt > 0) {
                              // swap will route from the both pools
                              // temp_ = amountInCol_
                              temp_ = uint(s_.swapRoutingAmt);
                              unchecked {
                                  // temp3_ = amountInDebt_
                                  temp3_ = s_.amtInAdjusted - temp_;
                              }
                              (temp2_, temp4_) = (0, 0);
                              // debt pool price will be the same as collateral pool after the swap
                              s_.withdrawTo = extras_.to;
                              s_.borrowTo = extras_.to;
                          } else if ((temp_ == 1 && temp2_ == 0) || (s_.swapRoutingAmt >= int(s_.amtInAdjusted))) {
                              // entire swap will route through collateral pool
                              (temp_, temp2_, temp3_, temp4_) = (s_.amtInAdjusted, 0, 0, 0);
                              // price can slightly differ from debt pool but difference will be very small. Probably <0.01% for active DEX pools.
                              s_.withdrawTo = extras_.to;
                          } else if ((temp_ == 0 && temp2_ == 1) || (s_.swapRoutingAmt <= 0)) {
                              // entire swap will route through debt pool
                              (temp_, temp2_, temp3_, temp4_) = (0, 0, s_.amtInAdjusted, 0);
                              // price can slightly differ from collateral pool but difference will be very small. Probably <0.01% for active DEX pools.
                              s_.borrowTo = extras_.to;
                          } else {
                              // swap should never reach this point but if it does then reverting
                              revert FluidDexError(ErrorTypes.DexT1__NoSwapRoute);
                          }
                          if (temp_ > 0) {
                              // temp2_ = amountOutCol_
                              temp2_ = _getAmountOut(
                                  ((temp_ * s_.fee) / SIX_DECIMALS),
                                  cs_.tokenInImaginaryReserves,
                                  cs_.tokenOutImaginaryReserves
                              );
                              swap0to1_
                                  ? _verifyToken1Reserves(
                                      (cs_.tokenInRealReserves + temp_),
                                      (cs_.tokenOutRealReserves - temp2_),
                                      pex_.centerPrice,
                                      MINIMUM_LIQUIDITY_SWAP
                                  )
                                  : _verifyToken0Reserves(
                                      (cs_.tokenOutRealReserves - temp2_),
                                      (cs_.tokenInRealReserves + temp_),
                                      pex_.centerPrice,
                                      MINIMUM_LIQUIDITY_SWAP
                                  );
                          }
                          if (temp3_ > 0) {
                              // temp4_ = amountOutDebt_
                              temp4_ = _getAmountOut(
                                  ((temp3_ * s_.fee) / SIX_DECIMALS),
                                  ds_.tokenInImaginaryReserves,
                                  ds_.tokenOutImaginaryReserves
                              );
                              swap0to1_
                                  ? _verifyToken1Reserves(
                                      (ds_.tokenInRealReserves + temp3_),
                                      (ds_.tokenOutRealReserves - temp4_),
                                      pex_.centerPrice,
                                      MINIMUM_LIQUIDITY_SWAP
                                  )
                                  : _verifyToken0Reserves(
                                      (ds_.tokenOutRealReserves - temp4_),
                                      (ds_.tokenInRealReserves + temp3_),
                                      pex_.centerPrice,
                                      MINIMUM_LIQUIDITY_SWAP
                                  );
                          }
                          // (temp_ + temp3_) == amountIn_ == msg.value (for native token), if there is revenue cut then this statement is not true
                          temp_ = (temp_ * s_.revenueCut) / EIGHT_DECIMALS;
                          temp3_ = (temp3_ * s_.revenueCut) / EIGHT_DECIMALS;
                          // from whatever pool higher amount of swap is routing we are taking that as final price, does not matter much because both pools final price should be same
                          if (temp_ > temp3_) {
                              // new pool price from col pool
                              s_.price = swap0to1_
                                  ? ((cs_.tokenOutImaginaryReserves - temp2_) * 1e27) / (cs_.tokenInImaginaryReserves + temp_)
                                  : ((cs_.tokenInImaginaryReserves + temp_) * 1e27) / (cs_.tokenOutImaginaryReserves - temp2_);
                          } else {
                              // new pool price from debt pool
                              s_.price = swap0to1_
                                  ? ((ds_.tokenOutImaginaryReserves - temp4_) * 1e27) / (ds_.tokenInImaginaryReserves + temp3_)
                                  : ((ds_.tokenInImaginaryReserves + temp3_) * 1e27) / (ds_.tokenOutImaginaryReserves - temp4_);
                          }
                          // converting into normal token amounts
                          if (swap0to1_) {
                              temp_ = ((temp_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                              temp3_ = ((temp3_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                              // only adding uncheck in out amount
                              unchecked {
                                  temp2_ = ((temp2_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                                  temp4_ = ((temp4_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                              }
                          } else {
                              temp_ = ((temp_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                              temp3_ = ((temp3_ * TOKEN_1_DENOMINATOR_PRECISION) / TOKEN_1_NUMERATOR_PRECISION);
                              // only adding uncheck in out amount
                              unchecked {
                                  temp2_ = ((temp2_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                                  temp4_ = ((temp4_ * TOKEN_0_DENOMINATOR_PRECISION) / TOKEN_0_NUMERATOR_PRECISION);
                              }
                          }
                          unchecked {
                              amountOut_ = temp2_ + temp4_;
                          }
                          // if address dead then reverting with amountOut
                          if (extras_.to == ADDRESS_DEAD) revert FluidDexSwapResult(amountOut_);
                          if (amountOut_ < extras_.amountOutMin) revert FluidDexError(ErrorTypes.DexT1__NotEnoughAmountOut);
                          // allocating to avoid stack-too-deep error
                          // not setting in the callbackData as last 2nd to avoid SKIP_TRANSFERS clashing
                          s_.data = abi.encode(amountIn_, extras_.isCallback, msg.sender); // true/false is to decide if dex should do callback or directly transfer from user
                          // deposit & payback token in at liquidity
                          LIQUIDITY.operate{ value: msg.value }(s_.tokenIn, int(temp_), -int(temp3_), address(0), address(0), s_.data);
                          // withdraw & borrow token out at liquidity
                          LIQUIDITY.operate(s_.tokenOut, -int(temp2_), int(temp4_), s_.withdrawTo, s_.borrowTo, new bytes(0));
                          // if hook exists then calling hook
                          temp_ = (dexVariables2_ >> 142) & X30;
                          if (temp_ > 0) {
                              s_.swap0to1 = swap0to1_;
                              _hookVerify(temp_, 1, s_.swap0to1, s_.price);
                          }
                          swap0to1_
                              ? _utilizationVerify(((dexVariables2_ >> 238) & X10), EXCHANGE_PRICE_TOKEN_1_SLOT)
                              : _utilizationVerify(((dexVariables2_ >> 228) & X10), EXCHANGE_PRICE_TOKEN_0_SLOT);
                          dexVariables = _updateOracle(s_.price, pex_.centerPrice, dexVariables_);
                          emit Swap(swap0to1_, amountIn_, amountOut_, extras_.to);
                      }
                      /// @dev Swap tokens with perfect amount in
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountIn_ The exact amount of tokens to swap in
                      /// @param amountOutMin_ The minimum amount of tokens to receive after swap
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountOut_
                      /// @return amountOut_ The amount of output tokens received from the swap
                      function swapIn(
                          bool swap0to1_,
                          uint256 amountIn_,
                          uint256 amountOutMin_,
                          address to_
                      ) public payable returns (uint256 amountOut_) {
                          return _swapIn(swap0to1_, amountIn_, SwapInExtras(to_, amountOutMin_, false));
                      }
                      /// @dev Swap tokens with perfect amount in and callback functionality
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountIn_ The exact amount of tokens to swap in
                      /// @param amountOutMin_ The minimum amount of tokens to receive after swap
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountOut_
                      /// @return amountOut_ The amount of output tokens received from the swap
                      function swapInWithCallback(
                          bool swap0to1_,
                          uint256 amountIn_,
                          uint256 amountOutMin_,
                          address to_
                      ) public payable returns (uint256 amountOut_) {
                          return _swapIn(swap0to1_, amountIn_, SwapInExtras(to_, amountOutMin_, true));
                      }
                      /// @dev Swap tokens with perfect amount out
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountOut_ The exact amount of tokens to receive after swap
                      /// @param amountInMax_ Maximum amount of tokens to swap in
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountIn_
                      /// @return amountIn_ The amount of input tokens used for the swap
                      function swapOut(
                          bool swap0to1_,
                          uint256 amountOut_,
                          uint256 amountInMax_,
                          address to_
                      ) public payable returns (uint256 amountIn_) {
                          return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev Swap tokens with perfect amount out and callback functionality
                      /// @param swap0to1_ Direction of swap. If true, swaps token0 for token1; if false, swaps token1 for token0
                      /// @param amountOut_ The exact amount of tokens to receive after swap
                      /// @param amountInMax_ Maximum amount of tokens to swap in
                      /// @param to_ Recipient of swapped tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with amountIn_
                      /// @return amountIn_ The amount of input tokens used for the swap
                      function swapOutWithCallback(
                          bool swap0to1_,
                          uint256 amountOut_,
                          uint256 amountInMax_,
                          address to_
                      ) public payable returns (uint256 amountIn_) {
                          return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev Deposit tokens in equal proportion to the current pool ratio
                      /// @param shares_ The number of shares to mint
                      /// @param maxToken0Deposit_ Maximum amount of token0 to deposit
                      /// @param maxToken1Deposit_ Maximum amount of token1 to deposit
                      /// @param estimate_ If true, function will revert with estimated deposit amounts without executing the deposit
                      /// @return token0Amt_ Amount of token0 deposited
                      /// @return token1Amt_ Amount of token1 deposited
                      function depositPerfect(
                          uint shares_,
                          uint maxToken0Deposit_,
                          uint maxToken1Deposit_,
                          bool estimate_
                      ) public payable returns (uint token0Amt_, uint token1Amt_) {
                          return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                      }
                      /// @dev This function allows users to withdraw a perfect amount of collateral liquidity
                      /// @param shares_ The number of shares to withdraw
                      /// @param minToken0Withdraw_ The minimum amount of token0 the user is willing to accept
                      /// @param minToken1Withdraw_ The minimum amount of token1 the user is willing to accept
                      /// @param to_ Recipient of withdrawn tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with token0Amt_ & token1Amt_
                      /// @return token0Amt_ The amount of token0 withdrawn
                      /// @return token1Amt_ The amount of token1 withdrawn
                      function withdrawPerfect(
                          uint shares_,
                          uint minToken0Withdraw_,
                          uint minToken1Withdraw_,
                          address to_
                      ) public returns (uint token0Amt_, uint token1Amt_) {
                          return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                      }
                      /// @dev This function allows users to borrow tokens in equal proportion to the current debt pool ratio
                      /// @param shares_ The number of shares to borrow
                      /// @param minToken0Borrow_ Minimum amount of token0 to borrow
                      /// @param minToken1Borrow_ Minimum amount of token1 to borrow
                      /// @param to_ Recipient of borrowed tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with token0Amt_ & token1Amt_
                      /// @return token0Amt_ Amount of token0 borrowed
                      /// @return token1Amt_ Amount of token1 borrowed
                      function borrowPerfect(
                          uint shares_,
                          uint minToken0Borrow_,
                          uint minToken1Borrow_,
                          address to_
                      ) public returns (uint token0Amt_, uint token1Amt_) {
                          return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                      }
                      /// @dev This function allows users to pay back borrowed tokens in equal proportion to the current debt pool ratio
                      /// @param shares_ The number of shares to pay back
                      /// @param maxToken0Payback_ Maximum amount of token0 to pay back
                      /// @param maxToken1Payback_ Maximum amount of token1 to pay back
                      /// @param estimate_ If true, function will revert with estimated payback amounts without executing the payback
                      /// @return token0Amt_ Amount of token0 paid back
                      /// @return token1Amt_ Amount of token1 paid back
                      function paybackPerfect(
                          uint shares_,
                          uint maxToken0Payback_,
                          uint maxToken1Payback_,
                          bool estimate_
                      ) public payable returns (uint token0Amt_, uint token1Amt_) {
                          return abi.decode(_spell(PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION, msg.data), (uint256, uint256));
                      }
                      /// @dev This function allows users to deposit tokens in any proportion into the col pool
                      /// @param token0Amt_ The amount of token0 to deposit
                      /// @param token1Amt_ The amount of token1 to deposit
                      /// @param minSharesAmt_ The minimum amount of shares the user expects to receive
                      /// @param estimate_ If true, function will revert with estimated shares without executing the deposit
                      /// @return shares_ The amount of shares minted for the deposit
                      function deposit(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint minSharesAmt_,
                          bool estimate_
                      ) public payable returns (uint shares_) {
                          return abi.decode(_spell(COL_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev This function allows users to withdraw tokens in any proportion from the col pool
                      /// @param token0Amt_ The amount of token0 to withdraw
                      /// @param token1Amt_ The amount of token1 to withdraw
                      /// @param maxSharesAmt_ The maximum number of shares the user is willing to burn
                      /// @param to_ Recipient of withdrawn tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with shares_
                      /// @return shares_ The number of shares burned for the withdrawal
                      function withdraw(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint maxSharesAmt_,
                          address to_
                      ) public returns (uint shares_) {
                          return abi.decode(_spell(COL_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev This function allows users to borrow tokens in any proportion from the debt pool
                      /// @param token0Amt_ The amount of token0 to borrow
                      /// @param token1Amt_ The amount of token1 to borrow
                      /// @param maxSharesAmt_ The maximum amount of shares the user is willing to receive
                      /// @param to_ Recipient of borrowed tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with shares_
                      /// @return shares_ The amount of borrow shares minted to represent the borrowed amount
                      function borrow(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint maxSharesAmt_,
                          address to_
                      ) public returns (uint shares_) {
                          return abi.decode(_spell(DEBT_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev This function allows users to payback tokens in any proportion to the debt pool
                      /// @param token0Amt_ The amount of token0 to payback
                      /// @param token1Amt_ The amount of token1 to payback
                      /// @param minSharesAmt_ The minimum amount of shares the user expects to burn
                      /// @param estimate_ If true, function will revert with estimated shares without executing the payback
                      /// @return shares_ The amount of borrow shares burned for the payback
                      function payback(
                          uint token0Amt_,
                          uint token1Amt_,
                          uint minSharesAmt_,
                          bool estimate_
                      ) public payable returns (uint shares_) {
                          return abi.decode(_spell(DEBT_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev This function allows users to withdraw their collateral with perfect shares in one token
                      /// @param shares_ The number of shares to burn for withdrawal
                      /// @param minToken0_ The minimum amount of token0 the user expects to receive (set to 0 if withdrawing in token1)
                      /// @param minToken1_ The minimum amount of token1 the user expects to receive (set to 0 if withdrawing in token0)
                      /// @param to_ Recipient of withdrawn tokens. If to_ == address(0) then out tokens will be sent to msg.sender. If to_ == ADDRESS_DEAD then function will revert with withdrawAmt_
                      /// @return withdrawAmt_ The amount of tokens withdrawn in the chosen token
                      function withdrawPerfectInOneToken(
                          uint shares_,
                          uint minToken0_,
                          uint minToken1_,
                          address to_
                      ) public returns (uint withdrawAmt_) {
                          return abi.decode(_spell(COL_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev This function allows users to payback their debt with perfect shares in one token
                      /// @param shares_ The number of shares to burn for payback
                      /// @param maxToken0_ The maximum amount of token0 the user is willing to pay (set to 0 if paying back in token1)
                      /// @param maxToken1_ The maximum amount of token1 the user is willing to pay (set to 0 if paying back in token0)
                      /// @param estimate_ If true, the function will revert with the estimated payback amount without executing the payback
                      /// @return paybackAmt_ The amount of tokens paid back in the chosen token
                      function paybackPerfectInOneToken(
                          uint shares_,
                          uint maxToken0_,
                          uint maxToken1_,
                          bool estimate_
                      ) public payable returns (uint paybackAmt_) {
                          return abi.decode(_spell(DEBT_OPERATIONS_IMPLEMENTATION, msg.data), (uint256));
                      }
                      /// @dev liquidity callback for cheaper token transfers in case of deposit or payback.
                      /// only callable by Liquidity during an operation.
                      function liquidityCallback(address token_, uint amount_, bytes calldata data_) external {
                          if (msg.sender != address(LIQUIDITY)) revert FluidDexError(ErrorTypes.DexT1__MsgSenderNotLiquidity);
                          if (dexVariables & 1 == 0) revert FluidDexError(ErrorTypes.DexT1__ReentrancyBitShouldBeOn);
                          if (data_.length != 96) revert FluidDexError(ErrorTypes.DexT1__IncorrectDataLength);
                          (uint amountToSend_, bool isCallback_, address from_) = abi.decode(data_, (uint, bool, address));
                          if (amountToSend_ < amount_) revert FluidDexError(ErrorTypes.DexT1__AmountToSendLessThanAmount);
                          if (isCallback_) {
                              IDexCallback(from_).dexCallback(token_, amountToSend_);
                          } else {
                              SafeTransfer.safeTransferFrom(token_, from_, address(LIQUIDITY), amountToSend_);
                          }
                      }
                      /// @dev the oracle assumes last set price of pool till the next swap happens.
                      /// There's a possibility that during that time some interest is generated hence the last stored price is not the 100% correct price for the whole duration
                      /// but the difference due to interest will be super low so this difference is ignored
                      /// For example 2 swaps happened 10min (600 seconds) apart and 1 token has 10% higher interest than other.
                      /// then that token will accrue about 10% * 600 / secondsInAYear = ~0.0002%
                      /// @param secondsAgos_ array of seconds ago for which TWAP is needed. If user sends [10, 30, 60] then twaps_ will return [10-0, 30-10, 60-30]
                      /// @return twaps_ twap price, lowest price (aka minima) & highest price (aka maxima) between secondsAgo checkpoints
                      /// @return currentPrice_ price of pool after the most recent swap
                      function oraclePrice(
                          uint[] memory secondsAgos_
                      ) external view returns (Oracle[] memory twaps_, uint currentPrice_) {
                          OraclePriceMemory memory o_;
                          uint dexVariables_ = dexVariables;
                          if ((dexVariables_ >> 195) & 1 == 0) {
                              revert FluidDexError(ErrorTypes.DexT1__OracleNotActive);
                          }
                          twaps_ = new Oracle[](secondsAgos_.length);
                          uint totalTime_;
                          uint time_;
                          uint i;
                          uint secondsAgo_ = secondsAgos_[0];
                          currentPrice_ = (dexVariables_ >> 41) & X40;
                          currentPrice_ = (currentPrice_ >> DEFAULT_EXPONENT_SIZE) << (currentPrice_ & DEFAULT_EXPONENT_MASK);
                          uint price_ = currentPrice_;
                          o_.lowestPrice1by0 = currentPrice_;
                          o_.highestPrice1by0 = currentPrice_;
                          uint twap1by0_;
                          uint twap0by1_;
                          uint j;
                          o_.oracleSlot = (dexVariables_ >> 176) & X3;
                          o_.oracleMap = (dexVariables_ >> 179) & X16;
                          // if o_.oracleSlot == 7 then it'll enter the if statement in the below while loop
                          o_.oracle = o_.oracleSlot < 7 ? _oracle[o_.oracleMap] : 0;
                          uint slotData_;
                          uint percentDiff_;
                          if (((dexVariables_ >> 121) & X33) < block.timestamp) {
                              // last swap didn't occured in this block.
                              // hence last price is current price of pool & also the last price
                              time_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                          } else {
                              // last swap occured in this block, that means current price is active for 0 secs. Hence TWAP for it will be 0.
                              ++j;
                          }
                          while (true) {
                              if (j == 2) {
                                  if (++o_.oracleSlot == 8) {
                                      o_.oracleSlot = 0;
                                      if (o_.oracleMap == 0) {
                                          o_.oracleMap = TOTAL_ORACLE_MAPPING;
                                      }
                                      o_.oracle = _oracle[--o_.oracleMap];
                                  }
                                  slotData_ = (o_.oracle >> (o_.oracleSlot * 32)) & X32;
                                  if (slotData_ > 0) {
                                      time_ = slotData_ & X9;
                                      if (time_ == 0) {
                                          // time is in precision & sign bits
                                          time_ = slotData_ >> 9;
                                          // if o_.oracleSlot is 7 then precision & bits and stored in 1 less map
                                          if (o_.oracleSlot == 7) {
                                              o_.oracleSlot = 0;
                                              if (o_.oracleMap == 0) {
                                                  o_.oracleMap = TOTAL_ORACLE_MAPPING;
                                              }
                                              o_.oracle = _oracle[--o_.oracleMap];
                                              slotData_ = o_.oracle & X32;
                                          } else {
                                              ++o_.oracleSlot;
                                              slotData_ = (o_.oracle >> (o_.oracleSlot * 32)) & X32;
                                          }
                                      }
                                      percentDiff_ = slotData_ >> 10;
                                      percentDiff_ = (ORACLE_LIMIT * percentDiff_) / X22;
                                      if (((slotData_ >> 9) & 1 == 1)) {
                                          // if positive then old price was lower than current hence subtracting
                                          price_ = price_ - (price_ * percentDiff_) / ORACLE_PRECISION;
                                      } else {
                                          // if negative then old price was higher than current hence adding
                                          price_ = price_ + (price_ * percentDiff_) / ORACLE_PRECISION;
                                      }
                                  } else {
                                      // oracle data does not exist. Probably due to pool recently got initialized and not have much swaps.
                                      revert FluidDexError(ErrorTypes.DexT1__InsufficientOracleData);
                                  }
                              } else if (j == 1) {
                                  // last & last to last price
                                  price_ = (dexVariables_ >> 1) & X40;
                                  price_ = (price_ >> DEFAULT_EXPONENT_SIZE) << (price_ & DEFAULT_EXPONENT_MASK);
                                  time_ = (dexVariables_ >> 154) & X22;
                                  ++j;
                              } else if (j == 0) {
                                  ++j;
                              }
                              totalTime_ += time_;
                              if (o_.lowestPrice1by0 > price_) o_.lowestPrice1by0 = price_;
                              if (o_.highestPrice1by0 < price_) o_.highestPrice1by0 = price_;
                              if (totalTime_ < secondsAgo_) {
                                  twap1by0_ += price_ * time_;
                                  twap0by1_ += (1e54 / price_) * time_;
                              } else {
                                  time_ = time_ + secondsAgo_ - totalTime_;
                                  twap1by0_ += price_ * time_;
                                  twap0by1_ += (1e54 / price_) * time_;
                                  // also auto checks that secondsAgos_ should not be == 0
                                  twap1by0_ = twap1by0_ / secondsAgo_;
                                  twap0by1_ = twap0by1_ / secondsAgo_;
                                  twaps_[i] = Oracle(
                                      twap1by0_,
                                      o_.lowestPrice1by0,
                                      o_.highestPrice1by0,
                                      twap0by1_,
                                      (1e54 / o_.highestPrice1by0),
                                      (1e54 / o_.lowestPrice1by0)
                                  );
                                  // TWAP for next secondsAgo will start with price_
                                  o_.lowestPrice1by0 = price_;
                                  o_.highestPrice1by0 = price_;
                                  while (++i < secondsAgos_.length) {
                                      // secondsAgo_ = [60, 15, 0]
                                      time_ = totalTime_ - secondsAgo_;
                                      // updating total time as new seconds ago started
                                      totalTime_ = time_;
                                      // also auto checks that secondsAgos_[i + 1] > secondsAgos_[i]
                                      secondsAgo_ = secondsAgos_[i] - secondsAgos_[i - 1];
                                      if (totalTime_ < secondsAgo_) {
                                          twap1by0_ = price_ * time_;
                                          twap0by1_ = (1e54 / price_) * time_;
                                          // if time_ comes out as 0 here then lowestPrice & highestPrice should not be price_, it should be next price_ that we will calculate
                                          if (time_ == 0) {
                                              o_.lowestPrice1by0 = type(uint).max;
                                              o_.highestPrice1by0 = 0;
                                          }
                                          break;
                                      } else {
                                          time_ = time_ + secondsAgo_ - totalTime_;
                                          // twap1by0_ = price_ here
                                          twap1by0_ = price_ * time_;
                                          // twap0by1_ = (1e54 / price_) * time_;
                                          twap0by1_ = (1e54 / price_) * time_;
                                          twap1by0_ = twap1by0_ / secondsAgo_;
                                          twap0by1_ = twap0by1_ / secondsAgo_;
                                          twaps_[i] = Oracle(
                                              twap1by0_,
                                              o_.lowestPrice1by0,
                                              o_.highestPrice1by0,
                                              twap0by1_,
                                              (1e54 / o_.highestPrice1by0),
                                              (1e54 / o_.lowestPrice1by0)
                                          );
                                      }
                                  }
                                  if (i == secondsAgos_.length) return (twaps_, currentPrice_); // oracle fetch over
                              }
                          }
                      }
                      function getPricesAndExchangePrices() public {
                          uint dexVariables_ = dexVariables;
                          uint dexVariables2_ = dexVariables2;
                          _check(dexVariables_, dexVariables2_);
                          PricesAndExchangePrice memory pex_ = _getPricesAndExchangePrices(dexVariables, dexVariables2);
                          revert FluidDexPricesAndExchangeRates(pex_);
                      }
                      /// @dev Internal fallback function to handle calls to non-existent functions
                      /// @notice This function is called when a transaction is sent to the contract without matching any other function
                      /// @notice It checks if the caller is authorized, enables re-entrancy protection, delegates the call to the admin implementation, and then disables re-entrancy protection
                      /// @notice Only authorized callers (global or dex auth) can trigger this function
                      /// @notice This function uses assembly to perform a delegatecall to the admin implementation to update configs related to DEX
                      function _fallback() private {
                          if (!(DEX_FACTORY.isGlobalAuth(msg.sender) || DEX_FACTORY.isDexAuth(address(this), msg.sender))) {
                              revert FluidDexError(ErrorTypes.DexT1__NotAnAuth);
                          }
                          uint dexVariables_ = dexVariables;
                          if (dexVariables_ & 1 == 1) revert FluidDexError(ErrorTypes.DexT1__AlreadyEntered);
                          // enabling re-entrancy
                          dexVariables = dexVariables_ | 1;
                          // Delegate the current call to `ADMIN_IMPLEMENTATION`.
                          _spell(ADMIN_IMPLEMENTATION, msg.data);
                          // disabling re-entrancy
                          // directly fetching from storage so updates from Admin module will get auto covered
                          dexVariables = dexVariables & ~uint(1);
                      }
                      fallback() external payable {
                          _fallback();
                      }
                      receive() external payable {
                          if (msg.sig != 0x00000000) {
                              _fallback();
                          }
                      }
                      /// @notice returns all Vault constants
                      function constantsView() external view returns (ConstantViews memory constantsView_) {
                          constantsView_.dexId = DEX_ID;
                          constantsView_.liquidity = address(LIQUIDITY);
                          constantsView_.factory = address(DEX_FACTORY);
                          constantsView_.token0 = TOKEN_0;
                          constantsView_.token1 = TOKEN_1;
                          constantsView_.implementations.shift = SHIFT_IMPLEMENTATION;
                          constantsView_.implementations.admin = ADMIN_IMPLEMENTATION;
                          constantsView_.implementations.colOperations = COL_OPERATIONS_IMPLEMENTATION;
                          constantsView_.implementations.debtOperations = DEBT_OPERATIONS_IMPLEMENTATION;
                          constantsView_.implementations.perfectOperationsAndSwapOut = PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION;
                          constantsView_.deployerContract = DEPLOYER_CONTRACT;
                          constantsView_.supplyToken0Slot = SUPPLY_TOKEN_0_SLOT;
                          constantsView_.borrowToken0Slot = BORROW_TOKEN_0_SLOT;
                          constantsView_.supplyToken1Slot = SUPPLY_TOKEN_1_SLOT;
                          constantsView_.borrowToken1Slot = BORROW_TOKEN_1_SLOT;
                          constantsView_.exchangePriceToken0Slot = EXCHANGE_PRICE_TOKEN_0_SLOT;
                          constantsView_.exchangePriceToken1Slot = EXCHANGE_PRICE_TOKEN_1_SLOT;
                          constantsView_.oracleMapping = TOTAL_ORACLE_MAPPING;
                      }
                      /// @notice returns all Vault constants
                      function constantsView2() external view returns (ConstantViews2 memory constantsView2_) {
                          constantsView2_.token0NumeratorPrecision = TOKEN_0_NUMERATOR_PRECISION;
                          constantsView2_.token0DenominatorPrecision = TOKEN_0_DENOMINATOR_PRECISION;
                          constantsView2_.token1NumeratorPrecision = TOKEN_1_NUMERATOR_PRECISION;
                          constantsView2_.token1DenominatorPrecision = TOKEN_1_DENOMINATOR_PRECISION;
                      }
                      /// @notice Calculates the real and imaginary reserves for collateral tokens
                      /// @dev This function retrieves the supply of both tokens from the liquidity layer,
                      ///      adjusts them based on exchange prices, and calculates imaginary reserves
                      ///      based on the geometric mean and price range
                      /// @param geometricMean_ The geometric mean of the token prices
                      /// @param upperRange_ The upper price range
                      /// @param lowerRange_ The lower price range
                      /// @param token0SupplyExchangePrice_ The exchange price for token0 from liquidity layer
                      /// @param token1SupplyExchangePrice_ The exchange price for token1 from liquidity layer
                      /// @return c_ A struct containing the calculated real and imaginary reserves for both tokens:
                      ///         - token0RealReserves: The real reserves of token0
                      ///         - token1RealReserves: The real reserves of token1
                      ///         - token0ImaginaryReserves: The imaginary reserves of token0
                      ///         - token1ImaginaryReserves: The imaginary reserves of token1
                      function getCollateralReserves(
                          uint geometricMean_,
                          uint upperRange_,
                          uint lowerRange_,
                          uint token0SupplyExchangePrice_,
                          uint token1SupplyExchangePrice_
                      ) public view returns (CollateralReserves memory c_) {
                          return
                              _getCollateralReserves(
                                  geometricMean_,
                                  upperRange_,
                                  lowerRange_,
                                  token0SupplyExchangePrice_,
                                  token1SupplyExchangePrice_
                              );
                      }
                      /// @notice Calculates the debt reserves for both tokens
                      /// @param geometricMean_ The geometric mean of the upper and lower price ranges
                      /// @param upperRange_ The upper price range
                      /// @param lowerRange_ The lower price range
                      /// @param token0BorrowExchangePrice_ The exchange price of token0 from liquidity layer
                      /// @param token1BorrowExchangePrice_ The exchange price of token1 from liquidity layer
                      /// @return d_ The calculated debt reserves for both tokens, containing:
                      ///         - token0Debt: The debt amount of token0
                      ///         - token1Debt: The debt amount of token1
                      ///         - token0RealReserves: The real reserves of token0 derived from token1 debt
                      ///         - token1RealReserves: The real reserves of token1 derived from token0 debt
                      ///         - token0ImaginaryReserves: The imaginary debt reserves of token0
                      ///         - token1ImaginaryReserves: The imaginary debt reserves of token1
                      function getDebtReserves(
                          uint geometricMean_,
                          uint upperRange_,
                          uint lowerRange_,
                          uint token0BorrowExchangePrice_,
                          uint token1BorrowExchangePrice_
                      ) public view returns (DebtReserves memory d_) {
                          return
                              _getDebtReserves(
                                  geometricMean_,
                                  upperRange_,
                                  lowerRange_,
                                  token0BorrowExchangePrice_,
                                  token1BorrowExchangePrice_
                              );
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  abstract contract Events {
                      /// @notice Emitted on token swaps
                      /// @param swap0to1 Indicates whether the swap is from token0 to token1 or vice-versa.
                      /// @param amountIn The amount of tokens to be sent to the vault to swap.
                      /// @param amountOut The amount of tokens user got from the swap.
                      /// @param to Recepient of swapped tokens.
                      event Swap(bool swap0to1, uint256 amountIn, uint256 amountOut, address to);
                      /// @notice Emitted when liquidity is added with shares specified.
                      /// @param shares Expected exact shares to be received.
                      /// @param token0Amt Amount of token0 deposited.
                      /// @param token0Amt Amount of token1 deposited.
                      event LogDepositPerfectColLiquidity(uint shares, uint token0Amt, uint token1Amt);
                      /// @notice Emitted when liquidity is withdrawn with shares specified.
                      /// @param shares shares burned
                      /// @param token0Amt Amount of token0 withdrawn.
                      /// @param token1Amt Amount of token1 withdrawn.
                      event LogWithdrawPerfectColLiquidity(uint shares, uint token0Amt, uint token1Amt);
                      /// @notice Emitted when liquidity is borrowed with shares specified.
                      /// @param shares shares minted
                      /// @param token0Amt Amount of token0 borrowed.
                      /// @param token1Amt Amount of token1 borrowed.
                      event LogBorrowPerfectDebtLiquidity(uint shares, uint token0Amt, uint token1Amt);
                      /// @notice Emitted when liquidity is paid back with shares specified.
                      /// @param shares shares burned
                      /// @param token0Amt Amount of token0 paid back.
                      /// @param token1Amt Amount of token1 paid back.
                      event LogPaybackPerfectDebtLiquidity(uint shares, uint token0Amt, uint token1Amt);
                      /// @notice Emitted when liquidity is deposited with specified token0 & token1 amount
                      /// @param amount0 Amount of token0 deposited.
                      /// @param amount1 Amount of token1 deposited.
                      /// @param shares Amount of shares minted.
                      event LogDepositColLiquidity(uint amount0, uint amount1, uint shares);
                      /// @notice Emitted when liquidity is withdrawn with specified token0 & token1 amount
                      /// @param amount0 Amount of token0 withdrawn.
                      /// @param amount1 Amount of token1 withdrawn.
                      /// @param shares Amount of shares burned.
                      event LogWithdrawColLiquidity(uint amount0, uint amount1, uint shares);
                      /// @notice Emitted when liquidity is borrowed with specified token0 & token1 amount
                      /// @param amount0 Amount of token0 borrowed.
                      /// @param amount1 Amount of token1 borrowed.
                      /// @param shares Amount of shares minted.
                      event LogBorrowDebtLiquidity(uint amount0, uint amount1, uint shares);
                      /// @notice Emitted when liquidity is paid back with specified token0 & token1 amount
                      /// @param amount0 Amount of token0 paid back.
                      /// @param amount1 Amount of token1 paid back.
                      /// @param shares Amount of shares burned.
                      event LogPaybackDebtLiquidity(uint amount0, uint amount1, uint shares);
                      /// @notice Emitted when liquidity is withdrawn with shares specified into one token only.
                      /// @param shares shares burned
                      /// @param token0Amt Amount of token0 withdrawn.
                      /// @param token1Amt Amount of token1 withdrawn.
                      event LogWithdrawColInOneToken(uint shares, uint token0Amt, uint token1Amt);
                      /// @notice Emitted when liquidity is paid back with shares specified from one token only.
                      /// @param shares shares burned
                      /// @param token0Amt Amount of token0 paid back.
                      /// @param token1Amt Amount of token1 paid back.
                      event LogPaybackDebtInOneToken(uint shares, uint token0Amt, uint token1Amt);
                      /// @notice Emitted when internal arbitrage between 2 pools happen
                      /// @param routing if positive then routing is amtIn of token0 in deposit & borrow else token0 withdraw & payback
                      /// @param amtOut if routing is positive then token1 withdraw & payback amount else token1 deposit & borrow
                      event LogArbitrage(int routing, uint amtOut);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
                  import { Variables } from "../../common/variables.sol";
                  import { ImmutableVariables } from "../immutableVariables.sol";
                  import { Events } from "../events.sol";
                  import { ErrorTypes } from "../../../errorTypes.sol";
                  import { IHook, ICenterPrice } from "../interfaces.sol";
                  import { LiquiditySlotsLink } from "../../../../../libraries/liquiditySlotsLink.sol";
                  import { LiquidityCalcs } from "../../../../../libraries/liquidityCalcs.sol";
                  import { DexSlotsLink } from "../../../../../libraries/dexSlotsLink.sol";
                  import { DexCalcs } from "../../../../../libraries/dexCalcs.sol";
                  import { BigMathMinified } from "../../../../../libraries/bigMathMinified.sol";
                  import { AddressCalcs } from "../../../../../libraries/addressCalcs.sol";
                  interface IShifting {
                      /// @dev Calculates the new upper and lower range values during an active range shift
                      /// @param upperRange_ The target upper range value
                      /// @param lowerRange_ The target lower range value
                      /// @param dexVariables2_ needed in case shift is ended and we need to update dexVariables2
                      /// @return The updated upper range, lower range, and dexVariables2
                      function _calcRangeShifting(
                          uint upperRange_,
                          uint lowerRange_,
                          uint dexVariables2_
                      ) external returns (uint, uint, uint);
                      /// @dev Calculates the new threshold values during an active threshold shift
                      /// @param upperThreshold_ The target upper threshold value
                      /// @param lowerThreshold_ The target lower threshold value
                      /// @param dexVariables2_ needed in case shift is ended and we need to update dexVariables2
                      /// @return The updated upper threshold, lower threshold, and dexVariables2
                      function _calcThresholdShifting(
                          uint upperThreshold_,
                          uint lowerThreshold_,
                          uint dexVariables2_
                      ) external returns (uint, uint, uint);
                      /// @dev Calculates the new center price during an active center price shift
                      /// @param dexVariables_ The current state of dex variables
                      /// @param dexVariables2_ Additional dex variables
                      /// @return The updated center price
                      function _calcCenterPrice(
                          uint dexVariables_,
                          uint dexVariables2_
                      ) external returns (uint);
                  }
                  abstract contract CoreHelpers is Variables, ImmutableVariables, Events {
                      using BigMathMinified for uint256;
                      /// @dev            do any arbitrary call
                      /// @param target_  Address to which the call needs to be delegated
                      /// @param data_    Data to execute at the delegated address
                      function _spell(address target_, bytes memory data_) internal returns (bytes memory response_) {
                          assembly {
                              let succeeded := delegatecall(gas(), target_, add(data_, 0x20), mload(data_), 0, 0)
                              let size := returndatasize()
                              response_ := mload(0x40)
                              mstore(0x40, add(response_, and(add(add(size, 0x20), 0x1f), not(0x1f))))
                              mstore(response_, size)
                              returndatacopy(add(response_, 0x20), 0, size)
                              if iszero(succeeded) {
                                  // throw if delegatecall failed
                                  returndatacopy(0x00, 0x00, size)
                                  revert(0x00, size)
                              }
                          }
                      }
                      /// @dev Given an input amount of asset and pair reserves, returns the maximum output amount of the other asset
                      /// @param amountIn_ The amount of input asset.
                      /// @param iReserveIn_ Imaginary token reserve with input amount.
                      /// @param iReserveOut_ Imaginary token reserve of output amount.
                      function _getAmountOut(
                          uint256 amountIn_,
                          uint iReserveIn_,
                          uint iReserveOut_
                      ) internal pure returns (uint256 amountOut_) {
                          unchecked {
                              // Both numerator and denominator are scaled to 1e6 to factor in fee scaling.
                              uint256 numerator_ = amountIn_ * iReserveOut_;
                              uint256 denominator_ = iReserveIn_ + amountIn_;
                              // Using the swap formula: (AmountIn * iReserveY) / (iReserveX + AmountIn)
                              amountOut_ = numerator_ / denominator_;
                          }
                      }
                      /// @dev Given an output amount of asset and pair reserves, returns the input amount of the other asset
                      /// @param amountOut_ Desired output amount of the asset.
                      /// @param iReserveIn_ Imaginary token reserve of input amount.
                      /// @param iReserveOut_ Imaginary token reserve of output amount.
                      function _getAmountIn(
                          uint256 amountOut_,
                          uint iReserveIn_,
                          uint iReserveOut_
                      ) internal pure returns (uint256 amountIn_) {
                          // Both numerator and denominator are scaled to 1e6 to factor in fee scaling.
                          uint256 numerator_ = amountOut_ * iReserveIn_;
                          uint256 denominator_ = iReserveOut_ - amountOut_;
                          // Using the swap formula: (AmountOut * iReserveX) / (iReserveY - AmountOut)
                          amountIn_ = numerator_ / denominator_;
                      }
                      /// @param t total amount in
                      /// @param x imaginary reserves of token out of collateral
                      /// @param y imaginary reserves of token in of collateral
                      /// @param x2 imaginary reserves of token out of debt
                      /// @param y2 imaginary reserves of token in of debt
                      /// @return a_ how much swap should go through collateral pool. Remaining will go from debt
                      /// note if a < 0 then entire trade route through debt pool and debt pool arbitrage with col pool
                      /// note if a > t then entire trade route through col pool and col pool arbitrage with debt pool
                      /// note if a > 0 & a < t then swap will route through both pools
                      function _swapRoutingIn(uint t, uint x, uint y, uint x2, uint y2) internal pure returns (int a_) {
                          // Main equations:
                          // 1. out = x * a / (y + a)
                          // 2. out2 = x2 * (t - a) / (y2 + (t - a))
                          // final price should be same
                          // 3. (y + a) / (x - out) = (y2 + (t - a)) / (x2 - out2)
                          // derivation: https://chatgpt.com/share/dce6f381-ee5f-4d5f-b6ea-5996e84d5b57
                          // adding 1e18 precision
                          uint xyRoot_ = FixedPointMathLib.sqrt(x * y * 1e18);
                          uint x2y2Root_ = FixedPointMathLib.sqrt(x2 * y2 * 1e18);
                          a_ = (int(y2 * xyRoot_ + t * xyRoot_) - int(y * x2y2Root_)) / int(xyRoot_ + x2y2Root_);
                      }
                      /// @param t total amount out
                      /// @param x imaginary reserves of token in of collateral
                      /// @param y imaginary reserves of token out of collateral
                      /// @param x2 imaginary reserves of token in of debt
                      /// @param y2 imaginary reserves of token out of debt
                      /// @return a_ how much swap should go through collateral pool. Remaining will go from debt
                      /// note if a < 0 then entire trade route through debt pool and debt pool arbitrage with col pool
                      /// note if a > t then entire trade route through col pool and col pool arbitrage with debt pool
                      /// note if a > 0 & a < t then swap will route through both pools
                      function _swapRoutingOut(uint t, uint x, uint y, uint x2, uint y2) internal pure returns (int a_) {
                          // Main equations:
                          // 1. in = (x * a) / (y - a)
                          // 2. in2 = (x2 * (t - a)) / (y2 - (t - a))
                          // final price should be same
                          // 3. (y - a) / (x + in) = (y2 - (t - a)) / (x2 + in2)
                          // derivation: https://chatgpt.com/share/6585bc28-841f-49ec-aea2-1e5c5b7f4fa9
                          // adding 1e18 precision
                          uint xyRoot_ = FixedPointMathLib.sqrt(x * y * 1e18);
                          uint x2y2Root_ = FixedPointMathLib.sqrt(x2 * y2 * 1e18);
                          // 1e18 precision gets cancelled out in division
                          a_ = (int(t * xyRoot_ + y * x2y2Root_) - int(y2 * xyRoot_)) / int(xyRoot_ + x2y2Root_);
                      }
                      function _utilizationVerify(uint utilizationLimit_, bytes32 exchangePriceSlot_) internal view {
                          if (utilizationLimit_ < THREE_DECIMALS) {
                              utilizationLimit_ = utilizationLimit_ * 10;
                              // extracting utilization of token from liquidity layer
                              uint liquidityLayerUtilization_ = LIQUIDITY.readFromStorage(exchangePriceSlot_);
                              liquidityLayerUtilization_ =
                                  (liquidityLayerUtilization_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) &
                                  X14;
                              // Note: this can go slightly above the utilization limit if no update is written to storage at liquidity layer
                              // if swap was not big enough to go far enough above or any other storage update threshold write cause there
                              // so just to keep in mind when configuring the actual limit reachable can be utilizationLimit_ + storageUpdateThreshold at Liquidity
                              if (liquidityLayerUtilization_ > utilizationLimit_)
                                  revert FluidDexError(ErrorTypes.DexT1__LiquidityLayerTokenUtilizationCapReached);
                          }
                      }
                      function _check(uint dexVariables_, uint dexVariables2_) internal {
                          if (dexVariables_ & 1 == 1) revert FluidDexError(ErrorTypes.DexT1__AlreadyEntered);
                          if (dexVariables2_ & 3 == 0) revert FluidDexError(ErrorTypes.DexT1__PoolNotInitialized);
                          // enabling re-entrancy
                          dexVariables = dexVariables_ | 1;
                      }
                      /// @dev if token0 reserves are too low w.r.t token1 then revert, this is to avoid edge case scenario and making sure that precision on calculations should be high enough
                      function _verifyToken0Reserves(
                          uint token0Reserves_,
                          uint token1Reserves_,
                          uint centerPrice_,
                          uint minLiquidity_
                      ) internal pure {
                          if (((token0Reserves_) < ((token1Reserves_ * 1e27) / (centerPrice_ * minLiquidity_)))) {
                              revert FluidDexError(ErrorTypes.DexT1__TokenReservesTooLow);
                          }
                      }
                      /// @dev if token1 reserves are too low w.r.t token0 then revert, this is to avoid edge case scenario and making sure that precision on calculations should be high enough
                      function _verifyToken1Reserves(
                          uint token0Reserves_,
                          uint token1Reserves_,
                          uint centerPrice_,
                          uint minLiquidity_
                      ) internal pure {
                          if (((token1Reserves_) < ((token0Reserves_ * centerPrice_) / (1e27 * minLiquidity_)))) {
                              revert FluidDexError(ErrorTypes.DexT1__TokenReservesTooLow);
                          }
                      }
                      function _verifySwapAndNonPerfectActions(uint amountAdjusted_, uint amount_) internal pure {
                          // after shifting amount should not become 0
                          // limiting to six decimals which means in case of USDC, USDT it's 1 wei, for WBTC 100 wei, for ETH 1000 gwei
                          if (amountAdjusted_ < SIX_DECIMALS || amountAdjusted_ > X96 || amount_ < TWO_DECIMALS || amount_ > X128)
                              revert FluidDexError(ErrorTypes.DexT1__LimitingAmountsSwapAndNonPerfectActions);
                      }
                      /// @dev Calculates the new upper and lower range values during an active range shift
                      /// @param upperRange_ The target upper range value
                      /// @param lowerRange_ The target lower range value
                      /// @param dexVariables2_ needed in case shift is ended and we need to update dexVariables2
                      /// @return The updated upper range, lower range, and dexVariables2
                      /// @notice This function handles the gradual shifting of range values over time
                      /// @notice If the shift is complete, it updates the state and clears the shift data
                      function _calcRangeShifting(
                          uint upperRange_,
                          uint lowerRange_,
                          uint dexVariables2_
                      ) internal returns (uint, uint, uint) {
                          return
                              abi.decode(
                                  _spell(
                                      SHIFT_IMPLEMENTATION,
                                      abi.encodeWithSelector(
                                          IShifting._calcRangeShifting.selector,
                                          upperRange_,
                                          lowerRange_,
                                          dexVariables2_
                                      )
                                  ),
                                  (uint, uint, uint)
                              );
                      }
                      /// @dev Calculates the new upper and lower threshold values during an active threshold shift
                      /// @param upperThreshold_ The target upper threshold value
                      /// @param lowerThreshold_ The target lower threshold value
                      /// @param thresholdTime_ The time passed since shifting started
                      /// @return The updated upper threshold, lower threshold, and threshold time
                      /// @notice This function handles the gradual shifting of threshold values over time
                      /// @notice If the shift is complete, it updates the state and clears the shift data
                      function _calcThresholdShifting(
                          uint upperThreshold_,
                          uint lowerThreshold_,
                          uint thresholdTime_
                      ) internal returns (uint, uint, uint) {
                          return
                              abi.decode(
                                  _spell(
                                      SHIFT_IMPLEMENTATION,
                                      abi.encodeWithSelector(
                                          IShifting._calcThresholdShifting.selector,
                                          upperThreshold_,
                                          lowerThreshold_,
                                          thresholdTime_
                                      )
                                  ),
                                  (uint, uint, uint)
                              );
                      }
                      /// @dev Calculates the new center price during an active price shift
                      /// @param dexVariables_ The current state of dex variables
                      /// @param dexVariables2_ Additional dex variables
                      /// @return newCenterPrice_ The updated center price
                      /// @notice This function gradually shifts the center price towards a new target price over time
                      /// @notice It uses an external price source (via ICenterPrice) to determine the target price
                      /// @notice The shift continues until the current price reaches the target, or the shift duration ends
                      /// @notice Once the shift is complete, it updates the state and clears the shift data
                      /// @notice The shift rate is dynamic and depends on:
                      /// @notice - Time remaining in the shift duration
                      /// @notice - The new center price (fetched externally, which may change)
                      /// @notice - The current (old) center price
                      /// @notice This results in a fuzzy shifting mechanism where the rate can change as these parameters evolve
                      /// @notice The externally fetched new center price is expected to not differ significantly from the last externally fetched center price
                      function _calcCenterPrice(uint dexVariables_, uint dexVariables2_) internal returns (uint newCenterPrice_) {
                          return
                              abi.decode(
                                  _spell(
                                      SHIFT_IMPLEMENTATION,
                                      abi.encodeWithSelector(IShifting._calcCenterPrice.selector, dexVariables_, dexVariables2_)
                                  ),
                                  (uint)
                              );
                      }
                      /// @notice Calculates and returns the current prices and exchange prices for the pool
                      /// @param dexVariables_ The first set of DEX variables containing various pool parameters
                      /// @param dexVariables2_ The second set of DEX variables containing additional pool parameters
                      /// @return pex_ A struct containing the calculated prices and exchange prices:
                      ///         - pex_.lastStoredPrice: The last stored price in 1e27 decimals
                      ///         - pex_.centerPrice: The calculated or fetched center price in 1e27 decimals
                      ///         - pex_.upperRange: The upper range price limit in 1e27 decimals
                      ///         - pex_.lowerRange: The lower range price limit in 1e27 decimals
                      ///         - pex_.geometricMean: The geometric mean of upper range & lower range in 1e27 decimals
                      ///         - pex_.supplyToken0ExchangePrice: The current exchange price for supplying token0
                      ///         - pex_.borrowToken0ExchangePrice: The current exchange price for borrowing token0
                      ///         - pex_.supplyToken1ExchangePrice: The current exchange price for supplying token1
                      ///         - pex_.borrowToken1ExchangePrice: The current exchange price for borrowing token1
                      /// @dev This function performs the following operations:
                      ///      1. Determines the center price (either from storage, external source, or calculated)
                      ///      2. Retrieves the last stored price from dexVariables_
                      ///      3. Calculates the upper and lower range prices based on the center price and range percentages
                      ///      4. Checks if rebalancing is needed based on threshold settings
                      ///      5. Adjusts prices if necessary based on the time elapsed and threshold conditions
                      ///      6. Update the dexVariables2_ if changes were made
                      ///      7. Returns the calculated prices and exchange prices in the PricesAndExchangePrice struct
                      function _getPricesAndExchangePrices(
                          uint dexVariables_,
                          uint dexVariables2_
                      ) internal returns (PricesAndExchangePrice memory pex_) {
                          uint centerPrice_;
                          if (((dexVariables2_ >> 248) & 1) == 0) {
                              // centerPrice_ => center price hook
                              centerPrice_ = (dexVariables2_ >> 112) & X30;
                              if (centerPrice_ == 0) {
                                  centerPrice_ = (dexVariables_ >> 81) & X40;
                                  centerPrice_ = (centerPrice_ >> DEFAULT_EXPONENT_SIZE) << (centerPrice_ & DEFAULT_EXPONENT_MASK);
                              } else {
                                  // center price should be fetched from external source. For exmaple, in case of wstETH <> ETH pool,
                                  // we would want the center price to be pegged to wstETH exchange rate into ETH
                                  centerPrice_ = ICenterPrice(AddressCalcs.addressCalc(DEPLOYER_CONTRACT, centerPrice_)).centerPrice();
                              }
                          } else {
                              // an active centerPrice_ shift is going on
                              centerPrice_ = _calcCenterPrice(dexVariables_, dexVariables2_);
                          }
                          uint lastStoredPrice_ = (dexVariables_ >> 41) & X40;
                          lastStoredPrice_ = (lastStoredPrice_ >> DEFAULT_EXPONENT_SIZE) << (lastStoredPrice_ & DEFAULT_EXPONENT_MASK);
                          uint upperRange_ = ((dexVariables2_ >> 27) & X20);
                          uint lowerRange_ = ((dexVariables2_ >> 47) & X20);
                          if (((dexVariables2_ >> 26) & 1) == 1) {
                              // an active range shift is going on
                              (upperRange_, lowerRange_, dexVariables2_) = _calcRangeShifting(upperRange_, lowerRange_, dexVariables2_);
                          }
                          unchecked {
                              // adding into unchecked because upperRange_ & lowerRange_ can only be > 0 & < SIX_DECIMALS
                              // 1% = 1e4, 100% = 1e6
                              upperRange_ = (centerPrice_ * SIX_DECIMALS) / (SIX_DECIMALS - upperRange_);
                              // 1% = 1e4, 100% = 1e6
                              lowerRange_ = (centerPrice_ * (SIX_DECIMALS - lowerRange_)) / SIX_DECIMALS;
                          }
                          bool changed_;
                          {
                              // goal will be to keep threshold percents 0 if center price is fetched from external source
                              // checking if threshold are set non 0 then only rebalancing is on
                              if (((dexVariables2_ >> 68) & X20) > 0) {
                                  uint upperThreshold_ = (dexVariables2_ >> 68) & X10;
                                  uint lowerThreshold_ = (dexVariables2_ >> 78) & X10;
                                  uint shiftingTime_ = (dexVariables2_ >> 88) & X24;
                                  if (((dexVariables2_ >> 67) & 1) == 1) {
                                      // if active shift is going on for threshold then calculate threshold real time
                                      (upperThreshold_, lowerThreshold_, shiftingTime_) = _calcThresholdShifting(
                                          upperThreshold_,
                                          lowerThreshold_,
                                          shiftingTime_
                                      );
                                  }
                                  unchecked {
                                      if (
                                          lastStoredPrice_ >
                                          (centerPrice_ +
                                              ((upperRange_ - centerPrice_) * (THREE_DECIMALS - upperThreshold_)) /
                                              THREE_DECIMALS)
                                      ) {
                                          uint timeElapsed_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                                          // price shifting towards upper range
                                          if (timeElapsed_ < shiftingTime_) {
                                              centerPrice_ = centerPrice_ + ((upperRange_ - centerPrice_) * timeElapsed_) / shiftingTime_;
                                          } else {
                                              // 100% price shifted
                                              centerPrice_ = upperRange_;
                                          }
                                          changed_ = true;
                                      } else if (
                                          lastStoredPrice_ <
                                          (centerPrice_ -
                                              ((centerPrice_ - lowerRange_) * (THREE_DECIMALS - lowerThreshold_)) /
                                              THREE_DECIMALS)
                                      ) {
                                          uint timeElapsed_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                                          // price shifting towards lower range
                                          if (timeElapsed_ < shiftingTime_) {
                                              centerPrice_ = centerPrice_ - ((centerPrice_ - lowerRange_) * timeElapsed_) / shiftingTime_;
                                          } else {
                                              // 100% price shifted
                                              centerPrice_ = lowerRange_;
                                          }
                                          changed_ = true;
                                      }
                                  }
                              }
                          }
                          // temp_ => max center price
                          uint temp_ = (dexVariables2_ >> 172) & X28;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (centerPrice_ > temp_) {
                              // if center price is greater than max center price
                              centerPrice_ = temp_;
                              changed_ = true;
                          } else {
                              // check if center price is less than min center price
                              // temp_ => min center price
                              temp_ = (dexVariables2_ >> 200) & X28;
                              temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                              if (centerPrice_ < temp_) {
                                  centerPrice_ = temp_;
                                  changed_ = true;
                              }
                          }
                          // if centerPrice_ is changed then calculating upper and lower range again
                          if (changed_) {
                              upperRange_ = ((dexVariables2_ >> 27) & X20);
                              lowerRange_ = ((dexVariables2_ >> 47) & X20);
                              if (((dexVariables2_ >> 26) & 1) == 1) {
                                  (upperRange_, lowerRange_, dexVariables2_) = _calcRangeShifting(
                                      upperRange_,
                                      lowerRange_,
                                      dexVariables2_
                                  );
                              }
                              unchecked {
                                  // adding into unchecked because upperRange_ & lowerRange_ can only be > 0 & < SIX_DECIMALS
                                  // 1% = 1e4, 100% = 1e6
                                  upperRange_ = (centerPrice_ * SIX_DECIMALS) / (SIX_DECIMALS - upperRange_);
                                  // 1% = 1e4, 100% = 1e6
                                  lowerRange_ = (centerPrice_ * (SIX_DECIMALS - lowerRange_)) / SIX_DECIMALS;
                              }
                          }
                          pex_.lastStoredPrice = lastStoredPrice_;
                          pex_.centerPrice = centerPrice_;
                          pex_.upperRange = upperRange_;
                          pex_.lowerRange = lowerRange_;
                          unchecked {
                              if (upperRange_ < 1e38) {
                                  // 1e38 * 1e38 = 1e76 which is less than max uint limit
                                  pex_.geometricMean = FixedPointMathLib.sqrt(upperRange_ * lowerRange_);
                              } else {
                                  // upperRange_ price is pretty large hence lowerRange_ will also be pretty large
                                  pex_.geometricMean = FixedPointMathLib.sqrt((upperRange_ / 1e18) * (lowerRange_ / 1e18)) * 1e18;
                              }
                          }
                          // Exchange price will remain same as Liquidity Layer
                          (pex_.supplyToken0ExchangePrice, pex_.borrowToken0ExchangePrice) = LiquidityCalcs.calcExchangePrices(
                              LIQUIDITY.readFromStorage(EXCHANGE_PRICE_TOKEN_0_SLOT)
                          );
                          (pex_.supplyToken1ExchangePrice, pex_.borrowToken1ExchangePrice) = LiquidityCalcs.calcExchangePrices(
                              LIQUIDITY.readFromStorage(EXCHANGE_PRICE_TOKEN_1_SLOT)
                          );
                      }
                      /// @dev getting reserves outside range.
                      /// @param gp_ is geometric mean pricing of upper percent & lower percent
                      /// @param pa_ price of upper range or lower range
                      /// @param rx_ real reserves of token0 or token1
                      /// @param ry_ whatever is rx_ the other will be ry_
                      function _calculateReservesOutsideRange(
                          uint gp_,
                          uint pa_,
                          uint rx_,
                          uint ry_
                      ) internal pure returns (uint xa_, uint yb_) {
                          // equations we have:
                          // 1. x*y = k
                          // 2. xa*ya = k
                          // 3. xb*yb = k
                          // 4. Pa = ya / xa = upperRange_ (known)
                          // 5. Pb = yb / xb = lowerRange_ (known)
                          // 6. x - xa = rx = real reserve of x (known)
                          // 7. y - yb = ry = real reserve of y (known)
                          // With solving we get:
                          // ((Pa*Pb)^(1/2) - Pa)*xa^2 + (rx * (Pa*Pb)^(1/2) + ry)*xa + rx*ry = 0
                          // yb = yb = xa * (Pa * Pb)^(1/2)
                          // xa = (GP⋅rx + ry + (-rx⋅ry⋅4⋅(GP - Pa) + (GP⋅rx + ry)^2)^0.5) / (2Pa - 2GP)
                          // multiply entire equation by 1e27 to remove the price decimals precision of 1e27
                          // xa = (GP⋅rx + ry⋅1e27 + (rx⋅ry⋅4⋅(Pa - GP)⋅1e27 + (GP⋅rx + ry⋅1e27)^2)^0.5) / 2*(Pa - GP)
                          // dividing the equation with 2*(Pa - GP). Pa is always > GP so answer will be positive.
                          // xa = (((GP⋅rx + ry⋅1e27) / 2*(Pa - GP)) + (((rx⋅ry⋅4⋅(Pa - GP)⋅1e27) / 4*(Pa - GP)^2) + ((GP⋅rx + ry⋅1e27) / 2*(Pa - GP))^2)^0.5)
                          // xa = (((GP⋅rx + ry⋅1e27) / 2*(Pa - GP)) + (((rx⋅ry⋅1e27) / (Pa - GP)) + ((GP⋅rx + ry⋅1e27) / 2*(Pa - GP))^2)^0.5)
                          // dividing in 3 parts for simplification:
                          // part1 = (Pa - GP)
                          // part2 = (GP⋅rx + ry⋅1e27) / (2*part1)
                          // part3 = rx⋅ry
                          // note: part1 will almost always be < 1e28 but in case it goes above 1e27 then it's extremely unlikely it'll go above > 1e29
                          uint p1_ = pa_ - gp_;
                          uint p2_ = ((gp_ * rx_) + (ry_ * 1e27)) / (2 * p1_);
                          uint p3_ = rx_ * ry_;
                          // to avoid overflowing
                          p3_ = (p3_ < 1e50) ? ((p3_ * 1e27) / p1_) : (p3_ / p1_) * 1e27;
                          // xa = part2 + (part3 + (part2 * part2))^(1/2)
                          // yb = xa_ * gp_
                          xa_ = p2_ + FixedPointMathLib.sqrt((p3_ + (p2_ * p2_)));
                          yb_ = (xa_ * gp_) / 1e27;
                      }
                      /// @dev Retrieves collateral amount from liquidity layer for a given token
                      /// @param supplyTokenSlot_ The storage slot for the supply token data
                      /// @param tokenExchangePrice_ The exchange price of the token
                      /// @param isToken0_ Boolean indicating if the token is token0 (true) or token1 (false)
                      /// @return tokenSupply_ The calculated liquidity collateral amount
                      function _getLiquidityCollateral(
                          bytes32 supplyTokenSlot_,
                          uint tokenExchangePrice_,
                          bool isToken0_
                      ) internal view returns (uint tokenSupply_) {
                          uint tokenSupplyData_ = LIQUIDITY.readFromStorage(supplyTokenSlot_);
                          tokenSupply_ = (tokenSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64;
                          tokenSupply_ = (tokenSupply_ >> DEFAULT_EXPONENT_SIZE) << (tokenSupply_ & DEFAULT_EXPONENT_MASK);
                          if (tokenSupplyData_ & 1 == 1) {
                              // supply with interest is on
                              unchecked {
                                  tokenSupply_ = (tokenSupply_ * tokenExchangePrice_) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
                              }
                          }
                          unchecked {
                              tokenSupply_ = isToken0_
                                  ? ((tokenSupply_ * TOKEN_0_NUMERATOR_PRECISION) / TOKEN_0_DENOMINATOR_PRECISION)
                                  : ((tokenSupply_ * TOKEN_1_NUMERATOR_PRECISION) / TOKEN_1_DENOMINATOR_PRECISION);
                          }
                      }
                      /// @notice Calculates the real and imaginary reserves for collateral tokens
                      /// @dev This function retrieves the supply of both tokens from the liquidity layer,
                      ///      adjusts them based on exchange prices, and calculates imaginary reserves
                      ///      based on the geometric mean and price range
                      /// @param geometricMean_ The geometric mean of the token prices
                      /// @param upperRange_ The upper price range
                      /// @param lowerRange_ The lower price range
                      /// @param token0SupplyExchangePrice_ The exchange price for token0 from liquidity layer
                      /// @param token1SupplyExchangePrice_ The exchange price for token1 from liquidity layer
                      /// @return c_ A struct containing the calculated real and imaginary reserves for both tokens:
                      ///         - token0RealReserves: The real reserves of token0
                      ///         - token1RealReserves: The real reserves of token1
                      ///         - token0ImaginaryReserves: The imaginary reserves of token0
                      ///         - token1ImaginaryReserves: The imaginary reserves of token1
                      function _getCollateralReserves(
                          uint geometricMean_,
                          uint upperRange_,
                          uint lowerRange_,
                          uint token0SupplyExchangePrice_,
                          uint token1SupplyExchangePrice_
                      ) internal view returns (CollateralReserves memory c_) {
                          uint token0Supply_ = _getLiquidityCollateral(SUPPLY_TOKEN_0_SLOT, token0SupplyExchangePrice_, true);
                          uint token1Supply_ = _getLiquidityCollateral(SUPPLY_TOKEN_1_SLOT, token1SupplyExchangePrice_, false);
                          if (geometricMean_ < 1e27) {
                              (c_.token0ImaginaryReserves, c_.token1ImaginaryReserves) = _calculateReservesOutsideRange(
                                  geometricMean_,
                                  upperRange_,
                                  token0Supply_,
                                  token1Supply_
                              );
                          } else {
                              // inversing, something like `xy = k` so for calculation we are making everything related to x into y & y into x
                              // 1 / geometricMean for new geometricMean
                              // 1 / lowerRange will become upper range
                              // 1 / upperRange will become lower range
                              (c_.token1ImaginaryReserves, c_.token0ImaginaryReserves) = _calculateReservesOutsideRange(
                                  (1e54 / geometricMean_),
                                  (1e54 / lowerRange_),
                                  token1Supply_,
                                  token0Supply_
                              );
                          }
                          c_.token0RealReserves = token0Supply_;
                          c_.token1RealReserves = token1Supply_;
                          unchecked {
                              c_.token0ImaginaryReserves += token0Supply_;
                              c_.token1ImaginaryReserves += token1Supply_;
                          }
                      }
                      /// @notice Calculates the real and imaginary debt reserves for both tokens
                      /// @dev This function uses a quadratic equation to determine the debt reserves
                      ///      based on the geometric mean price and the current debt amounts
                      /// @param gp_ The geometric mean price of upper range & lower range
                      /// @param pb_ The price of lower range
                      /// @param dx_ The debt amount of one token
                      /// @param dy_ The debt amount of the other token
                      /// @return rx_ The real debt reserve of the first token
                      /// @return ry_ The real debt reserve of the second token
                      /// @return irx_ The imaginary debt reserve of the first token
                      /// @return iry_ The imaginary debt reserve of the second token
                      function _calculateDebtReserves(
                          uint gp_,
                          uint pb_,
                          uint dx_,
                          uint dy_
                      ) internal pure returns (uint rx_, uint ry_, uint irx_, uint iry_) {
                          // Assigning letter to knowns:
                          // c = debtA
                          // d = debtB
                          // e = upperPrice
                          // f = lowerPrice
                          // g = upperPrice^1/2
                          // h = lowerPrice^1/2
                          // c = 1
                          // d = 2000
                          // e = 2222.222222
                          // f = 1800
                          // g = 2222.222222^1/2
                          // h = 1800^1/2
                          // Assigning letter to unknowns:
                          // w = realDebtReserveA
                          // x = realDebtReserveB
                          // y = imaginaryDebtReserveA
                          // z = imaginaryDebtReserveB
                          // k = k
                          // below quadratic will give answer of realDebtReserveB
                          // A, B, C of quadratic equation:
                          // A = h
                          // B = dh - cfg
                          // C = -cfdh
                          // A = lowerPrice^1/2
                          // B = debtB⋅lowerPrice^1/2 - debtA⋅lowerPrice⋅upperPrice^1/2
                          // C = -(debtA⋅lowerPrice⋅debtB⋅lowerPrice^1/2)
                          // x = (cfg − dh + (4cdf(h^2)+(cfg−dh)^2))^(1/2)) / 2h
                          // simplifying dividing by h, note h = f^1/2
                          // x = ((c⋅g⋅(f^1/2) − d) / 2 + ((4⋅c⋅d⋅f⋅f) / (4h^2) + ((c⋅f⋅g) / 2h − (d⋅h) / 2h)^2))^(1/2))
                          // x = ((c⋅g⋅(f^1/2) − d) / 2 + ((c⋅d⋅f) + ((c⋅g⋅(f^1/2) − d) / 2)^2))^(1/2))
                          // dividing in 3 parts for simplification:
                          // part1 = (c⋅g⋅(f^1/2) − d) / 2
                          // part2 = (c⋅d⋅f)
                          // x = (part1 + (part2 + part1^2)^(1/2))
                          // note: part1 will almost always be < 1e27 but in case it goes above 1e27 then it's extremely unlikely it'll go above > 1e28
                          // part1 = ((debtA * upperPrice^1/2 * lowerPrice^1/2) - debtB) / 2
                          // note: upperPrice^1/2 * lowerPrice^1/2 = geometric mean
                          // part1 = ((debtA * geometricMean) - debtB) / 2
                          // part2 = debtA * debtB * lowerPrice
                          // converting decimals properly as price is in 1e27 decimals
                          // part1 = ((debtA * geometricMean) - (debtB * 1e27)) / (2 * 1e27)
                          // part2 = (debtA * debtB * lowerPrice) / 1e27
                          // final x equals:
                          // x = (part1 + (part2 + part1^2)^(1/2))
                          int p1_ = (int(dx_ * gp_) - int(dy_ * 1e27)) / (2 * 1e27);
                          uint p2_ = (dx_ * dy_);
                          p2_ = p2_ < 1e50 ? (p2_ * pb_) / 1e27 : (p2_ / 1e27) * pb_;
                          ry_ = uint(p1_ + int(FixedPointMathLib.sqrt((p2_ + uint(p1_ * p1_)))));
                          // finding z:
                          // x^2 - zx + cfz = 0
                          // z*(x - cf) = x^2
                          // z = x^2 / (x - cf)
                          // z = x^2 / (x - debtA * lowerPrice)
                          // converting decimals properly as price is in 1e27 decimals
                          // z = (x^2 * 1e27) / ((x * 1e27) - (debtA * lowerPrice))
                          iry_ = ((ry_ * 1e27) - (dx_ * pb_));
                          if (iry_ < SIX_DECIMALS) {
                              // almost impossible situation to ever get here
                              revert FluidDexError(ErrorTypes.DexT1__DebtReservesTooLow);
                          }
                          if (ry_ < 1e25) {
                              iry_ = (ry_ * ry_ * 1e27) / iry_;
                          } else {
                              // note: it can never result in negative as final result will always be in positive
                              iry_ = (ry_ * ry_) / (iry_ / 1e27);
                          }
                          // finding y
                          // x = z * c / (y + c)
                          // y + c = z * c / x
                          // y = (z * c / x) - c
                          // y = (z * debtA / x) - debtA
                          irx_ = ((iry_ * dx_) / ry_) - dx_;
                          // finding w
                          // w = y * d / (z + d)
                          // w = (y * debtB) / (z + debtB)
                          rx_ = (irx_ * dy_) / (iry_ + dy_);
                      }
                      /// @notice Calculates the debt amount for a given token from liquidity layer
                      /// @param borrowTokenSlot_ The storage slot for the token's borrow data
                      /// @param tokenExchangePrice_ The current exchange price of the token
                      /// @param isToken0_ Boolean indicating if this is for token0 (true) or token1 (false)
                      /// @return tokenDebt_ The calculated debt amount for the token
                      function _getLiquidityDebt(
                          bytes32 borrowTokenSlot_,
                          uint tokenExchangePrice_,
                          bool isToken0_
                      ) internal view returns (uint tokenDebt_) {
                          uint tokenBorrowData_ = LIQUIDITY.readFromStorage(borrowTokenSlot_);
                          tokenDebt_ = (tokenBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64;
                          tokenDebt_ = (tokenDebt_ >> 8) << (tokenDebt_ & X8);
                          if (tokenBorrowData_ & 1 == 1) {
                              // borrow with interest is on
                              unchecked {
                                  tokenDebt_ = (tokenDebt_ * tokenExchangePrice_) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
                              }
                          }
                          unchecked {
                              tokenDebt_ = isToken0_
                                  ? ((tokenDebt_ * TOKEN_0_NUMERATOR_PRECISION) / TOKEN_0_DENOMINATOR_PRECISION)
                                  : ((tokenDebt_ * TOKEN_1_NUMERATOR_PRECISION) / TOKEN_1_DENOMINATOR_PRECISION);
                          }
                      }
                      /// @notice Calculates the debt reserves for both tokens
                      /// @param geometricMean_ The geometric mean of the upper and lower price ranges
                      /// @param upperRange_ The upper price range
                      /// @param lowerRange_ The lower price range
                      /// @param token0BorrowExchangePrice_ The exchange price of token0 from liquidity layer
                      /// @param token1BorrowExchangePrice_ The exchange price of token1 from liquidity layer
                      /// @return d_ The calculated debt reserves for both tokens, containing:
                      ///         - token0Debt: The debt amount of token0
                      ///         - token1Debt: The debt amount of token1
                      ///         - token0RealReserves: The real reserves of token0 derived from token1 debt
                      ///         - token1RealReserves: The real reserves of token1 derived from token0 debt
                      ///         - token0ImaginaryReserves: The imaginary debt reserves of token0
                      ///         - token1ImaginaryReserves: The imaginary debt reserves of token1
                      function _getDebtReserves(
                          uint geometricMean_,
                          uint upperRange_,
                          uint lowerRange_,
                          uint token0BorrowExchangePrice_,
                          uint token1BorrowExchangePrice_
                      ) internal view returns (DebtReserves memory d_) {
                          uint token0Debt_ = _getLiquidityDebt(BORROW_TOKEN_0_SLOT, token0BorrowExchangePrice_, true);
                          uint token1Debt_ = _getLiquidityDebt(BORROW_TOKEN_1_SLOT, token1BorrowExchangePrice_, false);
                          d_.token0Debt = token0Debt_;
                          d_.token1Debt = token1Debt_;
                          if (geometricMean_ < 1e27) {
                              (
                                  d_.token0RealReserves,
                                  d_.token1RealReserves,
                                  d_.token0ImaginaryReserves,
                                  d_.token1ImaginaryReserves
                              ) = _calculateDebtReserves(geometricMean_, lowerRange_, token0Debt_, token1Debt_);
                          } else {
                              // inversing, something like `xy = k` so for calculation we are making everything related to x into y & y into x
                              // 1 / geometricMean for new geometricMean
                              // 1 / lowerRange will become upper range
                              // 1 / upperRange will become lower range
                              (
                                  d_.token1RealReserves,
                                  d_.token0RealReserves,
                                  d_.token1ImaginaryReserves,
                                  d_.token0ImaginaryReserves
                              ) = _calculateDebtReserves((1e54 / geometricMean_), (1e54 / upperRange_), token1Debt_, token0Debt_);
                          }
                      }
                      function _priceDiffCheck(uint oldPrice_, uint newPrice_) internal pure returns (int priceDiff_) {
                          // check newPrice_ & oldPrice_ difference should not be more than 5%
                          // old price w.r.t new price
                          priceDiff_ = int(ORACLE_PRECISION) - int((oldPrice_ * ORACLE_PRECISION) / newPrice_);
                          unchecked {
                              if ((priceDiff_ > int(ORACLE_LIMIT)) || (priceDiff_ < -int(ORACLE_LIMIT))) {
                                  // if oracle price difference is more than 5% then revert
                                  // in 1 swap price should only change by <= 5%
                                  // if a total fall by let's say 8% then in current block price can only fall by 5% and
                                  // in next block it'll fall the remaining 3%
                                  revert FluidDexError(ErrorTypes.DexT1__OracleUpdateHugeSwapDiff);
                              }
                          }
                      }
                      function _updateOracle(uint newPrice_, uint centerPrice_, uint dexVariables_) internal returns (uint) {
                          // time difference between last & current swap
                          uint timeDiff_ = block.timestamp - ((dexVariables_ >> 121) & X33);
                          uint temp_;
                          uint temp2_;
                          uint temp3_;
                          if (timeDiff_ == 0) {
                              // doesn't matter if oracle is on or off when timediff = 0 code for both is same
                              // temp_ => oldCenterPrice
                              temp_ = (dexVariables_ >> 81) & X40;
                              temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                              // Ensure that the center price is within the acceptable range of the old center price if it's not the first swap in the same block
                              unchecked {
                                  if (
                                      (centerPrice_ < (((EIGHT_DECIMALS - 1) * temp_) / EIGHT_DECIMALS)) ||
                                      (centerPrice_ > (((EIGHT_DECIMALS + 1) * temp_) / EIGHT_DECIMALS))
                                  ) {
                                      revert FluidDexError(ErrorTypes.DexT1__CenterPriceOutOfRange);
                                  }
                              }
                              // olderPrice_ => temp_
                              temp_ = (dexVariables_ >> 1) & X40;
                              temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                              _priceDiffCheck(temp_, newPrice_);
                              // 2nd swap in same block no need to update anything around oracle, only need to update last swap price in dexVariables
                              return ((dexVariables_ & 0xfffffffffffffffffffffffffffffffffffffffffffe0000000001ffffffffff) |
                                  (newPrice_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 41));
                          }
                          if (((dexVariables_ >> 195) & 1) == 0) {
                              // if oracle is not active then just returning updated DEX variable
                              temp_ = ((dexVariables_ >> 41) & X40);
                              temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                              _priceDiffCheck(temp_, newPrice_);
                              
                              return ((dexVariables_ & 0xfffffffffffffffffffffffffc00000000000000000000000000000000000001) |
                                  (((dexVariables_ >> 41) & X40) << 1) |
                                  (newPrice_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 41) |
                                  (centerPrice_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 81) |
                                  (block.timestamp << 121));
                          } else {
                              // oracle is active hence update oracle
                              // olderPrice_ => temp_
                              temp_ = (dexVariables_ >> 1) & X40;
                              temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                              // oldPrice_ => temp2_
                              temp2_ = (dexVariables_ >> 41) & X40;
                              temp2_ = (temp2_ >> DEFAULT_EXPONENT_SIZE) << (temp2_ & DEFAULT_EXPONENT_MASK);
                              int priceDiff_ = _priceDiffCheck(temp2_, newPrice_);
                              unchecked {
                                  // older price w.r.t old price
                                  priceDiff_ = int(ORACLE_PRECISION) - int((temp_ * ORACLE_PRECISION) / temp2_);
                              }
                              // priceDiffInPercentAndSign_ => temp3_
                              // priceDiff_ will always be lower than ORACLE_LIMIT due to above check
                              unchecked {
                                  if (priceDiff_ < 0) {
                                      temp3_ = ((uint(-priceDiff_) * X22) / ORACLE_LIMIT) << 1;
                                  } else {
                                      // if greater than or equal to 0 then make sign flag 1
                                      temp3_ = (((uint(priceDiff_) * X22) / ORACLE_LIMIT) << 1) | 1;
                                  }
                              }
                              if (timeDiff_ > X22) {
                                  // if time difference is this then that means DEX has been inactive ~45 days
                                  // that means oracle price of this DEX should not be used.
                                  timeDiff_ = X22;
                              }
                              // temp_ => lastTimeDiff_
                              temp_ = (dexVariables_ >> 154) & X22;
                              uint nextOracleSlot_ = ((dexVariables_ >> 176) & X3);
                              uint oracleMap_ = (dexVariables_ >> 179) & X16;
                              if (temp_ > X9) {
                                  if (nextOracleSlot_ > 0) {
                                      // if greater than 0 then current slot has 2 or more oracle slot empty
                                      // First 9 bits are of time, so not using that
                                      temp3_ = (temp3_ << 41) | (temp_ << 9);
                                      _oracle[oracleMap_] = _oracle[oracleMap_] | (temp3_ << (--nextOracleSlot_ * 32));
                                      if (nextOracleSlot_ > 0) {
                                          --nextOracleSlot_;
                                      } else {
                                          // if == 0 that means the oracle slots will get filled and shift to next oracle map
                                          nextOracleSlot_ = 7;
                                          unchecked {
                                              oracleMap_ = (oracleMap_ + 1) % TOTAL_ORACLE_MAPPING;
                                          }
                                          _oracle[oracleMap_] = 0;
                                      }
                                  } else {
                                      // if == 0
                                      // then seconds will be in last map
                                      // precision will be in last map + 1
                                      // Storing precision & sign slot in first precision & sign slot and leaving time slot empty
                                      temp3_ = temp3_ << 9;
                                      _oracle[oracleMap_] = _oracle[oracleMap_] | temp3_;
                                      nextOracleSlot_ = 6; // storing 6 here as 7 is going to occupied right now
                                      unchecked {
                                          oracleMap_ = (oracleMap_ + 1) % TOTAL_ORACLE_MAPPING;
                                      }
                                      // Storing time in 2nd precision & sign and leaving time slot empty
                                      _oracle[oracleMap_] = temp_ << ((7 * 32) + 9);
                                  }
                              } else {
                                  temp3_ = (temp3_ << 9) | temp_;
                                  unchecked {
                                      if (nextOracleSlot_ < 7) {
                                          _oracle[oracleMap_] = _oracle[oracleMap_] | (temp3_ << (nextOracleSlot_ * 32));
                                      } else {
                                          _oracle[oracleMap_] = temp3_ << ((7 * 32));
                                      }
                                  }
                                  if (nextOracleSlot_ > 0) {
                                      --nextOracleSlot_;
                                  } else {
                                      nextOracleSlot_ = 7;
                                      unchecked {
                                          oracleMap_ = (oracleMap_ + 1) % TOTAL_ORACLE_MAPPING;
                                      }
                                      _oracle[oracleMap_] = 0;
                                  }
                              }
                              // doing this due to stack too deep error when using params memory variables
                              temp_ = newPrice_;
                              temp2_ = centerPrice_;
                              temp3_ = dexVariables_;
                              // then update last price
                              return ((temp3_ & 0xfffffffffffffff8000000000000000000000000000000000000000000000001) |
                                  (((temp3_ >> 41) & X40) << 1) |
                                  (temp_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 41) |
                                  (temp2_.toBigNumber(32, 8, BigMathMinified.ROUND_DOWN) << 81) |
                                  (block.timestamp << 121) |
                                  (timeDiff_ << 154) |
                                  (nextOracleSlot_ << 176) |
                                  (oracleMap_ << 179));
                          }
                      }
                      function _hookVerify(uint hookAddress_, uint mode_, bool swap0to1_, uint price_) internal {
                          try
                              IHook(AddressCalcs.addressCalc(DEPLOYER_CONTRACT, hookAddress_)).dexPrice(
                                  mode_,
                                  swap0to1_,
                                  TOKEN_0,
                                  TOKEN_1,
                                  price_
                              )
                          returns (bool isOk_) {
                              if (!isOk_) revert FluidDexError(ErrorTypes.DexT1__HookReturnedFalse);
                          } catch (bytes memory /*lowLevelData*/) {
                              // skip checking hook nothing
                          }
                      }
                      function _updateSupplyShares(uint newTotalShares_) internal {
                          uint totalSupplyShares_ = _totalSupplyShares;
                          // new total shares are greater than old total shares && new total shares are greater than max supply shares
                          if (
                              (newTotalShares_ > (totalSupplyShares_ & X128)) && 
                              newTotalShares_ > (totalSupplyShares_ >> 128)
                          ) {
                              revert FluidDexError(ErrorTypes.DexT1__SupplySharesOverflow);
                          }
                          // keeping max supply shares intact
                          _totalSupplyShares = ((totalSupplyShares_ >> 128) << 128) | newTotalShares_;
                      }
                      function _updateBorrowShares(uint newTotalShares_) internal {
                          uint totalBorrowShares_ = _totalBorrowShares;
                          // new total shares are greater than old total shares && new total shares are greater than max borrow shares
                          if (
                              (newTotalShares_ > (totalBorrowShares_ & X128)) && 
                              newTotalShares_ > (totalBorrowShares_ >> 128)
                          ) {
                              revert FluidDexError(ErrorTypes.DexT1__BorrowSharesOverflow);
                          }
                          // keeping max borrow shares intact
                          _totalBorrowShares = ((totalBorrowShares_ >> 128) << 128) | newTotalShares_;
                      }
                      constructor(ConstantViews memory constantViews_) ImmutableVariables(constantViews_) {}
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { IFluidLiquidity } from "../../../../liquidity/interfaces/iLiquidity.sol";
                  import { Structs } from "./structs.sol";
                  import { ConstantVariables } from "../common/constantVariables.sol";
                  import { IFluidDexFactory } from "../../interfaces/iDexFactory.sol";
                  import { Error } from "../../error.sol";
                  import { ErrorTypes } from "../../errorTypes.sol";
                  abstract contract ImmutableVariables is ConstantVariables, Structs, Error {
                      /*//////////////////////////////////////////////////////////////
                                            CONSTANTS / IMMUTABLES
                      //////////////////////////////////////////////////////////////*/
                      uint256 public immutable DEX_ID;
                      /// @dev Address of token 0
                      address internal immutable TOKEN_0;
                      /// @dev Address of token 1
                      address internal immutable TOKEN_1;
                      address internal immutable THIS_CONTRACT;
                      uint256 internal immutable TOKEN_0_NUMERATOR_PRECISION;
                      uint256 internal immutable TOKEN_0_DENOMINATOR_PRECISION;
                      uint256 internal immutable TOKEN_1_NUMERATOR_PRECISION;
                      uint256 internal immutable TOKEN_1_DENOMINATOR_PRECISION;
                      /// @dev Address of liquidity contract
                      IFluidLiquidity internal immutable LIQUIDITY;
                      /// @dev Address of DEX factory contract
                      IFluidDexFactory internal immutable DEX_FACTORY;
                      /// @dev Address of Shift implementation
                      address internal immutable SHIFT_IMPLEMENTATION;
                      /// @dev Address of Admin implementation
                      address internal immutable ADMIN_IMPLEMENTATION;
                      /// @dev Address of Col Operations implementation
                      address internal immutable COL_OPERATIONS_IMPLEMENTATION;
                      /// @dev Address of Debt Operations implementation
                      address internal immutable DEBT_OPERATIONS_IMPLEMENTATION;
                      /// @dev Address of Perfect Operations and Swap Out implementation
                      address internal immutable PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION;
                      /// @dev Address of contract used for deploying center price & hook related contract
                      address internal immutable DEPLOYER_CONTRACT;
                      /// @dev Liquidity layer slots
                      bytes32 internal immutable SUPPLY_TOKEN_0_SLOT;
                      bytes32 internal immutable BORROW_TOKEN_0_SLOT;
                      bytes32 internal immutable SUPPLY_TOKEN_1_SLOT;
                      bytes32 internal immutable BORROW_TOKEN_1_SLOT;
                      bytes32 internal immutable EXCHANGE_PRICE_TOKEN_0_SLOT;
                      bytes32 internal immutable EXCHANGE_PRICE_TOKEN_1_SLOT;
                      uint256 internal immutable TOTAL_ORACLE_MAPPING;
                      function _calcNumeratorAndDenominator(
                          address token_
                      ) private view returns (uint256 numerator_, uint256 denominator_) {
                          uint256 decimals_ = _decimals(token_);
                          if (decimals_ > TOKENS_DECIMALS_PRECISION) {
                              numerator_ = 1;
                              denominator_ = 10 ** (decimals_ - TOKENS_DECIMALS_PRECISION);
                          } else {
                              numerator_ = 10 ** (TOKENS_DECIMALS_PRECISION - decimals_);
                              denominator_ = 1;
                          }
                      }
                      constructor(ConstantViews memory constants_) {
                          THIS_CONTRACT = address(this);
                          DEX_ID = constants_.dexId;
                          LIQUIDITY = IFluidLiquidity(constants_.liquidity);
                          DEX_FACTORY = IFluidDexFactory(constants_.factory);
                          TOKEN_0 = constants_.token0;
                          TOKEN_1 = constants_.token1;
                          if (TOKEN_0 >= TOKEN_1) revert FluidDexError(ErrorTypes.DexT1__Token0ShouldBeSmallerThanToken1);
                          (TOKEN_0_NUMERATOR_PRECISION, TOKEN_0_DENOMINATOR_PRECISION) = _calcNumeratorAndDenominator(TOKEN_0);
                          (TOKEN_1_NUMERATOR_PRECISION, TOKEN_1_DENOMINATOR_PRECISION) = _calcNumeratorAndDenominator(TOKEN_1);
                          if (constants_.implementations.shift != address(0)) {
                              SHIFT_IMPLEMENTATION = constants_.implementations.shift;
                          } else {
                              SHIFT_IMPLEMENTATION = address(this);
                          }
                          if (constants_.implementations.admin != address(0)) {
                              ADMIN_IMPLEMENTATION = constants_.implementations.admin;
                          } else {
                              ADMIN_IMPLEMENTATION = address(this);
                          }
                          if (constants_.implementations.colOperations != address(0)) {
                              COL_OPERATIONS_IMPLEMENTATION = constants_.implementations.colOperations;
                          } else {
                              COL_OPERATIONS_IMPLEMENTATION = address(this);
                          }
                          if (constants_.implementations.debtOperations != address(0)) {
                              DEBT_OPERATIONS_IMPLEMENTATION = constants_.implementations.debtOperations;
                          } else {
                              DEBT_OPERATIONS_IMPLEMENTATION = address(this);
                          }
                          if (constants_.implementations.perfectOperationsAndSwapOut != address(0)) {
                              PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION = constants_.implementations.perfectOperationsAndSwapOut;
                          } else {
                              PERFECT_OPERATIONS_AND_SWAP_OUT_IMPLEMENTATION = address(this);
                          }
                          DEPLOYER_CONTRACT = constants_.deployerContract;
                          SUPPLY_TOKEN_0_SLOT = constants_.supplyToken0Slot;
                          BORROW_TOKEN_0_SLOT = constants_.borrowToken0Slot;
                          SUPPLY_TOKEN_1_SLOT = constants_.supplyToken1Slot;
                          BORROW_TOKEN_1_SLOT = constants_.borrowToken1Slot;
                          EXCHANGE_PRICE_TOKEN_0_SLOT = constants_.exchangePriceToken0Slot;
                          EXCHANGE_PRICE_TOKEN_1_SLOT = constants_.exchangePriceToken1Slot;
                          if (constants_.oracleMapping > X16) revert FluidDexError(ErrorTypes.DexT1__OracleMappingOverflow);
                          TOTAL_ORACLE_MAPPING = constants_.oracleMapping;
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  interface IHook {
                      /// @notice Hook function to check for liquidation opportunities before external swaps
                      /// @dev The primary use of this hook is to check if a particular pair vault has liquidation available.
                      ///      If liquidation is available, it gives priority to the liquidation process before allowing external swaps.
                      ///      In most cases, this hook will not be set.
                      /// @param id_ Identifier for the operation type: 1 for swap, 2 for internal arbitrage
                      /// @param swap0to1_ Direction of the swap: true if swapping token0 for token1, false otherwise
                      /// @param token0_ Address of the first token in the pair
                      /// @param token1_ Address of the second token in the pair
                      /// @param price_ The price ratio of token1 to token0, expressed with 27 decimal places
                      /// @return isOk_ Boolean indicating whether the operation should proceed
                      function dexPrice(
                          uint id_,
                          bool swap0to1_,
                          address token0_,
                          address token1_,
                          uint price_
                      ) external returns (bool isOk_);
                  }
                  interface ICenterPrice {
                      /// @notice Retrieves the center price for the pool
                      /// @dev This function is marked as non-constant (potentially state-changing) to allow flexibility in price fetching mechanisms.
                      ///      While typically used as a read-only operation, this design permits write operations if needed for certain token pairs
                      ///      (e.g., fetching up-to-date exchange rates that may require state changes).
                      /// @return price The current price ratio of token1 to token0, expressed with 27 decimal places
                      function centerPrice() external returns (uint price);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  abstract contract Structs {
                      struct PricesAndExchangePrice {
                          uint lastStoredPrice; // last stored price in 1e27 decimals
                          uint centerPrice; // last stored price in 1e27 decimals
                          uint upperRange; // price at upper range in 1e27 decimals
                          uint lowerRange; // price at lower range in 1e27 decimals
                          uint geometricMean; // geometric mean of upper range & lower range in 1e27 decimals
                          uint supplyToken0ExchangePrice;
                          uint borrowToken0ExchangePrice;
                          uint supplyToken1ExchangePrice;
                          uint borrowToken1ExchangePrice;
                      }
                      struct ExchangePrices {
                          uint supplyToken0ExchangePrice;
                          uint borrowToken0ExchangePrice;
                          uint supplyToken1ExchangePrice;
                          uint borrowToken1ExchangePrice;
                      }
                      struct CollateralReserves {
                          uint token0RealReserves;
                          uint token1RealReserves;
                          uint token0ImaginaryReserves;
                          uint token1ImaginaryReserves;
                      }
                      struct CollateralReservesSwap {
                          uint tokenInRealReserves;
                          uint tokenOutRealReserves;
                          uint tokenInImaginaryReserves;
                          uint tokenOutImaginaryReserves;
                      }
                      struct DebtReserves {
                          uint token0Debt;
                          uint token1Debt;
                          uint token0RealReserves;
                          uint token1RealReserves;
                          uint token0ImaginaryReserves;
                          uint token1ImaginaryReserves;
                      }
                      struct DebtReservesSwap {
                          uint tokenInDebt;
                          uint tokenOutDebt;
                          uint tokenInRealReserves;
                          uint tokenOutRealReserves;
                          uint tokenInImaginaryReserves;
                          uint tokenOutImaginaryReserves;
                      }
                      struct SwapInMemory {
                          address tokenIn;
                          address tokenOut;
                          uint256 amtInAdjusted;
                          address withdrawTo;
                          address borrowTo;
                          uint price; // price of pool after swap
                          uint fee; // fee of pool
                          uint revenueCut; // revenue cut of pool
                          bool swap0to1;
                          int swapRoutingAmt;
                          bytes data; // just added to avoid stack-too-deep error
                      }
                      struct SwapOutMemory {
                          address tokenIn;
                          address tokenOut;
                          uint256 amtOutAdjusted;
                          address withdrawTo;
                          address borrowTo;
                          uint price; // price of pool after swap
                          uint fee;
                          uint revenueCut; // revenue cut of pool
                          bool swap0to1;
                          int swapRoutingAmt;
                          bytes data; // just added to avoid stack-too-deep error
                          uint msgValue;
                      }
                      struct DepositColMemory {
                          uint256 token0AmtAdjusted;
                          uint256 token1AmtAdjusted;
                          uint256 token0ReservesInitial;
                          uint256 token1ReservesInitial;
                      }
                      struct WithdrawColMemory {
                          uint256 token0AmtAdjusted;
                          uint256 token1AmtAdjusted;
                          uint256 token0ReservesInitial;
                          uint256 token1ReservesInitial;
                          address to;
                      }
                      struct BorrowDebtMemory {
                          uint256 token0AmtAdjusted;
                          uint256 token1AmtAdjusted;
                          uint256 token0DebtInitial;
                          uint256 token1DebtInitial;
                          address to;
                      }
                      struct PaybackDebtMemory {
                          uint256 token0AmtAdjusted;
                          uint256 token1AmtAdjusted;
                          uint256 token0DebtInitial;
                          uint256 token1DebtInitial;
                      }
                      struct OraclePriceMemory {
                          uint lowestPrice1by0;
                          uint highestPrice1by0;
                          uint oracleSlot;
                          uint oracleMap;
                          uint oracle;
                      }
                      struct Oracle {
                          uint twap1by0; // TWAP price
                          uint lowestPrice1by0; // lowest price point
                          uint highestPrice1by0; // highest price point
                          uint twap0by1; // TWAP price
                          uint lowestPrice0by1; // lowest price point
                          uint highestPrice0by1; // highest price point
                      }
                      struct Implementations {
                          address shift;
                          address admin;
                          address colOperations;
                          address debtOperations;
                          address perfectOperationsAndSwapOut;
                      }
                      struct ConstantViews {
                          uint256 dexId;
                          address liquidity;
                          address factory;
                          Implementations implementations;
                          address deployerContract;
                          address token0;
                          address token1;
                          bytes32 supplyToken0Slot;
                          bytes32 borrowToken0Slot;
                          bytes32 supplyToken1Slot;
                          bytes32 borrowToken1Slot;
                          bytes32 exchangePriceToken0Slot;
                          bytes32 exchangePriceToken1Slot;
                          uint256 oracleMapping;
                      }
                      struct ConstantViews2 {
                          uint token0NumeratorPrecision;
                          uint token0DenominatorPrecision;
                          uint token1NumeratorPrecision;
                          uint token1DenominatorPrecision;
                      }
                  }
                  // SPDX-License-Identifier: AGPL-3.0-only
                  pragma solidity >=0.8.0;
                  /// @notice Arithmetic library with operations for fixed-point numbers.
                  /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
                  /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
                  library FixedPointMathLib {
                      /*//////////////////////////////////////////////////////////////
                                      SIMPLIFIED FIXED POINT OPERATIONS
                      //////////////////////////////////////////////////////////////*/
                      uint256 internal constant MAX_UINT256 = 2**256 - 1;
                      uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
                      function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
                      }
                      function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
                      }
                      function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
                      }
                      function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
                      }
                      /*//////////////////////////////////////////////////////////////
                                      LOW LEVEL FIXED POINT OPERATIONS
                      //////////////////////////////////////////////////////////////*/
                      function mulDivDown(
                          uint256 x,
                          uint256 y,
                          uint256 denominator
                      ) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                              if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                                  revert(0, 0)
                              }
                              // Divide x * y by the denominator.
                              z := div(mul(x, y), denominator)
                          }
                      }
                      function mulDivUp(
                          uint256 x,
                          uint256 y,
                          uint256 denominator
                      ) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                              if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                                  revert(0, 0)
                              }
                              // If x * y modulo the denominator is strictly greater than 0,
                              // 1 is added to round up the division of x * y by the denominator.
                              z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
                          }
                      }
                      function rpow(
                          uint256 x,
                          uint256 n,
                          uint256 scalar
                      ) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              switch x
                              case 0 {
                                  switch n
                                  case 0 {
                                      // 0 ** 0 = 1
                                      z := scalar
                                  }
                                  default {
                                      // 0 ** n = 0
                                      z := 0
                                  }
                              }
                              default {
                                  switch mod(n, 2)
                                  case 0 {
                                      // If n is even, store scalar in z for now.
                                      z := scalar
                                  }
                                  default {
                                      // If n is odd, store x in z for now.
                                      z := x
                                  }
                                  // Shifting right by 1 is like dividing by 2.
                                  let half := shr(1, scalar)
                                  for {
                                      // Shift n right by 1 before looping to halve it.
                                      n := shr(1, n)
                                  } n {
                                      // Shift n right by 1 each iteration to halve it.
                                      n := shr(1, n)
                                  } {
                                      // Revert immediately if x ** 2 would overflow.
                                      // Equivalent to iszero(eq(div(xx, x), x)) here.
                                      if shr(128, x) {
                                          revert(0, 0)
                                      }
                                      // Store x squared.
                                      let xx := mul(x, x)
                                      // Round to the nearest number.
                                      let xxRound := add(xx, half)
                                      // Revert if xx + half overflowed.
                                      if lt(xxRound, xx) {
                                          revert(0, 0)
                                      }
                                      // Set x to scaled xxRound.
                                      x := div(xxRound, scalar)
                                      // If n is even:
                                      if mod(n, 2) {
                                          // Compute z * x.
                                          let zx := mul(z, x)
                                          // If z * x overflowed:
                                          if iszero(eq(div(zx, x), z)) {
                                              // Revert if x is non-zero.
                                              if iszero(iszero(x)) {
                                                  revert(0, 0)
                                              }
                                          }
                                          // Round to the nearest number.
                                          let zxRound := add(zx, half)
                                          // Revert if zx + half overflowed.
                                          if lt(zxRound, zx) {
                                              revert(0, 0)
                                          }
                                          // Return properly scaled zxRound.
                                          z := div(zxRound, scalar)
                                      }
                                  }
                              }
                          }
                      }
                      /*//////////////////////////////////////////////////////////////
                                          GENERAL NUMBER UTILITIES
                      //////////////////////////////////////////////////////////////*/
                      function sqrt(uint256 x) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let y := x // We start y at x, which will help us make our initial estimate.
                              z := 181 // The "correct" value is 1, but this saves a multiplication later.
                              // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
                              // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
                              // We check y >= 2^(k + 8) but shift right by k bits
                              // each branch to ensure that if x >= 256, then y >= 256.
                              if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                                  y := shr(128, y)
                                  z := shl(64, z)
                              }
                              if iszero(lt(y, 0x1000000000000000000)) {
                                  y := shr(64, y)
                                  z := shl(32, z)
                              }
                              if iszero(lt(y, 0x10000000000)) {
                                  y := shr(32, y)
                                  z := shl(16, z)
                              }
                              if iszero(lt(y, 0x1000000)) {
                                  y := shr(16, y)
                                  z := shl(8, z)
                              }
                              // Goal was to get z*z*y within a small factor of x. More iterations could
                              // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
                              // We ensured y >= 256 so that the relative difference between y and y+1 is small.
                              // That's not possible if x < 256 but we can just verify those cases exhaustively.
                              // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
                              // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
                              // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
                              // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
                              // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
                              // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
                              // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
                              // There is no overflow risk here since y < 2^136 after the first branch above.
                              z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
                              // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              // If x+1 is a perfect square, the Babylonian method cycles between
                              // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
                              // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
                              // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
                              // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
                              z := sub(z, lt(div(x, z), z))
                          }
                      }
                      function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Mod x by y. Note this will return
                              // 0 instead of reverting if y is zero.
                              z := mod(x, y)
                          }
                      }
                      function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Divide x by y. Note this will return
                              // 0 instead of reverting if y is zero.
                              r := div(x, y)
                          }
                      }
                      function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Add 1 to x * y if x % y > 0. Note this will
                              // return 0 instead of reverting if y is zero.
                              z := add(gt(mod(x, y), 0), div(x, y))
                          }
                      }
                  }
                  

                  File 5 of 7: WstETHContractRate
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { IWstETH } from "../../interfaces/external/IWstETH.sol";
                  import { IFluidOracle } from "../../interfaces/iFluidOracle.sol";
                  import { FluidCenterPrice } from "../../fluidCenterPrice.sol";
                  import { Error as OracleError } from "../../error.sol";
                  import { ErrorTypes } from "../../errorTypes.sol";
                  abstract contract Events {
                      /// @notice emitted when rebalancer successfully changes the contract rate
                      event LogRebalanceRate(uint256 oldRate, uint256 newRate);
                  }
                  abstract contract Constants {
                      /// @dev WSTETH contract; on mainnet 0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0
                      IWstETH internal immutable _WSTETH;
                      /// @dev Minimum difference to trigger update in percent 1e4 decimals, 10000 = 1%
                      uint256 internal immutable _MIN_UPDATE_DIFF_PERCENT;
                      /// @dev Minimum time after which an update can trigger, even if it does not reach `_MIN_UPDATE_DIFF_PERCENT`
                      uint256 internal immutable _MIN_HEART_BEAT;
                  }
                  abstract contract Variables is Constants {
                      /// @dev amount of stETH for 1 wstETH, in 1e27 decimals
                      uint216 internal _rate;
                      /// @dev time when last update for rate happened
                      uint40 internal _lastUpdateTime;
                  }
                  /// @notice This contract stores the rate of stETH for 1 wstETH in intervals to optimize gas cost.
                  /// @notice Properly implements all interfaces for use as IFluidCenterPrice and IFluidOracle.
                  contract WstETHContractRate is IWstETH, IFluidOracle, FluidCenterPrice, Variables, Events {
                      /// @dev Validates that an address is not the zero address
                      modifier validAddress(address value_) {
                          if (value_ == address(0)) {
                              revert FluidOracleError(ErrorTypes.ContractRate__InvalidParams);
                          }
                          _;
                      }
                      constructor(
                          string memory infoName_,
                          IWstETH wstETH_,
                          uint256 minUpdateDiffPercent_,
                          uint256 minHeartBeat_
                      ) validAddress(address(wstETH_)) FluidCenterPrice(infoName_) {
                          if (minUpdateDiffPercent_ == 0 || minUpdateDiffPercent_ > 1e5 || minHeartBeat_ == 0) {
                              // revert if > 10% or 0
                              revert FluidOracleError(ErrorTypes.ContractRate__InvalidParams);
                          }
                          _WSTETH = wstETH_;
                          _MIN_UPDATE_DIFF_PERCENT = minUpdateDiffPercent_;
                          _MIN_HEART_BEAT = minHeartBeat_;
                          _rate = uint216(_WSTETH.stEthPerToken() * 1e9);
                          _lastUpdateTime = uint40(block.timestamp);
                      }
                      /// @inheritdoc FluidCenterPrice
                      function infoName() public view override(IFluidOracle, FluidCenterPrice) returns (string memory) {
                          return super.infoName();
                      }
                      /// @notice Rebalance the contract rate by updating the stored rate with the current rate from the WSTETH contract.
                      /// @dev The rate is only updated if the difference between the current rate and the new rate is greater than or
                      ///      equal to the minimum update difference percentage.
                      function rebalance() external {
                          uint256 curRate_ = _rate;
                          uint256 newRate_ = _WSTETH.stEthPerToken() * 1e9; // scale to 1e27
                          uint256 rateDiffPercent;
                          unchecked {
                              if (curRate_ > newRate_) {
                                  rateDiffPercent = ((curRate_ - newRate_) * 1e6) / curRate_;
                              } else if (newRate_ > curRate_) {
                                  rateDiffPercent = ((newRate_ - curRate_) * 1e6) / curRate_;
                              }
                          }
                          if (rateDiffPercent < _MIN_UPDATE_DIFF_PERCENT) {
                              revert FluidOracleError(ErrorTypes.ContractRate__MinUpdateDiffNotReached);
                          }
                          _rate = uint216(newRate_);
                          _lastUpdateTime = uint40(block.timestamp);
                          emit LogRebalanceRate(curRate_, newRate_);
                      }
                      /// @inheritdoc IWstETH
                      function stEthPerToken() external view override returns (uint256) {
                          return _rate / 1e9; // scale to 1e18
                      }
                      /// @inheritdoc IWstETH
                      function tokensPerStEth() external view override returns (uint256) {
                          return 1e45 / _rate; // scale to 1e18
                      }
                      /// @inheritdoc FluidCenterPrice
                      function centerPrice() external override returns (uint256 price_) {
                          // heart beat check update for Dex swaps
                          if (_lastUpdateTime + _MIN_HEART_BEAT < block.timestamp) {
                              uint256 curRate_ = _rate;
                              uint256 newRate_ = _WSTETH.stEthPerToken() * 1e9; // scale to 1e27
                              _rate = uint216(newRate_);
                              _lastUpdateTime = uint40(block.timestamp);
                              emit LogRebalanceRate(curRate_, newRate_);
                          }
                          return _rate;
                      }
                      /// @inheritdoc IFluidOracle
                      function getExchangeRate() external view virtual returns (uint256 exchangeRate_) {
                          return _rate;
                      }
                      /// @inheritdoc IFluidOracle
                      function getExchangeRateOperate() external view virtual returns (uint256 exchangeRate_) {
                          return _rate;
                      }
                      /// @inheritdoc IFluidOracle
                      function getExchangeRateLiquidate() external view virtual returns (uint256 exchangeRate_) {
                          return _rate;
                      }
                      /// @notice returns how much the new rate would be different from current rate in percent (10000 = 1%, 1 = 0.0001%).
                      function configPercentDiff() public view virtual returns (uint256 configPercentDiff_) {
                          uint256 curRate_ = _rate;
                          uint256 newRate_ = _WSTETH.stEthPerToken() * 1e9; // scale to 1e27
                          unchecked {
                              if (curRate_ > newRate_) {
                                  configPercentDiff_ = ((curRate_ - newRate_) * 1e6) / curRate_;
                              } else if (newRate_ > curRate_) {
                                  configPercentDiff_ = ((newRate_ - curRate_) * 1e6) / curRate_;
                              }
                          }
                      }
                      /// @notice returns all config vars, last update timestamp, and wsteth address
                      function configData()
                          external
                          view
                          returns (uint256 minUpdateDiffPercent_, uint256 minHeartBeat_, uint40 lastUpdateTime_, address wsteth_)
                      {
                          return (_MIN_UPDATE_DIFF_PERCENT, _MIN_HEART_BEAT, _lastUpdateTime, address(_WSTETH));
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  contract Error {
                      error FluidOracleError(uint256 errorId_);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  library ErrorTypes {
                      /***********************************|
                      |           FluidOracleL2           | 
                      |__________________________________*/
                      /// @notice thrown when sequencer on a L2 has an outage and grace period has not yet passed.
                      uint256 internal constant FluidOracleL2__SequencerOutage = 60000;
                      /***********************************|
                      |     UniV3CheckCLRSOracle          | 
                      |__________________________________*/
                      /// @notice thrown when the delta between main price source and check rate source is exceeding the allowed delta
                      uint256 internal constant UniV3CheckCLRSOracle__InvalidPrice = 60001;
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant UniV3CheckCLRSOracle__InvalidParams = 60002;
                      /// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
                      uint256 internal constant UniV3CheckCLRSOracle__ExchangeRateZero = 60003;
                      /***********************************|
                      |           FluidOracle             | 
                      |__________________________________*/
                      /// @notice thrown when an invalid info name is passed into a fluid oracle (e.g. not set or too long)
                      uint256 internal constant FluidOracle__InvalidInfoName = 60010;
                      /***********************************|
                      |            sUSDe Oracle           | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant SUSDeOracle__InvalidParams = 60102;
                      /***********************************|
                      |           Pendle Oracle           | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant PendleOracle__InvalidParams = 60201;
                      /// @notice thrown when the Pendle market Oracle has not been initialized yet
                      uint256 internal constant PendleOracle__MarketNotInitialized = 60202;
                      /// @notice thrown when the Pendle market does not have 18 decimals
                      uint256 internal constant PendleOracle__MarketInvalidDecimals = 60203;
                      /// @notice thrown when the Pendle market returns an unexpected price
                      uint256 internal constant PendleOracle__InvalidPrice = 60204;
                      /***********************************|
                      |    CLRS2UniV3CheckCLRSOracleL2    | 
                      |__________________________________*/
                      /// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
                      uint256 internal constant CLRS2UniV3CheckCLRSOracleL2__ExchangeRateZero = 60301;
                      /***********************************|
                      |    Ratio2xFallbackCLRSOracleL2    | 
                      |__________________________________*/
                      /// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
                      uint256 internal constant Ratio2xFallbackCLRSOracleL2__ExchangeRateZero = 60311;
                      /***********************************|
                      |            WeETHsOracle           | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant WeETHsOracle__InvalidParams = 60321;
                      /***********************************|
                      |        DexSmartColOracle          | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant DexSmartColOracle__InvalidParams = 60331;
                      /// @notice thrown when smart col is not enabled
                      uint256 internal constant DexSmartColOracle__SmartColNotEnabled = 60332;
                      /***********************************|
                      |        DexSmartDebtOracle         | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant DexSmartDebtOracle__InvalidParams = 60341;
                      /// @notice thrown when smart debt is not enabled
                      uint256 internal constant DexSmartDebtOracle__SmartDebtNotEnabled = 60342;
                      /***********************************|
                      |            ContractRate           | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant ContractRate__InvalidParams = 60351;
                      /// @notice thrown when caller is not authorized
                      uint256 internal constant ContractRate__Unauthorized = 60352;
                      /// @notice thrown when minimum diff for triggering update on the stared rate is not reached
                      uint256 internal constant ContractRate__MinUpdateDiffNotReached = 60353;
                      /***********************************|
                      |            sUSDs Oracle           | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant SUSDsOracle__InvalidParams = 60361;
                      /***********************************|
                      |            Peg Oracle             | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant PegOracle__InvalidParams = 60371;
                      /***********************************|
                      |          Chainlink Oracle         | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant ChainlinkOracle__InvalidParams = 61001;
                      /***********************************|
                      |          UniswapV3 Oracle         | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant UniV3Oracle__InvalidParams = 62001;
                      /// @notice thrown when constructor is called with invalid ordered seconds agos values
                      uint256 internal constant UniV3Oracle__InvalidSecondsAgos = 62002;
                      /// @notice thrown when constructor is called with invalid delta values > 100%
                      uint256 internal constant UniV3Oracle__InvalidDeltas = 62003;
                      /***********************************|
                      |            WstETh Oracle          | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant WstETHOracle__InvalidParams = 63001;
                      /***********************************|
                      |           Redstone Oracle         | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant RedstoneOracle__InvalidParams = 64001;
                      /***********************************|
                      |          Fallback Oracle          | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant FallbackOracle__InvalidParams = 65001;
                      /***********************************|
                      |       FallbackCLRSOracle          | 
                      |__________________________________*/
                      /// @notice thrown when the exchange rate is zero, even for the fallback oracle source (if enabled)
                      uint256 internal constant FallbackCLRSOracle__ExchangeRateZero = 66001;
                      /***********************************|
                      |         WstETHCLRSOracle          | 
                      |__________________________________*/
                      /// @notice thrown when the exchange rate is zero, even for the fallback oracle source (if enabled)
                      uint256 internal constant WstETHCLRSOracle__ExchangeRateZero = 67001;
                      /***********************************|
                      |        CLFallbackUniV3Oracle      | 
                      |__________________________________*/
                      /// @notice thrown when the exchange rate is zero, even for the uniV3 rate
                      uint256 internal constant CLFallbackUniV3Oracle__ExchangeRateZero = 68001;
                      /***********************************|
                      |  WstETHCLRS2UniV3CheckCLRSOracle  | 
                      |__________________________________*/
                      /// @notice thrown when the exchange rate is zero, even for the uniV3 rate
                      uint256 internal constant WstETHCLRS2UniV3CheckCLRSOracle__ExchangeRateZero = 69001;
                      /***********************************|
                      |             WeETh Oracle          | 
                      |__________________________________*/
                      /// @notice thrown when an invalid parameter is passed to a method
                      uint256 internal constant WeETHOracle__InvalidParams = 70001;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { IFluidCenterPrice } from "./interfaces/iFluidCenterPrice.sol";
                  import { ErrorTypes } from "./errorTypes.sol";
                  import { Error as OracleError } from "./error.sol";
                  /// @title   FluidCenterPrice
                  /// @notice  Base contract that any Fluid Center Price must implement
                  abstract contract FluidCenterPrice is IFluidCenterPrice, OracleError {
                      /// @dev short helper string to easily identify the center price oracle. E.g. token symbols
                      //
                      // using a bytes32 because string can not be immutable.
                      bytes32 private immutable _infoName;
                      constructor(string memory infoName_) {
                          if (bytes(infoName_).length > 32 || bytes(infoName_).length == 0) {
                              revert FluidOracleError(ErrorTypes.FluidOracle__InvalidInfoName);
                          }
                          // convert string to bytes32
                          bytes32 infoNameBytes32_;
                          assembly {
                              infoNameBytes32_ := mload(add(infoName_, 32))
                          }
                          _infoName = infoNameBytes32_;
                      }
                      /// @inheritdoc IFluidCenterPrice
                      function infoName() public view virtual returns (string memory) {
                          // convert bytes32 to string
                          uint256 length_;
                          while (length_ < 32 && _infoName[length_] != 0) {
                              length_++;
                          }
                          bytes memory infoNameBytes_ = new bytes(length_);
                          for (uint256 i; i < length_; i++) {
                              infoNameBytes_[i] = _infoName[i];
                          }
                          return string(infoNameBytes_);
                      }
                      /// @inheritdoc IFluidCenterPrice
                      function centerPrice() external virtual returns (uint256 price_);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  interface IWstETH {
                      /**
                       * @notice Get amount of stETH for 1 wstETH
                       * @return Amount of stETH for 1 wstETH
                       */
                      function stEthPerToken() external view returns (uint256);
                      /**
                       * @notice Get amount of wstETH for 1 stETH
                       * @return Amount of wstETH for 1 stETH
                       */
                      function tokensPerStEth() external view returns (uint256);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  interface IFluidCenterPrice {
                      /// @notice Retrieves the center price for the pool
                      /// @dev This function is marked as non-constant (potentially state-changing) to allow flexibility in price fetching mechanisms.
                      ///      While typically used as a read-only operation, this design permits write operations if needed for certain token pairs
                      ///      (e.g., fetching up-to-date exchange rates that may require state changes).
                      /// @return price_ The current price ratio of token1 to token0, expressed with 27 decimal places
                      function centerPrice() external returns (uint256 price_);
                      /// @notice helper string to easily identify the oracle. E.g. token symbols
                      function infoName() external view returns (string memory);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.8.21;
                  interface IFluidOracle {
                      /// @dev Deprecated. Use `getExchangeRateOperate()` and `getExchangeRateLiquidate()` instead. Only implemented for
                      ///      backwards compatibility.
                      function getExchangeRate() external view returns (uint256 exchangeRate_);
                      /// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for operates
                      function getExchangeRateOperate() external view returns (uint256 exchangeRate_);
                      /// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for liquidations
                      function getExchangeRateLiquidate() external view returns (uint256 exchangeRate_);
                      /// @notice helper string to easily identify the oracle. E.g. token symbols
                      function infoName() external view returns (string memory);
                  }
                  

                  File 6 of 7: FluidLiquidityUserModule
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)
                  pragma solidity ^0.8.0;
                  import "../token/ERC20/IERC20.sol";
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20 {
                      /**
                       * @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);
                      /**
                       * @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 `to`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address to, 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 `from` to `to` 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 from,
                          address to,
                          uint256 amount
                      ) external returns (bool);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @title library that represents a number in BigNumber(coefficient and exponent) format to store in smaller bits.
                  /// @notice the number is divided into two parts: a coefficient and an exponent. This comes at a cost of losing some precision
                  /// at the end of the number because the exponent simply fills it with zeroes. This precision is oftentimes negligible and can
                  /// result in significant gas cost reduction due to storage space reduction.
                  /// Also note, a valid big number is as follows: if the exponent is > 0, then coefficient last bits should be occupied to have max precision.
                  /// @dev roundUp is more like a increase 1, which happens everytime for the same number.
                  /// roundDown simply sets trailing digits after coefficientSize to zero (floor), only once for the same number.
                  library BigMathMinified {
                      /// @dev constants to use for `roundUp` input param to increase readability
                      bool internal constant ROUND_DOWN = false;
                      bool internal constant ROUND_UP = true;
                      /// @dev converts `normal` number to BigNumber with `exponent` and `coefficient` (or precision).
                      /// e.g.:
                      /// 5035703444687813576399599 (normal) = (coefficient[32bits], exponent[8bits])[40bits]
                      /// 5035703444687813576399599 (decimal) => 10000101010010110100000011111011110010100110100000000011100101001101001101011101111 (binary)
                      ///                                     => 10000101010010110100000011111011000000000000000000000000000000000000000000000000000
                      ///                                                                        ^-------------------- 51(exponent) -------------- ^
                      /// coefficient = 1000,0101,0100,1011,0100,0000,1111,1011               (2236301563)
                      /// exponent =                                            0011,0011     (51)
                      /// bigNumber =   1000,0101,0100,1011,0100,0000,1111,1011,0011,0011     (572493200179)
                      ///
                      /// @param normal number which needs to be converted into Big Number
                      /// @param coefficientSize at max how many bits of precision there should be (64 = uint64 (64 bits precision))
                      /// @param exponentSize at max how many bits of exponent there should be (8 = uint8 (8 bits exponent))
                      /// @param roundUp signals if result should be rounded down or up
                      /// @return bigNumber converted bigNumber (coefficient << exponent)
                      function toBigNumber(
                          uint256 normal,
                          uint256 coefficientSize,
                          uint256 exponentSize,
                          bool roundUp
                      ) internal pure returns (uint256 bigNumber) {
                          assembly {
                              let lastBit_
                              let number_ := normal
                              if gt(number_, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x80, number_)
                                  lastBit_ := 0x80
                              }
                              if gt(number_, 0xFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x40, number_)
                                  lastBit_ := add(lastBit_, 0x40)
                              }
                              if gt(number_, 0xFFFFFFFF) {
                                  number_ := shr(0x20, number_)
                                  lastBit_ := add(lastBit_, 0x20)
                              }
                              if gt(number_, 0xFFFF) {
                                  number_ := shr(0x10, number_)
                                  lastBit_ := add(lastBit_, 0x10)
                              }
                              if gt(number_, 0xFF) {
                                  number_ := shr(0x8, number_)
                                  lastBit_ := add(lastBit_, 0x8)
                              }
                              if gt(number_, 0xF) {
                                  number_ := shr(0x4, number_)
                                  lastBit_ := add(lastBit_, 0x4)
                              }
                              if gt(number_, 0x3) {
                                  number_ := shr(0x2, number_)
                                  lastBit_ := add(lastBit_, 0x2)
                              }
                              if gt(number_, 0x1) {
                                  lastBit_ := add(lastBit_, 1)
                              }
                              if gt(number_, 0) {
                                  lastBit_ := add(lastBit_, 1)
                              }
                              if lt(lastBit_, coefficientSize) {
                                  // for throw exception
                                  lastBit_ := coefficientSize
                              }
                              let exponent := sub(lastBit_, coefficientSize)
                              let coefficient := shr(exponent, normal)
                              if and(roundUp, gt(exponent, 0)) {
                                  // rounding up is only needed if exponent is > 0, as otherwise the coefficient fully holds the original number
                                  coefficient := add(coefficient, 1)
                                  if eq(shl(coefficientSize, 1), coefficient) {
                                      // case were coefficient was e.g. 111, with adding 1 it became 1000 (in binary) and coefficientSize 3 bits
                                      // final coefficient would exceed it's size. -> reduce coefficent to 100 and increase exponent by 1.
                                      coefficient := shl(sub(coefficientSize, 1), 1)
                                      exponent := add(exponent, 1)
                                  }
                              }
                              if iszero(lt(exponent, shl(exponentSize, 1))) {
                                  // if exponent is >= exponentSize, the normal number is too big to fit within
                                  // BigNumber with too small sizes for coefficient and exponent
                                  revert(0, 0)
                              }
                              bigNumber := shl(exponentSize, coefficient)
                              bigNumber := add(bigNumber, exponent)
                          }
                      }
                      /// @dev get `normal` number from `bigNumber`, `exponentSize` and `exponentMask`
                      function fromBigNumber(
                          uint256 bigNumber,
                          uint256 exponentSize,
                          uint256 exponentMask
                      ) internal pure returns (uint256 normal) {
                          assembly {
                              let coefficient := shr(exponentSize, bigNumber)
                              let exponent := and(bigNumber, exponentMask)
                              normal := shl(exponent, coefficient)
                          }
                      }
                      /// @dev gets the most significant bit `lastBit` of a `normal` number (length of given number of binary format).
                      /// e.g.
                      /// 5035703444687813576399599 = 10000101010010110100000011111011110010100110100000000011100101001101001101011101111
                      /// lastBit =                   ^---------------------------------   83   ----------------------------------------^
                      function mostSignificantBit(uint256 normal) internal pure returns (uint lastBit) {
                          assembly {
                              let number_ := normal
                              if gt(normal, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x80, number_)
                                  lastBit := 0x80
                              }
                              if gt(number_, 0xFFFFFFFFFFFFFFFF) {
                                  number_ := shr(0x40, number_)
                                  lastBit := add(lastBit, 0x40)
                              }
                              if gt(number_, 0xFFFFFFFF) {
                                  number_ := shr(0x20, number_)
                                  lastBit := add(lastBit, 0x20)
                              }
                              if gt(number_, 0xFFFF) {
                                  number_ := shr(0x10, number_)
                                  lastBit := add(lastBit, 0x10)
                              }
                              if gt(number_, 0xFF) {
                                  number_ := shr(0x8, number_)
                                  lastBit := add(lastBit, 0x8)
                              }
                              if gt(number_, 0xF) {
                                  number_ := shr(0x4, number_)
                                  lastBit := add(lastBit, 0x4)
                              }
                              if gt(number_, 0x3) {
                                  number_ := shr(0x2, number_)
                                  lastBit := add(lastBit, 0x2)
                              }
                              if gt(number_, 0x1) {
                                  lastBit := add(lastBit, 1)
                              }
                              if gt(number_, 0) {
                                  lastBit := add(lastBit, 1)
                              }
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  library LibsErrorTypes {
                      /***********************************|
                      |         LiquidityCalcs            | 
                      |__________________________________*/
                      /// @notice thrown when supply or borrow exchange price is zero at calc token data (token not configured yet)
                      uint256 internal constant LiquidityCalcs__ExchangePriceZero = 70001;
                      /// @notice thrown when rate data is set to a version that is not implemented
                      uint256 internal constant LiquidityCalcs__UnsupportedRateVersion = 70002;
                      /// @notice thrown when the calculated borrow rate turns negative. This should never happen.
                      uint256 internal constant LiquidityCalcs__BorrowRateNegative = 70003;
                      /***********************************|
                      |           SafeTransfer            | 
                      |__________________________________*/
                      /// @notice thrown when safe transfer from for an ERC20 fails
                      uint256 internal constant SafeTransfer__TransferFromFailed = 71001;
                      /// @notice thrown when safe transfer for an ERC20 fails
                      uint256 internal constant SafeTransfer__TransferFailed = 71002;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
                  import { LiquiditySlotsLink } from "./liquiditySlotsLink.sol";
                  import { BigMathMinified } from "./bigMathMinified.sol";
                  /// @notice implements calculation methods used for Fluid liquidity such as updated exchange prices,
                  /// borrow rate, withdrawal / borrow limits, revenue amount.
                  library LiquidityCalcs {
                      error FluidLiquidityCalcsError(uint256 errorId_);
                      /// @notice emitted if the calculated borrow rate surpassed max borrow rate (16 bits) and was capped at maximum value 65535
                      event BorrowRateMaxCap();
                      /// @dev constants as from Liquidity variables.sol
                      uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
                      /// @dev Ignoring leap years
                      uint256 internal constant SECONDS_PER_YEAR = 365 days;
                      // constants used for BigMath conversion from and to storage
                      uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
                      uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
                      uint256 internal constant FOUR_DECIMALS = 1e4;
                      uint256 internal constant TWELVE_DECIMALS = 1e12;
                      uint256 internal constant X14 = 0x3fff;
                      uint256 internal constant X15 = 0x7fff;
                      uint256 internal constant X16 = 0xffff;
                      uint256 internal constant X18 = 0x3ffff;
                      uint256 internal constant X24 = 0xffffff;
                      uint256 internal constant X33 = 0x1ffffffff;
                      uint256 internal constant X64 = 0xffffffffffffffff;
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                  CALC EXCHANGE PRICES                  /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev calculates interest (exchange prices) for a token given its' exchangePricesAndConfig from storage.
                      /// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
                      /// @return supplyExchangePrice_ updated supplyExchangePrice
                      /// @return borrowExchangePrice_ updated borrowExchangePrice
                      function calcExchangePrices(
                          uint256 exchangePricesAndConfig_
                      ) internal view returns (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) {
                          // Extracting exchange prices
                          supplyExchangePrice_ =
                              (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) &
                              X64;
                          borrowExchangePrice_ =
                              (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) &
                              X64;
                          if (supplyExchangePrice_ == 0 || borrowExchangePrice_ == 0) {
                              revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__ExchangePriceZero);
                          }
                          uint256 temp_ = exchangePricesAndConfig_ & X16; // temp_ = borrowRate
                          unchecked {
                              // last timestamp can not be > current timestamp
                              uint256 secondsSinceLastUpdate_ = block.timestamp -
                                  ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) & X33);
                              uint256 borrowRatio_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO) &
                                  X15;
                              if (secondsSinceLastUpdate_ == 0 || temp_ == 0 || borrowRatio_ == 1) {
                                  // if no time passed, borrow rate is 0, or no raw borrowings: no exchange price update needed
                                  // (if borrowRatio_ == 1 means there is only borrowInterestFree, as first bit is 1 and rest is 0)
                                  return (supplyExchangePrice_, borrowExchangePrice_);
                              }
                              // calculate new borrow exchange price.
                              // formula borrowExchangePriceIncrease: previous price * borrow rate * secondsSinceLastUpdate_.
                              // nominator is max uint112 (uint64 * uint16 * uint32). Divisor can not be 0.
                              borrowExchangePrice_ +=
                                  (borrowExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
                                  (SECONDS_PER_YEAR * FOUR_DECIMALS);
                              // FOR SUPPLY EXCHANGE PRICE:
                              // all yield paid by borrowers (in mode with interest) goes to suppliers in mode with interest.
                              // formula: previous price * supply rate * secondsSinceLastUpdate_.
                              // where supply rate = (borrow rate  - revenueFee%) * ratioSupplyYield. And
                              // ratioSupplyYield = utilization * supplyRatio * borrowRatio
                              //
                              // Example:
                              // supplyRawInterest is 80, supplyInterestFree is 20. totalSupply is 100. BorrowedRawInterest is 50.
                              // BorrowInterestFree is 10. TotalBorrow is 60. borrow rate 40%, revenueFee 10%.
                              // yield is 10 (so half a year must have passed).
                              // supplyRawInterest must become worth 89. totalSupply must become 109. BorrowedRawInterest must become 60.
                              // borrowInterestFree must still be 10. supplyInterestFree still 20. totalBorrow 70.
                              // supplyExchangePrice would have to go from 1 to 1,125 (+ 0.125). borrowExchangePrice from 1 to 1,2 (+0.2).
                              // utilization is 60%. supplyRatio = 20 / 80 = 25% (only 80% of lenders receiving yield).
                              // borrowRatio = 10 / 50 = 20% (only 83,333% of borrowers paying yield):
                              // x of borrowers paying yield = 100% - (20 / (100 + 20)) = 100% - 16.6666666% = 83,333%.
                              // ratioSupplyYield = 60% * 83,33333% * (100% + 20%) = 62,5%
                              // supplyRate = (40% * (100% - 10%)) * = 36% * 62,5% = 22.5%
                              // increase in supplyExchangePrice, assuming 100 as previous price.
                              // 100 * 22,5% * 1/2 (half a year) = 0,1125.
                              // cross-check supplyRawInterest worth = 80 * 1.1125 = 89. totalSupply worth = 89 + 20.
                              // -------------- 1. calculate ratioSupplyYield --------------------------------
                              // step1: utilization * supplyRatio (or actually part of lenders receiving yield)
                              // temp_ => supplyRatio (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                              // if first bit 0 then ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
                              // else ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
                              temp_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) & X15;
                              if (temp_ == 1) {
                                  // if no raw supply: no exchange price update needed
                                  // (if supplyRatio_ == 1 means there is only supplyInterestFree, as first bit is 1 and rest is 0)
                                  return (supplyExchangePrice_, borrowExchangePrice_);
                              }
                              // ratioSupplyYield precision is 1e27 as 100% for increased precision when supplyInterestFree > supplyWithInterest
                              if (temp_ & 1 == 1) {
                                  // ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
                                  temp_ = temp_ >> 1;
                                  // Note: case where temp_ == 0 (only supplyInterestFree, no yield) already covered by early return
                                  // in the if statement a little above.
                                  // based on above example but supplyRawInterest is 20, supplyInterestFree is 80. no fee.
                                  // supplyRawInterest must become worth 30. totalSupply must become 110.
                                  // supplyExchangePrice would have to go from 1 to 1,5. borrowExchangePrice from 1 to 1,2.
                                  // so ratioSupplyYield must come out as 2.5 (250%).
                                  // supplyRatio would be (20 * 10_000 / 80) = 2500. but must be inverted.
                                  temp_ = (1e27 * FOUR_DECIMALS) / temp_; // e.g. 1e31 / 2500 = 4e27. (* 1e27 for precision)
                                  // e.g. 5_000 * (1e27 + 4e27) / 1e27 = 25_000 (=250%).
                                  temp_ =
                                      // utilization * (100% + 100% / supplyRatio)
                                      (((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) *
                                          (1e27 + temp_)) / // extract utilization (max 16_383 so there is no way this can overflow).
                                      (FOUR_DECIMALS);
                                  // max possible value of temp_ here is 16383 * (1e27 + 1e31) / 1e4 = ~1.64e31
                              } else {
                                  // ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
                                  temp_ = temp_ >> 1;
                                  // if temp_ == 0 then only supplyWithInterest => full yield. temp_ is already 0
                                  // e.g. 5_000 * 10_000 + (20 * 10_000 / 80) / 10_000 = 5000 * 12500 / 10000 = 6250 (=62.5%).
                                  temp_ =
                                      // 1e27 * utilization * (100% + supplyRatio) / 100%
                                      (1e27 *
                                          ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) * // extract utilization (max 16_383 so there is no way this can overflow).
                                          (FOUR_DECIMALS + temp_)) /
                                      (FOUR_DECIMALS * FOUR_DECIMALS);
                                  // max possible temp_ value: 1e27 * 16383 * 2e4 / 1e8 = 3.2766e27
                              }
                              // from here temp_ => ratioSupplyYield (utilization * supplyRatio part) scaled by 1e27. max possible value ~1.64e31
                              // step2 of ratioSupplyYield: add borrowRatio (only x% of borrowers paying yield)
                              if (borrowRatio_ & 1 == 1) {
                                  // ratio is borrowWithInterest / borrowInterestFree (borrowInterestFree is bigger)
                                  borrowRatio_ = borrowRatio_ >> 1;
                                  // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                                  // Note: case where borrowRatio_ == 0 (only borrowInterestFree, no yield) already covered
                                  // at the beginning of the method by early return if `borrowRatio_ == 1`.
                                  // based on above example but borrowRawInterest is 10, borrowInterestFree is 50. no fee. borrowRatio = 20%.
                                  // so only 16.66% of borrowers are paying yield. so the 100% - part of the formula is not needed.
                                  // x of borrowers paying yield = (borrowRatio / (100 + borrowRatio)) = 16.6666666%
                                  // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                                  borrowRatio_ = (borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_);
                                  // max value here for borrowRatio_ is (1e31 / (1e4 + 1e4))= 5e26 (= 50% of borrowers paying yield).
                              } else {
                                  // ratio is borrowInterestFree / borrowWithInterest (borrowWithInterest is bigger)
                                  borrowRatio_ = borrowRatio_ >> 1;
                                  // borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
                                  // x of borrowers paying yield = 100% - (borrowRatio / (100 + borrowRatio)) = 100% - 16.6666666% = 83,333%.
                                  borrowRatio_ = (1e27 - ((borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_)));
                                  // borrowRatio can never be > 100%. so max subtraction can be 100% - 100% / 200%.
                                  // or if borrowRatio_ is 0 -> 100% - 0. or if borrowRatio_ is 1 -> 100% - 1 / 101.
                                  // max value here for borrowRatio_ is 1e27 - 0 = 1e27 (= 100% of borrowers paying yield).
                              }
                              // temp_ => ratioSupplyYield. scaled down from 1e25 = 1% each to normal percent precision 1e2 = 1%.
                              // max nominator value is ~1.64e31 * 1e27 = 1.64e58. max result = 1.64e8
                              temp_ = (FOUR_DECIMALS * temp_ * borrowRatio_) / 1e54;
                              // 2. calculate supply rate
                              // temp_ => supply rate (borrow rate  - revenueFee%) * ratioSupplyYield.
                              // division part is done in next step to increase precision. (divided by 2x FOUR_DECIMALS, fee + borrowRate)
                              // Note that all calculation divisions for supplyExchangePrice are rounded down.
                              // Note supply rate can be bigger than the borrowRate, e.g. if there are only few lenders with interest
                              // but more suppliers not earning interest.
                              temp_ = ((exchangePricesAndConfig_ & X16) * // borrow rate
                                  temp_ * // ratioSupplyYield
                                  (FOUR_DECIMALS - ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_FEE) & X14))); // revenueFee
                              // fee can not be > 100%. max possible = 65535 * ~1.64e8 * 1e4 =~1.074774e17.
                              // 3. calculate increase in supply exchange price
                              supplyExchangePrice_ += ((supplyExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
                                  (SECONDS_PER_YEAR * FOUR_DECIMALS * FOUR_DECIMALS * FOUR_DECIMALS));
                              // max possible nominator = max uint 64 * 1.074774e17 * max uint32 = ~8.52e45. Denominator can not be 0.
                          }
                      }
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                     CALC REVENUE                       /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev gets the `revenueAmount_` for a token given its' totalAmounts and exchangePricesAndConfig from storage
                      /// and the current balance of the Fluid liquidity contract for the token.
                      /// @param totalAmounts_ total amounts packed uint256 read from storage
                      /// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
                      /// @param liquidityTokenBalance_   current balance of Liquidity contract (IERC20(token_).balanceOf(address(this)))
                      /// @return revenueAmount_ collectable revenue amount
                      function calcRevenue(
                          uint256 totalAmounts_,
                          uint256 exchangePricesAndConfig_,
                          uint256 liquidityTokenBalance_
                      ) internal view returns (uint256 revenueAmount_) {
                          // @dev no need to super-optimize this method as it is only used by admin
                          // calculate the new exchange prices based on earned interest
                          (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) = calcExchangePrices(exchangePricesAndConfig_);
                          // total supply = interest free + with interest converted from raw
                          uint256 totalSupply_ = getTotalSupply(totalAmounts_, supplyExchangePrice_);
                          if (totalSupply_ > 0) {
                              // available revenue: balanceOf(token) + totalBorrowings - totalLendings.
                              revenueAmount_ = liquidityTokenBalance_ + getTotalBorrow(totalAmounts_, borrowExchangePrice_);
                              // ensure there is no possible case because of rounding etc. where this would revert,
                              // explicitly check if >
                              revenueAmount_ = revenueAmount_ > totalSupply_ ? revenueAmount_ - totalSupply_ : 0;
                              // Note: if utilization > 100% (totalSupply < totalBorrow), then all the amount above 100% utilization
                              // can only be revenue.
                          } else {
                              // if supply is 0, then rest of balance can be withdrawn as revenue so that no amounts get stuck
                              revenueAmount_ = liquidityTokenBalance_;
                          }
                      }
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                      CALC LIMITS                       /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev calculates withdrawal limit before an operate execution:
                      /// amount of user supply that must stay supplied (not amount that can be withdrawn).
                      /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
                      /// @param userSupplyData_ user supply data packed uint256 from storage
                      /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and converted from BigMath
                      /// @return currentWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction.
                      ///         returned value is in raw for with interest mode, normal amount for interest free mode!
                      function calcWithdrawalLimitBeforeOperate(
                          uint256 userSupplyData_,
                          uint256 userSupply_
                      ) internal view returns (uint256 currentWithdrawalLimit_) {
                          // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet).
                          // first tx where timestamp is 0 will enter `if (lastWithdrawalLimit_ == 0)` because lastWithdrawalLimit_ is not set yet.
                          // returning max withdrawal allowed, which is not exactly right but doesn't matter because the first interaction must be
                          // a deposit anyway. Important is that it would not revert.
                          // Note the first time a deposit brings the user supply amount to above the base withdrawal limit, the active limit
                          // is the fully expanded limit immediately.
                          // extract last set withdrawal limit
                          uint256 lastWithdrawalLimit_ = (userSupplyData_ >>
                              LiquiditySlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) & X64;
                          lastWithdrawalLimit_ =
                              (lastWithdrawalLimit_ >> DEFAULT_EXPONENT_SIZE) <<
                              (lastWithdrawalLimit_ & DEFAULT_EXPONENT_MASK);
                          if (lastWithdrawalLimit_ == 0) {
                              // withdrawal limit is not activated. Max withdrawal allowed
                              return 0;
                          }
                          uint256 maxWithdrawableLimit_;
                          uint256 temp_;
                          unchecked {
                              // extract max withdrawable percent of user supply and
                              // calculate maximum withdrawable amount expandPercentage of user supply at full expansion duration elapsed
                              // e.g.: if 10% expandPercentage, meaning 10% is withdrawable after full expandDuration has elapsed.
                              // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              maxWithdrawableLimit_ =
                                  (((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14) * userSupply_) /
                                  FOUR_DECIMALS;
                              // time elapsed since last withdrawal limit was set (in seconds)
                              // @dev last process timestamp is guaranteed to exist for withdrawal, as a supply must have happened before.
                              // last timestamp can not be > current timestamp
                              temp_ =
                                  block.timestamp -
                                  ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) & X33);
                          }
                          // calculate withdrawable amount of expandPercent that is elapsed of expandDuration.
                          // e.g. if 60% of expandDuration has elapsed, then user should be able to withdraw 6% of user supply, down to 94%.
                          // Note: no explicit check for this needed, it is covered by setting minWithdrawalLimit_ if needed.
                          temp_ =
                              (maxWithdrawableLimit_ * temp_) /
                              // extract expand duration: After this, decrement won't happen (user can withdraw 100% of withdraw limit)
                              ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_DURATION) & X24); // expand duration can never be 0
                          // calculate expanded withdrawal limit: last withdrawal limit - withdrawable amount.
                          // Note: withdrawable amount here can grow bigger than userSupply if timeElapsed is a lot bigger than expandDuration,
                          // which would cause the subtraction `lastWithdrawalLimit_ - withdrawableAmount_` to revert. In that case, set 0
                          // which will cause minimum (fully expanded) withdrawal limit to be set in lines below.
                          unchecked {
                              // underflow explicitly checked & handled
                              currentWithdrawalLimit_ = lastWithdrawalLimit_ > temp_ ? lastWithdrawalLimit_ - temp_ : 0;
                              // calculate minimum withdrawal limit: minimum amount of user supply that must stay supplied at full expansion.
                              // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
                              temp_ = userSupply_ - maxWithdrawableLimit_;
                          }
                          // if withdrawal limit is decreased below minimum then set minimum
                          // (e.g. when more than expandDuration time has elapsed)
                          if (temp_ > currentWithdrawalLimit_) {
                              currentWithdrawalLimit_ = temp_;
                          }
                      }
                      /// @dev calculates withdrawal limit after an operate execution:
                      /// amount of user supply that must stay supplied (not amount that can be withdrawn).
                      /// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
                      /// @param userSupplyData_ user supply data packed uint256 from storage
                      /// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and added / subtracted with the executed operate amount
                      /// @param newWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction, result from `calcWithdrawalLimitBeforeOperate`
                      /// @return withdrawalLimit_ updated withdrawal limit that should be written to storage. returned value is in
                      ///                          raw for with interest mode, normal amount for interest free mode!
                      function calcWithdrawalLimitAfterOperate(
                          uint256 userSupplyData_,
                          uint256 userSupply_,
                          uint256 newWithdrawalLimit_
                      ) internal pure returns (uint256) {
                          // temp_ => base withdrawal limit. below this, maximum withdrawals are allowed
                          uint256 temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          // if user supply is below base limit then max withdrawals are allowed
                          if (userSupply_ < temp_) {
                              return 0;
                          }
                          // temp_ => withdrawal limit expandPercent (is in 1e2 decimals)
                          temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14;
                          unchecked {
                              // temp_ => minimum withdrawal limit: userSupply - max withdrawable limit (userSupply * expandPercent))
                              // userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              // subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
                              temp_ = userSupply_ - ((userSupply_ * temp_) / FOUR_DECIMALS);
                          }
                          // if new (before operation) withdrawal limit is less than minimum limit then set minimum limit.
                          // e.g. can happen on new deposits. withdrawal limit is instantly fully expanded in a scenario where
                          // increased deposit amount outpaces withrawals.
                          if (temp_ > newWithdrawalLimit_) {
                              return temp_;
                          }
                          return newWithdrawalLimit_;
                      }
                      /// @dev calculates borrow limit before an operate execution:
                      /// total amount user borrow can reach (not borrowable amount in current operation).
                      /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
                      /// @param userBorrowData_ user borrow data packed uint256 from storage
                      /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_`
                      /// @return currentBorrowLimit_ current borrow limit updated for expansion since last interaction. returned value is in
                      ///                             raw for with interest mode, normal amount for interest free mode!
                      function calcBorrowLimitBeforeOperate(
                          uint256 userBorrowData_,
                          uint256 userBorrow_
                      ) internal view returns (uint256 currentBorrowLimit_) {
                          // @dev must support handling the case where timestamp is 0 (config is set but no interactions yet) -> base limit.
                          // first tx where timestamp is 0 will enter `if (maxExpandedBorrowLimit_ < baseBorrowLimit_)` because `userBorrow_` and thus
                          // `maxExpansionLimit_` and thus `maxExpandedBorrowLimit_` is 0 and `baseBorrowLimit_` can not be 0.
                          // temp_ = extract borrow expand percent (is in 1e2 decimals)
                          uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14;
                          uint256 maxExpansionLimit_;
                          uint256 maxExpandedBorrowLimit_;
                          unchecked {
                              // calculate max expansion limit: Max amount limit can expand to since last interaction
                              // userBorrow_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              maxExpansionLimit_ = ((userBorrow_ * temp_) / FOUR_DECIMALS);
                              // calculate max borrow limit: Max point limit can increase to since last interaction
                              maxExpandedBorrowLimit_ = userBorrow_ + maxExpansionLimit_;
                          }
                          // currentBorrowLimit_ = extract base borrow limit
                          currentBorrowLimit_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
                          currentBorrowLimit_ =
                              (currentBorrowLimit_ >> DEFAULT_EXPONENT_SIZE) <<
                              (currentBorrowLimit_ & DEFAULT_EXPONENT_MASK);
                          if (maxExpandedBorrowLimit_ < currentBorrowLimit_) {
                              return currentBorrowLimit_;
                          }
                          // time elapsed since last borrow limit was set (in seconds)
                          unchecked {
                              // temp_ = timeElapsed_ (last timestamp can not be > current timestamp)
                              temp_ =
                                  block.timestamp -
                                  ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP) & X33); // extract last update timestamp
                          }
                          // currentBorrowLimit_ = expandedBorrowableAmount + extract last set borrow limit
                          currentBorrowLimit_ =
                              // calculate borrow limit expansion since last interaction for `expandPercent` that is elapsed of `expandDuration`.
                              // divisor is extract expand duration (after this, full expansion to expandPercentage happened).
                              ((maxExpansionLimit_ * temp_) /
                                  ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24)) + // expand duration can never be 0
                              //  extract last set borrow limit
                              BigMathMinified.fromBigNumber(
                                  (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) & X64,
                                  DEFAULT_EXPONENT_SIZE,
                                  DEFAULT_EXPONENT_MASK
                              );
                          // if timeElapsed is bigger than expandDuration, new borrow limit would be > max expansion,
                          // so set to `maxExpandedBorrowLimit_` in that case.
                          // also covers the case where last process timestamp = 0 (timeElapsed would simply be very big)
                          if (currentBorrowLimit_ > maxExpandedBorrowLimit_) {
                              currentBorrowLimit_ = maxExpandedBorrowLimit_;
                          }
                          // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
                          temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (currentBorrowLimit_ > temp_) {
                              currentBorrowLimit_ = temp_;
                          }
                      }
                      /// @dev calculates borrow limit after an operate execution:
                      /// total amount user borrow can reach (not borrowable amount in current operation).
                      /// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
                      /// @param userBorrowData_ user borrow data packed uint256 from storage
                      /// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_` and added / subtracted with the executed operate amount
                      /// @param newBorrowLimit_ current borrow limit updated for expansion since last interaction, result from `calcBorrowLimitBeforeOperate`
                      /// @return borrowLimit_ updated borrow limit that should be written to storage.
                      ///                      returned value is in raw for with interest mode, normal amount for interest free mode!
                      function calcBorrowLimitAfterOperate(
                          uint256 userBorrowData_,
                          uint256 userBorrow_,
                          uint256 newBorrowLimit_
                      ) internal pure returns (uint256 borrowLimit_) {
                          // temp_ = extract borrow expand percent
                          uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14; // (is in 1e2 decimals)
                          unchecked {
                              // borrowLimit_ = calculate maximum borrow limit at full expansion.
                              // userBorrow_ needs to be at least 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
                              borrowLimit_ = userBorrow_ + ((userBorrow_ * temp_) / FOUR_DECIMALS);
                          }
                          // temp_ = extract base borrow limit
                          temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          if (borrowLimit_ < temp_) {
                              // below base limit, borrow limit is always base limit
                              return temp_;
                          }
                          // temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
                          temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
                          temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
                          // make sure fully expanded borrow limit is not above hard max borrow limit
                          if (borrowLimit_ > temp_) {
                              borrowLimit_ = temp_;
                          }
                          // if new borrow limit (from before operate) is > max borrow limit, set max borrow limit.
                          // (e.g. on a repay shrinking instantly to fully expanded borrow limit from new borrow amount. shrinking is instant)
                          if (newBorrowLimit_ > borrowLimit_) {
                              return borrowLimit_;
                          }
                          return newBorrowLimit_;
                      }
                      ///////////////////////////////////////////////////////////////////////////
                      //////////                      CALC RATES                        /////////
                      ///////////////////////////////////////////////////////////////////////////
                      /// @dev Calculates new borrow rate from utilization for a token
                      /// @param rateData_ rate data packed uint256 from storage for the token
                      /// @param utilization_ totalBorrow / totalSupply. 1e4 = 100% utilization
                      /// @return rate_ rate for that particular token in 1e2 precision (e.g. 5% rate = 500)
                      function calcBorrowRateFromUtilization(uint256 rateData_, uint256 utilization_) internal returns (uint256 rate_) {
                          // extract rate version: 4 bits (0xF) starting from bit 0
                          uint256 rateVersion_ = (rateData_ & 0xF);
                          if (rateVersion_ == 1) {
                              rate_ = calcRateV1(rateData_, utilization_);
                          } else if (rateVersion_ == 2) {
                              rate_ = calcRateV2(rateData_, utilization_);
                          } else {
                              revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__UnsupportedRateVersion);
                          }
                          if (rate_ > X16) {
                              // hard cap for borrow rate at maximum value 16 bits (65535) to make sure it does not overflow storage space.
                              // this is unlikely to ever happen if configs stay within expected levels.
                              rate_ = X16;
                              // emit event to more easily become aware
                              emit BorrowRateMaxCap();
                          }
                      }
                      /// @dev calculates the borrow rate based on utilization for rate data version 1 (with one kink) in 1e2 precision
                      /// @param rateData_ rate data packed uint256 from storage for the token
                      /// @param utilization_  in 1e2 (100% = 1e4)
                      /// @return rate_ rate in 1e2 precision
                      function calcRateV1(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
                          /// For rate v1 (one kink) ------------------------------------------------------
                          /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  52- 67 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Last 188 bits =>  68-255 => blank, might come in use in future
                          // y = mx + c.
                          // y is borrow rate
                          // x is utilization
                          // m = slope (m can also be negative for declining rates)
                          // c is constant (c can be negative)
                          uint256 y1_;
                          uint256 y2_;
                          uint256 x1_;
                          uint256 x2_;
                          // extract kink1: 16 bits (0xFFFF) starting from bit 20
                          // kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
                          uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_UTILIZATION_AT_KINK) & X16;
                          if (utilization_ < kink1_) {
                              // if utilization is less than kink
                              y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO) & X16;
                              y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
                              x1_ = 0; // 0%
                              x2_ = kink1_;
                          } else {
                              // else utilization is greater than kink
                              y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
                              y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) & X16;
                              x1_ = kink1_;
                              x2_ = FOUR_DECIMALS; // 100%
                          }
                          int256 constant_;
                          int256 slope_;
                          unchecked {
                              // calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
                              // utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
                              // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
                              slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));
                              // calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
                              // maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
                              // maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
                              // maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
                              // subtraction most extreme case would be  0 - max value slope_ * x1_ => can not underflow int256
                              constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));
                              // calculating new borrow rate
                              // - slope_ max value is 65535 * 1e12,
                              // - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
                              // - constant max value is 65535 * 1e12
                              // so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
                              // divisor TWELVE_DECIMALS can not be 0
                              slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
                              if (slope_ < 0) {
                                  revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
                              }
                              rate_ = uint256(slope_) / TWELVE_DECIMALS;
                          }
                      }
                      /// @dev calculates the borrow rate based on utilization for rate data version 2 (with two kinks) in 1e4 precision
                      /// @param rateData_ rate data packed uint256 from storage for the token
                      /// @param utilization_  in 1e2 (100% = 1e4)
                      /// @return rate_ rate in 1e4 precision
                      function calcRateV2(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
                          /// For rate v2 (two kinks) -----------------------------------------------------
                          /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  52- 67 => Utilization at kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  68- 83 => Rate at utilization kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Next 16  bits =>  84- 99 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                          /// Last 156 bits => 100-255 => blank, might come in use in future
                          // y = mx + c.
                          // y is borrow rate
                          // x is utilization
                          // m = slope (m can also be negative for declining rates)
                          // c is constant (c can be negative)
                          uint256 y1_;
                          uint256 y2_;
                          uint256 x1_;
                          uint256 x2_;
                          // extract kink1: 16 bits (0xFFFF) starting from bit 20
                          // kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
                          uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1) & X16;
                          if (utilization_ < kink1_) {
                              // if utilization is less than kink1
                              y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO) & X16;
                              y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
                              x1_ = 0; // 0%
                              x2_ = kink1_;
                          } else {
                              // extract kink2: 16 bits (0xFFFF) starting from bit 52
                              uint256 kink2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2) & X16;
                              if (utilization_ < kink2_) {
                                  // if utilization is less than kink2
                                  y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
                                  y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
                                  x1_ = kink1_;
                                  x2_ = kink2_;
                              } else {
                                  // else utilization is greater than kink2
                                  y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
                                  y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) & X16;
                                  x1_ = kink2_;
                                  x2_ = FOUR_DECIMALS;
                              }
                          }
                          int256 constant_;
                          int256 slope_;
                          unchecked {
                              // calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
                              // utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
                              // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
                              slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));
                              // calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
                              // maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
                              // maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
                              // maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
                              // subtraction most extreme case would be  0 - max value slope_ * x1_ => can not underflow int256
                              constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));
                              // calculating new borrow rate
                              // - slope_ max value is 65535 * 1e12,
                              // - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
                              // - constant max value is 65535 * 1e12
                              // so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
                              // divisor TWELVE_DECIMALS can not be 0
                              slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
                              if (slope_ < 0) {
                                  revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
                              }
                              rate_ = uint256(slope_) / TWELVE_DECIMALS;
                          }
                      }
                      /// @dev reads the total supply out of Liquidity packed storage `totalAmounts_` for `supplyExchangePrice_`
                      function getTotalSupply(
                          uint256 totalAmounts_,
                          uint256 supplyExchangePrice_
                      ) internal pure returns (uint256 totalSupply_) {
                          // totalSupply_ => supplyInterestFree
                          totalSupply_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
                          totalSupply_ = (totalSupply_ >> DEFAULT_EXPONENT_SIZE) << (totalSupply_ & DEFAULT_EXPONENT_MASK);
                          uint256 totalSupplyRaw_ = totalAmounts_ & X64; // no shifting as supplyRaw is first 64 bits
                          totalSupplyRaw_ = (totalSupplyRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalSupplyRaw_ & DEFAULT_EXPONENT_MASK);
                          // totalSupply = supplyInterestFree + supplyRawInterest normalized from raw
                          totalSupply_ += ((totalSupplyRaw_ * supplyExchangePrice_) / EXCHANGE_PRICES_PRECISION);
                      }
                      /// @dev reads the total borrow out of Liquidity packed storage `totalAmounts_` for `borrowExchangePrice_`
                      function getTotalBorrow(
                          uint256 totalAmounts_,
                          uint256 borrowExchangePrice_
                      ) internal pure returns (uint256 totalBorrow_) {
                          // totalBorrow_ => borrowInterestFree
                          // no & mask needed for borrow interest free as it occupies the last bits in the storage slot
                          totalBorrow_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
                          totalBorrow_ = (totalBorrow_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrow_ & DEFAULT_EXPONENT_MASK);
                          uint256 totalBorrowRaw_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
                          totalBorrowRaw_ = (totalBorrowRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrowRaw_ & DEFAULT_EXPONENT_MASK);
                          // totalBorrow = borrowInterestFree + borrowRawInterest normalized from raw
                          totalBorrow_ += ((totalBorrowRaw_ * borrowExchangePrice_) / EXCHANGE_PRICES_PRECISION);
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  /// @notice library that helps in reading / working with storage slot data of Fluid Liquidity.
                  /// @dev as all data for Fluid Liquidity is internal, any data must be fetched directly through manual
                  /// slot reading through this library or, if gas usage is less important, through the FluidLiquidityResolver.
                  library LiquiditySlotsLink {
                      /// @dev storage slot for status at Liquidity
                      uint256 internal constant LIQUIDITY_STATUS_SLOT = 1;
                      /// @dev storage slot for auths mapping at Liquidity
                      uint256 internal constant LIQUIDITY_AUTHS_MAPPING_SLOT = 2;
                      /// @dev storage slot for guardians mapping at Liquidity
                      uint256 internal constant LIQUIDITY_GUARDIANS_MAPPING_SLOT = 3;
                      /// @dev storage slot for user class mapping at Liquidity
                      uint256 internal constant LIQUIDITY_USER_CLASS_MAPPING_SLOT = 4;
                      /// @dev storage slot for exchangePricesAndConfig mapping at Liquidity
                      uint256 internal constant LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT = 5;
                      /// @dev storage slot for rateData mapping at Liquidity
                      uint256 internal constant LIQUIDITY_RATE_DATA_MAPPING_SLOT = 6;
                      /// @dev storage slot for totalAmounts mapping at Liquidity
                      uint256 internal constant LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT = 7;
                      /// @dev storage slot for user supply double mapping at Liquidity
                      uint256 internal constant LIQUIDITY_USER_SUPPLY_DOUBLE_MAPPING_SLOT = 8;
                      /// @dev storage slot for user borrow double mapping at Liquidity
                      uint256 internal constant LIQUIDITY_USER_BORROW_DOUBLE_MAPPING_SLOT = 9;
                      /// @dev storage slot for listed tokens array at Liquidity
                      uint256 internal constant LIQUIDITY_LISTED_TOKENS_ARRAY_SLOT = 10;
                      /// @dev storage slot for listed tokens array at Liquidity
                      uint256 internal constant LIQUIDITY_CONFIGS2_MAPPING_SLOT = 11;
                      // --------------------------------
                      // @dev stacked uint256 storage slots bits position data for each:
                      // ExchangePricesAndConfig
                      uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATE = 0;
                      uint256 internal constant BITS_EXCHANGE_PRICES_FEE = 16;
                      uint256 internal constant BITS_EXCHANGE_PRICES_UTILIZATION = 30;
                      uint256 internal constant BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD = 44;
                      uint256 internal constant BITS_EXCHANGE_PRICES_LAST_TIMESTAMP = 58;
                      uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE = 91;
                      uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE = 155;
                      uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_RATIO = 219;
                      uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATIO = 234;
                      uint256 internal constant BITS_EXCHANGE_PRICES_USES_CONFIGS2 = 249;
                      // RateData:
                      uint256 internal constant BITS_RATE_DATA_VERSION = 0;
                      // RateData: V1
                      uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO = 4;
                      uint256 internal constant BITS_RATE_DATA_V1_UTILIZATION_AT_KINK = 20;
                      uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK = 36;
                      uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX = 52;
                      // RateData: V2
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO = 4;
                      uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1 = 20;
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1 = 36;
                      uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2 = 52;
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2 = 68;
                      uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX = 84;
                      // TotalAmounts
                      uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_WITH_INTEREST = 0;
                      uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE = 64;
                      uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST = 128;
                      uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE = 192;
                      // UserSupplyData
                      uint256 internal constant BITS_USER_SUPPLY_MODE = 0;
                      uint256 internal constant BITS_USER_SUPPLY_AMOUNT = 1;
                      uint256 internal constant BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT = 65;
                      uint256 internal constant BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP = 129;
                      uint256 internal constant BITS_USER_SUPPLY_EXPAND_PERCENT = 162;
                      uint256 internal constant BITS_USER_SUPPLY_EXPAND_DURATION = 176;
                      uint256 internal constant BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT = 200;
                      uint256 internal constant BITS_USER_SUPPLY_IS_PAUSED = 255;
                      // UserBorrowData
                      uint256 internal constant BITS_USER_BORROW_MODE = 0;
                      uint256 internal constant BITS_USER_BORROW_AMOUNT = 1;
                      uint256 internal constant BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT = 65;
                      uint256 internal constant BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP = 129;
                      uint256 internal constant BITS_USER_BORROW_EXPAND_PERCENT = 162;
                      uint256 internal constant BITS_USER_BORROW_EXPAND_DURATION = 176;
                      uint256 internal constant BITS_USER_BORROW_BASE_BORROW_LIMIT = 200;
                      uint256 internal constant BITS_USER_BORROW_MAX_BORROW_LIMIT = 218;
                      uint256 internal constant BITS_USER_BORROW_IS_PAUSED = 255;
                      // Configs2
                      uint256 internal constant BITS_CONFIGS2_MAX_UTILIZATION = 0;
                      // --------------------------------
                      /// @notice Calculating the slot ID for Liquidity contract for single mapping at `slot_` for `key_`
                      function calculateMappingStorageSlot(uint256 slot_, address key_) internal pure returns (bytes32) {
                          return keccak256(abi.encode(key_, slot_));
                      }
                      /// @notice Calculating the slot ID for Liquidity contract for double mapping at `slot_` for `key1_` and `key2_`
                      function calculateDoubleMappingStorageSlot(
                          uint256 slot_,
                          address key1_,
                          address key2_
                      ) internal pure returns (bytes32) {
                          bytes32 intermediateSlot_ = keccak256(abi.encode(key1_, slot_));
                          return keccak256(abi.encode(key2_, intermediateSlot_));
                      }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity 0.8.21;
                  import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
                  /// @notice provides minimalistic methods for safe transfers, e.g. ERC20 safeTransferFrom
                  library SafeTransfer {
                      uint256 internal constant MAX_NATIVE_TRANSFER_GAS = 20000; // pass max. 20k gas for native transfers
                      error FluidSafeTransferError(uint256 errorId_);
                      /// @dev Transfer `amount_` of `token_` from `from_` to `to_`, spending the approval given by `from_` to the
                      /// calling contract. If `token_` returns no value, non-reverting calls are assumed to be successful.
                      /// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
                      /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L31-L63
                      function safeTransferFrom(address token_, address from_, address to_, uint256 amount_) internal {
                          bool success_;
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Get a pointer to some free memory.
                              let freeMemoryPointer := mload(0x40)
                              // Write the abi-encoded calldata into memory, beginning with the function selector.
                              mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
                              mstore(add(freeMemoryPointer, 4), and(from_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from_" argument.
                              mstore(add(freeMemoryPointer, 36), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
                              mstore(add(freeMemoryPointer, 68), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.
                              success_ := and(
                                  // Set success to whether the call reverted, if not we check it either
                                  // returned exactly 1 (can't just be non-zero data), or had no return data.
                                  or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                                  // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                                  // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                                  // Counterintuitively, this call must be positioned second to the or() call in the
                                  // surrounding and() call or else returndatasize() will be zero during the computation.
                                  call(gas(), token_, 0, freeMemoryPointer, 100, 0, 32)
                              )
                          }
                          if (!success_) {
                              revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFromFailed);
                          }
                      }
                      /// @dev Transfer `amount_` of `token_` to `to_`.
                      /// If `token_` returns no value, non-reverting calls are assumed to be successful.
                      /// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
                      /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L65-L95
                      function safeTransfer(address token_, address to_, uint256 amount_) internal {
                          bool success_;
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Get a pointer to some free memory.
                              let freeMemoryPointer := mload(0x40)
                              // Write the abi-encoded calldata into memory, beginning with the function selector.
                              mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                              mstore(add(freeMemoryPointer, 4), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
                              mstore(add(freeMemoryPointer, 36), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.
                              success_ := and(
                                  // Set success to whether the call reverted, if not we check it either
                                  // returned exactly 1 (can't just be non-zero data), or had no return data.
                                  or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                                  // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                                  // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                                  // Counterintuitively, this call must be positioned second to the or() call in the
                                  // surrounding and() call or else returndatasize() will be zero during the computation.
                                  call(gas(), token_, 0, freeMemoryPointer, 68, 0, 32)
                              )
                          }
                          if (!success_) {
                              revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
                          }
                      }
                      /// @dev Transfer `amount_` of ` native token to `to_`.
                      /// Minimally modified from Solmate SafeTransferLib (Custom Error):
                      /// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L15-L25
                      function safeTransferNative(address to_, uint256 amount_) internal {
                          bool success_;
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Transfer the ETH and store if it succeeded or not. Pass limited gas
                              success_ := call(MAX_NATIVE_TRANSFER_GAS, to_, amount_, 0, 0, 0, 0)
                          }
                          if (!success_) {
                              revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { Variables } from "./variables.sol";
                  import { ErrorTypes } from "../errorTypes.sol";
                  import { Error } from "../error.sol";
                  /// @dev ReentrancyGuard based on OpenZeppelin implementation.
                  /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.8/contracts/security/ReentrancyGuard.sol
                  abstract contract ReentrancyGuard is Variables, Error {
                      uint8 internal constant REENTRANCY_NOT_ENTERED = 1;
                      uint8 internal constant REENTRANCY_ENTERED = 2;
                      constructor() {
                          // on logic contracts, switch reentrancy to entered so no call is possible (forces delegatecall)
                          _status = REENTRANCY_ENTERED; 
                      }
                      /// @dev Prevents a contract from calling itself, directly or indirectly.
                      /// See OpenZeppelin implementation for more info
                      modifier reentrancy() {
                          // On the first call to nonReentrant, _status will be NOT_ENTERED
                          if (_status == REENTRANCY_ENTERED) {
                              revert FluidLiquidityError(ErrorTypes.LiquidityHelpers__Reentrancy);
                          }
                          // Any calls to nonReentrant after this point will fail
                          _status = REENTRANCY_ENTERED;
                          _;
                          // By storing the original value once again, a refund is triggered (see
                          // https://eips.ethereum.org/EIPS/eip-2200)
                          _status = REENTRANCY_NOT_ENTERED;
                      }
                  }
                  abstract contract CommonHelpers is ReentrancyGuard {
                      /// @dev Returns the current admin (governance).
                      function _getGovernanceAddr() internal view returns (address governance_) {
                          assembly {
                              governance_ := sload(GOVERNANCE_SLOT)
                          }
                      }
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  contract ConstantVariables {
                      /// @dev Storage slot with the admin of the contract. Logic from "proxy.sol".
                      /// This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is validated in the constructor.
                      bytes32 internal constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                      uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
                      /// @dev address that is mapped to the chain native token
                      address internal constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                      /// @dev decimals for native token
                      // !! Double check compatibility with all code if this ever changes for a deployment !!
                      uint8 internal constant NATIVE_TOKEN_DECIMALS = 18;
                      /// @dev Minimum token decimals for any token that can be listed at Liquidity (inclusive)
                      uint8 internal constant MIN_TOKEN_DECIMALS = 6;
                      /// @dev Maximum token decimals for any token that can be listed at Liquidity (inclusive)
                      uint8 internal constant MAX_TOKEN_DECIMALS = 18;
                      /// @dev Ignoring leap years
                      uint256 internal constant SECONDS_PER_YEAR = 365 days;
                      /// @dev limit any total amount to be half of type(uint128).max (~3.4e38) at type(int128).max (~1.7e38) as safety
                      /// measure for any potential overflows / unexpected outcomes. This is checked for total borrow / supply.
                      uint256 internal constant MAX_TOKEN_AMOUNT_CAP = uint256(uint128(type(int128).max));
                      /// @dev limit for triggering a revert if sent along excess input amount diff is bigger than this percentage (in 1e2)
                      uint256 internal constant MAX_INPUT_AMOUNT_EXCESS = 100; // 1%
                      /// @dev if this bytes32 is set in the calldata, then token transfers are skipped as long as Liquidity layer is on the winning side.
                      bytes32 internal constant SKIP_TRANSFERS = keccak256(bytes("SKIP_TRANSFERS"));
                      /// @dev time after which a write to storage of exchangePricesAndConfig will happen always.
                      uint256 internal constant FORCE_STORAGE_WRITE_AFTER_TIME = 1 days;
                      /// @dev constants used for BigMath conversion from and to storage
                      uint256 internal constant SMALL_COEFFICIENT_SIZE = 10;
                      uint256 internal constant DEFAULT_COEFFICIENT_SIZE = 56;
                      uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
                      uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
                      /// @dev constants to increase readability for using bit masks
                      uint256 internal constant FOUR_DECIMALS = 1e4;
                      uint256 internal constant TWELVE_DECIMALS = 1e12;
                      uint256 internal constant X8 = 0xff;
                      uint256 internal constant X14 = 0x3fff;
                      uint256 internal constant X15 = 0x7fff;
                      uint256 internal constant X16 = 0xffff;
                      uint256 internal constant X18 = 0x3ffff;
                      uint256 internal constant X24 = 0xffffff;
                      uint256 internal constant X33 = 0x1ffffffff;
                      uint256 internal constant X64 = 0xffffffffffffffff;
                  }
                  contract Variables is ConstantVariables {
                      /// @dev address of contract that gets sent the revenue. Configurable by governance
                      address internal _revenueCollector;
                      // 12 bytes empty
                      // ----- storage slot 1 ------
                      /// @dev paused status: status = 1 -> normal. status = 2 -> paused.
                      /// not tightly packed with revenueCollector address to allow for potential changes later that improve gas more
                      /// (revenueCollector is only rarely used by admin methods, where optimization is not as important).
                      /// to be replaced with transient storage once EIP-1153 Transient storage becomes available with dencun upgrade.
                      uint256 internal _status;
                      // ----- storage slot 2 ------
                      /// @dev Auths can set most config values. E.g. contracts that automate certain flows like e.g. adding a new fToken.
                      /// Governance can add/remove auths.
                      /// Governance is auth by default
                      mapping(address => uint256) internal _isAuth;
                      // ----- storage slot 3 ------
                      /// @dev Guardians can pause lower class users
                      /// Governance can add/remove guardians
                      /// Governance is guardian by default
                      mapping(address => uint256) internal _isGuardian;
                      // ----- storage slot 4 ------
                      /// @dev class defines which protocols can be paused by guardians
                      /// Currently there are 2 classes: 0 can be paused by guardians. 1 cannot be paused by guardians.
                      /// New protocols are added as class 0 and will be upgraded to 1 over time.
                      mapping(address => uint256) internal _userClass;
                      // ----- storage slot 5 ------
                      /// @dev exchange prices and token config per token: token -> exchange prices & config
                      /// First 16 bits =>   0- 15 => borrow rate (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next  14 bits =>  16- 29 => fee on interest from borrowers to lenders (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
                      /// Next  14 bits =>  30- 43 => last stored utilization (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      /// Next  14 bits =>  44- 57 => update on storage threshold (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
                      /// Next  33 bits =>  58- 90 => last update timestamp (enough until 16 March 2242 -> max value 8589934591)
                      /// Next  64 bits =>  91-154 => supply exchange price (1e12 -> max value 18_446_744,073709551615)
                      /// Next  64 bits => 155-218 => borrow exchange price (1e12 -> max value 18_446_744,073709551615)
                      /// Next   1 bit  => 219-219 => if 0 then ratio is supplyInterestFree / supplyWithInterest else ratio is supplyWithInterest / supplyInterestFree
                      /// Next  14 bits => 220-233 => supplyRatio: supplyInterestFree / supplyWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      /// Next   1 bit  => 234-234 => if 0 then ratio is borrowInterestFree / borrowWithInterest else ratio is borrowWithInterest / borrowInterestFree
                      /// Next  14 bits => 235-248 => borrowRatio: borrowInterestFree / borrowWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      /// Next   1 bit  => 249-249 => flag for token uses config storage slot 2. (signals SLOAD for additional config slot is needed during execution)
                      /// Last   6 bits => 250-255 => empty for future use
                      ///                             if more free bits are needed in the future, update on storage threshold bits could be reduced to 7 bits
                      ///                             (can plan to add `MAX_TOKEN_CONFIG_UPDATE_THRESHOLD` but need to adjust more bits)
                      ///                             if more bits absolutely needed then we can convert fee, utilization, update on storage threshold,
                      ///                             supplyRatio & borrowRatio from 14 bits to 10bits (1023 max number) where 1000 = 100% & 1 = 0.1%
                      mapping(address => uint256) internal _exchangePricesAndConfig;
                      // ----- storage slot 6 ------
                      /// @dev Rate related data per token: token -> rate data
                      /// READ (SLOAD): all actions; WRITE (SSTORE): only on set config admin actions
                      /// token => rate related data
                      /// First 4 bits  =>     0-3 => rate version
                      /// rest of the bits are rate dependent:
                      /// For rate v1 (one kink) ------------------------------------------------------
                      /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  52- 67 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Last 188 bits =>  68-255 => empty for future use
                      /// For rate v2 (two kinks) -----------------------------------------------------
                      /// Next 16  bits =>  4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  52- 67 => Utilization at kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  68- 83 => Rate at utilization kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next 16  bits =>  84- 99 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Last 156 bits => 100-255 => empty for future use
                      mapping(address => uint256) internal _rateData;
                      // ----- storage slot 7 ------
                      /// @dev total supply / borrow amounts for with / without interest per token: token -> amounts
                      /// First  64 bits =>   0- 63 => total supply with interest in raw (totalSupply = totalSupplyRaw * supplyExchangePrice); BigMath: 56 | 8
                      /// Next   64 bits =>  64-127 => total interest free supply in normal token amount (totalSupply = totalSupply); BigMath: 56 | 8
                      /// Next   64 bits => 128-191 => total borrow with interest in raw (totalBorrow = totalBorrowRaw * borrowExchangePrice); BigMath: 56 | 8
                      /// Next   64 bits => 192-255 => total interest free borrow in normal token amount (totalBorrow = totalBorrow); BigMath: 56 | 8
                      mapping(address => uint256) internal _totalAmounts;
                      // ----- storage slot 8 ------
                      /// @dev user supply data per token: user -> token -> data
                      /// First  1 bit  =>       0 => mode: user supply with or without interest
                      ///                             0 = without, amounts are in normal (i.e. no need to multiply with exchange price)
                      ///                             1 = with interest, amounts are in raw (i.e. must multiply with exchange price to get actual token amounts)
                      /// Next  64 bits =>   1- 64 => user supply amount (normal or raw depends on 1st bit); BigMath: 56 | 8
                      /// Next  64 bits =>  65-128 => previous user withdrawal limit (normal or raw depends on 1st bit); BigMath: 56 | 8
                      /// Next  33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
                      /// Next  14 bits => 162-175 => expand withdrawal limit percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383).
                      ///                             @dev shrinking is instant
                      /// Next  24 bits => 176-199 => withdrawal limit expand duration in seconds.(Max value 16_777_215; ~4_660 hours, ~194 days)
                      /// Next  18 bits => 200-217 => base withdrawal limit: below this, 100% withdrawals can be done (normal or raw depends on 1st bit); BigMath: 10 | 8
                      /// Next  37 bits => 218-254 => empty for future use
                      /// Last     bit  => 255-255 => is user paused (1 = paused, 0 = not paused)
                      mapping(address => mapping(address => uint256)) internal _userSupplyData;
                      // ----- storage slot 9 ------
                      /// @dev user borrow data per token: user -> token -> data
                      /// First  1 bit  =>       0 => mode: user borrow with or without interest
                      ///                             0 = without, amounts are in normal (i.e. no need to multiply with exchange price)
                      ///                             1 = with interest, amounts are in raw (i.e. must multiply with exchange price to get actual token amounts)
                      /// Next  64 bits =>   1- 64 => user borrow amount (normal or raw depends on 1st bit); BigMath: 56 | 8
                      /// Next  64 bits =>  65-128 => previous user debt ceiling (normal or raw depends on 1st bit); BigMath: 56 | 8
                      /// Next  33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
                      /// Next  14 bits => 162-175 => expand debt ceiling percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      ///                             @dev shrinking is instant
                      /// Next  24 bits => 176-199 => debt ceiling expand duration in seconds (Max value 16_777_215; ~4_660 hours, ~194 days)
                      /// Next  18 bits => 200-217 => base debt ceiling: below this, there's no debt ceiling limits (normal or raw depends on 1st bit); BigMath: 10 | 8
                      /// Next  18 bits => 218-235 => max debt ceiling: absolute maximum debt ceiling can expand to (normal or raw depends on 1st bit); BigMath: 10 | 8
                      /// Next  19 bits => 236-254 => empty for future use
                      /// Last     bit  => 255-255 => is user paused (1 = paused, 0 = not paused)
                      mapping(address => mapping(address => uint256)) internal _userBorrowData;
                      // ----- storage slot 10 ------
                      /// @dev list of allowed tokens at Liquidity. tokens that are once configured can never be completely removed. so this
                      ///      array is append-only.
                      address[] internal _listedTokens;
                      // ----- storage slot 11 ------
                      /// @dev expanded token configs per token: token -> config data slot 2.
                      ///      Use of this is signaled by `_exchangePricesAndConfig` bit 249.
                      /// First 14 bits =>   0- 13 => max allowed utilization (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
                      /// Last 242 bits =>  14-255 => empty for future use
                      mapping(address => uint256) internal _configs2;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  contract Error {
                      error FluidLiquidityError(uint256 errorId_);
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  library ErrorTypes {
                      /***********************************|
                      |         Admin Module              | 
                      |__________________________________*/
                      /// @notice thrown when an input address is zero
                      uint256 internal constant AdminModule__AddressZero = 10001;
                      /// @notice thrown when msg.sender is not governance
                      uint256 internal constant AdminModule__OnlyGovernance = 10002;
                      /// @notice thrown when msg.sender is not auth
                      uint256 internal constant AdminModule__OnlyAuths = 10003;
                      /// @notice thrown when msg.sender is not guardian
                      uint256 internal constant AdminModule__OnlyGuardians = 10004;
                      /// @notice thrown when base withdrawal limit, base debt limit or max withdrawal limit is sent as 0
                      uint256 internal constant AdminModule__LimitZero = 10005;
                      /// @notice thrown whenever an invalid input param is given
                      uint256 internal constant AdminModule__InvalidParams = 10006;
                      /// @notice thrown if user class 1 is paused (can not be paused)
                      uint256 internal constant AdminModule__UserNotPausable = 10007;
                      /// @notice thrown if user is tried to be unpaused but is not paused in the first place
                      uint256 internal constant AdminModule__UserNotPaused = 10008;
                      /// @notice thrown if user is not defined yet: Governance didn't yet set any config for this user on a particular token
                      uint256 internal constant AdminModule__UserNotDefined = 10009;
                      /// @notice thrown if a token is configured in an invalid order:  1. Set rate config for token 2. Set token config 3. allow any user.
                      uint256 internal constant AdminModule__InvalidConfigOrder = 10010;
                      /// @notice thrown if revenue is collected when revenue collector address is not set
                      uint256 internal constant AdminModule__RevenueCollectorNotSet = 10011;
                      /// @notice all ValueOverflow errors below are thrown if a certain input param overflows the allowed storage size
                      uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_ZERO = 10012;
                      uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_KINK = 10013;
                      uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_MAX = 10014;
                      uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_KINK1 = 10015;
                      uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_KINK2 = 10016;
                      uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_MAX_V2 = 10017;
                      uint256 internal constant AdminModule__ValueOverflow__FEE = 10018;
                      uint256 internal constant AdminModule__ValueOverflow__THRESHOLD = 10019;
                      uint256 internal constant AdminModule__ValueOverflow__EXPAND_PERCENT = 10020;
                      uint256 internal constant AdminModule__ValueOverflow__EXPAND_DURATION = 10021;
                      uint256 internal constant AdminModule__ValueOverflow__EXPAND_PERCENT_BORROW = 10022;
                      uint256 internal constant AdminModule__ValueOverflow__EXPAND_DURATION_BORROW = 10023;
                      uint256 internal constant AdminModule__ValueOverflow__EXCHANGE_PRICES = 10024;
                      uint256 internal constant AdminModule__ValueOverflow__UTILIZATION = 10025;
                      /// @notice thrown when an address is not a contract
                      uint256 internal constant AdminModule__AddressNotAContract = 10026;
                      uint256 internal constant AdminModule__ValueOverflow__MAX_UTILIZATION = 10027;
                      /// @notice thrown if a token that is being listed has not between 6 and 18 decimals
                      uint256 internal constant AdminModule__TokenInvalidDecimalsRange = 10028;
                      /***********************************|
                      |          User Module              | 
                      |__________________________________*/
                      /// @notice thrown when user operations are paused for an interacted token
                      uint256 internal constant UserModule__UserNotDefined = 11001;
                      /// @notice thrown when user operations are paused for an interacted token
                      uint256 internal constant UserModule__UserPaused = 11002;
                      /// @notice thrown when user's try to withdraw below withdrawal limit
                      uint256 internal constant UserModule__WithdrawalLimitReached = 11003;
                      /// @notice thrown when user's try to borrow above borrow limit
                      uint256 internal constant UserModule__BorrowLimitReached = 11004;
                      /// @notice thrown when user sent supply/withdraw and borrow/payback both as 0
                      uint256 internal constant UserModule__OperateAmountsZero = 11005;
                      /// @notice thrown when user sent supply/withdraw or borrow/payback both as bigger than 2**128
                      uint256 internal constant UserModule__OperateAmountOutOfBounds = 11006;
                      /// @notice thrown when the operate amount for supply / withdraw / borrow / payback is below the minimum amount
                      /// that would cause a storage difference after BigMath & rounding imprecision. Extremely unlikely to ever happen
                      /// for all normal use-cases.
                      uint256 internal constant UserModule__OperateAmountInsufficient = 11007;
                      /// @notice thrown when withdraw or borrow is executed but withdrawTo or borrowTo is the zero address
                      uint256 internal constant UserModule__ReceiverNotDefined = 11008;
                      /// @notice thrown when user did send excess or insufficient amount (beyond rounding issues)
                      uint256 internal constant UserModule__TransferAmountOutOfBounds = 11009;
                      /// @notice thrown when user sent msg.value along for an operation not for the native token
                      uint256 internal constant UserModule__MsgValueForNonNativeToken = 11010;
                      /// @notice thrown when a borrow operation is done when utilization is above 100%
                      uint256 internal constant UserModule__MaxUtilizationReached = 11011;
                      /// @notice all ValueOverflow errors below are thrown if a certain input param or calc result overflows the allowed storage size
                      uint256 internal constant UserModule__ValueOverflow__EXCHANGE_PRICES = 11012;
                      uint256 internal constant UserModule__ValueOverflow__UTILIZATION = 11013;
                      uint256 internal constant UserModule__ValueOverflow__TOTAL_SUPPLY = 11014;
                      uint256 internal constant UserModule__ValueOverflow__TOTAL_BORROW = 11015;
                      /// @notice thrown when SKIP_TRANSFERS is set but the input params are invalid for skipping transfers
                      uint256 internal constant UserModule__SkipTransfersInvalid = 11016;
                      /***********************************|
                      |         LiquidityHelpers          | 
                      |__________________________________*/
                      /// @notice thrown when a reentrancy happens
                      uint256 internal constant LiquidityHelpers__Reentrancy = 12001;
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  contract Events {
                      /// @notice emitted on any `operate()` execution: deposit / supply / withdraw / borrow.
                      /// includes info related to the executed operation, new total amounts (packed uint256 of BigMath numbers as in storage)
                      /// and exchange prices (packed uint256 as in storage).
                      /// @param user protocol that triggered this operation (e.g. via an fToken or via Vault protocol)
                      /// @param token token address for which this operation was executed
                      /// @param supplyAmount supply amount for the operation. if >0 then a deposit happened, if <0 then a withdrawal happened.
                      ///                     if 0 then nothing.
                      /// @param borrowAmount borrow amount for the operation. if >0 then a borrow happened, if <0 then a payback happened.
                      ///                     if 0 then nothing.
                      /// @param withdrawTo   address that funds where withdrawn to (if supplyAmount <0)
                      /// @param borrowTo     address that funds where borrowed to (if borrowAmount >0)
                      /// @param totalAmounts updated total amounts, stacked uint256 as written to storage:
                      /// First  64 bits =>   0- 63 => total supply with interest in raw (totalSupply = totalSupplyRaw * supplyExchangePrice); BigMath: 56 | 8
                      /// Next   64 bits =>  64-127 => total interest free supply in normal token amount (totalSupply = totalSupply); BigMath: 56 | 8
                      /// Next   64 bits => 128-191 => total borrow with interest in raw (totalBorrow = totalBorrowRaw * borrowExchangePrice); BigMath: 56 | 8
                      /// Next   64 bits => 192-255 => total interest free borrow in normal token amount (totalBorrow = totalBorrow); BigMath: 56 | 8
                      /// @param exchangePricesAndConfig updated exchange prices and configs storage slot. Contains updated supply & borrow exchange price:
                      /// First 16 bits =>   0- 15 => borrow rate (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
                      /// Next  14 bits =>  16- 29 => fee on interest from borrowers to lenders (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
                      /// Next  14 bits =>  30- 43 => last stored utilization (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      /// Next  14 bits =>  44- 57 => update on storage threshold (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
                      /// Next  33 bits =>  58- 90 => last update timestamp (enough until 16 March 2242 -> max value 8589934591)
                      /// Next  64 bits =>  91-154 => supply exchange price (1e12 -> max value 18_446_744,073709551615)
                      /// Next  64 bits => 155-218 => borrow exchange price (1e12 -> max value 18_446_744,073709551615)
                      /// Next   1 bit  => 219-219 => if 0 then ratio is supplyInterestFree / supplyWithInterest else ratio is supplyWithInterest / supplyInterestFree
                      /// Next  14 bits => 220-233 => supplyRatio: supplyInterestFree / supplyWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      /// Next   1 bit  => 234-234 => if 0 then ratio is borrowInterestFree / borrowWithInterest else ratio is borrowWithInterest / borrowInterestFree
                      /// Next  14 bits => 235-248 => borrowRatio: borrowInterestFree / borrowWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
                      event LogOperate(
                          address indexed user,
                          address indexed token,
                          int256 supplyAmount,
                          int256 borrowAmount,
                          address withdrawTo,
                          address borrowTo,
                          uint256 totalAmounts,
                          uint256 exchangePricesAndConfig
                      );
                  }
                  // SPDX-License-Identifier: BUSL-1.1
                  pragma solidity 0.8.21;
                  import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
                  import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
                  import { BigMathMinified } from "../../libraries/bigMathMinified.sol";
                  import { LiquidityCalcs } from "../../libraries/liquidityCalcs.sol";
                  import { LiquiditySlotsLink } from "../../libraries/liquiditySlotsLink.sol";
                  import { SafeTransfer } from "../../libraries/safeTransfer.sol";
                  import { CommonHelpers } from "../common/helpers.sol";
                  import { Events } from "./events.sol";
                  import { ErrorTypes } from "../errorTypes.sol";
                  import { Error } from "../error.sol";
                  interface IProtocol {
                      function liquidityCallback(address token_, uint256 amount_, bytes calldata data_) external;
                  }
                  abstract contract CoreInternals is Error, CommonHelpers, Events {
                      using BigMathMinified for uint256;
                      /// @dev supply or withdraw for both with interest & interest free.
                      /// positive `amount_` is deposit, negative `amount_` is withdraw.
                      function _supplyOrWithdraw(
                          address token_,
                          int256 amount_,
                          uint256 supplyExchangePrice_
                      ) internal returns (int256 newSupplyInterestRaw_, int256 newSupplyInterestFree_) {
                          uint256 userSupplyData_ = _userSupplyData[msg.sender][token_];
                          if (userSupplyData_ == 0) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__UserNotDefined);
                          }
                          if ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_IS_PAUSED) & 1 == 1) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__UserPaused);
                          }
                          // extract user supply amount
                          uint256 userSupply_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64;
                          userSupply_ = (userSupply_ >> DEFAULT_EXPONENT_SIZE) << (userSupply_ & DEFAULT_EXPONENT_MASK);
                          // calculate current, updated (expanded etc.) withdrawal limit
                          uint256 newWithdrawalLimit_ = LiquidityCalcs.calcWithdrawalLimitBeforeOperate(userSupplyData_, userSupply_);
                          // calculate updated user supply amount
                          if (userSupplyData_ & 1 == 1) {
                              // mode: with interest
                              if (amount_ > 0) {
                                  // convert amount from normal to raw (divide by exchange price) -> round down for deposit
                                  newSupplyInterestRaw_ = (amount_ * int256(EXCHANGE_PRICES_PRECISION)) / int256(supplyExchangePrice_);
                                  userSupply_ = userSupply_ + uint256(newSupplyInterestRaw_);
                              } else {
                                  // convert amount from normal to raw (divide by exchange price) -> round up for withdraw
                                  newSupplyInterestRaw_ = -int256(
                                      FixedPointMathLib.mulDivUp(uint256(-amount_), EXCHANGE_PRICES_PRECISION, supplyExchangePrice_)
                                  );
                                  // if withdrawal is more than user's supply then solidity will throw here
                                  userSupply_ = userSupply_ - uint256(-newSupplyInterestRaw_);
                              }
                          } else {
                              // mode: without interest
                              newSupplyInterestFree_ = amount_;
                              if (newSupplyInterestFree_ > 0) {
                                  userSupply_ = userSupply_ + uint256(newSupplyInterestFree_);
                              } else {
                                  // if withdrawal is more than user's supply then solidity will throw here
                                  userSupply_ = userSupply_ - uint256(-newSupplyInterestFree_);
                              }
                          }
                          if (amount_ < 0 && userSupply_ < newWithdrawalLimit_) {
                              // if withdraw, then check the user supply after withdrawal is above withdrawal limit
                              revert FluidLiquidityError(ErrorTypes.UserModule__WithdrawalLimitReached);
                          }
                          // calculate withdrawal limit to store as previous withdrawal limit in storage
                          newWithdrawalLimit_ = LiquidityCalcs.calcWithdrawalLimitAfterOperate(
                              userSupplyData_,
                              userSupply_,
                              newWithdrawalLimit_
                          );
                          // Converting user's supply into BigNumber
                          userSupply_ = userSupply_.toBigNumber(
                              DEFAULT_COEFFICIENT_SIZE,
                              DEFAULT_EXPONENT_SIZE,
                              BigMathMinified.ROUND_DOWN
                          );
                          if (((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64) == userSupply_) {
                              // make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
                              // is present then rounding will be in the right direction to avoid any potential manipulation.
                              revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
                          }
                          // Converting withdrawal limit into BigNumber
                          newWithdrawalLimit_ = newWithdrawalLimit_.toBigNumber(
                              DEFAULT_COEFFICIENT_SIZE,
                              DEFAULT_EXPONENT_SIZE,
                              BigMathMinified.ROUND_DOWN
                          );
                          // Updating on storage
                          _userSupplyData[msg.sender][token_] =
                              // mask to update bits 1-161 (supply amount, withdrawal limit, timestamp)
                              (userSupplyData_ & 0xfffffffffffffffffffffffc0000000000000000000000000000000000000001) |
                              (userSupply_ << LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) | // converted to BigNumber can not overflow
                              (newWithdrawalLimit_ << LiquiditySlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) | // converted to BigNumber can not overflow
                              (block.timestamp << LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP);
                      }
                      /// @dev borrow or payback for both with interest & interest free.
                      /// positive `amount_` is borrow, negative `amount_` is payback.
                      function _borrowOrPayback(
                          address token_,
                          int256 amount_,
                          uint256 borrowExchangePrice_
                      ) internal returns (int256 newBorrowInterestRaw_, int256 newBorrowInterestFree_) {
                          uint256 userBorrowData_ = _userBorrowData[msg.sender][token_];
                          if (userBorrowData_ == 0) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__UserNotDefined);
                          }
                          if ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_IS_PAUSED) & 1 == 1) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__UserPaused);
                          }
                          // extract user borrow amount
                          uint256 userBorrow_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64;
                          userBorrow_ = (userBorrow_ >> DEFAULT_EXPONENT_SIZE) << (userBorrow_ & DEFAULT_EXPONENT_MASK);
                          // calculate current, updated (expanded etc.) borrow limit
                          uint256 newBorrowLimit_ = LiquidityCalcs.calcBorrowLimitBeforeOperate(userBorrowData_, userBorrow_);
                          // calculate updated user borrow amount
                          if (userBorrowData_ & 1 == 1) {
                              // with interest
                              if (amount_ > 0) {
                                  // convert amount normal to raw (divide by exchange price) -> round up for borrow
                                  newBorrowInterestRaw_ = int256(
                                      FixedPointMathLib.mulDivUp(uint256(amount_), EXCHANGE_PRICES_PRECISION, borrowExchangePrice_)
                                  );
                                  userBorrow_ = userBorrow_ + uint256(newBorrowInterestRaw_);
                              } else {
                                  // convert amount from normal to raw (divide by exchange price) -> round down for payback
                                  newBorrowInterestRaw_ = (amount_ * int256(EXCHANGE_PRICES_PRECISION)) / int256(borrowExchangePrice_);
                                  userBorrow_ = userBorrow_ - uint256(-newBorrowInterestRaw_);
                              }
                          } else {
                              // without interest
                              newBorrowInterestFree_ = amount_;
                              if (newBorrowInterestFree_ > 0) {
                                  // borrowing
                                  userBorrow_ = userBorrow_ + uint256(newBorrowInterestFree_);
                              } else {
                                  // payback
                                  userBorrow_ = userBorrow_ - uint256(-newBorrowInterestFree_);
                              }
                          }
                          if (amount_ > 0 && userBorrow_ > newBorrowLimit_) {
                              // if borrow, then check the user borrow amount after borrowing is below borrow limit
                              revert FluidLiquidityError(ErrorTypes.UserModule__BorrowLimitReached);
                          }
                          // calculate borrow limit to store as previous borrow limit in storage
                          newBorrowLimit_ = LiquidityCalcs.calcBorrowLimitAfterOperate(userBorrowData_, userBorrow_, newBorrowLimit_);
                          // Converting user's borrowings into bignumber
                          userBorrow_ = userBorrow_.toBigNumber(
                              DEFAULT_COEFFICIENT_SIZE,
                              DEFAULT_EXPONENT_SIZE,
                              BigMathMinified.ROUND_UP
                          );
                          if (((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64) == userBorrow_) {
                              // make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
                              // is present then rounding will be in the right direction to avoid any potential manipulation.
                              revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
                          }
                          // Converting borrow limit into bignumber
                          newBorrowLimit_ = newBorrowLimit_.toBigNumber(
                              DEFAULT_COEFFICIENT_SIZE,
                              DEFAULT_EXPONENT_SIZE,
                              BigMathMinified.ROUND_DOWN
                          );
                          // Updating on storage
                          _userBorrowData[msg.sender][token_] =
                              // mask to update bits 1-161 (borrow amount, borrow limit, timestamp)
                              (userBorrowData_ & 0xfffffffffffffffffffffffc0000000000000000000000000000000000000001) |
                              (userBorrow_ << LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) | // converted to BigNumber can not overflow
                              (newBorrowLimit_ << LiquiditySlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) | // converted to BigNumber can not overflow
                              (block.timestamp << LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP);
                      }
                      /// @dev checks if `supplyAmount_` & `borrowAmount_` amounts transfers can be skipped (DEX-protocol use-case).
                      /// -   Requirements:
                      /// -  ` callbackData_` MUST be encoded so that "from" address is the last 20 bytes in the last 32 bytes slot,
                      ///     also for native token operations where liquidityCallback is not triggered!
                      ///     from address must come at last position if there is more data. I.e. encode like:
                      ///     abi.encode(otherVar1, otherVar2, FROM_ADDRESS). Note dynamic types used with abi.encode come at the end
                      ///     so if dynamic types are needed, you must use abi.encodePacked to ensure the from address is at the end.
                      /// -   this "from" address must match withdrawTo_ or borrowTo_ and must be == `msg.sender`
                      /// -   `callbackData_` must in addition to the from address as described above include bytes32 SKIP_TRANSFERS
                      ///     in the slot before (bytes 32 to 63)
                      /// -   `msg.value` must be 0.
                      /// -   Amounts must be either:
                      ///     -  supply(+) == borrow(+), withdraw(-) == payback(-).
                      ///     -  Liquidity must be on the winning side (deposit < borrow OR payback < withdraw).
                      function _isInOutBalancedOut(
                          int256 supplyAmount_,
                          int256 borrowAmount_,
                          address withdrawTo_,
                          address borrowTo_,
                          bytes memory callbackData_
                      ) internal view returns (bool) {
                          // callbackData_ being at least > 63 in length is already verified before calling this method.
                          // 1. SKIP_TRANSFERS must be set in callbackData_ 32 bytes before last 32 bytes
                          bytes32 skipTransfers_;
                          assembly {
                              skipTransfers_ := mload(
                                  add(
                                      // add padding for length as present for dynamic arrays in memory
                                      add(callbackData_, 32),
                                      // Load from memory offset of 2 slots (64 bytes): 1 slot: bytes32 skipTransfers_ + 2 slot: address inFrom_
                                      sub(mload(callbackData_), 64)
                                  )
                              )
                          }
                          if (skipTransfers_ != SKIP_TRANSFERS) {
                              return false;
                          }
                          // after here, if invalid, protocol intended to skip transfers, but something is invalid. so we don't just
                          // NOT skip transfers, we actually revert because there must be something wrong on protocol side.
                          // 2. amounts must be
                          // a) equal: supply(+) == borrow(+), withdraw(-) == payback(-) OR
                          // b) Liquidity must be on the winning side.
                          // EITHER:
                          // deposit and borrow, both positive. there must be more borrow than deposit.
                          // so supply amount must be less, e.g. 80 deposit and 100 borrow.
                          // OR:
                          // withdraw and payback, both negative. there must be more withdraw than payback.
                          // so supplyAmount must be less (e.g. -100 withdraw and -80 payback )
                          if (
                              msg.value != 0 || // no msg.value should be sent along when trying to skip transfers.
                              supplyAmount_ == 0 ||
                              borrowAmount_ == 0 || // it must be a 2 actions operation, not just e.g. only deposit or only payback.
                              supplyAmount_ > borrowAmount_ // allow case a) and b): supplyAmount must be <=
                          ) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__SkipTransfersInvalid);
                          }
                          // 3. inFrom_ must be in last 32 bytes and must match receiver
                          address inFrom_;
                          assembly {
                              inFrom_ := mload(
                                  add(
                                      // add padding for length as present for dynamic arrays in memory
                                      add(callbackData_, 32),
                                      // assembly expects address with leading zeros / left padded so need to use 32 as length here
                                      sub(mload(callbackData_), 32)
                                  )
                              )
                          }
                          if (supplyAmount_ > 0) {
                              // deposit and borrow
                              if (!(inFrom_ == borrowTo_ && inFrom_ == msg.sender)) {
                                  revert FluidLiquidityError(ErrorTypes.UserModule__SkipTransfersInvalid);
                              }
                          } else {
                              // withdraw and payback
                              if (!(inFrom_ == withdrawTo_ && inFrom_ == msg.sender)) {
                                  revert FluidLiquidityError(ErrorTypes.UserModule__SkipTransfersInvalid);
                              }
                          }
                          return true;
                      }
                  }
                  interface IZtakingPool {
                      ///@notice Stake a specified amount of a particular supported token into the Ztaking Pool
                      ///@param _token The token to deposit/stake in the Ztaking Pool
                      ///@param _for The user to deposit/stake on behalf of
                      ///@param _amount The amount of token to deposit/stake into the Ztaking Pool
                      function depositFor(address _token, address _for, uint256 _amount) external;
                      ///@notice Withdraw a specified amount of a particular supported token previously staked into the Ztaking Pool
                      ///@param _token The token to withdraw from the Ztaking Pool
                      ///@param _amount The amount of token to withdraw from the Ztaking Pool
                      function withdraw(address _token, uint256 _amount) external;
                  }
                  /// @title  Fluid Liquidity UserModule
                  /// @notice Fluid Liquidity public facing endpoint logic contract that implements the `operate()` method.
                  ///         operate can be used to deposit, withdraw, borrow & payback funds, given that they have the necessary
                  ///         user config allowance. Interacting users must be allowed via the Fluid Liquidity AdminModule first.
                  ///         Intended users are thus allow-listed protocols, e.g. the Lending protocol (fTokens), Vault protocol etc.
                  /// @dev For view methods / accessing data, use the "LiquidityResolver" periphery contract.
                  contract FluidLiquidityUserModule is CoreInternals {
                      using BigMathMinified for uint256;
                      address private constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee;
                      address private constant WEETHS = 0x917ceE801a67f933F2e6b33fC0cD1ED2d5909D88;
                      IZtakingPool private constant ZIRCUIT = IZtakingPool(0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6);
                      /// @dev struct for vars used in operate() that would otherwise cause a Stack too deep error
                      struct OperateMemoryVars {
                          bool skipTransfers;
                          uint256 supplyExchangePrice;
                          uint256 borrowExchangePrice;
                          uint256 supplyRawInterest;
                          uint256 supplyInterestFree;
                          uint256 borrowRawInterest;
                          uint256 borrowInterestFree;
                          uint256 totalAmounts;
                          uint256 exchangePricesAndConfig;
                      }
                      /// @notice inheritdoc IFluidLiquidity
                      function operate(
                          address token_,
                          int256 supplyAmount_,
                          int256 borrowAmount_,
                          address withdrawTo_,
                          address borrowTo_,
                          bytes calldata callbackData_
                      ) external payable reentrancy returns (uint256 memVar3_, uint256 memVar4_) {
                          if (supplyAmount_ == 0 && borrowAmount_ == 0) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountsZero);
                          }
                          if (
                              supplyAmount_ < type(int128).min ||
                              supplyAmount_ > type(int128).max ||
                              borrowAmount_ < type(int128).min ||
                              borrowAmount_ > type(int128).max
                          ) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountOutOfBounds);
                          }
                          if ((supplyAmount_ < 0 && withdrawTo_ == address(0)) || (borrowAmount_ > 0 && borrowTo_ == address(0))) {
                              revert FluidLiquidityError(ErrorTypes.UserModule__ReceiverNotDefined);
                          }
                          if (token_ != NATIVE_TOKEN_ADDRESS && msg.value > 0) {
                              // revert: there should not be msg.value if the token is not the native token
                              revert FluidLiquidityError(ErrorTypes.UserModule__MsgValueForNonNativeToken);
                          }
                          OperateMemoryVars memory o_;
                          // @dev temporary memory variables used as helper in between to avoid assigning new memory variables
                          uint256 memVar_;
                          // memVar2_ => operateAmountIn: deposit + payback
                          uint256 memVar2_ = uint256((supplyAmount_ > 0 ? supplyAmount_ : int256(0))) +
                              uint256((borrowAmount_ < 0 ? -borrowAmount_ : int256(0)));
                          // check if token transfers can be skipped. see `_isInOutBalancedOut` for details.
                          if (
                              callbackData_.length > 63 &&
                              _isInOutBalancedOut(supplyAmount_, borrowAmount_, withdrawTo_, borrowTo_, callbackData_)
                          ) {
                              memVar2_ = 0; // set to 0 to skip transfers IN
                              o_.skipTransfers = true; // set flag to true to skip transfers OUT
                          }
                          if (token_ == NATIVE_TOKEN_ADDRESS) {
                              unchecked {
                                  // check supply and payback amount is covered by available sent msg.value and
                                  // protection that msg.value is not unintentionally way more than actually used in operate()
                                  if (
                                      memVar2_ > msg.value ||
                                      msg.value > (memVar2_ * (FOUR_DECIMALS + MAX_INPUT_AMOUNT_EXCESS)) / FOUR_DECIMALS
                                  ) {
                                      revert FluidLiquidityError(ErrorTypes.UserModule__TransferAmountOutOfBounds);
                                  }
                              }
                              memVar2_ = 0; // set to 0 to skip transfers IN more gas efficient. No need for native token.
                          }
                          // if supply or payback or both -> transfer token amount from sender to here.
                          // for native token this is already covered by msg.value checks in operate(). memVar2_ is set to 0
                          // for same amounts in same operate(): supply(+) == borrow(+), withdraw(-) == payback(-). memVar2_ is set to 0
                          if (memVar2_ > 0) {
                              // memVar_ => initial token balance of this contract
                              memVar_ = IERC20(token_).balanceOf(address(this));
                              // trigger protocol to send token amount and pass callback data
                              IProtocol(msg.sender).liquidityCallback(token_, memVar2_, callbackData_);
                              // memVar_ => current token balance of this contract - initial balance
                              memVar_ = IERC20(token_).balanceOf(address(this)) - memVar_;
                              unchecked {
                                  if (
                                      memVar_ < memVar2_ ||
                                      memVar_ > (memVar2_ * (FOUR_DECIMALS + MAX_INPUT_AMOUNT_EXCESS)) / FOUR_DECIMALS
                                  ) {
                                      // revert if protocol did not send enough to cover supply / payback
                                      // or if protocol sent more than expected, with 1% tolerance for any potential rounding issues (and for DEX revenue cut)
                                      revert FluidLiquidityError(ErrorTypes.UserModule__TransferAmountOutOfBounds);
                                  }
                              }
                              // ---------- temporary code start -----------------------
                              // temporary addition for weETH & weETHs: if token is weETH or weETHs -> deposit to Zircuit
                              if (token_ == WEETH) {
                                  if (IERC20(WEETH).allowance(address(this), address(ZIRCUIT)) > 0) {
                                      ZIRCUIT.depositFor(WEETH, address(this), memVar_);
                                  }
                              } else if (token_ == WEETHS) {
                                  if ((IERC20(WEETHS).allowance(address(this), address(ZIRCUIT)) > 0)) {
                                      ZIRCUIT.depositFor(WEETHS, address(this), memVar_);
                                  }
                              }
                              // temporary code also includes: WEETH, WEETHS & ZIRCUIT constant, IZtakingPool interface
                              // ---------- temporary code end -----------------------
                          }
                          o_.exchangePricesAndConfig = _exchangePricesAndConfig[token_];
                          // calculate updated exchange prices
                          (o_.supplyExchangePrice, o_.borrowExchangePrice) = LiquidityCalcs.calcExchangePrices(
                              o_.exchangePricesAndConfig
                          );
                          // Extract total supply / borrow amounts for the token
                          o_.totalAmounts = _totalAmounts[token_];
                          memVar_ = o_.totalAmounts & X64;
                          o_.supplyRawInterest = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
                          memVar_ = (o_.totalAmounts >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
                          o_.supplyInterestFree = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
                          memVar_ = (o_.totalAmounts >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
                          o_.borrowRawInterest = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
                          // no & mask needed for borrow interest free as it occupies the last bits in the storage slot
                          memVar_ = (o_.totalAmounts >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
                          o_.borrowInterestFree = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
                          if (supplyAmount_ != 0) {
                              // execute supply or withdraw and update total amounts
                              {
                                  uint256 totalAmountsBefore_ = o_.totalAmounts;
                                  (int256 newSupplyInterestRaw_, int256 newSupplyInterestFree_) = _supplyOrWithdraw(
                                      token_,
                                      supplyAmount_,
                                      o_.supplyExchangePrice
                                  );
                                  // update total amounts. this is done here so that values are only written to storage once
                                  // if a borrow / payback also happens in the same `operate()` call
                                  if (newSupplyInterestFree_ == 0) {
                                      // Note newSupplyInterestFree_ can ONLY be 0 if mode is with interest,
                                      // easy to check as that variable is NOT the result of a dvision etc.
                                      // supply or withdraw with interest -> raw amount
                                      if (newSupplyInterestRaw_ > 0) {
                                          o_.supplyRawInterest += uint256(newSupplyInterestRaw_);
                                      } else {
                                          unchecked {
                                              o_.supplyRawInterest = o_.supplyRawInterest > uint256(-newSupplyInterestRaw_)
                                                  ? o_.supplyRawInterest - uint256(-newSupplyInterestRaw_)
                                                  : 0; // withdraw amount is > total supply -> withdraw total supply down to 0
                                              // Note no risk here as if the user withdraws more than supplied it would revert already
                                              // earlier. Total amounts can end up < sum of user amounts because of rounding
                                          }
                                      }
                                      // Note check for revert {UserModule}__ValueOverflow__TOTAL_SUPPLY is further down when we anyway
                                      // calculate the normal amount from raw
                                      // Converting the updated total amount into big number for storage
                                      memVar_ = o_.supplyRawInterest.toBigNumber(
                                          DEFAULT_COEFFICIENT_SIZE,
                                          DEFAULT_EXPONENT_SIZE,
                                          BigMathMinified.ROUND_DOWN
                                      );
                                      // update total supply with interest at total amounts in storage (only update changed values)
                                      o_.totalAmounts =
                                          // mask to update bits 0-63
                                          (o_.totalAmounts & 0xffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000) |
                                          memVar_; // converted to BigNumber can not overflow
                                  } else {
                                      // supply or withdraw interest free -> normal amount
                                      if (newSupplyInterestFree_ > 0) {
                                          o_.supplyInterestFree += uint256(newSupplyInterestFree_);
                                      } else {
                                          unchecked {
                                              o_.supplyInterestFree = o_.supplyInterestFree > uint256(-newSupplyInterestFree_)
                                                  ? o_.supplyInterestFree - uint256(-newSupplyInterestFree_)
                                                  : 0; // withdraw amount is > total supply -> withdraw total supply down to 0
                                              // Note no risk here as if the user withdraws more than supplied it would revert already
                                              // earlier. Total amounts can end up < sum of user amounts because of rounding
                                          }
                                      }
                                      if (o_.supplyInterestFree > MAX_TOKEN_AMOUNT_CAP) {
                                          // only withdrawals allowed if total supply interest free reaches MAX_TOKEN_AMOUNT_CAP
                                          revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_SUPPLY);
                                      }
                                      // Converting the updated total amount into big number for storage
                                      memVar_ = o_.supplyInterestFree.toBigNumber(
                                          DEFAULT_COEFFICIENT_SIZE,
                                          DEFAULT_EXPONENT_SIZE,
                                          BigMathMinified.ROUND_DOWN
                                      );
                                      // update total supply interest free at total amounts in storage (only update changed values)
                                      o_.totalAmounts =
                                          // mask to update bits 64-127
                                          (o_.totalAmounts & 0xffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff) |
                                          (memVar_ << LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE); // converted to BigNumber can not overflow
                                  }
                                  if (totalAmountsBefore_ == o_.totalAmounts) {
                                      // make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
                                      // is present then rounding will be in the right direction to avoid any potential manipulation.
                                      revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
                                  }
                              }
                          }
                          if (borrowAmount_ != 0) {
                              // execute borrow or payback and update total amounts
                              {
                                  uint256 totalAmountsBefore_ = o_.totalAmounts;
                                  (int256 newBorrowInterestRaw_, int256 newBorrowInterestFree_) = _borrowOrPayback(
                                      token_,
                                      borrowAmount_,
                                      o_.borrowExchangePrice
                                  );
                                  // update total amounts. this is done here so that values are only written to storage once
                                  // if a supply / withdraw also happens in the same `operate()` call
                                  if (newBorrowInterestFree_ == 0) {
                                      // Note newBorrowInterestFree_ can ONLY be 0 if mode is with interest,
                                      // easy to check as that variable is NOT the result of a dvision etc.
                                      // borrow or payback with interest -> raw amount
                                      if (newBorrowInterestRaw_ > 0) {
                                          o_.borrowRawInterest += uint256(newBorrowInterestRaw_);
                                      } else {
                                          unchecked {
                                              o_.borrowRawInterest = o_.borrowRawInterest > uint256(-newBorrowInterestRaw_)
                                                  ? o_.borrowRawInterest - uint256(-newBorrowInterestRaw_)
                                                  : 0; // payback amount is > total borrow -> payback total borrow down to 0
                                          }
                                      }
                                      // Note check for revert UserModule__ValueOverflow__TOTAL_BORROW is further down when we anyway
                                      // calculate the normal amount from raw
                                      // Converting the updated total amount into big number for storage
                                      memVar_ = o_.borrowRawInterest.toBigNumber(
                                          DEFAULT_COEFFICIENT_SIZE,
                                          DEFAULT_EXPONENT_SIZE,
                                          BigMathMinified.ROUND_UP
                                      );
                                      // update total borrow with interest at total amounts in storage (only update changed values)
                                      o_.totalAmounts =
                                          // mask to update bits 128-191
                                          (o_.totalAmounts & 0xffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff) |
                                          (memVar_ << LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST); // converted to BigNumber can not overflow
                                  } else {
                                      // borrow or payback interest free -> normal amount
                                      if (newBorrowInterestFree_ > 0) {
                                          o_.borrowInterestFree += uint256(newBorrowInterestFree_);
                                      } else {
                                          unchecked {
                                              o_.borrowInterestFree = o_.borrowInterestFree > uint256(-newBorrowInterestFree_)
                                                  ? o_.borrowInterestFree - uint256(-newBorrowInterestFree_)
                                                  : 0; // payback amount is > total borrow -> payback total borrow down to 0
                                          }
                                      }
                                      if (o_.borrowInterestFree > MAX_TOKEN_AMOUNT_CAP) {
                                          // only payback allowed if total borrow interest free reaches MAX_TOKEN_AMOUNT_CAP
                                          revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_BORROW);
                                      }
                                      // Converting the updated total amount into big number for storage
                                      memVar_ = o_.borrowInterestFree.toBigNumber(
                                          DEFAULT_COEFFICIENT_SIZE,
                                          DEFAULT_EXPONENT_SIZE,
                                          BigMathMinified.ROUND_UP
                                      );
                                      // update total borrow interest free at total amounts in storage (only update changed values)
                                      o_.totalAmounts =
                                          // mask to update bits 192-255
                                          (o_.totalAmounts & 0x0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff) |
                                          (memVar_ << LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE); // converted to BigNumber can not overflow
                                  }
                                  if (totalAmountsBefore_ == o_.totalAmounts) {
                                      // make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
                                      // is present then rounding will be in the right direction to avoid any potential manipulation.
                                      revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
                                  }
                              }
                          }
                          // Updating total amounts on storage
                          _totalAmounts[token_] = o_.totalAmounts;
                          {
                              // update exchange prices / utilization / ratios
                              // exchangePricesAndConfig is only written to storage if either utilization, supplyRatio or borrowRatio
                              // change is above the required storageUpdateThreshold config value or if the last write was > 1 day ago.
                              // 1. calculate new supply ratio, borrow ratio & utilization.
                              // 2. check if last storage write was > 1 day ago.
                              // 3. If false -> check if utilization is above update threshold
                              // 4. If false -> check if supply ratio is above update threshold
                              // 5. If false -> check if borrow ratio is above update threshold
                              // 6. If any true, then update on storage
                              // ########## calculating supply ratio ##########
                              // supplyWithInterest in normal amount
                              memVar3_ = ((o_.supplyRawInterest * o_.supplyExchangePrice) / EXCHANGE_PRICES_PRECISION);
                              if (memVar3_ > MAX_TOKEN_AMOUNT_CAP && supplyAmount_ > 0) {
                                  // only withdrawals allowed if total supply raw reaches MAX_TOKEN_AMOUNT_CAP
                                  revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_SUPPLY);
                              }
                              // memVar_ => total supply. set here so supplyWithInterest (memVar3_) is only calculated once. For utilization
                              memVar_ = o_.supplyInterestFree + memVar3_;
                              if (memVar3_ > o_.supplyInterestFree) {
                                  // memVar3_ is ratio with 1 bit as 0 as supply interest raw is bigger
                                  memVar3_ = ((o_.supplyInterestFree * FOUR_DECIMALS) / memVar3_) << 1;
                                  // because of checking to divide by bigger amount, ratio can never be > 100%
                              } else if (memVar3_ < o_.supplyInterestFree) {
                                  // memVar3_ is ratio with 1 bit as 1 as supply interest free is bigger
                                  memVar3_ = (((memVar3_ * FOUR_DECIMALS) / o_.supplyInterestFree) << 1) | 1;
                                  // because of checking to divide by bigger amount, ratio can never be > 100%
                              } else if (memVar_ > 0) {
                                  // supplies match exactly (memVar3_  == o_.supplyInterestFree) and total supplies are not 0
                                  // -> set ratio to 1 (with first bit set to 0, doesn't matter)
                                  memVar3_ = FOUR_DECIMALS << 1;
                              } // else if total supply = 0, memVar3_ (supplyRatio) is already 0.
                              // ########## calculating borrow ratio ##########
                              // borrowWithInterest in normal amount
                              memVar4_ = ((o_.borrowRawInterest * o_.borrowExchangePrice) / EXCHANGE_PRICES_PRECISION);
                              if (memVar4_ > MAX_TOKEN_AMOUNT_CAP && borrowAmount_ > 0) {
                                  // only payback allowed if total borrow raw reaches MAX_TOKEN_AMOUNT_CAP
                                  revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_BORROW);
                              }
                              // memVar2_ => total borrow. set here so borrowWithInterest (memVar4_) is only calculated once. For utilization
                              memVar2_ = o_.borrowInterestFree + memVar4_;
                              if (memVar4_ > o_.borrowInterestFree) {
                                  // memVar4_ is ratio with 1 bit as 0 as borrow interest raw is bigger
                                  memVar4_ = ((o_.borrowInterestFree * FOUR_DECIMALS) / memVar4_) << 1;
                                  // because of checking to divide by bigger amount, ratio can never be > 100%
                              } else if (memVar4_ < o_.borrowInterestFree) {
                                  // memVar4_ is ratio with 1 bit as 1 as borrow interest free is bigger
                                  memVar4_ = (((memVar4_ * FOUR_DECIMALS) / o_.borrowInterestFree) << 1) | 1;
                                  // because of checking to divide by bigger amount, ratio can never be > 100%
                              } else if (memVar2_ > 0) {
                                  // borrows match exactly (memVar4_  == o_.borrowInterestFree) and total borrows are not 0
                                  // -> set ratio to 1 (with first bit set to 0, doesn't matter)
                                  memVar4_ = FOUR_DECIMALS << 1;
                              } // else if total borrow = 0, memVar4_ (borrowRatio) is already 0.
                              // calculate utilization. If there is no supply, utilization must be 0 (avoid division by 0)
                              uint256 utilization_;
                              if (memVar_ > 0) {
                                  utilization_ = ((memVar2_ * FOUR_DECIMALS) / memVar_);
                                  // for borrow operations, ensure max utilization is not reached
                                  if (borrowAmount_ > 0) {
                                      // memVar_ => max utilization
                                      // if any max utilization other than 100% is set, the flag usesConfigs2 in
                                      // exchangePricesAndConfig is 1. (optimized to avoid SLOAD if not needed).
                                      memVar_ = (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_USES_CONFIGS2) &
                                          1 ==
                                          1
                                          ? (_configs2[token_] & X14) // read configured max utilization
                                          : FOUR_DECIMALS; // default max utilization = 100%
                                      if (utilization_ > memVar_) {
                                          revert FluidLiquidityError(ErrorTypes.UserModule__MaxUtilizationReached);
                                      }
                                  }
                              }
                              // check if time difference is big enough (> 1 day)
                              unchecked {
                                  if (
                                      block.timestamp >
                                      // extract last update timestamp + 1 day
                                      (((o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) & X33) +
                                          FORCE_STORAGE_WRITE_AFTER_TIME)
                                  ) {
                                      memVar_ = 1; // set write to storage flag
                                  } else {
                                      memVar_ = 0;
                                  }
                              }
                              if (memVar_ == 0) {
                                  // time difference is not big enough to cause storage write -> check utilization
                                  // memVar_ => extract last utilization
                                  memVar_ = (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14;
                                  // memVar2_ => storage update threshold in percent
                                  memVar2_ =
                                      (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD) &
                                      X14;
                                  unchecked {
                                      // set memVar_ to 1 if current utilization to previous utilization difference is > update storage threshold
                                      memVar_ = (utilization_ > memVar_ ? utilization_ - memVar_ : memVar_ - utilization_) > memVar2_
                                          ? 1
                                          : 0;
                                      if (memVar_ == 0) {
                                          // utilization & time difference is not big enough -> check supplyRatio difference
                                          // memVar_ => extract last supplyRatio
                                          memVar_ =
                                              (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) &
                                              X15;
                                          // set memVar_ to 1 if current supplyRatio to previous supplyRatio difference is > update storage threshold
                                          if ((memVar_ & 1) == (memVar3_ & 1)) {
                                              memVar_ = memVar_ >> 1;
                                              memVar_ = (
                                                  (memVar3_ >> 1) > memVar_ ? (memVar3_ >> 1) - memVar_ : memVar_ - (memVar3_ >> 1)
                                              ) > memVar2_
                                                  ? 1
                                                  : 0; // memVar3_ = supplyRatio, memVar_ = previous supplyRatio, memVar2_ = update storage threshold
                                          } else {
                                              // if inverse bit is changing then always update on storage
                                              memVar_ = 1;
                                          }
                                          if (memVar_ == 0) {
                                              // utilization, time, and supplyRatio difference is not big enough -> check borrowRatio difference
                                              // memVar_ => extract last borrowRatio
                                              memVar_ =
                                                  (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO) &
                                                  X15;
                                              // set memVar_ to 1 if current borrowRatio to previous borrowRatio difference is > update storage threshold
                                              if ((memVar_ & 1) == (memVar4_ & 1)) {
                                                  memVar_ = memVar_ >> 1;
                                                  memVar_ = (
                                                      (memVar4_ >> 1) > memVar_ ? (memVar4_ >> 1) - memVar_ : memVar_ - (memVar4_ >> 1)
                                                  ) > memVar2_
                                                      ? 1
                                                      : 0; // memVar4_ = borrowRatio, memVar_ = previous borrowRatio, memVar2_ = update storage threshold
                                              } else {
                                                  // if inverse bit is changing then always update on storage
                                                  memVar_ = 1;
                                              }
                                          }
                                      }
                                  }
                              }
                              // memVar_ is 1 if either time diff was enough or if
                              // utilization, supplyRatio or borrowRatio difference was > update storage threshold
                              if (memVar_ == 1) {
                                  // memVar_ => calculate new borrow rate for utilization. includes value overflow check.
                                  memVar_ = LiquidityCalcs.calcBorrowRateFromUtilization(_rateData[token_], utilization_);
                                  // ensure values written to storage do not exceed the dedicated bit space in packed uint256 slots
                                  if (o_.supplyExchangePrice > X64 || o_.borrowExchangePrice > X64) {
                                      revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__EXCHANGE_PRICES);
                                  }
                                  if (utilization_ > X14) {
                                      revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__UTILIZATION);
                                  }
                                  o_.exchangePricesAndConfig =
                                      (o_.exchangePricesAndConfig &
                                          // mask to update bits: 0-15 (borrow rate), 30-43 (utilization), 58-248 (timestamp, exchange prices, ratios)
                                          0xfe000000000000000000000000000000000000000000000003fff0003fff0000) |
                                      memVar_ | // calcBorrowRateFromUtilization already includes an overflow check
                                      (utilization_ << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) |
                                      (block.timestamp << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) |
                                      (o_.supplyExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) |
                                      (o_.borrowExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) |
                                      // ratios can never be > 100%, no overflow check needed
                                      (memVar3_ << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) | // supplyRatio (memVar3_ holds that value)
                                      (memVar4_ << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO); // borrowRatio (memVar4_ holds that value)
                                  // Updating on storage
                                  _exchangePricesAndConfig[token_] = o_.exchangePricesAndConfig;
                              } else {
                                  // do not update in storage but update o_.exchangePricesAndConfig for updated exchange prices at
                                  // event emit of LogOperate
                                  o_.exchangePricesAndConfig =
                                      (o_.exchangePricesAndConfig &
                                          // mask to update bits: 91-218 (exchange prices)
                                          0xfffffffffc00000000000000000000000000000007ffffffffffffffffffffff) |
                                      (o_.supplyExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) |
                                      (o_.borrowExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE);
                              }
                          }
                          // sending tokens to user at the end after updating everything
                          // only transfer to user in case of withdraw or borrow.
                          // do not transfer for same amounts in same operate(): supply(+) == borrow(+), withdraw(-) == payback(-). (DEX protocol use-case)
                          if ((supplyAmount_ < 0 || borrowAmount_ > 0) && !o_.skipTransfers) {
                              // sending tokens to user at the end after updating everything
                              // set memVar2_ to borrowAmount (if borrow) or reset memVar2_ var to 0 because
                              // it is used with > 0 check below to transfer withdraw / borrow / both
                              memVar2_ = borrowAmount_ > 0 ? uint256(borrowAmount_) : 0;
                              if (supplyAmount_ < 0) {
                                  unchecked {
                                      memVar_ = uint256(-supplyAmount_);
                                  }
                              } else {
                                  memVar_ = 0;
                              }
                              if (memVar_ > 0 && memVar2_ > 0 && withdrawTo_ == borrowTo_) {
                                  // if user is doing borrow & withdraw together and address for both is the same
                                  // then transfer tokens of borrow & withdraw together to save on gas
                                  if (token_ == NATIVE_TOKEN_ADDRESS) {
                                      SafeTransfer.safeTransferNative(withdrawTo_, memVar_ + memVar2_);
                                  } else {
                                      SafeTransfer.safeTransfer(token_, withdrawTo_, memVar_ + memVar2_);
                                  }
                              } else {
                                  if (token_ == NATIVE_TOKEN_ADDRESS) {
                                      // if withdraw
                                      if (memVar_ > 0) {
                                          SafeTransfer.safeTransferNative(withdrawTo_, memVar_);
                                      }
                                      // if borrow
                                      if (memVar2_ > 0) {
                                          SafeTransfer.safeTransferNative(borrowTo_, memVar2_);
                                      }
                                  } else {
                                      // if withdraw
                                      if (memVar_ > 0) {
                                          // ---------- temporary code start -----------------------
                                          // temporary addition for weETH & weETHs: if token is weETH or weETHs -> withdraw from Zircuit
                                          if (token_ == WEETH) {
                                              if ((IERC20(WEETH).balanceOf(address(this)) < memVar_)) {
                                                  ZIRCUIT.withdraw(WEETH, memVar_);
                                              }
                                          } else if (token_ == WEETHS) {
                                              if ((IERC20(WEETHS).balanceOf(address(this)) < memVar_)) {
                                                  ZIRCUIT.withdraw(WEETHS, memVar_);
                                              }
                                          }
                                          // temporary code also includes: WEETH, WEETHS & ZIRCUIT constant, IZtakingPool interface
                                          // ---------- temporary code end -----------------------
                                          SafeTransfer.safeTransfer(token_, withdrawTo_, memVar_);
                                      }
                                      // if borrow
                                      if (memVar2_ > 0) {
                                          SafeTransfer.safeTransfer(token_, borrowTo_, memVar2_);
                                      }
                                  }
                              }
                          }
                          // emit Operate event
                          emit LogOperate(
                              msg.sender,
                              token_,
                              supplyAmount_,
                              borrowAmount_,
                              withdrawTo_,
                              borrowTo_,
                              o_.totalAmounts,
                              o_.exchangePricesAndConfig
                          );
                          // set return values
                          memVar3_ = o_.supplyExchangePrice;
                          memVar4_ = o_.borrowExchangePrice;
                      }
                  }
                  // SPDX-License-Identifier: AGPL-3.0-only
                  pragma solidity >=0.8.0;
                  /// @notice Arithmetic library with operations for fixed-point numbers.
                  /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
                  /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
                  library FixedPointMathLib {
                      /*//////////////////////////////////////////////////////////////
                                      SIMPLIFIED FIXED POINT OPERATIONS
                      //////////////////////////////////////////////////////////////*/
                      uint256 internal constant MAX_UINT256 = 2**256 - 1;
                      uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
                      function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
                      }
                      function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
                      }
                      function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
                      }
                      function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                          return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
                      }
                      /*//////////////////////////////////////////////////////////////
                                      LOW LEVEL FIXED POINT OPERATIONS
                      //////////////////////////////////////////////////////////////*/
                      function mulDivDown(
                          uint256 x,
                          uint256 y,
                          uint256 denominator
                      ) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                              if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                                  revert(0, 0)
                              }
                              // Divide x * y by the denominator.
                              z := div(mul(x, y), denominator)
                          }
                      }
                      function mulDivUp(
                          uint256 x,
                          uint256 y,
                          uint256 denominator
                      ) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                              if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                                  revert(0, 0)
                              }
                              // If x * y modulo the denominator is strictly greater than 0,
                              // 1 is added to round up the division of x * y by the denominator.
                              z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
                          }
                      }
                      function rpow(
                          uint256 x,
                          uint256 n,
                          uint256 scalar
                      ) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              switch x
                              case 0 {
                                  switch n
                                  case 0 {
                                      // 0 ** 0 = 1
                                      z := scalar
                                  }
                                  default {
                                      // 0 ** n = 0
                                      z := 0
                                  }
                              }
                              default {
                                  switch mod(n, 2)
                                  case 0 {
                                      // If n is even, store scalar in z for now.
                                      z := scalar
                                  }
                                  default {
                                      // If n is odd, store x in z for now.
                                      z := x
                                  }
                                  // Shifting right by 1 is like dividing by 2.
                                  let half := shr(1, scalar)
                                  for {
                                      // Shift n right by 1 before looping to halve it.
                                      n := shr(1, n)
                                  } n {
                                      // Shift n right by 1 each iteration to halve it.
                                      n := shr(1, n)
                                  } {
                                      // Revert immediately if x ** 2 would overflow.
                                      // Equivalent to iszero(eq(div(xx, x), x)) here.
                                      if shr(128, x) {
                                          revert(0, 0)
                                      }
                                      // Store x squared.
                                      let xx := mul(x, x)
                                      // Round to the nearest number.
                                      let xxRound := add(xx, half)
                                      // Revert if xx + half overflowed.
                                      if lt(xxRound, xx) {
                                          revert(0, 0)
                                      }
                                      // Set x to scaled xxRound.
                                      x := div(xxRound, scalar)
                                      // If n is even:
                                      if mod(n, 2) {
                                          // Compute z * x.
                                          let zx := mul(z, x)
                                          // If z * x overflowed:
                                          if iszero(eq(div(zx, x), z)) {
                                              // Revert if x is non-zero.
                                              if iszero(iszero(x)) {
                                                  revert(0, 0)
                                              }
                                          }
                                          // Round to the nearest number.
                                          let zxRound := add(zx, half)
                                          // Revert if zx + half overflowed.
                                          if lt(zxRound, zx) {
                                              revert(0, 0)
                                          }
                                          // Return properly scaled zxRound.
                                          z := div(zxRound, scalar)
                                      }
                                  }
                              }
                          }
                      }
                      /*//////////////////////////////////////////////////////////////
                                          GENERAL NUMBER UTILITIES
                      //////////////////////////////////////////////////////////////*/
                      function sqrt(uint256 x) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              let y := x // We start y at x, which will help us make our initial estimate.
                              z := 181 // The "correct" value is 1, but this saves a multiplication later.
                              // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
                              // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
                              // We check y >= 2^(k + 8) but shift right by k bits
                              // each branch to ensure that if x >= 256, then y >= 256.
                              if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                                  y := shr(128, y)
                                  z := shl(64, z)
                              }
                              if iszero(lt(y, 0x1000000000000000000)) {
                                  y := shr(64, y)
                                  z := shl(32, z)
                              }
                              if iszero(lt(y, 0x10000000000)) {
                                  y := shr(32, y)
                                  z := shl(16, z)
                              }
                              if iszero(lt(y, 0x1000000)) {
                                  y := shr(16, y)
                                  z := shl(8, z)
                              }
                              // Goal was to get z*z*y within a small factor of x. More iterations could
                              // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
                              // We ensured y >= 256 so that the relative difference between y and y+1 is small.
                              // That's not possible if x < 256 but we can just verify those cases exhaustively.
                              // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
                              // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
                              // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
                              // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
                              // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
                              // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
                              // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
                              // There is no overflow risk here since y < 2^136 after the first branch above.
                              z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
                              // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              z := shr(1, add(z, div(x, z)))
                              // If x+1 is a perfect square, the Babylonian method cycles between
                              // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
                              // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
                              // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
                              // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
                              z := sub(z, lt(div(x, z), z))
                          }
                      }
                      function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Mod x by y. Note this will return
                              // 0 instead of reverting if y is zero.
                              z := mod(x, y)
                          }
                      }
                      function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Divide x by y. Note this will return
                              // 0 instead of reverting if y is zero.
                              r := div(x, y)
                          }
                      }
                      function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                          /// @solidity memory-safe-assembly
                          assembly {
                              // Add 1 to x * y if x % y > 0. Note this will
                              // return 0 instead of reverting if y is zero.
                              z := add(gt(mod(x, y), 0), div(x, y))
                          }
                      }
                  }
                  

                  File 7 of 7: TokenChwomper
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
                  pragma solidity >= 0.8.0;
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20 {
                      /**
                       * @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);
                      /**
                       * @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 `to`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address to, 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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
                  }// SPDX-License-Identifier: UNLICENSED
                  pragma solidity >= 0.8.0;
                  interface IRedSnwapper {
                      struct InputToken {
                          address token;
                          uint256 amountIn;
                          address transferTo;
                      }
                      struct OutputToken {
                          address token;
                          address recipient;
                          uint256 amountOutMin;
                      }
                      struct Executor {
                          address executor;
                          uint256 value;
                          bytes data;
                      }
                      function snwap(
                          address tokenIn,
                          uint256 amountIn,
                          address recipient,
                          address tokenOut,
                          uint256 amountOutMin,
                          address executor,
                          bytes calldata executorData
                      ) external returns (uint256 amountOut);
                      function snwapMultiple(
                          InputToken[] calldata inputTokens,
                          OutputToken[] calldata outputTokens,
                          Executor[] calldata executors
                      ) external returns (uint256[] memory amountOut);
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
                  pragma solidity ^0.8.0;
                  import "../utils/Context.sol";
                  /**
                   * @dev Contract module which provides a basic access control mechanism, where
                   * there is an account (an owner) that can be granted exclusive access to
                   * specific functions.
                   *
                   * By default, the owner account will be the one that deploys the contract. This
                   * can later be changed with {transferOwnership}.
                   *
                   * This module is used through inheritance. It will make available the modifier
                   * `onlyOwner`, which can be applied to your functions to restrict their use to
                   * the owner.
                   */
                  abstract contract Ownable is Context {
                      address private _owner;
                      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                      /**
                       * @dev Initializes the contract setting the deployer as the initial owner.
                       */
                      constructor() {
                          _transferOwnership(_msgSender());
                      }
                      /**
                       * @dev Throws if called by any account other than the owner.
                       */
                      modifier onlyOwner() {
                          _checkOwner();
                          _;
                      }
                      /**
                       * @dev Returns the address of the current owner.
                       */
                      function owner() public view virtual returns (address) {
                          return _owner;
                      }
                      /**
                       * @dev Throws if the sender is not the owner.
                       */
                      function _checkOwner() internal view virtual {
                          require(owner() == _msgSender(), "Ownable: caller is not the owner");
                      }
                      /**
                       * @dev Leaves the contract without owner. It will not be possible to call
                       * `onlyOwner` functions anymore. Can only be called by the current owner.
                       *
                       * NOTE: Renouncing ownership will leave the contract without an owner,
                       * thereby removing any functionality that is only available to the owner.
                       */
                      function renounceOwnership() public virtual onlyOwner {
                          _transferOwnership(address(0));
                      }
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`).
                       * Can only be called by the current owner.
                       */
                      function transferOwnership(address newOwner) public virtual onlyOwner {
                          require(newOwner != address(0), "Ownable: new owner is the zero address");
                          _transferOwnership(newOwner);
                      }
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`).
                       * Internal function without access restriction.
                       */
                      function _transferOwnership(address newOwner) internal virtual {
                          address oldOwner = _owner;
                          _owner = newOwner;
                          emit OwnershipTransferred(oldOwner, newOwner);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)
                  pragma solidity ^0.8.0;
                  import "./Ownable.sol";
                  /**
                   * @dev Contract module which provides access control mechanism, where
                   * there is an account (an owner) that can be granted exclusive access to
                   * specific functions.
                   *
                   * By default, the owner account will be the one that deploys the contract. This
                   * can later be changed with {transferOwnership} and {acceptOwnership}.
                   *
                   * This module is used through inheritance. It will make available all functions
                   * from parent (Ownable).
                   */
                  abstract contract Ownable2Step is Ownable {
                      address private _pendingOwner;
                      event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
                      /**
                       * @dev Returns the address of the pending owner.
                       */
                      function pendingOwner() public view virtual returns (address) {
                          return _pendingOwner;
                      }
                      /**
                       * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
                       * Can only be called by the current owner.
                       */
                      function transferOwnership(address newOwner) public virtual override onlyOwner {
                          _pendingOwner = newOwner;
                          emit OwnershipTransferStarted(owner(), newOwner);
                      }
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
                       * Internal function without access restriction.
                       */
                      function _transferOwnership(address newOwner) internal virtual override {
                          delete _pendingOwner;
                          super._transferOwnership(newOwner);
                      }
                      /**
                       * @dev The new owner accepts the ownership transfer.
                       */
                      function acceptOwnership() external {
                          address sender = _msgSender();
                          require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
                          _transferOwnership(sender);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @dev Provides information about the current execution context, including the
                   * sender of the transaction and its data. While these are generally available
                   * via msg.sender and msg.data, they should not be accessed in such a direct
                   * manner, since when dealing with meta-transactions the account sending and
                   * paying for execution may not be the actual sender (as far as an application
                   * is concerned).
                   *
                   * This contract is only required for intermediate, library-like contracts.
                   */
                  abstract contract Context {
                      function _msgSender() internal view virtual returns (address) {
                          return msg.sender;
                      }
                      function _msgData() internal view virtual returns (bytes calldata) {
                          return msg.data;
                      }
                  }
                  // SPDX-License-Identifier: GPL-3.0-or-later
                  pragma solidity >=0.8.0;
                  import "openzeppelin/access/Ownable2Step.sol";
                  abstract contract Auth is Ownable2Step {
                      event SetTrusted(address indexed user, bool isTrusted);
                      mapping(address => bool) public trusted;
                      error OnlyTrusted();
                      modifier onlyTrusted() {
                          if (!trusted[msg.sender]) revert OnlyTrusted();
                          _;
                      }
                      constructor(address trustedUser) {
                          trusted[trustedUser] = true;
                          emit SetTrusted(trustedUser, true);
                      }
                      function setTrusted(address user, bool isTrusted) external onlyOwner {
                          trusted[user] = isTrusted;
                          emit SetTrusted(user, isTrusted);
                      }
                  }// SPDX-License-Identifier: GPL-3.0-or-later
                  pragma solidity >=0.8.0;
                  import "interfaces/IRedSnwapper.sol";
                  import "interfaces/IERC20.sol";
                  import "./Auth.sol";
                  /// @title TokenChwomper for selling accumulated tokens for weth or other base assets
                  /// @notice This contract will be used for fee collection and breakdown
                  /// @dev Uses Auth contract for 2-step owner process and trust operators to guard functions
                  contract TokenChwomper is Auth {
                    address public immutable weth;
                    IRedSnwapper public redSnwapper;
                    bytes4 private constant TRANSFER_SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
                    error TransferFailed();
                    constructor(
                      address _operator,
                      address _redSnwapper,
                      address _weth
                    ) Auth(_operator) {
                      // initial owner is msg.sender
                      redSnwapper = IRedSnwapper(_redSnwapper);
                      weth = _weth;
                    }
                    /// @notice Updates the RedSnwapper to be used for swapping tokens
                    /// @dev make sure new RedSnwapper is backwards compatiable (should be)
                    /// @param _redSnwapper The address of the new route processor
                    function updateRedSnwapper(address _redSnwapper) external onlyOwner {
                      redSnwapper = IRedSnwapper(_redSnwapper);
                    }
                    
                    /// @notice Swaps tokens via the configured RedSnwapper
                    /// @dev Must be called by a trusted operator
                    /// @param tokenIn Address of the input token
                    /// @param amountIn Amount of the input token to swap
                    /// @param recipient Address to receive the output tokens
                    /// @param tokenOut Address of the output token
                    /// @param amountOutMin Minimum acceptable amount of output tokens (slippage protection)
                    /// @param executor Address of the executor contract to perform the swap logic
                    /// @param executorData Encoded data for the executor call
                    /// @return amountOut The actual amount of output tokens received
                    function snwap(
                      address tokenIn,
                      uint256 amountIn,
                      address recipient,
                      address tokenOut,
                      uint256 amountOutMin,
                      address executor,
                      bytes calldata executorData
                    ) external onlyTrusted returns (uint256 amountOut) {
                       // Pre-fund RedSnwapper with input tokens
                       _safeTransfer(tokenIn, address(redSnwapper), amountIn);
                      // Execute snwap with zero amountIn
                      amountOut = redSnwapper.snwap(
                        tokenIn,
                        0,
                        recipient,
                        tokenOut,
                        amountOutMin,
                        executor,
                        executorData
                      );
                    }
                    /// @notice Performs multiple swaps via the configured RedSnwapper
                    /// @dev Must be called by a trusted operator
                    /// @param inputTokens Array of input token parameters
                    /// @param outputTokens Array of output token requirements
                    /// @param executors Array of executor calls to perform
                    /// @return amountOut Array of actual amounts of output tokens received
                    function snwapMultiple(
                      IRedSnwapper.InputToken[] calldata inputTokens,
                      IRedSnwapper.OutputToken[] calldata outputTokens,
                      IRedSnwapper.Executor[] calldata executors
                    ) external onlyTrusted returns (uint256[] memory amountOut) {
                     uint256 length = inputTokens.length;
                      IRedSnwapper.InputToken[] memory _inputTokens = new IRedSnwapper.InputToken[](length);
                      for (uint256 i = 0; i < length; ++i) {
                          // Pre-fund RedSnwapper with input tokens
                          _safeTransfer(
                              inputTokens[i].token,
                              address(redSnwapper),
                              inputTokens[i].amountIn
                          );
                          // Build _inputTokens with zero amountIn
                          _inputTokens[i] = IRedSnwapper.InputToken({
                              token: inputTokens[i].token,
                              amountIn: 0,
                              transferTo: inputTokens[i].transferTo
                          });
                      }
                      // Execute snwapMultiple
                      amountOut = redSnwapper.snwapMultiple(
                          _inputTokens,
                          outputTokens,
                          executors
                      );
                    }
                    /// @notice Withdraw any token or eth from the contract
                    /// @dev can only be called by owner
                    /// @param token The address of the token to be withdrawn, 0x0 for eth
                    /// @param to The address to send the token to
                    /// @param _value The amount of the token to be withdrawn
                    function withdraw(address token, address to, uint256 _value) onlyOwner external {
                      if (token != address(0)) {
                        _safeTransfer(token, to, _value);
                      } 
                      else {
                        (bool success, ) = to.call{value: _value}("");
                        require(success);
                      }
                    }
                    
                    function _safeTransfer(address token, address to, uint value) internal {
                      (bool success, bytes memory data) = token.call(abi.encodeWithSelector(TRANSFER_SELECTOR, to, value));
                      if (!success || (data.length != 0 && !abi.decode(data, (bool)))) revert TransferFailed();
                    }
                    /// @notice In case we receive any unwrapped eth (native token) we can call this
                    /// @dev operators can call this 
                    function wrapEth() onlyTrusted external {
                      weth.call{value: address(this).balance}("");
                    }
                    /// @notice Available function in case we need to do any calls that aren't supported by the contract (unwinding lp positions, etc.)
                    /// @dev can only be called by owner
                    /// @param to The address to send the call to
                    /// @param _value The amount of eth to send with the call
                    /// @param data The data to be sent with the call
                    function doAction(address to, uint256 _value, bytes memory data) onlyOwner external {
                      (bool success, ) = to.call{value: _value}(data);
                      require(success);
                    }
                    receive() external payable {}
                  }