ETH Price: $2,507.52 (-0.87%)

Transaction Decoder

Block:
22717150 at Jun-16-2025 12:42:35 PM +UTC
Transaction Fee:
0.000817774470590538 ETH $2.05
Gas Used:
355,314 Gas / 2.301554317 Gwei

Emitted Events:

83 TetherToken.Transfer( from=[Sender] 0x5a7559675b047281e1cf341d81c54388c44f6616, to=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, value=700000000 )
84 TetherToken.Approval( owner=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, spender=Vyper_contract, value=700000000 )
85 TetherToken.Transfer( from=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, to=Vyper_contract, value=700000000 )
86 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7, 0x0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 0000000000000000000000000000000000000000000000000000000029b9f7ad )
87 Vyper_contract.TokenExchange( buyer=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, sold_id=2, tokens_sold=700000000, bought_id=1, tokens_bought=700053421 )
88 FiatTokenProxy.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 0x000000000000000000000000836951eb21f3df98273517b7249dceff270d34bf, 0000000000000000000000000000000000000000000000000000000029b9f7ad )
89 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 0x00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497, 0000000000000000000000000000000000000000000000000000000029b9f7ad )
90 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x000000000000000000000000836951eb21f3df98273517b7249dceff270d34bf, 0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48, 00000000000000000000000000000000000000000000000000000000199a8e28, ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefe0967c, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000a4ef6e4cc72c00000000000000000000d47537063b5700, 0000000000000008493c779e7800000805940f35e1a140442c01e7dbc3e80238 )
91 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x000000000000000000000000836951eb21f3df98273517b7249dceff270d34bf, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, fffffffffffffffffffffffffffffffffffffffffffffffffdb8a41354adff40, 000000000000000000000000000000000000000000000000016f578c5ceec2c0, 0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 000000000000000091f5999a6cda1f150000000000000000b242fb83e25db715, 0000000000000007ca864c8af8000007aba1daf781a14033ac01e81a83e80123 )
92 FluidDexT1.Swap( swap0to1=True, amountIn=700053421, amountOut=267598560286000000, to=0x3Ced11c610556e5292fBC2e75D68c3899098C14C )
93 0x3ced11c610556e5292fbc2e75d68c3899098c14c.0xbbb02a24579dc2e59c1609253b6ddab5457ba00895b3eda80dd41e03e2cd7e55( 0xbbb02a24579dc2e59c1609253b6ddab5457ba00895b3eda80dd41e03e2cd7e55, 0x000000000000000000000000ad27827c312cd5e71311d68e180a9872d42de23d, 0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7, 0x0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000003ced11c610556e5292fbc2e75d68c3899098c14c, 000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0000000000000000000000000000000000000000000000000000000029b92700, 00000000000000000000000000000000000000000000000003b6b3790840c380, ffffffffffffffffffffffffffffffffffffffffffffffffffffbdb8d28ed020 )

Account State Difference:

  Address   Before After State Difference Code
0x52Aa8994...360F4e497
(Fluid: Liquidity)
18,711.689553959193307991 Eth18,711.421955398907307991 Eth0.267598560286
0x5A755967...8C44F6616
0.020723484884686526 Eth
Nonce: 21
0.28683843800862171 Eth
Nonce: 22
0.266114953123935184
0x836951EB...F270d34bf
(Fluid: Dex USDC - ETH)
0xA0b86991...E3606eB48
0xbEbc4478...3032FF1C7
(Curve.fi: DAI/USDC/USDT Pool)
0xdAC17F95...13D831ec7
(BuilderNet)
33.374695370244247963 Eth33.374920342329264493 Eth0.00022497208501653
0xde725989...D581d5667 0.021563280246583755 Eth0.022229112938058033 Eth0.000665832691474278

Execution Trace

RedSnwapper.snwap( tokenIn=0xdAC17F958D2ee523a2206206994597C13D831ec7, amountIn=700000000, recipient=0x5A7559675B047281e1CF341d81C54388C44F6616, tokenOut=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, amountOutMin=265667243898236979, executor=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, executorData=0xBA3F2165000000000000000000000000DE7259893AF7CDBC9FD806C6BA61D22D581D566700000000000000000000000000000000000000000000000000025D92410E1F66000000000000000000000000DAC17F958D2EE523A2206206994597C13D831EC70000000000000000000000000000000000000000000000000000000029B92700000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE00000000000000000000000000000000000000000000000003B6F5C035B1F3600000000000000000000000005A7559675B047281E1CF341D81C54388C44F6616000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000009A01DAC17F958D2EE523A2206206994597C13D831EC701FFFF05BEBC44782C7DB0A1A60CB6FE97D0B483032FF1C70102013CED11C610556E5292FBC2E75D68C3899098C14CA0B86991C6218B36C1D19D4A2E9EB0CE3606EB4801A0B86991C6218B36C1D19D4A2E9EB0CE3606EB4801FFFF0B836951EB21F3DF98273517B7249DCEFF270D34BF013CED11C610556E5292FBC2E75D68C3899098C14C000000000000 ) => ( amountOut=266932727594525722 )
  • TetherToken.transferFrom( _from=0x5A7559675B047281e1CF341d81C54388C44F6616, _to=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, _value=700000000 )
  • 0xad27827c312cd5e71311d68e180a9872d42de23d.1cff79cd( )
    • 0x3ced11c610556e5292fbc2e75d68c3899098c14c.ba3f2165( )
      • TetherToken.balanceOf( who=0x3Ced11c610556e5292fBC2e75D68c3899098C14C ) => ( 700000001 )
      • TetherToken.approve( _spender=0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7, _value=700000000 )
      • Vyper_contract.exchange( i=2, j=1, dx=700000000, min_dy=0 )
        • TetherToken.balanceOf( who=0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7 ) => ( 32249712013665 )
        • Null: 0x000...004.CALL( )
        • Null: 0x000...004.00000000( )
        • TetherToken.transferFrom( _from=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, _to=0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7, _value=700000000 )
        • Null: 0x000...004.00000000( )
        • TetherToken.balanceOf( who=0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7 ) => ( 32250412013665 )
        • Null: 0x000...004.CALL( )
        • Null: 0x000...004.00000000( )
        • FiatTokenProxy.a9059cbb( )
          • FiatTokenV2_2.transfer( to=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, value=700053421 ) => ( True )
          • Null: 0x000...004.00000000( )
          • FiatTokenProxy.70a08231( )
            • FiatTokenV2_2.balanceOf( account=0x3Ced11c610556e5292fBC2e75D68c3899098C14C ) => ( 700053422 )
            • FiatTokenProxy.095ea7b3( )
              • FiatTokenV2_2.approve( spender=0x836951EB21F3Df98273517B7249dCEFF270d34bf, value=700053421 ) => ( True )
              • FluidDexT1.swapIn( swap0to1_=True, amountIn_=700053421, amountOutMin_=0, to_=0x3Ced11c610556e5292fBC2e75D68c3899098C14C ) => ( amountOut_=267598560286000000 )
                • FluidLiquidityProxy.readFromStorage( slot_=A8E1248EDDF82E10C0ADC6C737B6D8DA17674ABF51801EA5A4549F41C2DFDF21 ) => ( result_=52012555791261777890916441296752746262884858883814000689720 )
                • FluidLiquidityProxy.readFromStorage( slot_=A1829A9003092132F585B6CCDD167C19FE9774DBDEA4260287E8A8E8CA8185D7 ) => ( result_=48905551202889666558590080521071658564511016190482678546723 )
                • FluidLiquidityProxy.readFromStorage( slot_=89F27CBB3B930281DF7EF3528D38B6BD540933B5A322C200E817FF3C4DBC4896 ) => ( result_=344378071795852156899184042012630155413848348754662174573987793409 )
                • FluidLiquidityProxy.readFromStorage( slot_=5E5FDA7A9504D2BCFE88B229084646D0EEE09EA800412749D08CCC9F7D0865D0 ) => ( result_=352240819646411396317410862530455464027989488036926382085814073889 )
                • FluidLiquidityProxy.readFromStorage( slot_=B9E5B2CDEEE9653D2FEBD1B26B6C6CAAB99B55414C5BB37ADE8DA13BE3D0EFFB ) => ( result_=87150031470982297424711538739253578122173637368612359927010138824866817 )
                • FluidLiquidityProxy.readFromStorage( slot_=D678EA6031089A7E5AE0DE72204A1B7CC2EC5D2852CE956A271CEC8ECCBAF0EC ) => ( result_=90397453273383170695983062655151247889996389579263488793985083192202273 )
                • FluidLiquidityProxy.ad967e15( )
                  • FluidLiquidityUserModule.operate( token_=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, supplyAmount_=429559336, borrowAmount_=-270494084, withdrawTo_=0x0000000000000000000000000000000000000000, borrowTo_=0x0000000000000000000000000000000000000000, callbackData_=0x0000000000000000000000000000000000000000000000000000000029B9F7AD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003CED11C610556E5292FBC2E75D68C3899098C14C ) => ( memVar3_=1102506485436, memVar4_=1138830013391 )
                    • FiatTokenProxy.70a08231( )
                    • FluidDexT1.liquidityCallback( token_=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, amount_=700053420, data_=0x0000000000000000000000000000000000000000000000000000000029B9F7AD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003CED11C610556E5292FBC2E75D68C3899098C14C )
                    • FiatTokenProxy.70a08231( )
                    • FluidLiquidityProxy.ad967e15( )
                      • FluidLiquidityUserModule.operate( token_=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, supplyAmount_=-164200983467000000, borrowAmount_=103397576819000000, withdrawTo_=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, borrowTo_=0x3Ced11c610556e5292fBC2e75D68c3899098C14C, callbackData_=0x ) => ( memVar3_=1054217035504, memVar4_=1070802243935 )
                        • ETH 0.267598560286 0x3ced11c610556e5292fbc2e75d68c3899098c14c.CALL( )
                        • ETH 0.000133166538294855 TokenChwomper.CALL( )
                        • ETH 0.000532666153179423 TokenChwomper.CALL( )
                        • ETH 0.266932727594525722 0x5a7559675b047281e1cf341d81c54388c44f6616.CALL( )
                          File 1 of 9: 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 9: TetherToken
                          pragma solidity ^0.4.17;
                          
                          /**
                           * @title SafeMath
                           * @dev Math operations with safety checks that throw on error
                           */
                          library SafeMath {
                              function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                  if (a == 0) {
                                      return 0;
                                  }
                                  uint256 c = a * b;
                                  assert(c / a == b);
                                  return c;
                              }
                          
                              function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                  // assert(b > 0); // Solidity automatically throws when dividing by 0
                                  uint256 c = a / b;
                                  // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                                  return c;
                              }
                          
                              function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                  assert(b <= a);
                                  return a - b;
                              }
                          
                              function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                  uint256 c = a + b;
                                  assert(c >= a);
                                  return c;
                              }
                          }
                          
                          /**
                           * @title Ownable
                           * @dev The Ownable contract has an owner address, and provides basic authorization control
                           * functions, this simplifies the implementation of "user permissions".
                           */
                          contract Ownable {
                              address public owner;
                          
                              /**
                                * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                                * account.
                                */
                              function Ownable() public {
                                  owner = msg.sender;
                              }
                          
                              /**
                                * @dev Throws if called by any account other than the owner.
                                */
                              modifier onlyOwner() {
                                  require(msg.sender == owner);
                                  _;
                              }
                          
                              /**
                              * @dev Allows the current owner to transfer control of the contract to a newOwner.
                              * @param newOwner The address to transfer ownership to.
                              */
                              function transferOwnership(address newOwner) public onlyOwner {
                                  if (newOwner != address(0)) {
                                      owner = newOwner;
                                  }
                              }
                          
                          }
                          
                          /**
                           * @title ERC20Basic
                           * @dev Simpler version of ERC20 interface
                           * @dev see https://github.com/ethereum/EIPs/issues/20
                           */
                          contract ERC20Basic {
                              uint public _totalSupply;
                              function totalSupply() public constant returns (uint);
                              function balanceOf(address who) public constant returns (uint);
                              function transfer(address to, uint value) public;
                              event Transfer(address indexed from, address indexed to, uint value);
                          }
                          
                          /**
                           * @title ERC20 interface
                           * @dev see https://github.com/ethereum/EIPs/issues/20
                           */
                          contract ERC20 is ERC20Basic {
                              function allowance(address owner, address spender) public constant returns (uint);
                              function transferFrom(address from, address to, uint value) public;
                              function approve(address spender, uint value) public;
                              event Approval(address indexed owner, address indexed spender, uint value);
                          }
                          
                          /**
                           * @title Basic token
                           * @dev Basic version of StandardToken, with no allowances.
                           */
                          contract BasicToken is Ownable, ERC20Basic {
                              using SafeMath for uint;
                          
                              mapping(address => uint) public balances;
                          
                              // additional variables for use if transaction fees ever became necessary
                              uint public basisPointsRate = 0;
                              uint public maximumFee = 0;
                          
                              /**
                              * @dev Fix for the ERC20 short address attack.
                              */
                              modifier onlyPayloadSize(uint size) {
                                  require(!(msg.data.length < size + 4));
                                  _;
                              }
                          
                              /**
                              * @dev transfer token for a specified address
                              * @param _to The address to transfer to.
                              * @param _value The amount to be transferred.
                              */
                              function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
                                  uint fee = (_value.mul(basisPointsRate)).div(10000);
                                  if (fee > maximumFee) {
                                      fee = maximumFee;
                                  }
                                  uint sendAmount = _value.sub(fee);
                                  balances[msg.sender] = balances[msg.sender].sub(_value);
                                  balances[_to] = balances[_to].add(sendAmount);
                                  if (fee > 0) {
                                      balances[owner] = balances[owner].add(fee);
                                      Transfer(msg.sender, owner, fee);
                                  }
                                  Transfer(msg.sender, _to, sendAmount);
                              }
                          
                              /**
                              * @dev Gets the balance of the specified address.
                              * @param _owner The address to query the the balance of.
                              * @return An uint representing the amount owned by the passed address.
                              */
                              function balanceOf(address _owner) public constant returns (uint balance) {
                                  return balances[_owner];
                              }
                          
                          }
                          
                          /**
                           * @title Standard ERC20 token
                           *
                           * @dev Implementation of the basic standard token.
                           * @dev https://github.com/ethereum/EIPs/issues/20
                           * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
                           */
                          contract StandardToken is BasicToken, ERC20 {
                          
                              mapping (address => mapping (address => uint)) public allowed;
                          
                              uint public constant MAX_UINT = 2**256 - 1;
                          
                              /**
                              * @dev Transfer tokens from one address to another
                              * @param _from address The address which you want to send tokens from
                              * @param _to address The address which you want to transfer to
                              * @param _value uint the amount of tokens to be transferred
                              */
                              function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
                                  var _allowance = allowed[_from][msg.sender];
                          
                                  // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
                                  // if (_value > _allowance) throw;
                          
                                  uint fee = (_value.mul(basisPointsRate)).div(10000);
                                  if (fee > maximumFee) {
                                      fee = maximumFee;
                                  }
                                  if (_allowance < MAX_UINT) {
                                      allowed[_from][msg.sender] = _allowance.sub(_value);
                                  }
                                  uint sendAmount = _value.sub(fee);
                                  balances[_from] = balances[_from].sub(_value);
                                  balances[_to] = balances[_to].add(sendAmount);
                                  if (fee > 0) {
                                      balances[owner] = balances[owner].add(fee);
                                      Transfer(_from, owner, fee);
                                  }
                                  Transfer(_from, _to, sendAmount);
                              }
                          
                              /**
                              * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
                              * @param _spender The address which will spend the funds.
                              * @param _value The amount of tokens to be spent.
                              */
                              function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                          
                                  // To change the approve amount you first have to reduce the addresses`
                                  //  allowance to zero by calling `approve(_spender, 0)` if it is not
                                  //  already 0 to mitigate the race condition described here:
                                  //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                                  require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
                          
                                  allowed[msg.sender][_spender] = _value;
                                  Approval(msg.sender, _spender, _value);
                              }
                          
                              /**
                              * @dev Function to check the amount of tokens than an owner allowed to a spender.
                              * @param _owner address The address which owns the funds.
                              * @param _spender address The address which will spend the funds.
                              * @return A uint specifying the amount of tokens still available for the spender.
                              */
                              function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                                  return allowed[_owner][_spender];
                              }
                          
                          }
                          
                          
                          /**
                           * @title Pausable
                           * @dev Base contract which allows children to implement an emergency stop mechanism.
                           */
                          contract Pausable is Ownable {
                            event Pause();
                            event Unpause();
                          
                            bool public paused = false;
                          
                          
                            /**
                             * @dev Modifier to make a function callable only when the contract is not paused.
                             */
                            modifier whenNotPaused() {
                              require(!paused);
                              _;
                            }
                          
                            /**
                             * @dev Modifier to make a function callable only when the contract is paused.
                             */
                            modifier whenPaused() {
                              require(paused);
                              _;
                            }
                          
                            /**
                             * @dev called by the owner to pause, triggers stopped state
                             */
                            function pause() onlyOwner whenNotPaused public {
                              paused = true;
                              Pause();
                            }
                          
                            /**
                             * @dev called by the owner to unpause, returns to normal state
                             */
                            function unpause() onlyOwner whenPaused public {
                              paused = false;
                              Unpause();
                            }
                          }
                          
                          contract BlackList is Ownable, BasicToken {
                          
                              /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
                              function getBlackListStatus(address _maker) external constant returns (bool) {
                                  return isBlackListed[_maker];
                              }
                          
                              function getOwner() external constant returns (address) {
                                  return owner;
                              }
                          
                              mapping (address => bool) public isBlackListed;
                              
                              function addBlackList (address _evilUser) public onlyOwner {
                                  isBlackListed[_evilUser] = true;
                                  AddedBlackList(_evilUser);
                              }
                          
                              function removeBlackList (address _clearedUser) public onlyOwner {
                                  isBlackListed[_clearedUser] = false;
                                  RemovedBlackList(_clearedUser);
                              }
                          
                              function destroyBlackFunds (address _blackListedUser) public onlyOwner {
                                  require(isBlackListed[_blackListedUser]);
                                  uint dirtyFunds = balanceOf(_blackListedUser);
                                  balances[_blackListedUser] = 0;
                                  _totalSupply -= dirtyFunds;
                                  DestroyedBlackFunds(_blackListedUser, dirtyFunds);
                              }
                          
                              event DestroyedBlackFunds(address _blackListedUser, uint _balance);
                          
                              event AddedBlackList(address _user);
                          
                              event RemovedBlackList(address _user);
                          
                          }
                          
                          contract UpgradedStandardToken is StandardToken{
                              // those methods are called by the legacy contract
                              // and they must ensure msg.sender to be the contract address
                              function transferByLegacy(address from, address to, uint value) public;
                              function transferFromByLegacy(address sender, address from, address spender, uint value) public;
                              function approveByLegacy(address from, address spender, uint value) public;
                          }
                          
                          contract TetherToken is Pausable, StandardToken, BlackList {
                          
                              string public name;
                              string public symbol;
                              uint public decimals;
                              address public upgradedAddress;
                              bool public deprecated;
                          
                              //  The contract can be initialized with a number of tokens
                              //  All the tokens are deposited to the owner address
                              //
                              // @param _balance Initial supply of the contract
                              // @param _name Token Name
                              // @param _symbol Token symbol
                              // @param _decimals Token decimals
                              function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
                                  _totalSupply = _initialSupply;
                                  name = _name;
                                  symbol = _symbol;
                                  decimals = _decimals;
                                  balances[owner] = _initialSupply;
                                  deprecated = false;
                              }
                          
                              // Forward ERC20 methods to upgraded contract if this one is deprecated
                              function transfer(address _to, uint _value) public whenNotPaused {
                                  require(!isBlackListed[msg.sender]);
                                  if (deprecated) {
                                      return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
                                  } else {
                                      return super.transfer(_to, _value);
                                  }
                              }
                          
                              // Forward ERC20 methods to upgraded contract if this one is deprecated
                              function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
                                  require(!isBlackListed[_from]);
                                  if (deprecated) {
                                      return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
                                  } else {
                                      return super.transferFrom(_from, _to, _value);
                                  }
                              }
                          
                              // Forward ERC20 methods to upgraded contract if this one is deprecated
                              function balanceOf(address who) public constant returns (uint) {
                                  if (deprecated) {
                                      return UpgradedStandardToken(upgradedAddress).balanceOf(who);
                                  } else {
                                      return super.balanceOf(who);
                                  }
                              }
                          
                              // Forward ERC20 methods to upgraded contract if this one is deprecated
                              function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                                  if (deprecated) {
                                      return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
                                  } else {
                                      return super.approve(_spender, _value);
                                  }
                              }
                          
                              // Forward ERC20 methods to upgraded contract if this one is deprecated
                              function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                                  if (deprecated) {
                                      return StandardToken(upgradedAddress).allowance(_owner, _spender);
                                  } else {
                                      return super.allowance(_owner, _spender);
                                  }
                              }
                          
                              // deprecate current contract in favour of a new one
                              function deprecate(address _upgradedAddress) public onlyOwner {
                                  deprecated = true;
                                  upgradedAddress = _upgradedAddress;
                                  Deprecate(_upgradedAddress);
                              }
                          
                              // deprecate current contract if favour of a new one
                              function totalSupply() public constant returns (uint) {
                                  if (deprecated) {
                                      return StandardToken(upgradedAddress).totalSupply();
                                  } else {
                                      return _totalSupply;
                                  }
                              }
                          
                              // Issue a new amount of tokens
                              // these tokens are deposited into the owner address
                              //
                              // @param _amount Number of tokens to be issued
                              function issue(uint amount) public onlyOwner {
                                  require(_totalSupply + amount > _totalSupply);
                                  require(balances[owner] + amount > balances[owner]);
                          
                                  balances[owner] += amount;
                                  _totalSupply += amount;
                                  Issue(amount);
                              }
                          
                              // Redeem tokens.
                              // These tokens are withdrawn from the owner address
                              // if the balance must be enough to cover the redeem
                              // or the call will fail.
                              // @param _amount Number of tokens to be issued
                              function redeem(uint amount) public onlyOwner {
                                  require(_totalSupply >= amount);
                                  require(balances[owner] >= amount);
                          
                                  _totalSupply -= amount;
                                  balances[owner] -= amount;
                                  Redeem(amount);
                              }
                          
                              function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
                                  // Ensure transparency by hardcoding limit beyond which fees can never be added
                                  require(newBasisPoints < 20);
                                  require(newMaxFee < 50);
                          
                                  basisPointsRate = newBasisPoints;
                                  maximumFee = newMaxFee.mul(10**decimals);
                          
                                  Params(basisPointsRate, maximumFee);
                              }
                          
                              // Called when new token are issued
                              event Issue(uint amount);
                          
                              // Called when tokens are redeemed
                              event Redeem(uint amount);
                          
                              // Called when contract is deprecated
                              event Deprecate(address newAddress);
                          
                              // Called if contract ever adds fees
                              event Params(uint feeBasisPoints, uint maxFee);
                          }

                          File 3 of 9: Vyper_contract
                          # @version 0.2.4
                          # (c) Curve.Fi, 2020
                          # Pool for DAI/USDC/USDT
                          
                          from vyper.interfaces import ERC20
                          
                          interface CurveToken:
                              def totalSupply() -> uint256: view
                              def mint(_to: address, _value: uint256) -> bool: nonpayable
                              def burnFrom(_to: address, _value: uint256) -> bool: nonpayable
                          
                          
                          # Events
                          event TokenExchange:
                              buyer: indexed(address)
                              sold_id: int128
                              tokens_sold: uint256
                              bought_id: int128
                              tokens_bought: uint256
                          
                          
                          event AddLiquidity:
                              provider: indexed(address)
                              token_amounts: uint256[N_COINS]
                              fees: uint256[N_COINS]
                              invariant: uint256
                              token_supply: uint256
                          
                          event RemoveLiquidity:
                              provider: indexed(address)
                              token_amounts: uint256[N_COINS]
                              fees: uint256[N_COINS]
                              token_supply: uint256
                          
                          event RemoveLiquidityOne:
                              provider: indexed(address)
                              token_amount: uint256
                              coin_amount: uint256
                          
                          event RemoveLiquidityImbalance:
                              provider: indexed(address)
                              token_amounts: uint256[N_COINS]
                              fees: uint256[N_COINS]
                              invariant: uint256
                              token_supply: uint256
                          
                          event CommitNewAdmin:
                              deadline: indexed(uint256)
                              admin: indexed(address)
                          
                          event NewAdmin:
                              admin: indexed(address)
                          
                          
                          event CommitNewFee:
                              deadline: indexed(uint256)
                              fee: uint256
                              admin_fee: uint256
                          
                          event NewFee:
                              fee: uint256
                              admin_fee: uint256
                          
                          event RampA:
                              old_A: uint256
                              new_A: uint256
                              initial_time: uint256
                              future_time: uint256
                          
                          event StopRampA:
                              A: uint256
                              t: uint256
                          
                          
                          # This can (and needs to) be changed at compile time
                          N_COINS: constant(int128) = 3  # <- change
                          
                          FEE_DENOMINATOR: constant(uint256) = 10 ** 10
                          LENDING_PRECISION: constant(uint256) = 10 ** 18
                          PRECISION: constant(uint256) = 10 ** 18  # The precision to convert to
                          PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1000000000000, 1000000000000]
                          RATES: constant(uint256[N_COINS]) = [1000000000000000000, 1000000000000000000000000000000, 1000000000000000000000000000000]
                          FEE_INDEX: constant(int128) = 2  # Which coin may potentially have fees (USDT)
                          
                          MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9
                          MAX_FEE: constant(uint256) = 5 * 10 ** 9
                          MAX_A: constant(uint256) = 10 ** 6
                          MAX_A_CHANGE: constant(uint256) = 10
                          
                          ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400
                          MIN_RAMP_TIME: constant(uint256) = 86400
                          
                          coins: public(address[N_COINS])
                          balances: public(uint256[N_COINS])
                          fee: public(uint256)  # fee * 1e10
                          admin_fee: public(uint256)  # admin_fee * 1e10
                          
                          owner: public(address)
                          token: CurveToken
                          
                          initial_A: public(uint256)
                          future_A: public(uint256)
                          initial_A_time: public(uint256)
                          future_A_time: public(uint256)
                          
                          admin_actions_deadline: public(uint256)
                          transfer_ownership_deadline: public(uint256)
                          future_fee: public(uint256)
                          future_admin_fee: public(uint256)
                          future_owner: public(address)
                          
                          is_killed: bool
                          kill_deadline: uint256
                          KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400
                          
                          
                          @external
                          def __init__(
                              _owner: address,
                              _coins: address[N_COINS],
                              _pool_token: address,
                              _A: uint256,
                              _fee: uint256,
                              _admin_fee: uint256
                          ):
                              """
                              @notice Contract constructor
                              @param _owner Contract owner address
                              @param _coins Addresses of ERC20 conracts of coins
                              @param _pool_token Address of the token representing LP share
                              @param _A Amplification coefficient multiplied by n * (n - 1)
                              @param _fee Fee to charge for exchanges
                              @param _admin_fee Admin fee
                              """
                              for i in range(N_COINS):
                                  assert _coins[i] != ZERO_ADDRESS
                              self.coins = _coins
                              self.initial_A = _A
                              self.future_A = _A
                              self.fee = _fee
                              self.admin_fee = _admin_fee
                              self.owner = _owner
                              self.kill_deadline = block.timestamp + KILL_DEADLINE_DT
                              self.token = CurveToken(_pool_token)
                          
                          
                          @view
                          @internal
                          def _A() -> uint256:
                              """
                              Handle ramping A up or down
                              """
                              t1: uint256 = self.future_A_time
                              A1: uint256 = self.future_A
                          
                              if block.timestamp < t1:
                                  A0: uint256 = self.initial_A
                                  t0: uint256 = self.initial_A_time
                                  # Expressions in uint256 cannot have negative numbers, thus "if"
                                  if A1 > A0:
                                      return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0)
                                  else:
                                      return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0)
                          
                              else:  # when t1 == 0 or block.timestamp >= t1
                                  return A1
                          
                          
                          @view
                          @external
                          def A() -> uint256:
                              return self._A()
                          
                          
                          @view
                          @internal
                          def _xp() -> uint256[N_COINS]:
                              result: uint256[N_COINS] = RATES
                              for i in range(N_COINS):
                                  result[i] = result[i] * self.balances[i] / LENDING_PRECISION
                              return result
                          
                          
                          @pure
                          @internal
                          def _xp_mem(_balances: uint256[N_COINS]) -> uint256[N_COINS]:
                              result: uint256[N_COINS] = RATES
                              for i in range(N_COINS):
                                  result[i] = result[i] * _balances[i] / PRECISION
                              return result
                          
                          
                          @pure
                          @internal
                          def get_D(xp: uint256[N_COINS], amp: uint256) -> uint256:
                              S: uint256 = 0
                              for _x in xp:
                                  S += _x
                              if S == 0:
                                  return 0
                          
                              Dprev: uint256 = 0
                              D: uint256 = S
                              Ann: uint256 = amp * N_COINS
                              for _i in range(255):
                                  D_P: uint256 = D
                                  for _x in xp:
                                      D_P = D_P * D / (_x * N_COINS)  # If division by 0, this will be borked: only withdrawal will work. And that is good
                                  Dprev = D
                                  D = (Ann * S + D_P * N_COINS) * D / ((Ann - 1) * D + (N_COINS + 1) * D_P)
                                  # Equality with the precision of 1
                                  if D > Dprev:
                                      if D - Dprev <= 1:
                                          break
                                  else:
                                      if Dprev - D <= 1:
                                          break
                              return D
                          
                          
                          @view
                          @internal
                          def get_D_mem(_balances: uint256[N_COINS], amp: uint256) -> uint256:
                              return self.get_D(self._xp_mem(_balances), amp)
                          
                          
                          @view
                          @external
                          def get_virtual_price() -> uint256:
                              """
                              Returns portfolio virtual price (for calculating profit)
                              scaled up by 1e18
                              """
                              D: uint256 = self.get_D(self._xp(), self._A())
                              # D is in the units similar to DAI (e.g. converted to precision 1e18)
                              # When balanced, D = n * x_u - total virtual value of the portfolio
                              token_supply: uint256 = self.token.totalSupply()
                              return D * PRECISION / token_supply
                          
                          
                          @view
                          @external
                          def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256:
                              """
                              Simplified method to calculate addition or reduction in token supply at
                              deposit or withdrawal without taking fees into account (but looking at
                              slippage).
                              Needed to prevent front-running, not for precise calculations!
                              """
                              _balances: uint256[N_COINS] = self.balances
                              amp: uint256 = self._A()
                              D0: uint256 = self.get_D_mem(_balances, amp)
                              for i in range(N_COINS):
                                  if deposit:
                                      _balances[i] += amounts[i]
                                  else:
                                      _balances[i] -= amounts[i]
                              D1: uint256 = self.get_D_mem(_balances, amp)
                              token_amount: uint256 = self.token.totalSupply()
                              diff: uint256 = 0
                              if deposit:
                                  diff = D1 - D0
                              else:
                                  diff = D0 - D1
                              return diff * token_amount / D0
                          
                          
                          @external
                          @nonreentrant('lock')
                          def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256):
                              assert not self.is_killed  # dev: is killed
                          
                              fees: uint256[N_COINS] = empty(uint256[N_COINS])
                              _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
                              _admin_fee: uint256 = self.admin_fee
                              amp: uint256 = self._A()
                          
                              token_supply: uint256 = self.token.totalSupply()
                              # Initial invariant
                              D0: uint256 = 0
                              old_balances: uint256[N_COINS] = self.balances
                              if token_supply > 0:
                                  D0 = self.get_D_mem(old_balances, amp)
                              new_balances: uint256[N_COINS] = old_balances
                          
                              for i in range(N_COINS):
                                  in_amount: uint256 = amounts[i]
                                  if token_supply == 0:
                                      assert in_amount > 0  # dev: initial deposit requires all coins
                                  in_coin: address = self.coins[i]
                          
                                  # Take coins from the sender
                                  if in_amount > 0:
                                      if i == FEE_INDEX:
                                          in_amount = ERC20(in_coin).balanceOf(self)
                          
                                      # "safeTransferFrom" which works for ERC20s which return bool or not
                                      _response: Bytes[32] = raw_call(
                                          in_coin,
                                          concat(
                                              method_id("transferFrom(address,address,uint256)"),
                                              convert(msg.sender, bytes32),
                                              convert(self, bytes32),
                                              convert(amounts[i], bytes32),
                                          ),
                                          max_outsize=32,
                                      )  # dev: failed transfer
                                      if len(_response) > 0:
                                          assert convert(_response, bool)  # dev: failed transfer
                          
                                      if i == FEE_INDEX:
                                          in_amount = ERC20(in_coin).balanceOf(self) - in_amount
                          
                                  new_balances[i] = old_balances[i] + in_amount
                          
                              # Invariant after change
                              D1: uint256 = self.get_D_mem(new_balances, amp)
                              assert D1 > D0
                          
                              # We need to recalculate the invariant accounting for fees
                              # to calculate fair user's share
                              D2: uint256 = D1
                              if token_supply > 0:
                                  # Only account for fees if we are not the first to deposit
                                  for i in range(N_COINS):
                                      ideal_balance: uint256 = D1 * old_balances[i] / D0
                                      difference: uint256 = 0
                                      if ideal_balance > new_balances[i]:
                                          difference = ideal_balance - new_balances[i]
                                      else:
                                          difference = new_balances[i] - ideal_balance
                                      fees[i] = _fee * difference / FEE_DENOMINATOR
                                      self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR)
                                      new_balances[i] -= fees[i]
                                  D2 = self.get_D_mem(new_balances, amp)
                              else:
                                  self.balances = new_balances
                          
                              # Calculate, how much pool tokens to mint
                              mint_amount: uint256 = 0
                              if token_supply == 0:
                                  mint_amount = D1  # Take the dust if there was any
                              else:
                                  mint_amount = token_supply * (D2 - D0) / D0
                          
                              assert mint_amount >= min_mint_amount, "Slippage screwed you"
                          
                              # Mint pool tokens
                              self.token.mint(msg.sender, mint_amount)
                          
                              log AddLiquidity(msg.sender, amounts, fees, D1, token_supply + mint_amount)
                          
                          
                          @view
                          @internal
                          def get_y(i: int128, j: int128, x: uint256, xp_: uint256[N_COINS]) -> uint256:
                              # x in the input is converted to the same price/precision
                          
                              assert i != j       # dev: same coin
                              assert j >= 0       # dev: j below zero
                              assert j < N_COINS  # dev: j above N_COINS
                          
                              # should be unreachable, but good for safety
                              assert i >= 0
                              assert i < N_COINS
                          
                              amp: uint256 = self._A()
                              D: uint256 = self.get_D(xp_, amp)
                              c: uint256 = D
                              S_: uint256 = 0
                              Ann: uint256 = amp * N_COINS
                          
                              _x: uint256 = 0
                              for _i in range(N_COINS):
                                  if _i == i:
                                      _x = x
                                  elif _i != j:
                                      _x = xp_[_i]
                                  else:
                                      continue
                                  S_ += _x
                                  c = c * D / (_x * N_COINS)
                              c = c * D / (Ann * N_COINS)
                              b: uint256 = S_ + D / Ann  # - D
                              y_prev: uint256 = 0
                              y: uint256 = D
                              for _i in range(255):
                                  y_prev = y
                                  y = (y*y + c) / (2 * y + b - D)
                                  # Equality with the precision of 1
                                  if y > y_prev:
                                      if y - y_prev <= 1:
                                          break
                                  else:
                                      if y_prev - y <= 1:
                                          break
                              return y
                          
                          
                          @view
                          @external
                          def get_dy(i: int128, j: int128, dx: uint256) -> uint256:
                              # dx and dy in c-units
                              rates: uint256[N_COINS] = RATES
                              xp: uint256[N_COINS] = self._xp()
                          
                              x: uint256 = xp[i] + (dx * rates[i] / PRECISION)
                              y: uint256 = self.get_y(i, j, x, xp)
                              dy: uint256 = (xp[j] - y - 1) * PRECISION / rates[j]
                              _fee: uint256 = self.fee * dy / FEE_DENOMINATOR
                              return dy - _fee
                          
                          
                          @view
                          @external
                          def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256:
                              # dx and dy in underlying units
                              xp: uint256[N_COINS] = self._xp()
                              precisions: uint256[N_COINS] = PRECISION_MUL
                          
                              x: uint256 = xp[i] + dx * precisions[i]
                              y: uint256 = self.get_y(i, j, x, xp)
                              dy: uint256 = (xp[j] - y - 1) / precisions[j]
                              _fee: uint256 = self.fee * dy / FEE_DENOMINATOR
                              return dy - _fee
                          
                          
                          
                          @external
                          @nonreentrant('lock')
                          def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256):
                              assert not self.is_killed  # dev: is killed
                              rates: uint256[N_COINS] = RATES
                          
                              old_balances: uint256[N_COINS] = self.balances
                              xp: uint256[N_COINS] = self._xp_mem(old_balances)
                          
                              # Handling an unexpected charge of a fee on transfer (USDT, PAXG)
                              dx_w_fee: uint256 = dx
                              input_coin: address = self.coins[i]
                          
                              if i == FEE_INDEX:
                                  dx_w_fee = ERC20(input_coin).balanceOf(self)
                          
                              # "safeTransferFrom" which works for ERC20s which return bool or not
                              _response: Bytes[32] = raw_call(
                                  input_coin,
                                  concat(
                                      method_id("transferFrom(address,address,uint256)"),
                                      convert(msg.sender, bytes32),
                                      convert(self, bytes32),
                                      convert(dx, bytes32),
                                  ),
                                  max_outsize=32,
                              )  # dev: failed transfer
                              if len(_response) > 0:
                                  assert convert(_response, bool)  # dev: failed transfer
                          
                              if i == FEE_INDEX:
                                  dx_w_fee = ERC20(input_coin).balanceOf(self) - dx_w_fee
                          
                              x: uint256 = xp[i] + dx_w_fee * rates[i] / PRECISION
                              y: uint256 = self.get_y(i, j, x, xp)
                          
                              dy: uint256 = xp[j] - y - 1  # -1 just in case there were some rounding errors
                              dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR
                          
                              # Convert all to real units
                              dy = (dy - dy_fee) * PRECISION / rates[j]
                              assert dy >= min_dy, "Exchange resulted in fewer coins than expected"
                          
                              dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR
                              dy_admin_fee = dy_admin_fee * PRECISION / rates[j]
                          
                              # Change balances exactly in same way as we change actual ERC20 coin amounts
                              self.balances[i] = old_balances[i] + dx_w_fee
                              # When rounding errors happen, we undercharge admin fee in favor of LP
                              self.balances[j] = old_balances[j] - dy - dy_admin_fee
                          
                              # "safeTransfer" which works for ERC20s which return bool or not
                              _response = raw_call(
                                  self.coins[j],
                                  concat(
                                      method_id("transfer(address,uint256)"),
                                      convert(msg.sender, bytes32),
                                      convert(dy, bytes32),
                                  ),
                                  max_outsize=32,
                              )  # dev: failed transfer
                              if len(_response) > 0:
                                  assert convert(_response, bool)  # dev: failed transfer
                          
                              log TokenExchange(msg.sender, i, dx, j, dy)
                          
                          
                          @external
                          @nonreentrant('lock')
                          def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]):
                              total_supply: uint256 = self.token.totalSupply()
                              amounts: uint256[N_COINS] = empty(uint256[N_COINS])
                              fees: uint256[N_COINS] = empty(uint256[N_COINS])  # Fees are unused but we've got them historically in event
                          
                              for i in range(N_COINS):
                                  value: uint256 = self.balances[i] * _amount / total_supply
                                  assert value >= min_amounts[i], "Withdrawal resulted in fewer coins than expected"
                                  self.balances[i] -= value
                                  amounts[i] = value
                          
                                  # "safeTransfer" which works for ERC20s which return bool or not
                                  _response: Bytes[32] = raw_call(
                                      self.coins[i],
                                      concat(
                                          method_id("transfer(address,uint256)"),
                                          convert(msg.sender, bytes32),
                                          convert(value, bytes32),
                                      ),
                                      max_outsize=32,
                                  )  # dev: failed transfer
                                  if len(_response) > 0:
                                      assert convert(_response, bool)  # dev: failed transfer
                          
                              self.token.burnFrom(msg.sender, _amount)  # dev: insufficient funds
                          
                              log RemoveLiquidity(msg.sender, amounts, fees, total_supply - _amount)
                          
                          
                          @external
                          @nonreentrant('lock')
                          def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256):
                              assert not self.is_killed  # dev: is killed
                          
                              token_supply: uint256 = self.token.totalSupply()
                              assert token_supply != 0  # dev: zero total supply
                              _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
                              _admin_fee: uint256 = self.admin_fee
                              amp: uint256 = self._A()
                          
                              old_balances: uint256[N_COINS] = self.balances
                              new_balances: uint256[N_COINS] = old_balances
                              D0: uint256 = self.get_D_mem(old_balances, amp)
                              for i in range(N_COINS):
                                  new_balances[i] -= amounts[i]
                              D1: uint256 = self.get_D_mem(new_balances, amp)
                              fees: uint256[N_COINS] = empty(uint256[N_COINS])
                              for i in range(N_COINS):
                                  ideal_balance: uint256 = D1 * old_balances[i] / D0
                                  difference: uint256 = 0
                                  if ideal_balance > new_balances[i]:
                                      difference = ideal_balance - new_balances[i]
                                  else:
                                      difference = new_balances[i] - ideal_balance
                                  fees[i] = _fee * difference / FEE_DENOMINATOR
                                  self.balances[i] = new_balances[i] - (fees[i] * _admin_fee / FEE_DENOMINATOR)
                                  new_balances[i] -= fees[i]
                              D2: uint256 = self.get_D_mem(new_balances, amp)
                          
                              token_amount: uint256 = (D0 - D2) * token_supply / D0
                              assert token_amount != 0  # dev: zero tokens burned
                              token_amount += 1  # In case of rounding errors - make it unfavorable for the "attacker"
                              assert token_amount <= max_burn_amount, "Slippage screwed you"
                          
                              self.token.burnFrom(msg.sender, token_amount)  # dev: insufficient funds
                              for i in range(N_COINS):
                                  if amounts[i] != 0:
                          
                                      # "safeTransfer" which works for ERC20s which return bool or not
                                      _response: Bytes[32] = raw_call(
                                          self.coins[i],
                                          concat(
                                              method_id("transfer(address,uint256)"),
                                              convert(msg.sender, bytes32),
                                              convert(amounts[i], bytes32),
                                          ),
                                          max_outsize=32,
                                      )  # dev: failed transfer
                                      if len(_response) > 0:
                                          assert convert(_response, bool)  # dev: failed transfer
                          
                              log RemoveLiquidityImbalance(msg.sender, amounts, fees, D1, token_supply - token_amount)
                          
                          
                          @view
                          @internal
                          def get_y_D(A_: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256:
                              """
                              Calculate x[i] if one reduces D from being calculated for xp to D
                          
                              Done by solving quadratic equation iteratively.
                              x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
                              x_1**2 + b*x_1 = c
                          
                              x_1 = (x_1**2 + c) / (2*x_1 + b)
                              """
                              # x in the input is converted to the same price/precision
                          
                              assert i >= 0  # dev: i below zero
                              assert i < N_COINS  # dev: i above N_COINS
                          
                              c: uint256 = D
                              S_: uint256 = 0
                              Ann: uint256 = A_ * N_COINS
                          
                              _x: uint256 = 0
                              for _i in range(N_COINS):
                                  if _i != i:
                                      _x = xp[_i]
                                  else:
                                      continue
                                  S_ += _x
                                  c = c * D / (_x * N_COINS)
                              c = c * D / (Ann * N_COINS)
                              b: uint256 = S_ + D / Ann
                              y_prev: uint256 = 0
                              y: uint256 = D
                              for _i in range(255):
                                  y_prev = y
                                  y = (y*y + c) / (2 * y + b - D)
                                  # Equality with the precision of 1
                                  if y > y_prev:
                                      if y - y_prev <= 1:
                                          break
                                  else:
                                      if y_prev - y <= 1:
                                          break
                              return y
                          
                          
                          @view
                          @internal
                          def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256):
                              # First, need to calculate
                              # * Get current D
                              # * Solve Eqn against y_i for D - _token_amount
                              amp: uint256 = self._A()
                              _fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))
                              precisions: uint256[N_COINS] = PRECISION_MUL
                              total_supply: uint256 = self.token.totalSupply()
                          
                              xp: uint256[N_COINS] = self._xp()
                          
                              D0: uint256 = self.get_D(xp, amp)
                              D1: uint256 = D0 - _token_amount * D0 / total_supply
                              xp_reduced: uint256[N_COINS] = xp
                          
                              new_y: uint256 = self.get_y_D(amp, i, xp, D1)
                              dy_0: uint256 = (xp[i] - new_y) / precisions[i]  # w/o fees
                          
                              for j in range(N_COINS):
                                  dx_expected: uint256 = 0
                                  if j == i:
                                      dx_expected = xp[j] * D1 / D0 - new_y
                                  else:
                                      dx_expected = xp[j] - xp[j] * D1 / D0
                                  xp_reduced[j] -= _fee * dx_expected / FEE_DENOMINATOR
                          
                              dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1)
                              dy = (dy - 1) / precisions[i]  # Withdraw less to account for rounding errors
                          
                              return dy, dy_0 - dy
                          
                          
                          @view
                          @external
                          def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256:
                              return self._calc_withdraw_one_coin(_token_amount, i)[0]
                          
                          
                          @external
                          @nonreentrant('lock')
                          def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256):
                              """
                              Remove _amount of liquidity all in a form of coin i
                              """
                              assert not self.is_killed  # dev: is killed
                          
                              dy: uint256 = 0
                              dy_fee: uint256 = 0
                              dy, dy_fee = self._calc_withdraw_one_coin(_token_amount, i)
                              assert dy >= min_amount, "Not enough coins removed"
                          
                              self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR)
                              self.token.burnFrom(msg.sender, _token_amount)  # dev: insufficient funds
                          
                              # "safeTransfer" which works for ERC20s which return bool or not
                              _response: Bytes[32] = raw_call(
                                  self.coins[i],
                                  concat(
                                      method_id("transfer(address,uint256)"),
                                      convert(msg.sender, bytes32),
                                      convert(dy, bytes32),
                                  ),
                                  max_outsize=32,
                              )  # dev: failed transfer
                              if len(_response) > 0:
                                  assert convert(_response, bool)  # dev: failed transfer
                          
                              log RemoveLiquidityOne(msg.sender, _token_amount, dy)
                          
                          
                          ### Admin functions ###
                          @external
                          def ramp_A(_future_A: uint256, _future_time: uint256):
                              assert msg.sender == self.owner  # dev: only owner
                              assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME
                              assert _future_time >= block.timestamp + MIN_RAMP_TIME  # dev: insufficient time
                          
                              _initial_A: uint256 = self._A()
                              assert (_future_A > 0) and (_future_A < MAX_A)
                              assert ((_future_A >= _initial_A) and (_future_A <= _initial_A * MAX_A_CHANGE)) or\
                                     ((_future_A < _initial_A) and (_future_A * MAX_A_CHANGE >= _initial_A))
                              self.initial_A = _initial_A
                              self.future_A = _future_A
                              self.initial_A_time = block.timestamp
                              self.future_A_time = _future_time
                          
                              log RampA(_initial_A, _future_A, block.timestamp, _future_time)
                          
                          
                          @external
                          def stop_ramp_A():
                              assert msg.sender == self.owner  # dev: only owner
                          
                              current_A: uint256 = self._A()
                              self.initial_A = current_A
                              self.future_A = current_A
                              self.initial_A_time = block.timestamp
                              self.future_A_time = block.timestamp
                              # now (block.timestamp < t1) is always False, so we return saved A
                          
                              log StopRampA(current_A, block.timestamp)
                          
                          
                          @external
                          def commit_new_fee(new_fee: uint256, new_admin_fee: uint256):
                              assert msg.sender == self.owner  # dev: only owner
                              assert self.admin_actions_deadline == 0  # dev: active action
                              assert new_fee <= MAX_FEE  # dev: fee exceeds maximum
                              assert new_admin_fee <= MAX_ADMIN_FEE  # dev: admin fee exceeds maximum
                          
                              _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
                              self.admin_actions_deadline = _deadline
                              self.future_fee = new_fee
                              self.future_admin_fee = new_admin_fee
                          
                              log CommitNewFee(_deadline, new_fee, new_admin_fee)
                          
                          
                          @external
                          def apply_new_fee():
                              assert msg.sender == self.owner  # dev: only owner
                              assert block.timestamp >= self.admin_actions_deadline  # dev: insufficient time
                              assert self.admin_actions_deadline != 0  # dev: no active action
                          
                              self.admin_actions_deadline = 0
                              _fee: uint256 = self.future_fee
                              _admin_fee: uint256 = self.future_admin_fee
                              self.fee = _fee
                              self.admin_fee = _admin_fee
                          
                              log NewFee(_fee, _admin_fee)
                          
                          
                          @external
                          def revert_new_parameters():
                              assert msg.sender == self.owner  # dev: only owner
                          
                              self.admin_actions_deadline = 0
                          
                          
                          @external
                          def commit_transfer_ownership(_owner: address):
                              assert msg.sender == self.owner  # dev: only owner
                              assert self.transfer_ownership_deadline == 0  # dev: active transfer
                          
                              _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
                              self.transfer_ownership_deadline = _deadline
                              self.future_owner = _owner
                          
                              log CommitNewAdmin(_deadline, _owner)
                          
                          
                          @external
                          def apply_transfer_ownership():
                              assert msg.sender == self.owner  # dev: only owner
                              assert block.timestamp >= self.transfer_ownership_deadline  # dev: insufficient time
                              assert self.transfer_ownership_deadline != 0  # dev: no active transfer
                          
                              self.transfer_ownership_deadline = 0
                              _owner: address = self.future_owner
                              self.owner = _owner
                          
                              log NewAdmin(_owner)
                          
                          
                          @external
                          def revert_transfer_ownership():
                              assert msg.sender == self.owner  # dev: only owner
                          
                              self.transfer_ownership_deadline = 0
                          
                          
                          @view
                          @external
                          def admin_balances(i: uint256) -> uint256:
                              return ERC20(self.coins[i]).balanceOf(self) - self.balances[i]
                          
                          
                          @external
                          def withdraw_admin_fees():
                              assert msg.sender == self.owner  # dev: only owner
                          
                              for i in range(N_COINS):
                                  c: address = self.coins[i]
                                  value: uint256 = ERC20(c).balanceOf(self) - self.balances[i]
                                  if value > 0:
                                      # "safeTransfer" which works for ERC20s which return bool or not
                                      _response: Bytes[32] = raw_call(
                                          c,
                                          concat(
                                              method_id("transfer(address,uint256)"),
                                              convert(msg.sender, bytes32),
                                              convert(value, bytes32),
                                          ),
                                          max_outsize=32,
                                      )  # dev: failed transfer
                                      if len(_response) > 0:
                                          assert convert(_response, bool)  # dev: failed transfer
                          
                          
                          @external
                          def donate_admin_fees():
                              assert msg.sender == self.owner  # dev: only owner
                              for i in range(N_COINS):
                                  self.balances[i] = ERC20(self.coins[i]).balanceOf(self)
                          
                          
                          @external
                          def kill_me():
                              assert msg.sender == self.owner  # dev: only owner
                              assert self.kill_deadline > block.timestamp  # dev: deadline has passed
                              self.is_killed = True
                          
                          
                          @external
                          def unkill_me():
                              assert msg.sender == self.owner  # dev: only owner
                              self.is_killed = False

                          File 4 of 9: FiatTokenProxy
                          pragma solidity ^0.4.24;
                          
                          // File: zos-lib/contracts/upgradeability/Proxy.sol
                          
                          /**
                           * @title Proxy
                           * @dev Implements delegation of calls to other contracts, with proper
                           * forwarding of return values and bubbling of failures.
                           * It defines a fallback function that delegates all calls to the address
                           * returned by the abstract _implementation() internal function.
                           */
                          contract Proxy {
                            /**
                             * @dev Fallback function.
                             * Implemented entirely in `_fallback`.
                             */
                            function () payable external {
                              _fallback();
                            }
                          
                            /**
                             * @return The Address of the implementation.
                             */
                            function _implementation() internal view returns (address);
                          
                            /**
                             * @dev Delegates execution to an implementation contract.
                             * This is a low level function that doesn't return to its internal call site.
                             * It will return to the external caller whatever the implementation returns.
                             * @param implementation Address to delegate.
                             */
                            function _delegate(address implementation) internal {
                              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)
                          
                                switch result
                                // delegatecall returns 0 on error.
                                case 0 { revert(0, returndatasize) }
                                default { return(0, returndatasize) }
                              }
                            }
                          
                            /**
                             * @dev Function that is run as the first thing in the fallback function.
                             * Can be redefined in derived contracts to add functionality.
                             * Redefinitions must call super._willFallback().
                             */
                            function _willFallback() internal {
                            }
                          
                            /**
                             * @dev fallback implementation.
                             * Extracted to enable manual triggering.
                             */
                            function _fallback() internal {
                              _willFallback();
                              _delegate(_implementation());
                            }
                          }
                          
                          // File: openzeppelin-solidity/contracts/AddressUtils.sol
                          
                          /**
                           * Utility library of inline functions on addresses
                           */
                          library AddressUtils {
                          
                            /**
                             * Returns whether the target address is a contract
                             * @dev This function will return false if invoked during the constructor of a contract,
                             * as the code is not actually created until after the constructor finishes.
                             * @param addr address to check
                             * @return whether the target address is a contract
                             */
                            function isContract(address addr) internal view returns (bool) {
                              uint256 size;
                              // XXX Currently there is no better way to check if there is a contract in an address
                              // than to check the size of the code at that address.
                              // See https://ethereum.stackexchange.com/a/14016/36603
                              // for more details about how this works.
                              // TODO Check this again before the Serenity release, because all addresses will be
                              // contracts then.
                              // solium-disable-next-line security/no-inline-assembly
                              assembly { size := extcodesize(addr) }
                              return size > 0;
                            }
                          
                          }
                          
                          // File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
                          
                          /**
                           * @title UpgradeabilityProxy
                           * @dev This contract implements a proxy that allows to change the
                           * implementation address to which it will delegate.
                           * Such a change is called an implementation upgrade.
                           */
                          contract UpgradeabilityProxy is Proxy {
                            /**
                             * @dev Emitted when the implementation is upgraded.
                             * @param implementation Address of the new implementation.
                             */
                            event Upgraded(address implementation);
                          
                            /**
                             * @dev Storage slot with the address of the current implementation.
                             * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
                             * validated in the constructor.
                             */
                            bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
                          
                            /**
                             * @dev Contract constructor.
                             * @param _implementation Address of the initial implementation.
                             */
                            constructor(address _implementation) public {
                              assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
                          
                              _setImplementation(_implementation);
                            }
                          
                            /**
                             * @dev Returns the current implementation.
                             * @return Address of the current implementation
                             */
                            function _implementation() internal view returns (address impl) {
                              bytes32 slot = IMPLEMENTATION_SLOT;
                              assembly {
                                impl := sload(slot)
                              }
                            }
                          
                            /**
                             * @dev Upgrades the proxy to a new implementation.
                             * @param newImplementation Address of the new implementation.
                             */
                            function _upgradeTo(address newImplementation) internal {
                              _setImplementation(newImplementation);
                              emit Upgraded(newImplementation);
                            }
                          
                            /**
                             * @dev Sets the implementation address of the proxy.
                             * @param newImplementation Address of the new implementation.
                             */
                            function _setImplementation(address newImplementation) private {
                              require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                          
                              bytes32 slot = IMPLEMENTATION_SLOT;
                          
                              assembly {
                                sstore(slot, newImplementation)
                              }
                            }
                          }
                          
                          // File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
                          
                          /**
                           * @title AdminUpgradeabilityProxy
                           * @dev This contract combines an upgradeability proxy with an authorization
                           * mechanism for administrative tasks.
                           * All external functions in this contract must be guarded by the
                           * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                           * feature proposal that would enable this to be done automatically.
                           */
                          contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
                            /**
                             * @dev Emitted when the administration has been transferred.
                             * @param previousAdmin Address of the previous admin.
                             * @param newAdmin Address of the new admin.
                             */
                            event AdminChanged(address previousAdmin, address newAdmin);
                          
                            /**
                             * @dev Storage slot with the admin of the contract.
                             * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
                             * validated in the constructor.
                             */
                            bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
                          
                            /**
                             * @dev Modifier to check whether the `msg.sender` is the admin.
                             * If it is, it will run the function. Otherwise, it will delegate the call
                             * to the implementation.
                             */
                            modifier ifAdmin() {
                              if (msg.sender == _admin()) {
                                _;
                              } else {
                                _fallback();
                              }
                            }
                          
                            /**
                             * Contract constructor.
                             * It sets the `msg.sender` as the proxy administrator.
                             * @param _implementation address of the initial implementation.
                             */
                            constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
                              assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
                          
                              _setAdmin(msg.sender);
                            }
                          
                            /**
                             * @return The address of the proxy admin.
                             */
                            function admin() external view ifAdmin returns (address) {
                              return _admin();
                            }
                          
                            /**
                             * @return The address of the implementation.
                             */
                            function implementation() external view ifAdmin returns (address) {
                              return _implementation();
                            }
                          
                            /**
                             * @dev Changes the admin of the proxy.
                             * Only the current admin can call this function.
                             * @param newAdmin Address to transfer proxy administration to.
                             */
                            function changeAdmin(address newAdmin) external ifAdmin {
                              require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                              emit AdminChanged(_admin(), newAdmin);
                              _setAdmin(newAdmin);
                            }
                          
                            /**
                             * @dev Upgrade the backing implementation of the proxy.
                             * Only the admin can call this function.
                             * @param newImplementation Address of the new implementation.
                             */
                            function upgradeTo(address newImplementation) external ifAdmin {
                              _upgradeTo(newImplementation);
                            }
                          
                            /**
                             * @dev Upgrade the backing implementation of the proxy and call a function
                             * on the new implementation.
                             * This is useful to initialize the proxied contract.
                             * @param newImplementation Address of the new implementation.
                             * @param data Data to send as msg.data in the low level call.
                             * It should include the signature and the parameters of the function to be
                             * called, as described in
                             * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
                             */
                            function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
                              _upgradeTo(newImplementation);
                              require(address(this).call.value(msg.value)(data));
                            }
                          
                            /**
                             * @return The admin slot.
                             */
                            function _admin() internal view returns (address adm) {
                              bytes32 slot = ADMIN_SLOT;
                              assembly {
                                adm := sload(slot)
                              }
                            }
                          
                            /**
                             * @dev Sets the address of the proxy admin.
                             * @param newAdmin Address of the new proxy admin.
                             */
                            function _setAdmin(address newAdmin) internal {
                              bytes32 slot = ADMIN_SLOT;
                          
                              assembly {
                                sstore(slot, newAdmin)
                              }
                            }
                          
                            /**
                             * @dev Only fall back when the sender is not the admin.
                             */
                            function _willFallback() internal {
                              require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                              super._willFallback();
                            }
                          }
                          
                          // File: contracts/FiatTokenProxy.sol
                          
                          /**
                          * Copyright CENTRE SECZ 2018
                          *
                          * Permission is hereby granted, free of charge, to any person obtaining a copy 
                          * of this software and associated documentation files (the "Software"), to deal 
                          * in the Software without restriction, including without limitation the rights 
                          * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
                          * copies of the Software, and to permit persons to whom the Software is furnished to 
                          * do so, subject to the following conditions:
                          *
                          * The above copyright notice and this permission notice shall be included in all 
                          * copies or substantial portions of the Software.
                          *
                          * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
                          * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
                          * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
                          * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
                          * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
                          * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
                          */
                          
                          pragma solidity ^0.4.24;
                          
                          
                          /**
                           * @title FiatTokenProxy
                           * @dev This contract proxies FiatToken calls and enables FiatToken upgrades
                          */ 
                          contract FiatTokenProxy is AdminUpgradeabilityProxy {
                              constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
                              }
                          }

                          File 5 of 9: 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 6 of 9: 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 payable 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 payable 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 payable 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 7 of 9: FiatTokenV2_2
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { EIP712Domain } from "./EIP712Domain.sol"; // solhint-disable-line no-unused-import
                          import { Blacklistable } from "../v1/Blacklistable.sol"; // solhint-disable-line no-unused-import
                          import { FiatTokenV1 } from "../v1/FiatTokenV1.sol"; // solhint-disable-line no-unused-import
                          import { FiatTokenV2 } from "./FiatTokenV2.sol"; // solhint-disable-line no-unused-import
                          import { FiatTokenV2_1 } from "./FiatTokenV2_1.sol";
                          import { EIP712 } from "../util/EIP712.sol";
                          // solhint-disable func-name-mixedcase
                          /**
                           * @title FiatToken V2.2
                           * @notice ERC20 Token backed by fiat reserves, version 2.2
                           */
                          contract FiatTokenV2_2 is FiatTokenV2_1 {
                              /**
                               * @notice Initialize v2.2
                               * @param accountsToBlacklist   A list of accounts to migrate from the old blacklist
                               * @param newSymbol             New token symbol
                               * data structure to the new blacklist data structure.
                               */
                              function initializeV2_2(
                                  address[] calldata accountsToBlacklist,
                                  string calldata newSymbol
                              ) external {
                                  // solhint-disable-next-line reason-string
                                  require(_initializedVersion == 2);
                                  // Update fiat token symbol
                                  symbol = newSymbol;
                                  // Add previously blacklisted accounts to the new blacklist data structure
                                  // and remove them from the old blacklist data structure.
                                  for (uint256 i = 0; i < accountsToBlacklist.length; i++) {
                                      require(
                                          _deprecatedBlacklisted[accountsToBlacklist[i]],
                                          "FiatTokenV2_2: Blacklisting previously unblacklisted account!"
                                      );
                                      _blacklist(accountsToBlacklist[i]);
                                      delete _deprecatedBlacklisted[accountsToBlacklist[i]];
                                  }
                                  _blacklist(address(this));
                                  delete _deprecatedBlacklisted[address(this)];
                                  _initializedVersion = 3;
                              }
                              /**
                               * @dev Internal function to get the current chain id.
                               * @return The current chain id.
                               */
                              function _chainId() internal virtual view returns (uint256) {
                                  uint256 chainId;
                                  assembly {
                                      chainId := chainid()
                                  }
                                  return chainId;
                              }
                              /**
                               * @inheritdoc EIP712Domain
                               */
                              function _domainSeparator() internal override view returns (bytes32) {
                                  return EIP712.makeDomainSeparator(name, "2", _chainId());
                              }
                              /**
                               * @notice Update allowance with a signed permit
                               * @dev EOA wallet signatures should be packed in the order of r, s, v.
                               * @param owner       Token owner's address (Authorizer)
                               * @param spender     Spender's address
                               * @param value       Amount of allowance
                               * @param deadline    The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                               * @param signature   Signature bytes signed by an EOA wallet or a contract wallet
                               */
                              function permit(
                                  address owner,
                                  address spender,
                                  uint256 value,
                                  uint256 deadline,
                                  bytes memory signature
                              ) external whenNotPaused {
                                  _permit(owner, spender, value, deadline, signature);
                              }
                              /**
                               * @notice Execute a transfer with a signed authorization
                               * @dev EOA wallet signatures should be packed in the order of r, s, v.
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                               */
                              function transferWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  bytes memory signature
                              ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                  _transferWithAuthorization(
                                      from,
                                      to,
                                      value,
                                      validAfter,
                                      validBefore,
                                      nonce,
                                      signature
                                  );
                              }
                              /**
                               * @notice Receive a transfer with a signed authorization from the payer
                               * @dev This has an additional check to ensure that the payee's address
                               * matches the caller of this function to prevent front-running attacks.
                               * EOA wallet signatures should be packed in the order of r, s, v.
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                               */
                              function receiveWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  bytes memory signature
                              ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                  _receiveWithAuthorization(
                                      from,
                                      to,
                                      value,
                                      validAfter,
                                      validBefore,
                                      nonce,
                                      signature
                                  );
                              }
                              /**
                               * @notice Attempt to cancel an authorization
                               * @dev Works only if the authorization is not yet used.
                               * EOA wallet signatures should be packed in the order of r, s, v.
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               * @param signature     Signature bytes signed by an EOA wallet or a contract wallet
                               */
                              function cancelAuthorization(
                                  address authorizer,
                                  bytes32 nonce,
                                  bytes memory signature
                              ) external whenNotPaused {
                                  _cancelAuthorization(authorizer, nonce, signature);
                              }
                              /**
                               * @dev Helper method that sets the blacklist state of an account on balanceAndBlacklistStates.
                               * If _shouldBlacklist is true, we apply a (1 << 255) bitmask with an OR operation on the
                               * account's balanceAndBlacklistState. This flips the high bit for the account to 1,
                               * indicating that the account is blacklisted.
                               *
                               * If _shouldBlacklist if false, we reset the account's balanceAndBlacklistStates to their
                               * balances. This clears the high bit for the account, indicating that the account is unblacklisted.
                               * @param _account         The address of the account.
                               * @param _shouldBlacklist True if the account should be blacklisted, false if the account should be unblacklisted.
                               */
                              function _setBlacklistState(address _account, bool _shouldBlacklist)
                                  internal
                                  override
                              {
                                  balanceAndBlacklistStates[_account] = _shouldBlacklist
                                      ? balanceAndBlacklistStates[_account] | (1 << 255)
                                      : _balanceOf(_account);
                              }
                              /**
                               * @dev Helper method that sets the balance of an account on balanceAndBlacklistStates.
                               * Since balances are stored in the last 255 bits of the balanceAndBlacklistStates value,
                               * we need to ensure that the updated balance does not exceed (2^255 - 1).
                               * Since blacklisted accounts' balances cannot be updated, the method will also
                               * revert if the account is blacklisted
                               * @param _account The address of the account.
                               * @param _balance The new fiat token balance of the account (max: (2^255 - 1)).
                               */
                              function _setBalance(address _account, uint256 _balance) internal override {
                                  require(
                                      _balance <= ((1 << 255) - 1),
                                      "FiatTokenV2_2: Balance exceeds (2^255 - 1)"
                                  );
                                  require(
                                      !_isBlacklisted(_account),
                                      "FiatTokenV2_2: Account is blacklisted"
                                  );
                                  balanceAndBlacklistStates[_account] = _balance;
                              }
                              /**
                               * @inheritdoc Blacklistable
                               */
                              function _isBlacklisted(address _account)
                                  internal
                                  override
                                  view
                                  returns (bool)
                              {
                                  return balanceAndBlacklistStates[_account] >> 255 == 1;
                              }
                              /**
                               * @dev Helper method to obtain the balance of an account. Since balances
                               * are stored in the last 255 bits of the balanceAndBlacklistStates value,
                               * we apply a ((1 << 255) - 1) bit bitmask with an AND operation on the
                               * balanceAndBlacklistState to obtain the balance.
                               * @param _account  The address of the account.
                               * @return          The fiat token balance of the account.
                               */
                              function _balanceOf(address _account)
                                  internal
                                  override
                                  view
                                  returns (uint256)
                              {
                                  return balanceAndBlacklistStates[_account] & ((1 << 255) - 1);
                              }
                              /**
                               * @inheritdoc FiatTokenV1
                               */
                              function approve(address spender, uint256 value)
                                  external
                                  override
                                  whenNotPaused
                                  returns (bool)
                              {
                                  _approve(msg.sender, spender, value);
                                  return true;
                              }
                              /**
                               * @inheritdoc FiatTokenV2
                               */
                              function permit(
                                  address owner,
                                  address spender,
                                  uint256 value,
                                  uint256 deadline,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) external override whenNotPaused {
                                  _permit(owner, spender, value, deadline, v, r, s);
                              }
                              /**
                               * @inheritdoc FiatTokenV2
                               */
                              function increaseAllowance(address spender, uint256 increment)
                                  external
                                  override
                                  whenNotPaused
                                  returns (bool)
                              {
                                  _increaseAllowance(msg.sender, spender, increment);
                                  return true;
                              }
                              /**
                               * @inheritdoc FiatTokenV2
                               */
                              function decreaseAllowance(address spender, uint256 decrement)
                                  external
                                  override
                                  whenNotPaused
                                  returns (bool)
                              {
                                  _decreaseAllowance(msg.sender, spender, decrement);
                                  return true;
                              }
                          }
                          // SPDX-License-Identifier: MIT
                          pragma solidity >=0.6.2 <0.8.0;
                          /**
                           * @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
                               * ====
                               */
                              function isContract(address account) internal view returns (bool) {
                                  // This method relies on extcodesize, which returns 0 for contracts in
                                  // construction, since the code is only stored at the end of the
                                  // constructor execution.
                                  uint256 size;
                                  // solhint-disable-next-line no-inline-assembly
                                  assembly { size := extcodesize(account) }
                                  return size > 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");
                                  // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                                  (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 functionCall(target, data, "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");
                                  require(isContract(target), "Address: call to non-contract");
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = target.call{ value: value }(data);
                                  return _verifyCallResult(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) {
                                  require(isContract(target), "Address: static call to non-contract");
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = target.staticcall(data);
                                  return _verifyCallResult(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) {
                                  require(isContract(target), "Address: delegate call to non-contract");
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (bool success, bytes memory returndata) = target.delegatecall(data);
                                  return _verifyCallResult(success, returndata, errorMessage);
                              }
                              function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                                  if (success) {
                                      return returndata;
                                  } else {
                                      // 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
                                          // solhint-disable-next-line no-inline-assembly
                                          assembly {
                                              let returndata_size := mload(returndata)
                                              revert(add(32, returndata), returndata_size)
                                          }
                                      } else {
                                          revert(errorMessage);
                                      }
                                  }
                              }
                          }
                          // SPDX-License-Identifier: MIT
                          pragma solidity >=0.6.0 <0.8.0;
                          import "./IERC20.sol";
                          import "../../math/SafeMath.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 SafeMath for uint256;
                              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'
                                  // solhint-disable-next-line max-line-length
                                  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).add(value);
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                              function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                  uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                              }
                              /**
                               * @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
                                      // solhint-disable-next-line max-line-length
                                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                  }
                              }
                          }
                          // SPDX-License-Identifier: MIT
                          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);
                          }
                          // SPDX-License-Identifier: MIT
                          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;
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { FiatTokenV2 } from "./FiatTokenV2.sol";
                          // solhint-disable func-name-mixedcase
                          /**
                           * @title FiatToken V2.1
                           * @notice ERC20 Token backed by fiat reserves, version 2.1
                           */
                          contract FiatTokenV2_1 is FiatTokenV2 {
                              /**
                               * @notice Initialize v2.1
                               * @param lostAndFound  The address to which the locked funds are sent
                               */
                              function initializeV2_1(address lostAndFound) external {
                                  // solhint-disable-next-line reason-string
                                  require(_initializedVersion == 1);
                                  uint256 lockedAmount = _balanceOf(address(this));
                                  if (lockedAmount > 0) {
                                      _transfer(address(this), lostAndFound, lockedAmount);
                                  }
                                  _blacklist(address(this));
                                  _initializedVersion = 2;
                              }
                              /**
                               * @notice Version string for the EIP712 domain separator
                               * @return Version string
                               */
                              function version() external pure returns (string memory) {
                                  return "2";
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { FiatTokenV1_1 } from "../v1.1/FiatTokenV1_1.sol";
                          import { EIP712 } from "../util/EIP712.sol";
                          import { EIP3009 } from "./EIP3009.sol";
                          import { EIP2612 } from "./EIP2612.sol";
                          /**
                           * @title FiatToken V2
                           * @notice ERC20 Token backed by fiat reserves, version 2
                           */
                          contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
                              uint8 internal _initializedVersion;
                              /**
                               * @notice Initialize v2
                               * @param newName   New token name
                               */
                              function initializeV2(string calldata newName) external {
                                  // solhint-disable-next-line reason-string
                                  require(initialized && _initializedVersion == 0);
                                  name = newName;
                                  _DEPRECATED_CACHED_DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(
                                      newName,
                                      "2"
                                  );
                                  _initializedVersion = 1;
                              }
                              /**
                               * @notice Increase the allowance by a given increment
                               * @param spender   Spender's address
                               * @param increment Amount of increase in allowance
                               * @return True if successful
                               */
                              function increaseAllowance(address spender, uint256 increment)
                                  external
                                  virtual
                                  whenNotPaused
                                  notBlacklisted(msg.sender)
                                  notBlacklisted(spender)
                                  returns (bool)
                              {
                                  _increaseAllowance(msg.sender, spender, increment);
                                  return true;
                              }
                              /**
                               * @notice Decrease the allowance by a given decrement
                               * @param spender   Spender's address
                               * @param decrement Amount of decrease in allowance
                               * @return True if successful
                               */
                              function decreaseAllowance(address spender, uint256 decrement)
                                  external
                                  virtual
                                  whenNotPaused
                                  notBlacklisted(msg.sender)
                                  notBlacklisted(spender)
                                  returns (bool)
                              {
                                  _decreaseAllowance(msg.sender, spender, decrement);
                                  return true;
                              }
                              /**
                               * @notice Execute a transfer with a signed authorization
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param v             v of the signature
                               * @param r             r of the signature
                               * @param s             s of the signature
                               */
                              function transferWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                  _transferWithAuthorization(
                                      from,
                                      to,
                                      value,
                                      validAfter,
                                      validBefore,
                                      nonce,
                                      v,
                                      r,
                                      s
                                  );
                              }
                              /**
                               * @notice Receive a transfer with a signed authorization from the payer
                               * @dev This has an additional check to ensure that the payee's address
                               * matches the caller of this function to prevent front-running attacks.
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param v             v of the signature
                               * @param r             r of the signature
                               * @param s             s of the signature
                               */
                              function receiveWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                                  _receiveWithAuthorization(
                                      from,
                                      to,
                                      value,
                                      validAfter,
                                      validBefore,
                                      nonce,
                                      v,
                                      r,
                                      s
                                  );
                              }
                              /**
                               * @notice Attempt to cancel an authorization
                               * @dev Works only if the authorization is not yet used.
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               * @param v             v of the signature
                               * @param r             r of the signature
                               * @param s             s of the signature
                               */
                              function cancelAuthorization(
                                  address authorizer,
                                  bytes32 nonce,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) external whenNotPaused {
                                  _cancelAuthorization(authorizer, nonce, v, r, s);
                              }
                              /**
                               * @notice Update allowance with a signed permit
                               * @param owner       Token owner's address (Authorizer)
                               * @param spender     Spender's address
                               * @param value       Amount of allowance
                               * @param deadline    The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                               * @param v           v of the signature
                               * @param r           r of the signature
                               * @param s           s of the signature
                               */
                              function permit(
                                  address owner,
                                  address spender,
                                  uint256 value,
                                  uint256 deadline,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              )
                                  external
                                  virtual
                                  whenNotPaused
                                  notBlacklisted(owner)
                                  notBlacklisted(spender)
                              {
                                  _permit(owner, spender, value, deadline, v, r, s);
                              }
                              /**
                               * @dev Internal function to increase the allowance by a given increment
                               * @param owner     Token owner's address
                               * @param spender   Spender's address
                               * @param increment Amount of increase
                               */
                              function _increaseAllowance(
                                  address owner,
                                  address spender,
                                  uint256 increment
                              ) internal override {
                                  _approve(owner, spender, allowed[owner][spender].add(increment));
                              }
                              /**
                               * @dev Internal function to decrease the allowance by a given decrement
                               * @param owner     Token owner's address
                               * @param spender   Spender's address
                               * @param decrement Amount of decrease
                               */
                              function _decreaseAllowance(
                                  address owner,
                                  address spender,
                                  uint256 decrement
                              ) internal override {
                                  _approve(
                                      owner,
                                      spender,
                                      allowed[owner][spender].sub(
                                          decrement,
                                          "ERC20: decreased allowance below zero"
                                      )
                                  );
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          // solhint-disable func-name-mixedcase
                          /**
                           * @title EIP712 Domain
                           */
                          contract EIP712Domain {
                              // was originally DOMAIN_SEPARATOR
                              // but that has been moved to a method so we can override it in V2_2+
                              bytes32 internal _DEPRECATED_CACHED_DOMAIN_SEPARATOR;
                              /**
                               * @notice Get the EIP712 Domain Separator.
                               * @return The bytes32 EIP712 domain separator.
                               */
                              function DOMAIN_SEPARATOR() external view returns (bytes32) {
                                  return _domainSeparator();
                              }
                              /**
                               * @dev Internal method to get the EIP712 Domain Separator.
                               * @return The bytes32 EIP712 domain separator.
                               */
                              function _domainSeparator() internal virtual view returns (bytes32) {
                                  return _DEPRECATED_CACHED_DOMAIN_SEPARATOR;
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { AbstractFiatTokenV2 } from "./AbstractFiatTokenV2.sol";
                          import { EIP712Domain } from "./EIP712Domain.sol";
                          import { SignatureChecker } from "../util/SignatureChecker.sol";
                          import { MessageHashUtils } from "../util/MessageHashUtils.sol";
                          /**
                           * @title EIP-3009
                           * @notice Provide internal implementation for gas-abstracted transfers
                           * @dev Contracts that inherit from this must wrap these with publicly
                           * accessible functions, optionally adding modifiers where necessary
                           */
                          abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
                              // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
                              bytes32
                                  public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
                              // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
                              bytes32
                                  public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
                              // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
                              bytes32
                                  public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
                              /**
                               * @dev authorizer address => nonce => bool (true if nonce is used)
                               */
                              mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
                              event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
                              event AuthorizationCanceled(
                                  address indexed authorizer,
                                  bytes32 indexed nonce
                              );
                              /**
                               * @notice Returns the state of an authorization
                               * @dev Nonces are randomly generated 32-byte data unique to the
                               * authorizer's address
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               * @return True if the nonce is used
                               */
                              function authorizationState(address authorizer, bytes32 nonce)
                                  external
                                  view
                                  returns (bool)
                              {
                                  return _authorizationStates[authorizer][nonce];
                              }
                              /**
                               * @notice Execute a transfer with a signed authorization
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param v             v of the signature
                               * @param r             r of the signature
                               * @param s             s of the signature
                               */
                              function _transferWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) internal {
                                  _transferWithAuthorization(
                                      from,
                                      to,
                                      value,
                                      validAfter,
                                      validBefore,
                                      nonce,
                                      abi.encodePacked(r, s, v)
                                  );
                              }
                              /**
                               * @notice Execute a transfer with a signed authorization
                               * @dev EOA wallet signatures should be packed in the order of r, s, v.
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                               */
                              function _transferWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  bytes memory signature
                              ) internal {
                                  _requireValidAuthorization(from, nonce, validAfter, validBefore);
                                  _requireValidSignature(
                                      from,
                                      keccak256(
                                          abi.encode(
                                              TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
                                              from,
                                              to,
                                              value,
                                              validAfter,
                                              validBefore,
                                              nonce
                                          )
                                      ),
                                      signature
                                  );
                                  _markAuthorizationAsUsed(from, nonce);
                                  _transfer(from, to, value);
                              }
                              /**
                               * @notice Receive a transfer with a signed authorization from the payer
                               * @dev This has an additional check to ensure that the payee's address
                               * matches the caller of this function to prevent front-running attacks.
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param v             v of the signature
                               * @param r             r of the signature
                               * @param s             s of the signature
                               */
                              function _receiveWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) internal {
                                  _receiveWithAuthorization(
                                      from,
                                      to,
                                      value,
                                      validAfter,
                                      validBefore,
                                      nonce,
                                      abi.encodePacked(r, s, v)
                                  );
                              }
                              /**
                               * @notice Receive a transfer with a signed authorization from the payer
                               * @dev This has an additional check to ensure that the payee's address
                               * matches the caller of this function to prevent front-running attacks.
                               * EOA wallet signatures should be packed in the order of r, s, v.
                               * @param from          Payer's address (Authorizer)
                               * @param to            Payee's address
                               * @param value         Amount to be transferred
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               * @param nonce         Unique nonce
                               * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                               */
                              function _receiveWithAuthorization(
                                  address from,
                                  address to,
                                  uint256 value,
                                  uint256 validAfter,
                                  uint256 validBefore,
                                  bytes32 nonce,
                                  bytes memory signature
                              ) internal {
                                  require(to == msg.sender, "FiatTokenV2: caller must be the payee");
                                  _requireValidAuthorization(from, nonce, validAfter, validBefore);
                                  _requireValidSignature(
                                      from,
                                      keccak256(
                                          abi.encode(
                                              RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
                                              from,
                                              to,
                                              value,
                                              validAfter,
                                              validBefore,
                                              nonce
                                          )
                                      ),
                                      signature
                                  );
                                  _markAuthorizationAsUsed(from, nonce);
                                  _transfer(from, to, value);
                              }
                              /**
                               * @notice Attempt to cancel an authorization
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               * @param v             v of the signature
                               * @param r             r of the signature
                               * @param s             s of the signature
                               */
                              function _cancelAuthorization(
                                  address authorizer,
                                  bytes32 nonce,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) internal {
                                  _cancelAuthorization(authorizer, nonce, abi.encodePacked(r, s, v));
                              }
                              /**
                               * @notice Attempt to cancel an authorization
                               * @dev EOA wallet signatures should be packed in the order of r, s, v.
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                               */
                              function _cancelAuthorization(
                                  address authorizer,
                                  bytes32 nonce,
                                  bytes memory signature
                              ) internal {
                                  _requireUnusedAuthorization(authorizer, nonce);
                                  _requireValidSignature(
                                      authorizer,
                                      keccak256(
                                          abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce)
                                      ),
                                      signature
                                  );
                                  _authorizationStates[authorizer][nonce] = true;
                                  emit AuthorizationCanceled(authorizer, nonce);
                              }
                              /**
                               * @notice Validates that signature against input data struct
                               * @param signer        Signer's address
                               * @param dataHash      Hash of encoded data struct
                               * @param signature     Signature byte array produced by an EOA wallet or a contract wallet
                               */
                              function _requireValidSignature(
                                  address signer,
                                  bytes32 dataHash,
                                  bytes memory signature
                              ) private view {
                                  require(
                                      SignatureChecker.isValidSignatureNow(
                                          signer,
                                          MessageHashUtils.toTypedDataHash(_domainSeparator(), dataHash),
                                          signature
                                      ),
                                      "FiatTokenV2: invalid signature"
                                  );
                              }
                              /**
                               * @notice Check that an authorization is unused
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               */
                              function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
                                  private
                                  view
                              {
                                  require(
                                      !_authorizationStates[authorizer][nonce],
                                      "FiatTokenV2: authorization is used or canceled"
                                  );
                              }
                              /**
                               * @notice Check that authorization is valid
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               * @param validAfter    The time after which this is valid (unix time)
                               * @param validBefore   The time before which this is valid (unix time)
                               */
                              function _requireValidAuthorization(
                                  address authorizer,
                                  bytes32 nonce,
                                  uint256 validAfter,
                                  uint256 validBefore
                              ) private view {
                                  require(
                                      now > validAfter,
                                      "FiatTokenV2: authorization is not yet valid"
                                  );
                                  require(now < validBefore, "FiatTokenV2: authorization is expired");
                                  _requireUnusedAuthorization(authorizer, nonce);
                              }
                              /**
                               * @notice Mark an authorization as used
                               * @param authorizer    Authorizer's address
                               * @param nonce         Nonce of the authorization
                               */
                              function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
                                  private
                              {
                                  _authorizationStates[authorizer][nonce] = true;
                                  emit AuthorizationUsed(authorizer, nonce);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { AbstractFiatTokenV2 } from "./AbstractFiatTokenV2.sol";
                          import { EIP712Domain } from "./EIP712Domain.sol";
                          import { MessageHashUtils } from "../util/MessageHashUtils.sol";
                          import { SignatureChecker } from "../util/SignatureChecker.sol";
                          /**
                           * @title EIP-2612
                           * @notice Provide internal implementation for gas-abstracted approvals
                           */
                          abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
                              // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                              bytes32
                                  public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
                              mapping(address => uint256) private _permitNonces;
                              /**
                               * @notice Nonces for permit
                               * @param owner Token owner's address (Authorizer)
                               * @return Next nonce
                               */
                              function nonces(address owner) external view returns (uint256) {
                                  return _permitNonces[owner];
                              }
                              /**
                               * @notice Verify a signed approval permit and execute if valid
                               * @param owner     Token owner's address (Authorizer)
                               * @param spender   Spender's address
                               * @param value     Amount of allowance
                               * @param deadline  The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                               * @param v         v of the signature
                               * @param r         r of the signature
                               * @param s         s of the signature
                               */
                              function _permit(
                                  address owner,
                                  address spender,
                                  uint256 value,
                                  uint256 deadline,
                                  uint8 v,
                                  bytes32 r,
                                  bytes32 s
                              ) internal {
                                  _permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
                              }
                              /**
                               * @notice Verify a signed approval permit and execute if valid
                               * @dev EOA wallet signatures should be packed in the order of r, s, v.
                               * @param owner      Token owner's address (Authorizer)
                               * @param spender    Spender's address
                               * @param value      Amount of allowance
                               * @param deadline   The time at which the signature expires (unix time), or max uint256 value to signal no expiration
                               * @param signature  Signature byte array signed by an EOA wallet or a contract wallet
                               */
                              function _permit(
                                  address owner,
                                  address spender,
                                  uint256 value,
                                  uint256 deadline,
                                  bytes memory signature
                              ) internal {
                                  require(
                                      deadline == type(uint256).max || deadline >= now,
                                      "FiatTokenV2: permit is expired"
                                  );
                                  bytes32 typedDataHash = MessageHashUtils.toTypedDataHash(
                                      _domainSeparator(),
                                      keccak256(
                                          abi.encode(
                                              PERMIT_TYPEHASH,
                                              owner,
                                              spender,
                                              value,
                                              _permitNonces[owner]++,
                                              deadline
                                          )
                                      )
                                  );
                                  require(
                                      SignatureChecker.isValidSignatureNow(
                                          owner,
                                          typedDataHash,
                                          signature
                                      ),
                                      "EIP2612: invalid signature"
                                  );
                                  _approve(owner, spender, value);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { AbstractFiatTokenV1 } from "../v1/AbstractFiatTokenV1.sol";
                          abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
                              function _increaseAllowance(
                                  address owner,
                                  address spender,
                                  uint256 increment
                              ) internal virtual;
                              function _decreaseAllowance(
                                  address owner,
                                  address spender,
                                  uint256 decrement
                              ) internal virtual;
                          }
                          /**
                           * SPDX-License-Identifier: MIT
                           *
                           * Copyright (c) 2016 Smart Contract Solutions, Inc.
                           * Copyright (c) 2018-2020 CENTRE SECZ
                           *
                           * Permission is hereby granted, free of charge, to any person obtaining a copy
                           * of this software and associated documentation files (the "Software"), to deal
                           * in the Software without restriction, including without limitation the rights
                           * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                           * copies of the Software, and to permit persons to whom the Software is
                           * furnished to do so, subject to the following conditions:
                           *
                           * The above copyright notice and this permission notice shall be included in
                           * copies or substantial portions of the Software.
                           *
                           * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                           * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                           * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                           * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                           * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                           * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                           * SOFTWARE.
                           */
                          pragma solidity 0.6.12;
                          import { Ownable } from "./Ownable.sol";
                          /**
                           * @notice Base contract which allows children to implement an emergency stop
                           * mechanism
                           * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
                           * Modifications:
                           * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
                           * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
                           * 3. Removed whenPaused (6/14/2018)
                           * 4. Switches ownable library to use ZeppelinOS (7/12/18)
                           * 5. Remove constructor (7/13/18)
                           * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
                           * 7. Make public functions external (5/27/20)
                           */
                          contract Pausable is Ownable {
                              event Pause();
                              event Unpause();
                              event PauserChanged(address indexed newAddress);
                              address public pauser;
                              bool public paused = false;
                              /**
                               * @dev Modifier to make a function callable only when the contract is not paused.
                               */
                              modifier whenNotPaused() {
                                  require(!paused, "Pausable: paused");
                                  _;
                              }
                              /**
                               * @dev throws if called by any account other than the pauser
                               */
                              modifier onlyPauser() {
                                  require(msg.sender == pauser, "Pausable: caller is not the pauser");
                                  _;
                              }
                              /**
                               * @dev called by the owner to pause, triggers stopped state
                               */
                              function pause() external onlyPauser {
                                  paused = true;
                                  emit Pause();
                              }
                              /**
                               * @dev called by the owner to unpause, returns to normal state
                               */
                              function unpause() external onlyPauser {
                                  paused = false;
                                  emit Unpause();
                              }
                              /**
                               * @notice Updates the pauser address.
                               * @param _newPauser The address of the new pauser.
                               */
                              function updatePauser(address _newPauser) external onlyOwner {
                                  require(
                                      _newPauser != address(0),
                                      "Pausable: new pauser is the zero address"
                                  );
                                  pauser = _newPauser;
                                  emit PauserChanged(pauser);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: MIT
                           *
                           * Copyright (c) 2018 zOS Global Limited.
                           * Copyright (c) 2018-2020 CENTRE SECZ
                           *
                           * Permission is hereby granted, free of charge, to any person obtaining a copy
                           * of this software and associated documentation files (the "Software"), to deal
                           * in the Software without restriction, including without limitation the rights
                           * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                           * copies of the Software, and to permit persons to whom the Software is
                           * furnished to do so, subject to the following conditions:
                           *
                           * The above copyright notice and this permission notice shall be included in
                           * copies or substantial portions of the Software.
                           *
                           * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                           * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                           * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                           * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                           * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                           * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                           * SOFTWARE.
                           */
                          pragma solidity 0.6.12;
                          /**
                           * @notice The Ownable contract has an owner address, and provides basic
                           * authorization control functions
                           * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
                           * Modifications:
                           * 1. Consolidate OwnableStorage into this contract (7/13/18)
                           * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
                           * 3. Make public functions external (5/27/20)
                           */
                          contract Ownable {
                              // Owner of the contract
                              address private _owner;
                              /**
                               * @dev Event to show ownership has been transferred
                               * @param previousOwner representing the address of the previous owner
                               * @param newOwner representing the address of the new owner
                               */
                              event OwnershipTransferred(address previousOwner, address newOwner);
                              /**
                               * @dev The constructor sets the original owner of the contract to the sender account.
                               */
                              constructor() public {
                                  setOwner(msg.sender);
                              }
                              /**
                               * @dev Tells the address of the owner
                               * @return the address of the owner
                               */
                              function owner() external view returns (address) {
                                  return _owner;
                              }
                              /**
                               * @dev Sets a new owner address
                               */
                              function setOwner(address newOwner) internal {
                                  _owner = newOwner;
                              }
                              /**
                               * @dev Throws if called by any account other than the owner.
                               */
                              modifier onlyOwner() {
                                  require(msg.sender == _owner, "Ownable: caller is not the owner");
                                  _;
                              }
                              /**
                               * @dev Allows the current owner to transfer control of the contract to a newOwner.
                               * @param newOwner The address to transfer ownership to.
                               */
                              function transferOwnership(address newOwner) external onlyOwner {
                                  require(
                                      newOwner != address(0),
                                      "Ownable: new owner is the zero address"
                                  );
                                  emit OwnershipTransferred(_owner, newOwner);
                                  setOwner(newOwner);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
                          import { AbstractFiatTokenV1 } from "./AbstractFiatTokenV1.sol";
                          import { Ownable } from "./Ownable.sol";
                          import { Pausable } from "./Pausable.sol";
                          import { Blacklistable } from "./Blacklistable.sol";
                          /**
                           * @title FiatToken
                           * @dev ERC20 Token backed by fiat reserves
                           */
                          contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
                              using SafeMath for uint256;
                              string public name;
                              string public symbol;
                              uint8 public decimals;
                              string public currency;
                              address public masterMinter;
                              bool internal initialized;
                              /// @dev A mapping that stores the balance and blacklist states for a given address.
                              /// The first bit defines whether the address is blacklisted (1 if blacklisted, 0 otherwise).
                              /// The last 255 bits define the balance for the address.
                              mapping(address => uint256) internal balanceAndBlacklistStates;
                              mapping(address => mapping(address => uint256)) internal allowed;
                              uint256 internal totalSupply_ = 0;
                              mapping(address => bool) internal minters;
                              mapping(address => uint256) internal minterAllowed;
                              event Mint(address indexed minter, address indexed to, uint256 amount);
                              event Burn(address indexed burner, uint256 amount);
                              event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
                              event MinterRemoved(address indexed oldMinter);
                              event MasterMinterChanged(address indexed newMasterMinter);
                              /**
                               * @notice Initializes the fiat token contract.
                               * @param tokenName       The name of the fiat token.
                               * @param tokenSymbol     The symbol of the fiat token.
                               * @param tokenCurrency   The fiat currency that the token represents.
                               * @param tokenDecimals   The number of decimals that the token uses.
                               * @param newMasterMinter The masterMinter address for the fiat token.
                               * @param newPauser       The pauser address for the fiat token.
                               * @param newBlacklister  The blacklister address for the fiat token.
                               * @param newOwner        The owner of the fiat token.
                               */
                              function initialize(
                                  string memory tokenName,
                                  string memory tokenSymbol,
                                  string memory tokenCurrency,
                                  uint8 tokenDecimals,
                                  address newMasterMinter,
                                  address newPauser,
                                  address newBlacklister,
                                  address newOwner
                              ) public {
                                  require(!initialized, "FiatToken: contract is already initialized");
                                  require(
                                      newMasterMinter != address(0),
                                      "FiatToken: new masterMinter is the zero address"
                                  );
                                  require(
                                      newPauser != address(0),
                                      "FiatToken: new pauser is the zero address"
                                  );
                                  require(
                                      newBlacklister != address(0),
                                      "FiatToken: new blacklister is the zero address"
                                  );
                                  require(
                                      newOwner != address(0),
                                      "FiatToken: new owner is the zero address"
                                  );
                                  name = tokenName;
                                  symbol = tokenSymbol;
                                  currency = tokenCurrency;
                                  decimals = tokenDecimals;
                                  masterMinter = newMasterMinter;
                                  pauser = newPauser;
                                  blacklister = newBlacklister;
                                  setOwner(newOwner);
                                  initialized = true;
                              }
                              /**
                               * @dev Throws if called by any account other than a minter.
                               */
                              modifier onlyMinters() {
                                  require(minters[msg.sender], "FiatToken: caller is not a minter");
                                  _;
                              }
                              /**
                               * @notice Mints fiat tokens to an address.
                               * @param _to The address that will receive the minted tokens.
                               * @param _amount The amount of tokens to mint. Must be less than or equal
                               * to the minterAllowance of the caller.
                               * @return True if the operation was successful.
                               */
                              function mint(address _to, uint256 _amount)
                                  external
                                  whenNotPaused
                                  onlyMinters
                                  notBlacklisted(msg.sender)
                                  notBlacklisted(_to)
                                  returns (bool)
                              {
                                  require(_to != address(0), "FiatToken: mint to the zero address");
                                  require(_amount > 0, "FiatToken: mint amount not greater than 0");
                                  uint256 mintingAllowedAmount = minterAllowed[msg.sender];
                                  require(
                                      _amount <= mintingAllowedAmount,
                                      "FiatToken: mint amount exceeds minterAllowance"
                                  );
                                  totalSupply_ = totalSupply_.add(_amount);
                                  _setBalance(_to, _balanceOf(_to).add(_amount));
                                  minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
                                  emit Mint(msg.sender, _to, _amount);
                                  emit Transfer(address(0), _to, _amount);
                                  return true;
                              }
                              /**
                               * @dev Throws if called by any account other than the masterMinter
                               */
                              modifier onlyMasterMinter() {
                                  require(
                                      msg.sender == masterMinter,
                                      "FiatToken: caller is not the masterMinter"
                                  );
                                  _;
                              }
                              /**
                               * @notice Gets the minter allowance for an account.
                               * @param minter The address to check.
                               * @return The remaining minter allowance for the account.
                               */
                              function minterAllowance(address minter) external view returns (uint256) {
                                  return minterAllowed[minter];
                              }
                              /**
                               * @notice Checks if an account is a minter.
                               * @param account The address to check.
                               * @return True if the account is a minter, false if the account is not a minter.
                               */
                              function isMinter(address account) external view returns (bool) {
                                  return minters[account];
                              }
                              /**
                               * @notice Gets the remaining amount of fiat tokens a spender is allowed to transfer on
                               * behalf of the token owner.
                               * @param owner   The token owner's address.
                               * @param spender The spender's address.
                               * @return The remaining allowance.
                               */
                              function allowance(address owner, address spender)
                                  external
                                  override
                                  view
                                  returns (uint256)
                              {
                                  return allowed[owner][spender];
                              }
                              /**
                               * @notice Gets the totalSupply of the fiat token.
                               * @return The totalSupply of the fiat token.
                               */
                              function totalSupply() external override view returns (uint256) {
                                  return totalSupply_;
                              }
                              /**
                               * @notice Gets the fiat token balance of an account.
                               * @param account  The address to check.
                               * @return balance The fiat token balance of the account.
                               */
                              function balanceOf(address account)
                                  external
                                  override
                                  view
                                  returns (uint256)
                              {
                                  return _balanceOf(account);
                              }
                              /**
                               * @notice Sets a fiat token allowance for a spender to spend on behalf of the caller.
                               * @param spender The spender's address.
                               * @param value   The allowance amount.
                               * @return True if the operation was successful.
                               */
                              function approve(address spender, uint256 value)
                                  external
                                  virtual
                                  override
                                  whenNotPaused
                                  notBlacklisted(msg.sender)
                                  notBlacklisted(spender)
                                  returns (bool)
                              {
                                  _approve(msg.sender, spender, value);
                                  return true;
                              }
                              /**
                               * @dev Internal function to set allowance.
                               * @param owner     Token owner's address.
                               * @param spender   Spender's address.
                               * @param value     Allowance amount.
                               */
                              function _approve(
                                  address owner,
                                  address spender,
                                  uint256 value
                              ) internal override {
                                  require(owner != address(0), "ERC20: approve from the zero address");
                                  require(spender != address(0), "ERC20: approve to the zero address");
                                  allowed[owner][spender] = value;
                                  emit Approval(owner, spender, value);
                              }
                              /**
                               * @notice Transfers tokens from an address to another by spending the caller's allowance.
                               * @dev The caller must have some fiat token allowance on the payer's tokens.
                               * @param from  Payer's address.
                               * @param to    Payee's address.
                               * @param value Transfer amount.
                               * @return True if the operation was successful.
                               */
                              function transferFrom(
                                  address from,
                                  address to,
                                  uint256 value
                              )
                                  external
                                  override
                                  whenNotPaused
                                  notBlacklisted(msg.sender)
                                  notBlacklisted(from)
                                  notBlacklisted(to)
                                  returns (bool)
                              {
                                  require(
                                      value <= allowed[from][msg.sender],
                                      "ERC20: transfer amount exceeds allowance"
                                  );
                                  _transfer(from, to, value);
                                  allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
                                  return true;
                              }
                              /**
                               * @notice Transfers tokens from the caller.
                               * @param to    Payee's address.
                               * @param value Transfer amount.
                               * @return True if the operation was successful.
                               */
                              function transfer(address to, uint256 value)
                                  external
                                  override
                                  whenNotPaused
                                  notBlacklisted(msg.sender)
                                  notBlacklisted(to)
                                  returns (bool)
                              {
                                  _transfer(msg.sender, to, value);
                                  return true;
                              }
                              /**
                               * @dev Internal function to process transfers.
                               * @param from  Payer's address.
                               * @param to    Payee's address.
                               * @param value Transfer amount.
                               */
                              function _transfer(
                                  address from,
                                  address to,
                                  uint256 value
                              ) internal override {
                                  require(from != address(0), "ERC20: transfer from the zero address");
                                  require(to != address(0), "ERC20: transfer to the zero address");
                                  require(
                                      value <= _balanceOf(from),
                                      "ERC20: transfer amount exceeds balance"
                                  );
                                  _setBalance(from, _balanceOf(from).sub(value));
                                  _setBalance(to, _balanceOf(to).add(value));
                                  emit Transfer(from, to, value);
                              }
                              /**
                               * @notice Adds or updates a new minter with a mint allowance.
                               * @param minter The address of the minter.
                               * @param minterAllowedAmount The minting amount allowed for the minter.
                               * @return True if the operation was successful.
                               */
                              function configureMinter(address minter, uint256 minterAllowedAmount)
                                  external
                                  whenNotPaused
                                  onlyMasterMinter
                                  returns (bool)
                              {
                                  minters[minter] = true;
                                  minterAllowed[minter] = minterAllowedAmount;
                                  emit MinterConfigured(minter, minterAllowedAmount);
                                  return true;
                              }
                              /**
                               * @notice Removes a minter.
                               * @param minter The address of the minter to remove.
                               * @return True if the operation was successful.
                               */
                              function removeMinter(address minter)
                                  external
                                  onlyMasterMinter
                                  returns (bool)
                              {
                                  minters[minter] = false;
                                  minterAllowed[minter] = 0;
                                  emit MinterRemoved(minter);
                                  return true;
                              }
                              /**
                               * @notice Allows a minter to burn some of its own tokens.
                               * @dev The caller must be a minter, must not be blacklisted, and the amount to burn
                               * should be less than or equal to the account's balance.
                               * @param _amount the amount of tokens to be burned.
                               */
                              function burn(uint256 _amount)
                                  external
                                  whenNotPaused
                                  onlyMinters
                                  notBlacklisted(msg.sender)
                              {
                                  uint256 balance = _balanceOf(msg.sender);
                                  require(_amount > 0, "FiatToken: burn amount not greater than 0");
                                  require(balance >= _amount, "FiatToken: burn amount exceeds balance");
                                  totalSupply_ = totalSupply_.sub(_amount);
                                  _setBalance(msg.sender, balance.sub(_amount));
                                  emit Burn(msg.sender, _amount);
                                  emit Transfer(msg.sender, address(0), _amount);
                              }
                              /**
                               * @notice Updates the master minter address.
                               * @param _newMasterMinter The address of the new master minter.
                               */
                              function updateMasterMinter(address _newMasterMinter) external onlyOwner {
                                  require(
                                      _newMasterMinter != address(0),
                                      "FiatToken: new masterMinter is the zero address"
                                  );
                                  masterMinter = _newMasterMinter;
                                  emit MasterMinterChanged(masterMinter);
                              }
                              /**
                               * @inheritdoc Blacklistable
                               */
                              function _blacklist(address _account) internal override {
                                  _setBlacklistState(_account, true);
                              }
                              /**
                               * @inheritdoc Blacklistable
                               */
                              function _unBlacklist(address _account) internal override {
                                  _setBlacklistState(_account, false);
                              }
                              /**
                               * @dev Helper method that sets the blacklist state of an account.
                               * @param _account         The address of the account.
                               * @param _shouldBlacklist True if the account should be blacklisted, false if the account should be unblacklisted.
                               */
                              function _setBlacklistState(address _account, bool _shouldBlacklist)
                                  internal
                                  virtual
                              {
                                  _deprecatedBlacklisted[_account] = _shouldBlacklist;
                              }
                              /**
                               * @dev Helper method that sets the balance of an account.
                               * @param _account The address of the account.
                               * @param _balance The new fiat token balance of the account.
                               */
                              function _setBalance(address _account, uint256 _balance) internal virtual {
                                  balanceAndBlacklistStates[_account] = _balance;
                              }
                              /**
                               * @inheritdoc Blacklistable
                               */
                              function _isBlacklisted(address _account)
                                  internal
                                  virtual
                                  override
                                  view
                                  returns (bool)
                              {
                                  return _deprecatedBlacklisted[_account];
                              }
                              /**
                               * @dev Helper method to obtain the balance of an account.
                               * @param _account  The address of the account.
                               * @return          The fiat token balance of the account.
                               */
                              function _balanceOf(address _account)
                                  internal
                                  virtual
                                  view
                                  returns (uint256)
                              {
                                  return balanceAndBlacklistStates[_account];
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { Ownable } from "./Ownable.sol";
                          /**
                           * @title Blacklistable Token
                           * @dev Allows accounts to be blacklisted by a "blacklister" role
                           */
                          abstract contract Blacklistable is Ownable {
                              address public blacklister;
                              mapping(address => bool) internal _deprecatedBlacklisted;
                              event Blacklisted(address indexed _account);
                              event UnBlacklisted(address indexed _account);
                              event BlacklisterChanged(address indexed newBlacklister);
                              /**
                               * @dev Throws if called by any account other than the blacklister.
                               */
                              modifier onlyBlacklister() {
                                  require(
                                      msg.sender == blacklister,
                                      "Blacklistable: caller is not the blacklister"
                                  );
                                  _;
                              }
                              /**
                               * @dev Throws if argument account is blacklisted.
                               * @param _account The address to check.
                               */
                              modifier notBlacklisted(address _account) {
                                  require(
                                      !_isBlacklisted(_account),
                                      "Blacklistable: account is blacklisted"
                                  );
                                  _;
                              }
                              /**
                               * @notice Checks if account is blacklisted.
                               * @param _account The address to check.
                               * @return True if the account is blacklisted, false if the account is not blacklisted.
                               */
                              function isBlacklisted(address _account) external view returns (bool) {
                                  return _isBlacklisted(_account);
                              }
                              /**
                               * @notice Adds account to blacklist.
                               * @param _account The address to blacklist.
                               */
                              function blacklist(address _account) external onlyBlacklister {
                                  _blacklist(_account);
                                  emit Blacklisted(_account);
                              }
                              /**
                               * @notice Removes account from blacklist.
                               * @param _account The address to remove from the blacklist.
                               */
                              function unBlacklist(address _account) external onlyBlacklister {
                                  _unBlacklist(_account);
                                  emit UnBlacklisted(_account);
                              }
                              /**
                               * @notice Updates the blacklister address.
                               * @param _newBlacklister The address of the new blacklister.
                               */
                              function updateBlacklister(address _newBlacklister) external onlyOwner {
                                  require(
                                      _newBlacklister != address(0),
                                      "Blacklistable: new blacklister is the zero address"
                                  );
                                  blacklister = _newBlacklister;
                                  emit BlacklisterChanged(blacklister);
                              }
                              /**
                               * @dev Checks if account is blacklisted.
                               * @param _account The address to check.
                               * @return true if the account is blacklisted, false otherwise.
                               */
                              function _isBlacklisted(address _account)
                                  internal
                                  virtual
                                  view
                                  returns (bool);
                              /**
                               * @dev Helper method that blacklists an account.
                               * @param _account The address to blacklist.
                               */
                              function _blacklist(address _account) internal virtual;
                              /**
                               * @dev Helper method that unblacklists an account.
                               * @param _account The address to unblacklist.
                               */
                              function _unBlacklist(address _account) internal virtual;
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                          abstract contract AbstractFiatTokenV1 is IERC20 {
                              function _approve(
                                  address owner,
                                  address spender,
                                  uint256 value
                              ) internal virtual;
                              function _transfer(
                                  address from,
                                  address to,
                                  uint256 value
                              ) internal virtual;
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { Ownable } from "../v1/Ownable.sol";
                          import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                          import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
                          contract Rescuable is Ownable {
                              using SafeERC20 for IERC20;
                              address private _rescuer;
                              event RescuerChanged(address indexed newRescuer);
                              /**
                               * @notice Returns current rescuer
                               * @return Rescuer's address
                               */
                              function rescuer() external view returns (address) {
                                  return _rescuer;
                              }
                              /**
                               * @notice Revert if called by any account other than the rescuer.
                               */
                              modifier onlyRescuer() {
                                  require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
                                  _;
                              }
                              /**
                               * @notice Rescue ERC20 tokens locked up in this contract.
                               * @param tokenContract ERC20 token contract address
                               * @param to        Recipient address
                               * @param amount    Amount to withdraw
                               */
                              function rescueERC20(
                                  IERC20 tokenContract,
                                  address to,
                                  uint256 amount
                              ) external onlyRescuer {
                                  tokenContract.safeTransfer(to, amount);
                              }
                              /**
                               * @notice Updates the rescuer address.
                               * @param newRescuer The address of the new rescuer.
                               */
                              function updateRescuer(address newRescuer) external onlyOwner {
                                  require(
                                      newRescuer != address(0),
                                      "Rescuable: new rescuer is the zero address"
                                  );
                                  _rescuer = newRescuer;
                                  emit RescuerChanged(newRescuer);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { FiatTokenV1 } from "../v1/FiatTokenV1.sol";
                          import { Rescuable } from "./Rescuable.sol";
                          /**
                           * @title FiatTokenV1_1
                           * @dev ERC20 Token backed by fiat reserves
                           */
                          contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          import { ECRecover } from "./ECRecover.sol";
                          import { IERC1271 } from "../interface/IERC1271.sol";
                          /**
                           * @dev Signature verification helper that can be used instead of `ECRecover.recover` to seamlessly support both ECDSA
                           * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets.
                           *
                           * Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/SignatureChecker.sol
                           */
                          library SignatureChecker {
                              /**
                               * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
                               * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECRecover.recover`.
                               * @param signer        Address of the claimed signer
                               * @param digest        Keccak-256 hash digest of the signed message
                               * @param signature     Signature byte array associated with hash
                               */
                              function isValidSignatureNow(
                                  address signer,
                                  bytes32 digest,
                                  bytes memory signature
                              ) external view returns (bool) {
                                  if (!isContract(signer)) {
                                      return ECRecover.recover(digest, signature) == signer;
                                  }
                                  return isValidERC1271SignatureNow(signer, digest, signature);
                              }
                              /**
                               * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
                               * against the signer smart contract using ERC1271.
                               * @param signer        Address of the claimed signer
                               * @param digest        Keccak-256 hash digest of the signed message
                               * @param signature     Signature byte array associated with hash
                               *
                               * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
                               * change through time. It could return true at block N and false at block N+1 (or the opposite).
                               */
                              function isValidERC1271SignatureNow(
                                  address signer,
                                  bytes32 digest,
                                  bytes memory signature
                              ) internal view returns (bool) {
                                  (bool success, bytes memory result) = signer.staticcall(
                                      abi.encodeWithSelector(
                                          IERC1271.isValidSignature.selector,
                                          digest,
                                          signature
                                      )
                                  );
                                  return (success &&
                                      result.length >= 32 &&
                                      abi.decode(result, (bytes32)) ==
                                      bytes32(IERC1271.isValidSignature.selector));
                              }
                              /**
                               * @dev Checks if the input address is a smart contract.
                               */
                              function isContract(address addr) internal view returns (bool) {
                                  uint256 size;
                                  assembly {
                                      size := extcodesize(addr)
                                  }
                                  return size > 0;
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          /**
                           * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
                           *
                           * The library provides methods for generating a hash of a message that conforms to the
                           * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
                           * specifications.
                           */
                          library MessageHashUtils {
                              /**
                               * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
                               * Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c/contracts/utils/cryptography/MessageHashUtils.sol
                               *
                               * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
                               * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the
                               * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
                               *
                               * @param domainSeparator    Domain separator
                               * @param structHash         Hashed EIP-712 data struct
                               * @return digest            The keccak256 digest of an EIP-712 typed data
                               */
                              function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)
                                  internal
                                  pure
                                  returns (bytes32 digest)
                              {
                                  assembly {
                                      let ptr := mload(0x40)
                                      mstore(ptr, "\\x19\\x01")
                                      mstore(add(ptr, 0x02), domainSeparator)
                                      mstore(add(ptr, 0x22), structHash)
                                      digest := keccak256(ptr, 0x42)
                                  }
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          /**
                           * @title EIP712
                           * @notice A library that provides EIP712 helper functions
                           */
                          library EIP712 {
                              /**
                               * @notice Make EIP712 domain separator
                               * @param name      Contract name
                               * @param version   Contract version
                               * @param chainId   Blockchain ID
                               * @return Domain separator
                               */
                              function makeDomainSeparator(
                                  string memory name,
                                  string memory version,
                                  uint256 chainId
                              ) internal view returns (bytes32) {
                                  return
                                      keccak256(
                                          abi.encode(
                                              // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
                                              0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                                              keccak256(bytes(name)),
                                              keccak256(bytes(version)),
                                              chainId,
                                              address(this)
                                          )
                                      );
                              }
                              /**
                               * @notice Make EIP712 domain separator
                               * @param name      Contract name
                               * @param version   Contract version
                               * @return Domain separator
                               */
                              function makeDomainSeparator(string memory name, string memory version)
                                  internal
                                  view
                                  returns (bytes32)
                              {
                                  uint256 chainId;
                                  assembly {
                                      chainId := chainid()
                                  }
                                  return makeDomainSeparator(name, version, chainId);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          /**
                           * @title ECRecover
                           * @notice A library that provides a safe ECDSA recovery function
                           */
                          library ECRecover {
                              /**
                               * @notice Recover signer's address from a signed message
                               * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
                               * Modifications: Accept v, r, and s as separate arguments
                               * @param digest    Keccak-256 hash digest of the signed message
                               * @param v         v of the signature
                               * @param r         r of the signature
                               * @param s         s of the signature
                               * @return Signer address
                               */
                              function recover(
                                  bytes32 digest,
                                  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.
                                  if (
                                      uint256(s) >
                                      0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
                                  ) {
                                      revert("ECRecover: invalid signature 's' value");
                                  }
                                  if (v != 27 && v != 28) {
                                      revert("ECRecover: invalid signature 'v' value");
                                  }
                                  // If the signature is valid (and not malleable), return the signer address
                                  address signer = ecrecover(digest, v, r, s);
                                  require(signer != address(0), "ECRecover: invalid signature");
                                  return signer;
                              }
                              /**
                               * @notice Recover signer's address from a signed message
                               * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0053ee040a7ff1dbc39691c9e67a69f564930a88/contracts/utils/cryptography/ECDSA.sol
                               * @param digest    Keccak-256 hash digest of the signed message
                               * @param signature Signature byte array associated with hash
                               * @return Signer address
                               */
                              function recover(bytes32 digest, bytes memory signature)
                                  internal
                                  pure
                                  returns (address)
                              {
                                  require(signature.length == 65, "ECRecover: invalid signature length");
                                  bytes32 r;
                                  bytes32 s;
                                  uint8 v;
                                  // ecrecover takes the signature parameters, and the only way to get them
                                  // currently is to use assembly.
                                  /// @solidity memory-safe-assembly
                                  assembly {
                                      r := mload(add(signature, 0x20))
                                      s := mload(add(signature, 0x40))
                                      v := byte(0, mload(add(signature, 0x60)))
                                  }
                                  return recover(digest, v, r, s);
                              }
                          }
                          /**
                           * SPDX-License-Identifier: Apache-2.0
                           *
                           * Copyright (c) 2023, Circle Internet Financial, LLC.
                           *
                           * Licensed under the Apache License, Version 2.0 (the "License");
                           * you may not use this file except in compliance with the License.
                           * You may obtain a copy of the License at
                           *
                           * http://www.apache.org/licenses/LICENSE-2.0
                           *
                           * Unless required by applicable law or agreed to in writing, software
                           * distributed under the License is distributed on an "AS IS" BASIS,
                           * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                           * See the License for the specific language governing permissions and
                           * limitations under the License.
                           */
                          pragma solidity 0.6.12;
                          /**
                           * @dev Interface of the ERC1271 standard signature validation method for
                           * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
                           */
                          interface IERC1271 {
                              /**
                               * @dev Should return whether the signature provided is valid for the provided data
                               * @param hash          Hash of the data to be signed
                               * @param signature     Signature byte array associated with the provided data hash
                               * @return magicValue   bytes4 magic value 0x1626ba7e when function passes
                               */
                              function isValidSignature(bytes32 hash, bytes memory signature)
                                  external
                                  view
                                  returns (bytes4 magicValue);
                          }
                          

                          File 8 of 9: 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 9 of 9: 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 {}
                          }