ETH Price: $2,515.77 (-2.91%)

Transaction Decoder

Block:
22812743 at Jun-29-2025 09:24:11 PM +UTC
Transaction Fee:
0.000358356151904712 ETH $0.90
Gas Used:
153,596 Gas / 2.333108622 Gwei

Emitted Events:

240 FiatTokenProxy.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x000000000000000000000000f74510cbc142b34778661c5b6b121c7d3e4f3300, 0x0000000000000000000000009fb7b4477576fe5b32be4c1843afb1e55f251b33, 000000000000000000000000000000000000000000000000000000015e98cd13 )
241 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000f74510cbc142b34778661c5b6b121c7d3e4f3300, 0x00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497, 000000000000000000000000000000000000000000000000000000015e98cd13 )
242 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x0000000000000000000000009fb7b4477576fe5b32be4c1843afb1e55f251b33, 0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48, 000000000000000000000000000000000000000000000000000000015e98cd13, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000a4257328fb6d00000000000000000000da4946b8536600, 00000000000000084d73a787a000000808685e6511a18672cc01e79443e80223 )
243 fToken.Transfer( from=0x0000000000000000000000000000000000000000, to=[Receiver] 0xf74510cbc142b34778661c5b6b121c7d3e4f3300, value=5094453660 )
244 fToken.Deposit( sender=[Receiver] 0xf74510cbc142b34778661c5b6b121c7d3e4f3300, owner=[Receiver] 0xf74510cbc142b34778661c5b6b121c7d3e4f3300, assets=5882039571, shares=5094453660 )

Account State Difference:

  Address   Before After State Difference Code
(Titan Builder)
14.565391897161250886 Eth14.565699089161250886 Eth0.000307192
0x52Aa8994...360F4e497
(Fluid: Liquidity)
0x9Fb7b447...55F251B33
0xA0b86991...E3606eB48
0xF74510cB...d3e4F3300
0.99425144563475654 Eth
Nonce: 25
0.993893089482851828 Eth
Nonce: 27
0.000358356151904712From: 0 To: 22892026855592066050609947431602401211538835161166308139

Execution Trace

0xf74510cbc142b34778661c5b6b121c7d3e4f3300.e9ae5c53( )
  • FiatTokenProxy.095ea7b3( )
    • FiatTokenV2_2.approve( spender=0x9Fb7b4477576Fe5B32be4C1843aFB1e55F251B33, value=5882039571 ) => ( True )
    • fToken.deposit( assets_=5882039571, receiver_=0xF74510cBc142b34778661c5B6B121c7d3e4F3300 ) => ( shares_=5094453660 )
      • FluidLiquidityProxy.ad967e15( )
        • FluidLiquidityUserModule.operate( token_=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, supplyAmount_=5882039571, borrowAmount_=0, withdrawTo_=0x0000000000000000000000000000000000000000, borrowTo_=0x0000000000000000000000000000000000000000, callbackData_=0x000000000000000000000000F74510CBC142B34778661C5B6B121C7D3E4F3300 ) => ( memVar3_=1104025472162, memVar4_=1141093232884 )
          • FiatTokenProxy.70a08231( )
            • FiatTokenV2_2.balanceOf( account=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497 ) => ( 59089947047244 )
            • fToken.liquidityCallback( token_=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, amount_=5882039571, data_=0x000000000000000000000000F74510CBC142B34778661C5B6B121C7D3E4F3300 )
              • FiatTokenProxy.23b872dd( )
                • FiatTokenV2_2.transferFrom( from=0xF74510cBc142b34778661c5B6B121c7d3e4F3300, to=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497, value=5882039571 ) => ( True )
                • FiatTokenProxy.70a08231( )
                  • FiatTokenV2_2.balanceOf( account=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497 ) => ( 59095829086815 )
                    File 1 of 5: 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 2 of 5: 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 3 of 5: fToken
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (interfaces/IERC4626.sol)
                    pragma solidity ^0.8.0;
                    import "../token/ERC20/IERC20.sol";
                    import "../token/ERC20/extensions/IERC20Metadata.sol";
                    /**
                     * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
                     * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
                     *
                     * _Available since v4.7._
                     */
                    interface IERC4626 is IERC20, IERC20Metadata {
                        event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
                        event Withdraw(
                            address indexed sender,
                            address indexed receiver,
                            address indexed owner,
                            uint256 assets,
                            uint256 shares
                        );
                        /**
                         * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
                         *
                         * - MUST be an ERC-20 token contract.
                         * - MUST NOT revert.
                         */
                        function asset() external view returns (address assetTokenAddress);
                        /**
                         * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
                         *
                         * - SHOULD include any compounding that occurs from yield.
                         * - MUST be inclusive of any fees that are charged against assets in the Vault.
                         * - MUST NOT revert.
                         */
                        function totalAssets() external view returns (uint256 totalManagedAssets);
                        /**
                         * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
                         * scenario where all the conditions are met.
                         *
                         * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
                         * - MUST NOT show any variations depending on the caller.
                         * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
                         * - MUST NOT revert.
                         *
                         * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
                         * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
                         * from.
                         */
                        function convertToShares(uint256 assets) external view returns (uint256 shares);
                        /**
                         * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
                         * scenario where all the conditions are met.
                         *
                         * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
                         * - MUST NOT show any variations depending on the caller.
                         * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
                         * - MUST NOT revert.
                         *
                         * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
                         * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
                         * from.
                         */
                        function convertToAssets(uint256 shares) external view returns (uint256 assets);
                        /**
                         * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
                         * through a deposit call.
                         *
                         * - MUST return a limited value if receiver is subject to some deposit limit.
                         * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
                         * - MUST NOT revert.
                         */
                        function maxDeposit(address receiver) external view returns (uint256 maxAssets);
                        /**
                         * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
                         * current on-chain conditions.
                         *
                         * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
                         *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
                         *   in the same transaction.
                         * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
                         *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
                         * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
                         * - MUST NOT revert.
                         *
                         * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
                         * share price or some other type of condition, meaning the depositor will lose assets by depositing.
                         */
                        function previewDeposit(uint256 assets) external view returns (uint256 shares);
                        /**
                         * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
                         *
                         * - MUST emit the Deposit event.
                         * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
                         *   deposit execution, and are accounted for during deposit.
                         * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
                         *   approving enough underlying tokens to the Vault contract, etc).
                         *
                         * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
                         */
                        function deposit(uint256 assets, address receiver) external returns (uint256 shares);
                        /**
                         * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
                         * - MUST return a limited value if receiver is subject to some mint limit.
                         * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
                         * - MUST NOT revert.
                         */
                        function maxMint(address receiver) external view returns (uint256 maxShares);
                        /**
                         * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
                         * current on-chain conditions.
                         *
                         * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
                         *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
                         *   same transaction.
                         * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
                         *   would be accepted, regardless if the user has enough tokens approved, etc.
                         * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
                         * - MUST NOT revert.
                         *
                         * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
                         * share price or some other type of condition, meaning the depositor will lose assets by minting.
                         */
                        function previewMint(uint256 shares) external view returns (uint256 assets);
                        /**
                         * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
                         *
                         * - MUST emit the Deposit event.
                         * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
                         *   execution, and are accounted for during mint.
                         * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
                         *   approving enough underlying tokens to the Vault contract, etc).
                         *
                         * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
                         */
                        function mint(uint256 shares, address receiver) external returns (uint256 assets);
                        /**
                         * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
                         * Vault, through a withdraw call.
                         *
                         * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
                         * - MUST NOT revert.
                         */
                        function maxWithdraw(address owner) external view returns (uint256 maxAssets);
                        /**
                         * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
                         * given current on-chain conditions.
                         *
                         * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
                         *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
                         *   called
                         *   in the same transaction.
                         * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
                         *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
                         * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
                         * - MUST NOT revert.
                         *
                         * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
                         * share price or some other type of condition, meaning the depositor will lose assets by depositing.
                         */
                        function previewWithdraw(uint256 assets) external view returns (uint256 shares);
                        /**
                         * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
                         *
                         * - MUST emit the Withdraw event.
                         * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
                         *   withdraw execution, and are accounted for during withdraw.
                         * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
                         *   not having enough shares, etc).
                         *
                         * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
                         * Those methods should be performed separately.
                         */
                        function withdraw(
                            uint256 assets,
                            address receiver,
                            address owner
                        ) external returns (uint256 shares);
                        /**
                         * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
                         * through a redeem call.
                         *
                         * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
                         * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
                         * - MUST NOT revert.
                         */
                        function maxRedeem(address owner) external view returns (uint256 maxShares);
                        /**
                         * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
                         * given current on-chain conditions.
                         *
                         * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
                         *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
                         *   same transaction.
                         * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
                         *   redemption would be accepted, regardless if the user has enough shares, etc.
                         * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
                         * - MUST NOT revert.
                         *
                         * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
                         * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
                         */
                        function previewRedeem(uint256 shares) external view returns (uint256 assets);
                        /**
                         * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
                         *
                         * - MUST emit the Withdraw event.
                         * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
                         *   redeem execution, and are accounted for during redeem.
                         * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
                         *   not having enough shares, etc).
                         *
                         * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
                         * Those methods should be performed separately.
                         */
                        function redeem(
                            uint256 shares,
                            address receiver,
                            address owner
                        ) external returns (uint256 assets);
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
                    pragma solidity ^0.8.0;
                    import "./IERC20.sol";
                    import "./extensions/IERC20Metadata.sol";
                    import "../../utils/Context.sol";
                    /**
                     * @dev Implementation of the {IERC20} interface.
                     *
                     * This implementation is agnostic to the way tokens are created. This means
                     * that a supply mechanism has to be added in a derived contract using {_mint}.
                     * For a generic mechanism see {ERC20PresetMinterPauser}.
                     *
                     * TIP: For a detailed writeup see our guide
                     * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
                     * to implement supply mechanisms].
                     *
                     * We have followed general OpenZeppelin Contracts guidelines: functions revert
                     * instead returning `false` on failure. This behavior is nonetheless
                     * conventional and does not conflict with the expectations of ERC20
                     * applications.
                     *
                     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
                     * This allows applications to reconstruct the allowance for all accounts just
                     * by listening to said events. Other implementations of the EIP may not emit
                     * these events, as it isn't required by the specification.
                     *
                     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
                     * functions have been added to mitigate the well-known issues around setting
                     * allowances. See {IERC20-approve}.
                     */
                    contract ERC20 is Context, IERC20, IERC20Metadata {
                        mapping(address => uint256) private _balances;
                        mapping(address => mapping(address => uint256)) private _allowances;
                        uint256 private _totalSupply;
                        string private _name;
                        string private _symbol;
                        /**
                         * @dev Sets the values for {name} and {symbol}.
                         *
                         * The default value of {decimals} is 18. To select a different value for
                         * {decimals} you should overload it.
                         *
                         * All two of these values are immutable: they can only be set once during
                         * construction.
                         */
                        constructor(string memory name_, string memory symbol_) {
                            _name = name_;
                            _symbol = symbol_;
                        }
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() public view virtual override returns (string memory) {
                            return _name;
                        }
                        /**
                         * @dev Returns the symbol of the token, usually a shorter version of the
                         * name.
                         */
                        function symbol() public view virtual override returns (string memory) {
                            return _symbol;
                        }
                        /**
                         * @dev Returns the number of decimals used to get its user representation.
                         * For example, if `decimals` equals `2`, a balance of `505` tokens should
                         * be displayed to a user as `5.05` (`505 / 10 ** 2`).
                         *
                         * Tokens usually opt for a value of 18, imitating the relationship between
                         * Ether and Wei. This is the value {ERC20} uses, unless this function is
                         * overridden;
                         *
                         * NOTE: This information is only used for _display_ purposes: it in
                         * no way affects any of the arithmetic of the contract, including
                         * {IERC20-balanceOf} and {IERC20-transfer}.
                         */
                        function decimals() public view virtual override returns (uint8) {
                            return 18;
                        }
                        /**
                         * @dev See {IERC20-totalSupply}.
                         */
                        function totalSupply() public view virtual override returns (uint256) {
                            return _totalSupply;
                        }
                        /**
                         * @dev See {IERC20-balanceOf}.
                         */
                        function balanceOf(address account) public view virtual override returns (uint256) {
                            return _balances[account];
                        }
                        /**
                         * @dev See {IERC20-transfer}.
                         *
                         * Requirements:
                         *
                         * - `to` cannot be the zero address.
                         * - the caller must have a balance of at least `amount`.
                         */
                        function transfer(address to, uint256 amount) public virtual override returns (bool) {
                            address owner = _msgSender();
                            _transfer(owner, to, amount);
                            return true;
                        }
                        /**
                         * @dev See {IERC20-allowance}.
                         */
                        function allowance(address owner, address spender) public view virtual override returns (uint256) {
                            return _allowances[owner][spender];
                        }
                        /**
                         * @dev See {IERC20-approve}.
                         *
                         * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
                         * `transferFrom`. This is semantically equivalent to an infinite approval.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function approve(address spender, uint256 amount) public virtual override returns (bool) {
                            address owner = _msgSender();
                            _approve(owner, spender, amount);
                            return true;
                        }
                        /**
                         * @dev See {IERC20-transferFrom}.
                         *
                         * Emits an {Approval} event indicating the updated allowance. This is not
                         * required by the EIP. See the note at the beginning of {ERC20}.
                         *
                         * NOTE: Does not update the allowance if the current allowance
                         * is the maximum `uint256`.
                         *
                         * Requirements:
                         *
                         * - `from` and `to` cannot be the zero address.
                         * - `from` must have a balance of at least `amount`.
                         * - the caller must have allowance for ``from``'s tokens of at least
                         * `amount`.
                         */
                        function transferFrom(
                            address from,
                            address to,
                            uint256 amount
                        ) public virtual override returns (bool) {
                            address spender = _msgSender();
                            _spendAllowance(from, spender, amount);
                            _transfer(from, to, amount);
                            return true;
                        }
                        /**
                         * @dev Atomically increases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to {approve} that can be used as a mitigation for
                         * problems described in {IERC20-approve}.
                         *
                         * Emits an {Approval} event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                            address owner = _msgSender();
                            _approve(owner, spender, allowance(owner, spender) + addedValue);
                            return true;
                        }
                        /**
                         * @dev Atomically decreases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to {approve} that can be used as a mitigation for
                         * problems described in {IERC20-approve}.
                         *
                         * Emits an {Approval} event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         * - `spender` must have allowance for the caller of at least
                         * `subtractedValue`.
                         */
                        function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                            address owner = _msgSender();
                            uint256 currentAllowance = allowance(owner, spender);
                            require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
                            unchecked {
                                _approve(owner, spender, currentAllowance - subtractedValue);
                            }
                            return true;
                        }
                        /**
                         * @dev Moves `amount` of tokens from `from` to `to`.
                         *
                         * This internal function is equivalent to {transfer}, and can be used to
                         * e.g. implement automatic token fees, slashing mechanisms, etc.
                         *
                         * Emits a {Transfer} event.
                         *
                         * Requirements:
                         *
                         * - `from` cannot be the zero address.
                         * - `to` cannot be the zero address.
                         * - `from` must have a balance of at least `amount`.
                         */
                        function _transfer(
                            address from,
                            address to,
                            uint256 amount
                        ) internal virtual {
                            require(from != address(0), "ERC20: transfer from the zero address");
                            require(to != address(0), "ERC20: transfer to the zero address");
                            _beforeTokenTransfer(from, to, amount);
                            uint256 fromBalance = _balances[from];
                            require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
                            unchecked {
                                _balances[from] = fromBalance - amount;
                                // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
                                // decrementing then incrementing.
                                _balances[to] += amount;
                            }
                            emit Transfer(from, to, amount);
                            _afterTokenTransfer(from, to, amount);
                        }
                        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                         * the total supply.
                         *
                         * Emits a {Transfer} event with `from` set to the zero address.
                         *
                         * Requirements:
                         *
                         * - `account` cannot be the zero address.
                         */
                        function _mint(address account, uint256 amount) internal virtual {
                            require(account != address(0), "ERC20: mint to the zero address");
                            _beforeTokenTransfer(address(0), account, amount);
                            _totalSupply += amount;
                            unchecked {
                                // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
                                _balances[account] += amount;
                            }
                            emit Transfer(address(0), account, amount);
                            _afterTokenTransfer(address(0), account, amount);
                        }
                        /**
                         * @dev Destroys `amount` tokens from `account`, reducing the
                         * total supply.
                         *
                         * Emits a {Transfer} event with `to` set to the zero address.
                         *
                         * Requirements:
                         *
                         * - `account` cannot be the zero address.
                         * - `account` must have at least `amount` tokens.
                         */
                        function _burn(address account, uint256 amount) internal virtual {
                            require(account != address(0), "ERC20: burn from the zero address");
                            _beforeTokenTransfer(account, address(0), amount);
                            uint256 accountBalance = _balances[account];
                            require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                            unchecked {
                                _balances[account] = accountBalance - amount;
                                // Overflow not possible: amount <= accountBalance <= totalSupply.
                                _totalSupply -= amount;
                            }
                            emit Transfer(account, address(0), amount);
                            _afterTokenTransfer(account, address(0), amount);
                        }
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
                         *
                         * This internal function is equivalent to `approve`, and can be used to
                         * e.g. set automatic allowances for certain subsystems, etc.
                         *
                         * Emits an {Approval} event.
                         *
                         * Requirements:
                         *
                         * - `owner` cannot be the zero address.
                         * - `spender` cannot be the zero address.
                         */
                        function _approve(
                            address owner,
                            address spender,
                            uint256 amount
                        ) internal virtual {
                            require(owner != address(0), "ERC20: approve from the zero address");
                            require(spender != address(0), "ERC20: approve to the zero address");
                            _allowances[owner][spender] = amount;
                            emit Approval(owner, spender, amount);
                        }
                        /**
                         * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
                         *
                         * Does not update the allowance amount in case of infinite allowance.
                         * Revert if not enough allowance is available.
                         *
                         * Might emit an {Approval} event.
                         */
                        function _spendAllowance(
                            address owner,
                            address spender,
                            uint256 amount
                        ) internal virtual {
                            uint256 currentAllowance = allowance(owner, spender);
                            if (currentAllowance != type(uint256).max) {
                                require(currentAllowance >= amount, "ERC20: insufficient allowance");
                                unchecked {
                                    _approve(owner, spender, currentAllowance - amount);
                                }
                            }
                        }
                        /**
                         * @dev Hook that is called before any transfer of tokens. This includes
                         * minting and burning.
                         *
                         * Calling conditions:
                         *
                         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                         * will be transferred to `to`.
                         * - when `from` is zero, `amount` tokens will be minted for `to`.
                         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
                         * - `from` and `to` are never both zero.
                         *
                         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                         */
                        function _beforeTokenTransfer(
                            address from,
                            address to,
                            uint256 amount
                        ) internal virtual {}
                        /**
                         * @dev Hook that is called after any transfer of tokens. This includes
                         * minting and burning.
                         *
                         * Calling conditions:
                         *
                         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
                         * has been transferred to `to`.
                         * - when `from` is zero, `amount` tokens have been minted for `to`.
                         * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
                         * - `from` and `to` are never both zero.
                         *
                         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                         */
                        function _afterTokenTransfer(
                            address from,
                            address to,
                            uint256 amount
                        ) internal virtual {}
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol)
                    pragma solidity ^0.8.0;
                    import "./draft-IERC20Permit.sol";
                    import "../ERC20.sol";
                    import "../../../utils/cryptography/ECDSA.sol";
                    import "../../../utils/cryptography/EIP712.sol";
                    import "../../../utils/Counters.sol";
                    /**
                     * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
                     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
                     *
                     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
                     * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
                     * need to send a transaction, and thus is not required to hold Ether at all.
                     *
                     * _Available since v3.4._
                     */
                    abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
                        using Counters for Counters.Counter;
                        mapping(address => Counters.Counter) private _nonces;
                        // solhint-disable-next-line var-name-mixedcase
                        bytes32 private constant _PERMIT_TYPEHASH =
                            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
                        /**
                         * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
                         * However, to ensure consistency with the upgradeable transpiler, we will continue
                         * to reserve a slot.
                         * @custom:oz-renamed-from _PERMIT_TYPEHASH
                         */
                        // solhint-disable-next-line var-name-mixedcase
                        bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
                        /**
                         * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
                         *
                         * It's a good idea to use the same `name` that is defined as the ERC20 token name.
                         */
                        constructor(string memory name) EIP712(name, "1") {}
                        /**
                         * @dev See {IERC20Permit-permit}.
                         */
                        function permit(
                            address owner,
                            address spender,
                            uint256 value,
                            uint256 deadline,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) public virtual override {
                            require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
                            bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
                            bytes32 hash = _hashTypedDataV4(structHash);
                            address signer = ECDSA.recover(hash, v, r, s);
                            require(signer == owner, "ERC20Permit: invalid signature");
                            _approve(owner, spender, value);
                        }
                        /**
                         * @dev See {IERC20Permit-nonces}.
                         */
                        function nonces(address owner) public view virtual override returns (uint256) {
                            return _nonces[owner].current();
                        }
                        /**
                         * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
                         */
                        // solhint-disable-next-line func-name-mixedcase
                        function DOMAIN_SEPARATOR() external view override returns (bytes32) {
                            return _domainSeparatorV4();
                        }
                        /**
                         * @dev "Consume a nonce": return the current value and increment.
                         *
                         * _Available since v4.1._
                         */
                        function _useNonce(address owner) internal virtual returns (uint256 current) {
                            Counters.Counter storage nonce = _nonces[owner];
                            current = nonce.current();
                            nonce.increment();
                        }
                    }
                    // 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 v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
                    pragma solidity ^0.8.0;
                    import "../IERC20.sol";
                    /**
                     * @dev Interface for the optional metadata functions from the ERC20 standard.
                     *
                     * _Available since v4.1._
                     */
                    interface IERC20Metadata is IERC20 {
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() external view returns (string memory);
                        /**
                         * @dev Returns the symbol of the token.
                         */
                        function symbol() external view returns (string memory);
                        /**
                         * @dev Returns the decimals places of the token.
                         */
                        function decimals() external view returns (uint8);
                    }
                    // 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 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: MIT
                    // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
                    pragma solidity ^0.8.0;
                    /**
                     * @title Counters
                     * @author Matt Condon (@shrugs)
                     * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
                     * of elements in a mapping, issuing ERC721 ids, or counting request ids.
                     *
                     * Include with `using Counters for Counters.Counter;`
                     */
                    library Counters {
                        struct Counter {
                            // This variable should never be directly accessed by users of the library: interactions must be restricted to
                            // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
                            // this feature: see https://github.com/ethereum/solidity/issues/4637
                            uint256 _value; // default: 0
                        }
                        function current(Counter storage counter) internal view returns (uint256) {
                            return counter._value;
                        }
                        function increment(Counter storage counter) internal {
                            unchecked {
                                counter._value += 1;
                            }
                        }
                        function decrement(Counter storage counter) internal {
                            uint256 value = counter._value;
                            require(value > 0, "Counter: decrement overflow");
                            unchecked {
                                counter._value = value - 1;
                            }
                        }
                        function reset(Counter storage counter) internal {
                            counter._value = 0;
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
                    pragma solidity ^0.8.0;
                    import "../Strings.sol";
                    /**
                     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
                     *
                     * These functions can be used to verify that a message was signed by the holder
                     * of the private keys of a given address.
                     */
                    library ECDSA {
                        enum RecoverError {
                            NoError,
                            InvalidSignature,
                            InvalidSignatureLength,
                            InvalidSignatureS,
                            InvalidSignatureV // Deprecated in v4.8
                        }
                        function _throwError(RecoverError error) private pure {
                            if (error == RecoverError.NoError) {
                                return; // no error: do nothing
                            } else if (error == RecoverError.InvalidSignature) {
                                revert("ECDSA: invalid signature");
                            } else if (error == RecoverError.InvalidSignatureLength) {
                                revert("ECDSA: invalid signature length");
                            } else if (error == RecoverError.InvalidSignatureS) {
                                revert("ECDSA: invalid signature 's' value");
                            }
                        }
                        /**
                         * @dev Returns the address that signed a hashed message (`hash`) with
                         * `signature` or error string. This address can then be used for verification purposes.
                         *
                         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
                         * this function rejects them by requiring the `s` value to be in the lower
                         * half order, and the `v` value to be either 27 or 28.
                         *
                         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                         * verification to be secure: it is possible to craft signatures that
                         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                         * this is by receiving a hash of the original message (which may otherwise
                         * be too long), and then calling {toEthSignedMessageHash} on it.
                         *
                         * Documentation for signature generation:
                         * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
                         * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
                         *
                         * _Available since v4.3._
                         */
                        function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                            if (signature.length == 65) {
                                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 tryRecover(hash, v, r, s);
                            } else {
                                return (address(0), RecoverError.InvalidSignatureLength);
                            }
                        }
                        /**
                         * @dev Returns the address that signed a hashed message (`hash`) with
                         * `signature`. This address can then be used for verification purposes.
                         *
                         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
                         * this function rejects them by requiring the `s` value to be in the lower
                         * half order, and the `v` value to be either 27 or 28.
                         *
                         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                         * verification to be secure: it is possible to craft signatures that
                         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                         * this is by receiving a hash of the original message (which may otherwise
                         * be too long), and then calling {toEthSignedMessageHash} on it.
                         */
                        function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                            (address recovered, RecoverError error) = tryRecover(hash, signature);
                            _throwError(error);
                            return recovered;
                        }
                        /**
                         * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
                         *
                         * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
                         *
                         * _Available since v4.3._
                         */
                        function tryRecover(
                            bytes32 hash,
                            bytes32 r,
                            bytes32 vs
                        ) internal pure returns (address, RecoverError) {
                            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                            uint8 v = uint8((uint256(vs) >> 255) + 27);
                            return tryRecover(hash, v, r, s);
                        }
                        /**
                         * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
                         *
                         * _Available since v4.2._
                         */
                        function recover(
                            bytes32 hash,
                            bytes32 r,
                            bytes32 vs
                        ) internal pure returns (address) {
                            (address recovered, RecoverError error) = tryRecover(hash, r, vs);
                            _throwError(error);
                            return recovered;
                        }
                        /**
                         * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
                         * `r` and `s` signature fields separately.
                         *
                         * _Available since v4.3._
                         */
                        function tryRecover(
                            bytes32 hash,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) internal pure returns (address, RecoverError) {
                            // 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 (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): 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) {
                                return (address(0), RecoverError.InvalidSignatureS);
                            }
                            // If the signature is valid (and not malleable), return the signer address
                            address signer = ecrecover(hash, v, r, s);
                            if (signer == address(0)) {
                                return (address(0), RecoverError.InvalidSignature);
                            }
                            return (signer, RecoverError.NoError);
                        }
                        /**
                         * @dev Overload of {ECDSA-recover} that receives the `v`,
                         * `r` and `s` signature fields separately.
                         */
                        function recover(
                            bytes32 hash,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) internal pure returns (address) {
                            (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
                            _throwError(error);
                            return recovered;
                        }
                        /**
                         * @dev Returns an Ethereum Signed Message, created from a `hash`. This
                         * produces hash corresponding to the one signed with the
                         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
                         * JSON-RPC method as part of EIP-191.
                         *
                         * See {recover}.
                         */
                        function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                            // 32 is the length in bytes of hash,
                            // enforced by the type signature above
                            return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
                    32", hash));
                        }
                        /**
                         * @dev Returns an Ethereum Signed Message, created from `s`. This
                         * produces hash corresponding to the one signed with the
                         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
                         * JSON-RPC method as part of EIP-191.
                         *
                         * See {recover}.
                         */
                        function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                            return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
                    ", Strings.toString(s.length), s));
                        }
                        /**
                         * @dev Returns an Ethereum Signed Typed Data, created from a
                         * `domainSeparator` and a `structHash`. This produces hash corresponding
                         * to the one signed with the
                         * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
                         * JSON-RPC method as part of EIP-712.
                         *
                         * See {recover}.
                         */
                        function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
                            return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol)
                    pragma solidity ^0.8.0;
                    import "./ECDSA.sol";
                    /**
                     * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
                     *
                     * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
                     * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
                     * they need in their contracts using a combination of `abi.encode` and `keccak256`.
                     *
                     * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
                     * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
                     * ({_hashTypedDataV4}).
                     *
                     * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
                     * the chain id to protect against replay attacks on an eventual fork of the chain.
                     *
                     * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
                     * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
                     *
                     * _Available since v3.4._
                     */
                    abstract contract EIP712 {
                        /* solhint-disable var-name-mixedcase */
                        // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
                        // invalidate the cached domain separator if the chain id changes.
                        bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
                        uint256 private immutable _CACHED_CHAIN_ID;
                        address private immutable _CACHED_THIS;
                        bytes32 private immutable _HASHED_NAME;
                        bytes32 private immutable _HASHED_VERSION;
                        bytes32 private immutable _TYPE_HASH;
                        /* solhint-enable var-name-mixedcase */
                        /**
                         * @dev Initializes the domain separator and parameter caches.
                         *
                         * The meaning of `name` and `version` is specified in
                         * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
                         *
                         * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
                         * - `version`: the current major version of the signing domain.
                         *
                         * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
                         * contract upgrade].
                         */
                        constructor(string memory name, string memory version) {
                            bytes32 hashedName = keccak256(bytes(name));
                            bytes32 hashedVersion = keccak256(bytes(version));
                            bytes32 typeHash = keccak256(
                                "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                            );
                            _HASHED_NAME = hashedName;
                            _HASHED_VERSION = hashedVersion;
                            _CACHED_CHAIN_ID = block.chainid;
                            _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
                            _CACHED_THIS = address(this);
                            _TYPE_HASH = typeHash;
                        }
                        /**
                         * @dev Returns the domain separator for the current chain.
                         */
                        function _domainSeparatorV4() internal view returns (bytes32) {
                            if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
                                return _CACHED_DOMAIN_SEPARATOR;
                            } else {
                                return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
                            }
                        }
                        function _buildDomainSeparator(
                            bytes32 typeHash,
                            bytes32 nameHash,
                            bytes32 versionHash
                        ) private view returns (bytes32) {
                            return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
                        }
                        /**
                         * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
                         * function returns the hash of the fully encoded EIP712 message for this domain.
                         *
                         * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
                         *
                         * ```solidity
                         * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
                         *     keccak256("Mail(address to,string contents)"),
                         *     mailTo,
                         *     keccak256(bytes(mailContents))
                         * )));
                         * address signer = ECDSA.recover(digest, signature);
                         * ```
                         */
                        function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
                            return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Standard math utilities missing in the Solidity language.
                     */
                    library Math {
                        enum Rounding {
                            Down, // Toward negative infinity
                            Up, // Toward infinity
                            Zero // Toward zero
                        }
                        /**
                         * @dev Returns the largest of two numbers.
                         */
                        function max(uint256 a, uint256 b) internal pure returns (uint256) {
                            return a > b ? a : b;
                        }
                        /**
                         * @dev Returns the smallest of two numbers.
                         */
                        function min(uint256 a, uint256 b) internal pure returns (uint256) {
                            return a < b ? a : b;
                        }
                        /**
                         * @dev Returns the average of two numbers. The result is rounded towards
                         * zero.
                         */
                        function average(uint256 a, uint256 b) internal pure returns (uint256) {
                            // (a + b) / 2 can overflow.
                            return (a & b) + (a ^ b) / 2;
                        }
                        /**
                         * @dev Returns the ceiling of the division of two numbers.
                         *
                         * This differs from standard division with `/` in that it rounds up instead
                         * of rounding down.
                         */
                        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                            // (a + b - 1) / b can overflow on addition, so we distribute.
                            return a == 0 ? 0 : (a - 1) / b + 1;
                        }
                        /**
                         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
                         * with further edits by Uniswap Labs also under MIT license.
                         */
                        function mulDiv(
                            uint256 x,
                            uint256 y,
                            uint256 denominator
                        ) internal pure returns (uint256 result) {
                            unchecked {
                                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                                // variables such that product = prod1 * 2^256 + prod0.
                                uint256 prod0; // Least significant 256 bits of the product
                                uint256 prod1; // Most significant 256 bits of the product
                                assembly {
                                    let mm := mulmod(x, y, not(0))
                                    prod0 := mul(x, y)
                                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                                }
                                // Handle non-overflow cases, 256 by 256 division.
                                if (prod1 == 0) {
                                    return prod0 / denominator;
                                }
                                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                                require(denominator > prod1);
                                ///////////////////////////////////////////////
                                // 512 by 256 division.
                                ///////////////////////////////////////////////
                                // Make division exact by subtracting the remainder from [prod1 prod0].
                                uint256 remainder;
                                assembly {
                                    // Compute remainder using mulmod.
                                    remainder := mulmod(x, y, denominator)
                                    // Subtract 256 bit number from 512 bit number.
                                    prod1 := sub(prod1, gt(remainder, prod0))
                                    prod0 := sub(prod0, remainder)
                                }
                                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                                // See https://cs.stackexchange.com/q/138556/92363.
                                // Does not overflow because the denominator cannot be zero at this stage in the function.
                                uint256 twos = denominator & (~denominator + 1);
                                assembly {
                                    // Divide denominator by twos.
                                    denominator := div(denominator, twos)
                                    // Divide [prod1 prod0] by twos.
                                    prod0 := div(prod0, twos)
                                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                                    twos := add(div(sub(0, twos), twos), 1)
                                }
                                // Shift in bits from prod1 into prod0.
                                prod0 |= prod1 * twos;
                                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                                // four bits. That is, denominator * inv = 1 mod 2^4.
                                uint256 inverse = (3 * denominator) ^ 2;
                                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                                // in modular arithmetic, doubling the correct bits in each step.
                                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                                inverse *= 2 - denominator * inverse; // inverse mod 2^256
                                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                                // is no longer required.
                                result = prod0 * inverse;
                                return result;
                            }
                        }
                        /**
                         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
                         */
                        function mulDiv(
                            uint256 x,
                            uint256 y,
                            uint256 denominator,
                            Rounding rounding
                        ) internal pure returns (uint256) {
                            uint256 result = mulDiv(x, y, denominator);
                            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                                result += 1;
                            }
                            return result;
                        }
                        /**
                         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
                         *
                         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
                         */
                        function sqrt(uint256 a) internal pure returns (uint256) {
                            if (a == 0) {
                                return 0;
                            }
                            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                            //
                            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                            //
                            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                            //
                            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                            uint256 result = 1 << (log2(a) >> 1);
                            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                            // into the expected uint128 result.
                            unchecked {
                                result = (result + a / result) >> 1;
                                result = (result + a / result) >> 1;
                                result = (result + a / result) >> 1;
                                result = (result + a / result) >> 1;
                                result = (result + a / result) >> 1;
                                result = (result + a / result) >> 1;
                                result = (result + a / result) >> 1;
                                return min(result, a / result);
                            }
                        }
                        /**
                         * @notice Calculates sqrt(a), following the selected rounding direction.
                         */
                        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                            unchecked {
                                uint256 result = sqrt(a);
                                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                            }
                        }
                        /**
                         * @dev Return the log in base 2, rounded down, of a positive value.
                         * Returns 0 if given 0.
                         */
                        function log2(uint256 value) internal pure returns (uint256) {
                            uint256 result = 0;
                            unchecked {
                                if (value >> 128 > 0) {
                                    value >>= 128;
                                    result += 128;
                                }
                                if (value >> 64 > 0) {
                                    value >>= 64;
                                    result += 64;
                                }
                                if (value >> 32 > 0) {
                                    value >>= 32;
                                    result += 32;
                                }
                                if (value >> 16 > 0) {
                                    value >>= 16;
                                    result += 16;
                                }
                                if (value >> 8 > 0) {
                                    value >>= 8;
                                    result += 8;
                                }
                                if (value >> 4 > 0) {
                                    value >>= 4;
                                    result += 4;
                                }
                                if (value >> 2 > 0) {
                                    value >>= 2;
                                    result += 2;
                                }
                                if (value >> 1 > 0) {
                                    result += 1;
                                }
                            }
                            return result;
                        }
                        /**
                         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
                         * Returns 0 if given 0.
                         */
                        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                            unchecked {
                                uint256 result = log2(value);
                                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                            }
                        }
                        /**
                         * @dev Return the log in base 10, rounded down, of a positive value.
                         * Returns 0 if given 0.
                         */
                        function log10(uint256 value) internal pure returns (uint256) {
                            uint256 result = 0;
                            unchecked {
                                if (value >= 10**64) {
                                    value /= 10**64;
                                    result += 64;
                                }
                                if (value >= 10**32) {
                                    value /= 10**32;
                                    result += 32;
                                }
                                if (value >= 10**16) {
                                    value /= 10**16;
                                    result += 16;
                                }
                                if (value >= 10**8) {
                                    value /= 10**8;
                                    result += 8;
                                }
                                if (value >= 10**4) {
                                    value /= 10**4;
                                    result += 4;
                                }
                                if (value >= 10**2) {
                                    value /= 10**2;
                                    result += 2;
                                }
                                if (value >= 10**1) {
                                    result += 1;
                                }
                            }
                            return result;
                        }
                        /**
                         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
                         * Returns 0 if given 0.
                         */
                        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                            unchecked {
                                uint256 result = log10(value);
                                return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                            }
                        }
                        /**
                         * @dev Return the log in base 256, rounded down, of a positive value.
                         * Returns 0 if given 0.
                         *
                         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
                         */
                        function log256(uint256 value) internal pure returns (uint256) {
                            uint256 result = 0;
                            unchecked {
                                if (value >> 128 > 0) {
                                    value >>= 128;
                                    result += 16;
                                }
                                if (value >> 64 > 0) {
                                    value >>= 64;
                                    result += 8;
                                }
                                if (value >> 32 > 0) {
                                    value >>= 32;
                                    result += 4;
                                }
                                if (value >> 16 > 0) {
                                    value >>= 16;
                                    result += 2;
                                }
                                if (value >> 8 > 0) {
                                    result += 1;
                                }
                            }
                            return result;
                        }
                        /**
                         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
                         * Returns 0 if given 0.
                         */
                        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                            unchecked {
                                uint256 result = log256(value);
                                return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                            }
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
                    // This file was procedurally generated from scripts/generate/templates/SafeCast.js.
                    pragma solidity ^0.8.0;
                    /**
                     * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
                     * checks.
                     *
                     * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
                     * easily result in undesired exploitation or bugs, since developers usually
                     * assume that overflows raise errors. `SafeCast` restores this intuition by
                     * reverting the transaction when such 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.
                     *
                     * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
                     * all math on `uint256` and `int256` and then downcasting.
                     */
                    library SafeCast {
                        /**
                         * @dev Returns the downcasted uint248 from uint256, reverting on
                         * overflow (when the input is greater than largest uint248).
                         *
                         * Counterpart to Solidity's `uint248` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 248 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint248(uint256 value) internal pure returns (uint248) {
                            require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
                            return uint248(value);
                        }
                        /**
                         * @dev Returns the downcasted uint240 from uint256, reverting on
                         * overflow (when the input is greater than largest uint240).
                         *
                         * Counterpart to Solidity's `uint240` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 240 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint240(uint256 value) internal pure returns (uint240) {
                            require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
                            return uint240(value);
                        }
                        /**
                         * @dev Returns the downcasted uint232 from uint256, reverting on
                         * overflow (when the input is greater than largest uint232).
                         *
                         * Counterpart to Solidity's `uint232` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 232 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint232(uint256 value) internal pure returns (uint232) {
                            require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
                            return uint232(value);
                        }
                        /**
                         * @dev Returns the downcasted uint224 from uint256, reverting on
                         * overflow (when the input is greater than largest uint224).
                         *
                         * Counterpart to Solidity's `uint224` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 224 bits
                         *
                         * _Available since v4.2._
                         */
                        function toUint224(uint256 value) internal pure returns (uint224) {
                            require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
                            return uint224(value);
                        }
                        /**
                         * @dev Returns the downcasted uint216 from uint256, reverting on
                         * overflow (when the input is greater than largest uint216).
                         *
                         * Counterpart to Solidity's `uint216` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 216 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint216(uint256 value) internal pure returns (uint216) {
                            require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
                            return uint216(value);
                        }
                        /**
                         * @dev Returns the downcasted uint208 from uint256, reverting on
                         * overflow (when the input is greater than largest uint208).
                         *
                         * Counterpart to Solidity's `uint208` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 208 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint208(uint256 value) internal pure returns (uint208) {
                            require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
                            return uint208(value);
                        }
                        /**
                         * @dev Returns the downcasted uint200 from uint256, reverting on
                         * overflow (when the input is greater than largest uint200).
                         *
                         * Counterpart to Solidity's `uint200` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 200 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint200(uint256 value) internal pure returns (uint200) {
                            require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
                            return uint200(value);
                        }
                        /**
                         * @dev Returns the downcasted uint192 from uint256, reverting on
                         * overflow (when the input is greater than largest uint192).
                         *
                         * Counterpart to Solidity's `uint192` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 192 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint192(uint256 value) internal pure returns (uint192) {
                            require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
                            return uint192(value);
                        }
                        /**
                         * @dev Returns the downcasted uint184 from uint256, reverting on
                         * overflow (when the input is greater than largest uint184).
                         *
                         * Counterpart to Solidity's `uint184` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 184 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint184(uint256 value) internal pure returns (uint184) {
                            require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
                            return uint184(value);
                        }
                        /**
                         * @dev Returns the downcasted uint176 from uint256, reverting on
                         * overflow (when the input is greater than largest uint176).
                         *
                         * Counterpart to Solidity's `uint176` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 176 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint176(uint256 value) internal pure returns (uint176) {
                            require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
                            return uint176(value);
                        }
                        /**
                         * @dev Returns the downcasted uint168 from uint256, reverting on
                         * overflow (when the input is greater than largest uint168).
                         *
                         * Counterpart to Solidity's `uint168` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 168 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint168(uint256 value) internal pure returns (uint168) {
                            require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
                            return uint168(value);
                        }
                        /**
                         * @dev Returns the downcasted uint160 from uint256, reverting on
                         * overflow (when the input is greater than largest uint160).
                         *
                         * Counterpart to Solidity's `uint160` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 160 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint160(uint256 value) internal pure returns (uint160) {
                            require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
                            return uint160(value);
                        }
                        /**
                         * @dev Returns the downcasted uint152 from uint256, reverting on
                         * overflow (when the input is greater than largest uint152).
                         *
                         * Counterpart to Solidity's `uint152` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 152 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint152(uint256 value) internal pure returns (uint152) {
                            require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
                            return uint152(value);
                        }
                        /**
                         * @dev Returns the downcasted uint144 from uint256, reverting on
                         * overflow (when the input is greater than largest uint144).
                         *
                         * Counterpart to Solidity's `uint144` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 144 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint144(uint256 value) internal pure returns (uint144) {
                            require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
                            return uint144(value);
                        }
                        /**
                         * @dev Returns the downcasted uint136 from uint256, reverting on
                         * overflow (when the input is greater than largest uint136).
                         *
                         * Counterpart to Solidity's `uint136` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 136 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint136(uint256 value) internal pure returns (uint136) {
                            require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
                            return uint136(value);
                        }
                        /**
                         * @dev Returns the downcasted uint128 from uint256, reverting on
                         * overflow (when the input is greater than largest uint128).
                         *
                         * Counterpart to Solidity's `uint128` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 128 bits
                         *
                         * _Available since v2.5._
                         */
                        function toUint128(uint256 value) internal pure returns (uint128) {
                            require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
                            return uint128(value);
                        }
                        /**
                         * @dev Returns the downcasted uint120 from uint256, reverting on
                         * overflow (when the input is greater than largest uint120).
                         *
                         * Counterpart to Solidity's `uint120` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 120 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint120(uint256 value) internal pure returns (uint120) {
                            require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
                            return uint120(value);
                        }
                        /**
                         * @dev Returns the downcasted uint112 from uint256, reverting on
                         * overflow (when the input is greater than largest uint112).
                         *
                         * Counterpart to Solidity's `uint112` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 112 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint112(uint256 value) internal pure returns (uint112) {
                            require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
                            return uint112(value);
                        }
                        /**
                         * @dev Returns the downcasted uint104 from uint256, reverting on
                         * overflow (when the input is greater than largest uint104).
                         *
                         * Counterpart to Solidity's `uint104` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 104 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint104(uint256 value) internal pure returns (uint104) {
                            require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
                            return uint104(value);
                        }
                        /**
                         * @dev Returns the downcasted uint96 from uint256, reverting on
                         * overflow (when the input is greater than largest uint96).
                         *
                         * Counterpart to Solidity's `uint96` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 96 bits
                         *
                         * _Available since v4.2._
                         */
                        function toUint96(uint256 value) internal pure returns (uint96) {
                            require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
                            return uint96(value);
                        }
                        /**
                         * @dev Returns the downcasted uint88 from uint256, reverting on
                         * overflow (when the input is greater than largest uint88).
                         *
                         * Counterpart to Solidity's `uint88` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 88 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint88(uint256 value) internal pure returns (uint88) {
                            require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
                            return uint88(value);
                        }
                        /**
                         * @dev Returns the downcasted uint80 from uint256, reverting on
                         * overflow (when the input is greater than largest uint80).
                         *
                         * Counterpart to Solidity's `uint80` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 80 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint80(uint256 value) internal pure returns (uint80) {
                            require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
                            return uint80(value);
                        }
                        /**
                         * @dev Returns the downcasted uint72 from uint256, reverting on
                         * overflow (when the input is greater than largest uint72).
                         *
                         * Counterpart to Solidity's `uint72` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 72 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint72(uint256 value) internal pure returns (uint72) {
                            require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
                            return uint72(value);
                        }
                        /**
                         * @dev Returns the downcasted uint64 from uint256, reverting on
                         * overflow (when the input is greater than largest uint64).
                         *
                         * Counterpart to Solidity's `uint64` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 64 bits
                         *
                         * _Available since v2.5._
                         */
                        function toUint64(uint256 value) internal pure returns (uint64) {
                            require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
                            return uint64(value);
                        }
                        /**
                         * @dev Returns the downcasted uint56 from uint256, reverting on
                         * overflow (when the input is greater than largest uint56).
                         *
                         * Counterpart to Solidity's `uint56` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 56 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint56(uint256 value) internal pure returns (uint56) {
                            require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
                            return uint56(value);
                        }
                        /**
                         * @dev Returns the downcasted uint48 from uint256, reverting on
                         * overflow (when the input is greater than largest uint48).
                         *
                         * Counterpart to Solidity's `uint48` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 48 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint48(uint256 value) internal pure returns (uint48) {
                            require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
                            return uint48(value);
                        }
                        /**
                         * @dev Returns the downcasted uint40 from uint256, reverting on
                         * overflow (when the input is greater than largest uint40).
                         *
                         * Counterpart to Solidity's `uint40` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 40 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint40(uint256 value) internal pure returns (uint40) {
                            require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
                            return uint40(value);
                        }
                        /**
                         * @dev Returns the downcasted uint32 from uint256, reverting on
                         * overflow (when the input is greater than largest uint32).
                         *
                         * Counterpart to Solidity's `uint32` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 32 bits
                         *
                         * _Available since v2.5._
                         */
                        function toUint32(uint256 value) internal pure returns (uint32) {
                            require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
                            return uint32(value);
                        }
                        /**
                         * @dev Returns the downcasted uint24 from uint256, reverting on
                         * overflow (when the input is greater than largest uint24).
                         *
                         * Counterpart to Solidity's `uint24` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 24 bits
                         *
                         * _Available since v4.7._
                         */
                        function toUint24(uint256 value) internal pure returns (uint24) {
                            require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
                            return uint24(value);
                        }
                        /**
                         * @dev Returns the downcasted uint16 from uint256, reverting on
                         * overflow (when the input is greater than largest uint16).
                         *
                         * Counterpart to Solidity's `uint16` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 16 bits
                         *
                         * _Available since v2.5._
                         */
                        function toUint16(uint256 value) internal pure returns (uint16) {
                            require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
                            return uint16(value);
                        }
                        /**
                         * @dev Returns the downcasted uint8 from uint256, reverting on
                         * overflow (when the input is greater than largest uint8).
                         *
                         * Counterpart to Solidity's `uint8` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 8 bits
                         *
                         * _Available since v2.5._
                         */
                        function toUint8(uint256 value) internal pure returns (uint8) {
                            require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
                            return uint8(value);
                        }
                        /**
                         * @dev Converts a signed int256 into an unsigned uint256.
                         *
                         * Requirements:
                         *
                         * - input must be greater than or equal to 0.
                         *
                         * _Available since v3.0._
                         */
                        function toUint256(int256 value) internal pure returns (uint256) {
                            require(value >= 0, "SafeCast: value must be positive");
                            return uint256(value);
                        }
                        /**
                         * @dev Returns the downcasted int248 from int256, reverting on
                         * overflow (when the input is less than smallest int248 or
                         * greater than largest int248).
                         *
                         * Counterpart to Solidity's `int248` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 248 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt248(int256 value) internal pure returns (int248 downcasted) {
                            downcasted = int248(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
                        }
                        /**
                         * @dev Returns the downcasted int240 from int256, reverting on
                         * overflow (when the input is less than smallest int240 or
                         * greater than largest int240).
                         *
                         * Counterpart to Solidity's `int240` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 240 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt240(int256 value) internal pure returns (int240 downcasted) {
                            downcasted = int240(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
                        }
                        /**
                         * @dev Returns the downcasted int232 from int256, reverting on
                         * overflow (when the input is less than smallest int232 or
                         * greater than largest int232).
                         *
                         * Counterpart to Solidity's `int232` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 232 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt232(int256 value) internal pure returns (int232 downcasted) {
                            downcasted = int232(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
                        }
                        /**
                         * @dev Returns the downcasted int224 from int256, reverting on
                         * overflow (when the input is less than smallest int224 or
                         * greater than largest int224).
                         *
                         * Counterpart to Solidity's `int224` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 224 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt224(int256 value) internal pure returns (int224 downcasted) {
                            downcasted = int224(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
                        }
                        /**
                         * @dev Returns the downcasted int216 from int256, reverting on
                         * overflow (when the input is less than smallest int216 or
                         * greater than largest int216).
                         *
                         * Counterpart to Solidity's `int216` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 216 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt216(int256 value) internal pure returns (int216 downcasted) {
                            downcasted = int216(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
                        }
                        /**
                         * @dev Returns the downcasted int208 from int256, reverting on
                         * overflow (when the input is less than smallest int208 or
                         * greater than largest int208).
                         *
                         * Counterpart to Solidity's `int208` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 208 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt208(int256 value) internal pure returns (int208 downcasted) {
                            downcasted = int208(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
                        }
                        /**
                         * @dev Returns the downcasted int200 from int256, reverting on
                         * overflow (when the input is less than smallest int200 or
                         * greater than largest int200).
                         *
                         * Counterpart to Solidity's `int200` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 200 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt200(int256 value) internal pure returns (int200 downcasted) {
                            downcasted = int200(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
                        }
                        /**
                         * @dev Returns the downcasted int192 from int256, reverting on
                         * overflow (when the input is less than smallest int192 or
                         * greater than largest int192).
                         *
                         * Counterpart to Solidity's `int192` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 192 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt192(int256 value) internal pure returns (int192 downcasted) {
                            downcasted = int192(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
                        }
                        /**
                         * @dev Returns the downcasted int184 from int256, reverting on
                         * overflow (when the input is less than smallest int184 or
                         * greater than largest int184).
                         *
                         * Counterpart to Solidity's `int184` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 184 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt184(int256 value) internal pure returns (int184 downcasted) {
                            downcasted = int184(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
                        }
                        /**
                         * @dev Returns the downcasted int176 from int256, reverting on
                         * overflow (when the input is less than smallest int176 or
                         * greater than largest int176).
                         *
                         * Counterpart to Solidity's `int176` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 176 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt176(int256 value) internal pure returns (int176 downcasted) {
                            downcasted = int176(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
                        }
                        /**
                         * @dev Returns the downcasted int168 from int256, reverting on
                         * overflow (when the input is less than smallest int168 or
                         * greater than largest int168).
                         *
                         * Counterpart to Solidity's `int168` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 168 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt168(int256 value) internal pure returns (int168 downcasted) {
                            downcasted = int168(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
                        }
                        /**
                         * @dev Returns the downcasted int160 from int256, reverting on
                         * overflow (when the input is less than smallest int160 or
                         * greater than largest int160).
                         *
                         * Counterpart to Solidity's `int160` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 160 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt160(int256 value) internal pure returns (int160 downcasted) {
                            downcasted = int160(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
                        }
                        /**
                         * @dev Returns the downcasted int152 from int256, reverting on
                         * overflow (when the input is less than smallest int152 or
                         * greater than largest int152).
                         *
                         * Counterpart to Solidity's `int152` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 152 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt152(int256 value) internal pure returns (int152 downcasted) {
                            downcasted = int152(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
                        }
                        /**
                         * @dev Returns the downcasted int144 from int256, reverting on
                         * overflow (when the input is less than smallest int144 or
                         * greater than largest int144).
                         *
                         * Counterpart to Solidity's `int144` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 144 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt144(int256 value) internal pure returns (int144 downcasted) {
                            downcasted = int144(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
                        }
                        /**
                         * @dev Returns the downcasted int136 from int256, reverting on
                         * overflow (when the input is less than smallest int136 or
                         * greater than largest int136).
                         *
                         * Counterpart to Solidity's `int136` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 136 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt136(int256 value) internal pure returns (int136 downcasted) {
                            downcasted = int136(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
                        }
                        /**
                         * @dev Returns the downcasted int128 from int256, reverting on
                         * overflow (when the input is less than smallest int128 or
                         * greater than largest int128).
                         *
                         * Counterpart to Solidity's `int128` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 128 bits
                         *
                         * _Available since v3.1._
                         */
                        function toInt128(int256 value) internal pure returns (int128 downcasted) {
                            downcasted = int128(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
                        }
                        /**
                         * @dev Returns the downcasted int120 from int256, reverting on
                         * overflow (when the input is less than smallest int120 or
                         * greater than largest int120).
                         *
                         * Counterpart to Solidity's `int120` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 120 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt120(int256 value) internal pure returns (int120 downcasted) {
                            downcasted = int120(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
                        }
                        /**
                         * @dev Returns the downcasted int112 from int256, reverting on
                         * overflow (when the input is less than smallest int112 or
                         * greater than largest int112).
                         *
                         * Counterpart to Solidity's `int112` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 112 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt112(int256 value) internal pure returns (int112 downcasted) {
                            downcasted = int112(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
                        }
                        /**
                         * @dev Returns the downcasted int104 from int256, reverting on
                         * overflow (when the input is less than smallest int104 or
                         * greater than largest int104).
                         *
                         * Counterpart to Solidity's `int104` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 104 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt104(int256 value) internal pure returns (int104 downcasted) {
                            downcasted = int104(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
                        }
                        /**
                         * @dev Returns the downcasted int96 from int256, reverting on
                         * overflow (when the input is less than smallest int96 or
                         * greater than largest int96).
                         *
                         * Counterpart to Solidity's `int96` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 96 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt96(int256 value) internal pure returns (int96 downcasted) {
                            downcasted = int96(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
                        }
                        /**
                         * @dev Returns the downcasted int88 from int256, reverting on
                         * overflow (when the input is less than smallest int88 or
                         * greater than largest int88).
                         *
                         * Counterpart to Solidity's `int88` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 88 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt88(int256 value) internal pure returns (int88 downcasted) {
                            downcasted = int88(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
                        }
                        /**
                         * @dev Returns the downcasted int80 from int256, reverting on
                         * overflow (when the input is less than smallest int80 or
                         * greater than largest int80).
                         *
                         * Counterpart to Solidity's `int80` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 80 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt80(int256 value) internal pure returns (int80 downcasted) {
                            downcasted = int80(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
                        }
                        /**
                         * @dev Returns the downcasted int72 from int256, reverting on
                         * overflow (when the input is less than smallest int72 or
                         * greater than largest int72).
                         *
                         * Counterpart to Solidity's `int72` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 72 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt72(int256 value) internal pure returns (int72 downcasted) {
                            downcasted = int72(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
                        }
                        /**
                         * @dev Returns the downcasted int64 from int256, reverting on
                         * overflow (when the input is less than smallest int64 or
                         * greater than largest int64).
                         *
                         * Counterpart to Solidity's `int64` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 64 bits
                         *
                         * _Available since v3.1._
                         */
                        function toInt64(int256 value) internal pure returns (int64 downcasted) {
                            downcasted = int64(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
                        }
                        /**
                         * @dev Returns the downcasted int56 from int256, reverting on
                         * overflow (when the input is less than smallest int56 or
                         * greater than largest int56).
                         *
                         * Counterpart to Solidity's `int56` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 56 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt56(int256 value) internal pure returns (int56 downcasted) {
                            downcasted = int56(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
                        }
                        /**
                         * @dev Returns the downcasted int48 from int256, reverting on
                         * overflow (when the input is less than smallest int48 or
                         * greater than largest int48).
                         *
                         * Counterpart to Solidity's `int48` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 48 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt48(int256 value) internal pure returns (int48 downcasted) {
                            downcasted = int48(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
                        }
                        /**
                         * @dev Returns the downcasted int40 from int256, reverting on
                         * overflow (when the input is less than smallest int40 or
                         * greater than largest int40).
                         *
                         * Counterpart to Solidity's `int40` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 40 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt40(int256 value) internal pure returns (int40 downcasted) {
                            downcasted = int40(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
                        }
                        /**
                         * @dev Returns the downcasted int32 from int256, reverting on
                         * overflow (when the input is less than smallest int32 or
                         * greater than largest int32).
                         *
                         * Counterpart to Solidity's `int32` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 32 bits
                         *
                         * _Available since v3.1._
                         */
                        function toInt32(int256 value) internal pure returns (int32 downcasted) {
                            downcasted = int32(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
                        }
                        /**
                         * @dev Returns the downcasted int24 from int256, reverting on
                         * overflow (when the input is less than smallest int24 or
                         * greater than largest int24).
                         *
                         * Counterpart to Solidity's `int24` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 24 bits
                         *
                         * _Available since v4.7._
                         */
                        function toInt24(int256 value) internal pure returns (int24 downcasted) {
                            downcasted = int24(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
                        }
                        /**
                         * @dev Returns the downcasted int16 from int256, reverting on
                         * overflow (when the input is less than smallest int16 or
                         * greater than largest int16).
                         *
                         * Counterpart to Solidity's `int16` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 16 bits
                         *
                         * _Available since v3.1._
                         */
                        function toInt16(int256 value) internal pure returns (int16 downcasted) {
                            downcasted = int16(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
                        }
                        /**
                         * @dev Returns the downcasted int8 from int256, reverting on
                         * overflow (when the input is less than smallest int8 or
                         * greater than largest int8).
                         *
                         * Counterpart to Solidity's `int8` operator.
                         *
                         * Requirements:
                         *
                         * - input must fit into 8 bits
                         *
                         * _Available since v3.1._
                         */
                        function toInt8(int256 value) internal pure returns (int8 downcasted) {
                            downcasted = int8(value);
                            require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
                        }
                        /**
                         * @dev Converts an unsigned uint256 into a signed int256.
                         *
                         * Requirements:
                         *
                         * - input must be less than or equal to maxInt256.
                         *
                         * _Available since v3.0._
                         */
                        function toInt256(uint256 value) internal pure returns (int256) {
                            // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
                            require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
                            return int256(value);
                        }
                    }
                    // SPDX-License-Identifier: MIT
                    // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
                    pragma solidity ^0.8.0;
                    import "./math/Math.sol";
                    /**
                     * @dev String operations.
                     */
                    library Strings {
                        bytes16 private constant _SYMBOLS = "0123456789abcdef";
                        uint8 private constant _ADDRESS_LENGTH = 20;
                        /**
                         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                         */
                        function toString(uint256 value) internal pure returns (string memory) {
                            unchecked {
                                uint256 length = Math.log10(value) + 1;
                                string memory buffer = new string(length);
                                uint256 ptr;
                                /// @solidity memory-safe-assembly
                                assembly {
                                    ptr := add(buffer, add(32, length))
                                }
                                while (true) {
                                    ptr--;
                                    /// @solidity memory-safe-assembly
                                    assembly {
                                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                                    }
                                    value /= 10;
                                    if (value == 0) break;
                                }
                                return buffer;
                            }
                        }
                        /**
                         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                         */
                        function toHexString(uint256 value) internal pure returns (string memory) {
                            unchecked {
                                return toHexString(value, Math.log256(value) + 1);
                            }
                        }
                        /**
                         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                         */
                        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                            bytes memory buffer = new bytes(2 * length + 2);
                            buffer[0] = "0";
                            buffer[1] = "x";
                            for (uint256 i = 2 * length + 1; i > 1; --i) {
                                buffer[i] = _SYMBOLS[value & 0xf];
                                value >>= 4;
                            }
                            require(value == 0, "Strings: hex length insufficient");
                            return string(buffer);
                        }
                        /**
                         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                         */
                        function toHexString(address addr) internal pure returns (string memory) {
                            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                        }
                    }
                    // 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;
                    /// @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;
                        /***********************************|
                        |           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 udpate 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 be 0 but never negative)
                            // 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_;
                            uint256 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) and rate at y2 can not be < rate at y1
                                // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
                                slope_ = ((y2_ - y1_) * TWELVE_DECIMALS) / (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) - int256(slope_ * 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
                                rate_ = (uint256(int256(slope_ * utilization_) + constant_)) / 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 be 0 but never negative)
                            // 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_;
                            uint256 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) and rate at y2 can not be < rate at y1
                                // y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
                                slope_ = ((y2_ - y1_) * TWELVE_DECIMALS) / (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) - int256(slope_ * 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
                                rate_ = (uint256(int256(slope_ * utilization_) + constant_)) / 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 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;
                        // 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;
                        // --------------------------------
                        /// @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 {
                        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.
                                success_ := call(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;
                    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;
                        }
                        /// @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 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 when in&out amounts balance themselves out (gas optimization):
                        /// -   supply(+) == borrow(+), withdraw(-) == payback(-).
                        /// -   `withdrawTo_` / `borrowTo_` must be msg.sender (protocol)
                        /// -   `callbackData_` MUST be encoded so that "from" address is at last 20 bytes (if this optimization is desired),
                        ///     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.
                        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;
                    abstract contract Error {
                        error FluidLendingError(uint256 errorId_);
                    }
                    // SPDX-License-Identifier: BUSL-1.1
                    pragma solidity 0.8.21;
                    library ErrorTypes {
                        /***********************************|
                        |               fToken              | 
                        |__________________________________*/
                        /// @notice thrown when a deposit amount is too small to increase BigMath stored balance in Liquidity.
                        /// precision of BigMath is 1e12, so if token holds 120_000_000_000 USDC, min amount to make a difference would be 0.1 USDC.
                        /// i.e. user would send a very small deposit which mints no shares -> revert
                        uint256 internal constant fToken__DepositInsignificant = 20001;
                        /// @notice thrown when minimum output amount is not reached, e.g. for minimum shares minted (deposit) or
                        ///         minimum assets received (redeem)
                        uint256 internal constant fToken__MinAmountOut = 20002;
                        /// @notice thrown when maximum amount is surpassed, e.g. for maximum shares burned (withdraw) or
                        ///         maximum assets input (mint)
                        uint256 internal constant fToken__MaxAmount = 20003;
                        /// @notice thrown when invalid params are sent to a method, e.g. zero address
                        uint256 internal constant fToken__InvalidParams = 20004;
                        /// @notice thrown when an unauthorized caller is trying to execute an auth-protected method
                        uint256 internal constant fToken__Unauthorized = 20005;
                        /// @notice thrown when a with permit / signature method is called from msg.sender that is the owner.
                        /// Should call the method without permit instead if msg.sender is the owner.
                        uint256 internal constant fToken__PermitFromOwnerCall = 20006;
                        /// @notice thrown when a reentrancy is detected.
                        uint256 internal constant fToken__Reentrancy = 20007;
                        /// @notice thrown when _tokenExchangePrice overflows type(uint64).max
                        uint256 internal constant fToken__ExchangePriceOverflow = 20008;
                        /// @notice thrown when msg.sender is not rebalancer
                        uint256 internal constant fToken__NotRebalancer = 20009;
                        /// @notice thrown when rebalance is called with msg.value > 0 for non NativeUnderlying fToken
                        uint256 internal constant fToken__NotNativeUnderlying = 20010;
                        /// @notice thrown when the received new liquidity exchange price is of unexpected value (< than the old one)
                        uint256 internal constant fToken__LiquidityExchangePriceUnexpected = 20011;
                        /***********************************|
                        |     fToken Native Underlying      | 
                        |__________________________________*/
                        /// @notice thrown when native deposit is called but sent along `msg.value` does not cover the deposit amount
                        uint256 internal constant fTokenNativeUnderlying__TransferInsufficient = 21001;
                        /// @notice thrown when a liquidity callback is called for a native token operation
                        uint256 internal constant fTokenNativeUnderlying__UnexpectedLiquidityCallback = 21002;
                        /***********************************|
                        |         Lending Factory         | 
                        |__________________________________*/
                        /// @notice thrown when a method is called with invalid params
                        uint256 internal constant LendingFactory__InvalidParams = 22001;
                        /// @notice thrown when the provided input param address is zero
                        uint256 internal constant LendingFactory__ZeroAddress = 22002;
                        /// @notice thrown when the token already exists
                        uint256 internal constant LendingFactory__TokenExists = 22003;
                        /// @notice thrown when the fToken has not yet been configured at Liquidity
                        uint256 internal constant LendingFactory__LiquidityNotConfigured = 22004;
                        /// @notice thrown when an unauthorized caller is trying to execute an auth-protected method
                        uint256 internal constant LendingFactory__Unauthorized = 22005;
                        /***********************************|
                        |   Lending Rewards Rate Model      | 
                        |__________________________________*/
                        /// @notice thrown when invalid params are given as input
                        uint256 internal constant LendingRewardsRateModel__InvalidParams = 23001;
                        /// @notice thrown when calculated rewards rate is exceeding the maximum rate
                        uint256 internal constant LendingRewardsRateModel__MaxRate = 23002;
                        /// @notice thrown when start is called by any other address other than initiator
                        uint256 internal constant LendingRewardsRateModel__NotTheInitiator = 23003;
                        /// @notice thrown when start is called after the rewards are already started
                        uint256 internal constant LendingRewardsRateModel__AlreadyStarted = 23004;
                        /// @notice thrown when the provided input param address is zero
                        uint256 internal constant LendingRewardsRateModel__ZeroAddress = 23005;
                    }
                    // SPDX-License-Identifier: BUSL-1.1
                    pragma solidity 0.8.21;
                    import { IFluidLendingRewardsRateModel  } from "../interfaces/iLendingRewardsRateModel.sol";
                    abstract contract Events {
                        /// @notice emitted whenever admin updates rewards rate model
                        event LogUpdateRewards(IFluidLendingRewardsRateModel  indexed rewardsRateModel);
                        /// @notice emitted whenever rebalance is executed to fund difference between Liquidity deposit and totalAssets()
                        ///         as rewards through the rebalancer.
                        event LogRebalance(uint256 assets);
                        /// @notice emitted whenever exchange rates are updated
                        event LogUpdateRates(uint256 tokenExchangePrice, uint256 liquidityExchangePrice);
                        /// @notice emitted whenever funds for a certain `token` are rescued to Liquidity
                        event LogRescueFunds(address indexed token);
                        /// @notice emitted whenever rebalancer address is updated
                        event LogUpdateRebalancer(address indexed rebalancer);
                    }
                    // SPDX-License-Identifier: BUSL-1.1
                    pragma solidity 0.8.21;
                    import { ERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
                    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
                    import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
                    import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
                    import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
                    import { IAllowanceTransfer } from "../interfaces/permit2/iAllowanceTransfer.sol";
                    import { IFluidLendingRewardsRateModel } from "../interfaces/iLendingRewardsRateModel.sol";
                    import { IFluidLendingFactory } from "../interfaces/iLendingFactory.sol";
                    import { IFToken, IFTokenAdmin } from "../interfaces/iFToken.sol";
                    import { LiquidityCalcs } from "../../../libraries/liquidityCalcs.sol";
                    import { BigMathMinified } from "../../../libraries/bigMathMinified.sol";
                    import { LiquiditySlotsLink } from "../../../libraries/liquiditySlotsLink.sol";
                    import { SafeTransfer } from "../../../libraries/safeTransfer.sol";
                    import { IFluidLiquidity } from "../../../liquidity/interfaces/iLiquidity.sol";
                    import { Variables } from "./variables.sol";
                    import { Events } from "./events.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 Error, Variables {
                        uint8 internal constant REENTRANCY_NOT_ENTERED = 1;
                        uint8 internal constant REENTRANCY_ENTERED = 2;
                        constructor() {
                            _status = REENTRANCY_NOT_ENTERED;
                        }
                        /// @dev checks that no reentrancy occurs, reverts if so. Calling the method in the modifier reduces
                        /// bytecode size as modifiers are inlined into bytecode
                        function _checkReentrancy() internal {
                            // On the first call to nonReentrant, _status will be NOT_ENTERED
                            if (_status != REENTRANCY_NOT_ENTERED) {
                                revert FluidLendingError(ErrorTypes.fToken__Reentrancy);
                            }
                            // Any calls to nonReentrant after this point will fail
                            _status = REENTRANCY_ENTERED;
                        }
                        /// @dev Prevents a contract from calling itself, directly or indirectly.
                        /// See OpenZeppelin implementation for more info
                        modifier nonReentrant() {
                            _checkReentrancy();
                            _;
                            // storing original value triggers a refund (see https://eips.ethereum.org/EIPS/eip-2200)
                            _status = REENTRANCY_NOT_ENTERED;
                        }
                    }
                    /// @dev internal methods for fToken contracts
                    abstract contract fTokenCore is Error, IERC4626, IFToken, Variables, Events, ReentrancyGuard {
                        using FixedPointMathLib for uint256;
                        /// @dev Gets current (updated) Liquidity supply exchange price for the underyling asset
                        function _getLiquidityExchangePrice() internal view returns (uint256 supplyExchangePrice_) {
                            (supplyExchangePrice_, ) = LiquidityCalcs.calcExchangePrices(
                                LIQUIDITY.readFromStorage(LIQUIDITY_EXCHANGE_PRICES_SLOT)
                            );
                        }
                        /// @dev Gets current Liquidity supply balance of `address(this)` for the underyling asset
                        function _getLiquidityBalance() internal view returns (uint256 balance_) {
                            // extract user supply amount
                            uint256 userSupplyRaw_ = BigMathMinified.fromBigNumber(
                                (LIQUIDITY.readFromStorage(LIQUIDITY_USER_SUPPLY_SLOT) >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) &
                                    LiquidityCalcs.X64,
                                LiquidityCalcs.DEFAULT_EXPONENT_SIZE,
                                LiquidityCalcs.DEFAULT_EXPONENT_MASK
                            );
                            unchecked {
                                // can not overflow as userSupplyRaw_ can be maximally type(int128).max, liquidity exchange price type(uint64).max
                                return (userSupplyRaw_ * _getLiquidityExchangePrice()) / EXCHANGE_PRICES_PRECISION;
                            }
                        }
                        /// @dev Gets current Liquidity underlying token balance
                        function _getLiquidityUnderlyingBalance() internal view virtual returns (uint256) {
                            return ASSET.balanceOf(address(LIQUIDITY));
                        }
                        /// @dev Gets current withdrawable amount at Liquidity `withdrawalLimit_` (withdrawal limit or balance).
                        function _getLiquidityWithdrawable() internal view returns (uint256 withdrawalLimit_) {
                            uint256 userSupplyData_ = LIQUIDITY.readFromStorage(LIQUIDITY_USER_SUPPLY_SLOT);
                            uint256 userSupply_ = BigMathMinified.fromBigNumber(
                                (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & LiquidityCalcs.X64,
                                LiquidityCalcs.DEFAULT_EXPONENT_SIZE,
                                LiquidityCalcs.DEFAULT_EXPONENT_MASK
                            );
                            withdrawalLimit_ = LiquidityCalcs.calcWithdrawalLimitBeforeOperate(userSupplyData_, userSupply_);
                            // convert raw amounts to normal amounts
                            unchecked {
                                // can not overflow as userSupply_ can be maximally type(int128).max
                                // and withdrawalLimit is smaller than userSupply_
                                uint256 liquidityExchangePrice_ = _getLiquidityExchangePrice();
                                withdrawalLimit_ = (withdrawalLimit_ * liquidityExchangePrice_) / EXCHANGE_PRICES_PRECISION;
                                userSupply_ = (userSupply_ * liquidityExchangePrice_) / EXCHANGE_PRICES_PRECISION;
                            }
                            withdrawalLimit_ = userSupply_ > withdrawalLimit_ ? userSupply_ - withdrawalLimit_ : 0;
                            uint256 balanceAtLiquidity_ = _getLiquidityUnderlyingBalance();
                            return balanceAtLiquidity_ > withdrawalLimit_ ? withdrawalLimit_ : balanceAtLiquidity_;
                        }
                        /// @dev Calculates new token exchange price based on the current liquidity exchange price `newLiquidityExchangePrice_` and rewards rate.
                        /// @param newLiquidityExchangePrice_ new (current) liquidity exchange price
                        function _calculateNewTokenExchangePrice(
                            uint256 newLiquidityExchangePrice_
                        ) internal view returns (uint256 newTokenExchangePrice_, bool rewardsEnded_) {
                            uint256 oldTokenExchangePrice_ = _tokenExchangePrice;
                            uint256 oldLiquidityExchangePrice_ = _liquidityExchangePrice;
                            if (newLiquidityExchangePrice_ < oldLiquidityExchangePrice_) {
                                // liquidity exchange price should only ever increase. If not, something went wrong and avoid
                                // proceeding with unknown outcome.
                                revert FluidLendingError(ErrorTypes.fToken__LiquidityExchangePriceUnexpected);
                            }
                            uint256 totalReturnInPercent_; // rewardsRateInPercent + liquidityReturnInPercent
                            if (_rewardsActive) {
                                {
                                    // get rewards rate per year
                                    // only trigger call to rewardsRateModel if rewards are actually active to save gas
                                    uint256 rewardsRate_;
                                    uint256 rewardsStartTime_;
                                    (rewardsRate_, rewardsEnded_, rewardsStartTime_) = _rewardsRateModel.getRate(
                                        // use old tokenExchangeRate to calculate the total assets input for the rewards rate
                                        (oldTokenExchangePrice_ * totalSupply()) / EXCHANGE_PRICES_PRECISION
                                    );
                                    if (rewardsRate_ > MAX_REWARDS_RATE || rewardsEnded_) {
                                        // rewardsRate is capped, if it is bigger > MAX_REWARDS_RATE, then the rewardsRateModel
                                        // is configured wrongly (which should not be possible). Setting rewards to 0 in that case here.
                                        rewardsRate_ = 0;
                                    }
                                    uint256 lastUpdateTimestamp_ = _lastUpdateTimestamp;
                                    if (lastUpdateTimestamp_ < rewardsStartTime_) {
                                        // if last update was before the rewards started, make sure rewards actually only accrue
                                        // from the actual rewards start time, not from the last update timestamp to avoid overpayment.
                                        lastUpdateTimestamp_ = rewardsStartTime_;
                                        // Note: overpayment for block.timestamp being > rewards end time does not happen because
                                        // rewardsRate_ is forced 0 then.
                                    }
                                    // calculate rewards return in percent: (rewards_rate * time passed) / seconds_in_a_year.
                                    unchecked {
                                        // rewardsRate * timeElapsed / SECONDS_PER_YEAR.
                                        // no safe checks needed here because timeElapsed can not underflow,
                                        // rewardsRate is in 1e12 at max value being MAX_REWARDS_RATE = 25e12
                                        // max value would be 25e12 * 8589934591 / 31536000 (with buffers) = 6.8e15
                                        totalReturnInPercent_ =
                                            (rewardsRate_ * (block.timestamp - lastUpdateTimestamp_)) /
                                            SECONDS_PER_YEAR;
                                    }
                                }
                            }
                            unchecked {
                                // calculate liquidityReturnInPercent: (newLiquidityExchangePrice_ - oldLiquidityExchangePrice_) / oldLiquidityExchangePrice_.
                                // and add it to totalReturnInPercent_ that already holds rewardsRateInPercent_.
                                // max value (in absolute extreme unrealistic case) would be: 6.8e15 + (((max uint64 - 1e12) * 1e12) / 1e12) = 1.845e19
                                // oldLiquidityExchangePrice_ can not be 0, minimal value is 1e12. subtraction can not underflow because new exchange price
                                // can only be >= oldLiquidityExchangePrice_.
                                totalReturnInPercent_ +=
                                    ((newLiquidityExchangePrice_ - oldLiquidityExchangePrice_) * 1e14) /
                                    oldLiquidityExchangePrice_;
                            }
                            // newTokenExchangePrice_ = oldTokenExchangePrice_ + oldTokenExchangePrice_ * totalReturnInPercent_
                            newTokenExchangePrice_ = oldTokenExchangePrice_ + ((oldTokenExchangePrice_ * totalReturnInPercent_) / 1e14); // divided by 100% (1e14)
                        }
                        /// @dev calculates new exchange prices, updates values in storage and returns new tokenExchangePrice (with reward rates)
                        function _updateRates(
                            uint256 liquidityExchangePrice_,
                            bool forceUpdateStorage_
                        ) internal returns (uint256 tokenExchangePrice_) {
                            bool rewardsEnded_;
                            (tokenExchangePrice_, rewardsEnded_) = _calculateNewTokenExchangePrice(liquidityExchangePrice_);
                            if (_rewardsActive || forceUpdateStorage_) {
                                // Solidity will NOT cause a revert if values are too big to fit max uint type size. Explicitly check before
                                // writing to storage. Also see https://github.com/ethereum/solidity/issues/10195.
                                if (tokenExchangePrice_ > type(uint64).max) {
                                    revert FluidLendingError(ErrorTypes.fToken__ExchangePriceOverflow);
                                }
                                _tokenExchangePrice = uint64(tokenExchangePrice_);
                                _liquidityExchangePrice = uint64(liquidityExchangePrice_);
                                _lastUpdateTimestamp = uint40(block.timestamp);
                                emit LogUpdateRates(tokenExchangePrice_, liquidityExchangePrice_);
                            }
                            if (rewardsEnded_) {
                                // set rewardsActive flag to false to save gas for all future exchange prices calculations,
                                // without having to explicitly require setting `updateRewards` to address zero.
                                // Note that it would be fine that even the current tx does not update exchange prices in storage,
                                // because if rewardsEnded_ is true, rewardsRate_ must be 0, so the only yield is from LIQUIDITY.
                                // But to be extra safe, writing to storage in that one case too before setting _rewardsActive to false.
                                _rewardsActive = false;
                            }
                            return tokenExchangePrice_;
                        }
                        /// @dev splits a bytes signature `sig` into `v`, `r`, `s`.
                        /// Taken from https://docs.soliditylang.org/en/v0.8.17/solidity-by-example.html
                        function _splitSignature(bytes memory sig) internal pure returns (uint8 v, bytes32 r, bytes32 s) {
                            require(sig.length == 65);
                            assembly {
                                // first 32 bytes, after the length prefix.
                                r := mload(add(sig, 32))
                                // second 32 bytes.
                                s := mload(add(sig, 64))
                                // final byte (first byte of the next 32 bytes).
                                v := byte(0, mload(add(sig, 96)))
                            }
                            return (v, r, s);
                        }
                        /// @dev Deposit `assets_` amount of tokens to Liquidity
                        /// @param assets_ The amount of tokens to deposit
                        /// @param liquidityCallbackData_ callback data passed to Liquidity for `liquidityCallback`
                        /// @return exchangePrice_ liquidity exchange price for token
                        function _depositToLiquidity(
                            uint256 assets_,
                            bytes memory liquidityCallbackData_
                        ) internal virtual returns (uint256 exchangePrice_) {
                            // @dev Note: Although there might be some small difference between the `assets_` amount and the actual amount
                            // accredited at Liquidity due to BigMath rounding down, this amount is so small that it can be ignored.
                            // because of BigMath precision of 7.2057594e16 for a coefficient size of 56, it would require >72 trillion DAI
                            // to "benefit" 1 DAI in additional shares minted. Considering gas cost + APR per second, this ensures such
                            // a manipulation attempt becomes extremely unlikely.
                            // send funds to Liquidity protocol to generate yield
                            (exchangePrice_, ) = LIQUIDITY.operate(
                                address(ASSET),
                                SafeCast.toInt256(assets_),
                                0,
                                address(0),
                                address(0),
                                liquidityCallbackData_ // callback data. -> "from" for transferFrom in `liquidityCallback`
                            );
                        }
                        /// @dev Withdraw `assets_` amount of tokens from Liquidity directly to `receiver_`
                        /// @param assets_    The amount of tokens to withdraw
                        /// @param receiver_  the receiver address of withdraw amount
                        /// @return exchangePrice_   liquidity exchange price for token
                        function _withdrawFromLiquidity(
                            uint256 assets_,
                            address receiver_
                        ) internal virtual returns (uint256 exchangePrice_) {
                            // @dev See similar comment in `_depositToLiquidity()` regarding burning a tiny bit of additional shares here
                            // because of inaccuracies in Liquidity userSupply BigMath being rounded down.
                            // get funds back from Liquidity protocol to send to the user
                            (exchangePrice_, ) = LIQUIDITY.operate(
                                address(ASSET),
                                -SafeCast.toInt256(assets_),
                                0,
                                receiver_,
                                address(0),
                                new bytes(0) // callback data -> withdraw doesn't trigger a callback
                            );
                        }
                        /// @dev deposits `assets_` into liquidity and mints shares for `receiver_`. Returns amount of `sharesMinted_`.
                        function _executeDeposit(
                            uint256 assets_,
                            address receiver_,
                            bytes memory liquidityCallbackData_
                        ) internal virtual validAddress(receiver_) returns (uint256 sharesMinted_) {
                            // send funds to Liquidity protocol to generate yield -> returns updated liquidityExchangePrice
                            uint256 tokenExchangePrice_ = _depositToLiquidity(assets_, liquidityCallbackData_);
                            // update the exchange prices
                            tokenExchangePrice_ = _updateRates(tokenExchangePrice_, false);
                            // calculate the shares to mint
                            // not using previewDeposit here because we just got newTokenExchangePrice_
                            sharesMinted_ = (assets_ * EXCHANGE_PRICES_PRECISION) / tokenExchangePrice_;
                            if (sharesMinted_ == 0) {
                                revert FluidLendingError(ErrorTypes.fToken__DepositInsignificant);
                            }
                            _mint(receiver_, sharesMinted_);
                            emit Deposit(msg.sender, receiver_, assets_, sharesMinted_);
                        }
                        /// @dev withdraws `assets_` from liquidity to `receiver_` and burns shares from `owner_`.
                        /// Returns amount of `sharesBurned_`.
                        /// requires nonReentrant! modifier on calling method otherwise ERC777s could reenter!
                        function _executeWithdraw(
                            uint256 assets_,
                            address receiver_,
                            address owner_
                        ) internal virtual validAddress(receiver_) returns (uint256 sharesBurned_) {
                            // burn shares for assets_ amount: assets_ * EXCHANGE_PRICES_PRECISION / updatedTokenTexchangePrice. Rounded up.
                            // Note to be extra safe we do the shares burn before the withdrawFromLiquidity, even though that would return the
                            // updated liquidityExchangePrice and thus save gas.
                            sharesBurned_ = assets_.mulDivUp(EXCHANGE_PRICES_PRECISION, _updateRates(_getLiquidityExchangePrice(), false));
                            /*
                                The `mulDivUp` function is designed to round up the result of multiplication followed by division. 
                                Given non-zero `assets_` and the rounding-up behavior of this function, `sharesBurned_` will always 
                                be at least 1 if there's any remainder in the division.
                                Thus, if `assets_` is non-zero, `sharesBurned_` can never be 0. The nature of the function ensures 
                                that even the smallest fractional result (greater than 0) will be rounded up to 1. Hence, there's no need 
                                to check for a rounding error that results in 0.
                                Furthermore, if `assets_` was 0, an error 'UserModule__OperateAmountsZero' would already have been thrown 
                                during the `operate` function, ensuring the contract never reaches this point with a zero `assets_` value.
                                Note: If ever the logic or the function behavior changes in the future, this assertion may need to be reconsidered.
                            */
                            _burn(owner_, sharesBurned_);
                            // withdraw from liquidity directly to _receiver.
                            _withdrawFromLiquidity(assets_, receiver_);
                            emit Withdraw(msg.sender, receiver_, owner_, assets_, sharesBurned_);
                        }
                    }
                    /// @notice fToken view methods. Implements view methods for ERC4626 compatibility
                    abstract contract fTokenViews is fTokenCore {
                        using FixedPointMathLib for uint256;
                        /// @inheritdoc IFToken
                        function getData()
                            public
                            view
                            returns (
                                IFluidLiquidity liquidity_,
                                IFluidLendingFactory lendingFactory_,
                                IFluidLendingRewardsRateModel lendingRewardsRateModel_,
                                IAllowanceTransfer permit2_,
                                address rebalancer_,
                                bool rewardsActive_,
                                uint256 liquidityBalance_,
                                uint256 liquidityExchangePrice_,
                                uint256 tokenExchangePrice_
                            )
                        {
                            liquidityExchangePrice_ = _getLiquidityExchangePrice();
                            bool rewardsEnded_;
                            (tokenExchangePrice_, rewardsEnded_) = _calculateNewTokenExchangePrice(liquidityExchangePrice_);
                            return (
                                LIQUIDITY,
                                LENDING_FACTORY,
                                _rewardsRateModel,
                                PERMIT2,
                                _rebalancer,
                                _rewardsActive && !rewardsEnded_,
                                _getLiquidityBalance(),
                                liquidityExchangePrice_,
                                tokenExchangePrice_
                            );
                        }
                        /// @inheritdoc IERC4626
                        function asset() public view virtual override returns (address) {
                            return address(ASSET);
                        }
                        /// @inheritdoc IERC4626
                        function totalAssets() public view virtual override returns (uint256) {
                            (uint256 tokenExchangePrice_, ) = _calculateNewTokenExchangePrice(_getLiquidityExchangePrice());
                            return
                                // all the underlying tokens are stored in Liquidity contract at all times
                                (tokenExchangePrice_ * totalSupply()) / EXCHANGE_PRICES_PRECISION;
                        }
                        /// @inheritdoc IERC4626
                        function convertToShares(uint256 assets_) public view virtual override returns (uint256) {
                            (uint256 tokenExchangePrice_, ) = _calculateNewTokenExchangePrice(_getLiquidityExchangePrice());
                            return assets_.mulDivDown(EXCHANGE_PRICES_PRECISION, tokenExchangePrice_);
                        }
                        /// @inheritdoc IERC4626
                        function convertToAssets(uint256 shares_) public view virtual override returns (uint256) {
                            (uint256 tokenExchangePrice_, ) = _calculateNewTokenExchangePrice(_getLiquidityExchangePrice());
                            return shares_.mulDivDown(tokenExchangePrice_, EXCHANGE_PRICES_PRECISION);
                        }
                        /// @inheritdoc IERC4626
                        /// @notice returned amount might be slightly different from actual amount at execution.
                        function previewDeposit(uint256 assets_) public view virtual override returns (uint256) {
                            return convertToShares(assets_);
                        }
                        /// @inheritdoc IERC4626
                        function previewMint(uint256 shares_) public view virtual override returns (uint256) {
                            (uint256 tokenExchangePrice_, ) = _calculateNewTokenExchangePrice(_getLiquidityExchangePrice());
                            return shares_.mulDivUp(tokenExchangePrice_, EXCHANGE_PRICES_PRECISION);
                        }
                        /// @inheritdoc IERC4626
                        function previewWithdraw(uint256 assets_) public view virtual override returns (uint256) {
                            (uint256 tokenExchangePrice_, ) = _calculateNewTokenExchangePrice(_getLiquidityExchangePrice());
                            return assets_.mulDivUp(EXCHANGE_PRICES_PRECISION, tokenExchangePrice_);
                        }
                        /// @inheritdoc IERC4626
                        /// @notice returned amount might be slightly different from actual amount at execution.
                        function previewRedeem(uint256 shares_) public view virtual override returns (uint256) {
                            return convertToAssets(shares_);
                        }
                        /*//////////////////////////////////////////////////////////////
                                         DEPOSIT/WITHDRAWAL LIMIT LOGIC
                        //////////////////////////////////////////////////////////////*/
                        /// @inheritdoc IERC4626
                        function maxDeposit(address) public view virtual override returns (uint256) {
                            // read total supplyInterest_ for the token at Liquidity and convert from BigMath
                            uint256 supplyInterest_ = LIQUIDITY.readFromStorage(LIQUIDITY_TOTAL_AMOUNTS_SLOT) & LiquidityCalcs.X64;
                            supplyInterest_ =
                                (supplyInterest_ >> LiquidityCalcs.DEFAULT_EXPONENT_SIZE) <<
                                (supplyInterest_ & LiquidityCalcs.DEFAULT_EXPONENT_MASK);
                            unchecked {
                                // normalize from raw
                                supplyInterest_ = (supplyInterest_ * _getLiquidityExchangePrice()) / EXCHANGE_PRICES_PRECISION;
                                // compare against hardcoded max possible value for total supply considering BigMath rounding down:
                                // type(int128).max) after BigMath rounding (first 56 bits precision, then 71 bits getting set to 0)
                                // so 1111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000
                                // = 170141183460469229370504062281061498880. using minus 1
                                if (supplyInterest_ > 170141183460469229370504062281061498879) {
                                    return 0;
                                }
                                // type(int128).max is the maximum interactable amount at Liquidity. But also total token amounts
                                // must not overflow type(int128).max, so max depositable is type(int128).max - totalSupply.
                                return uint256(uint128(type(int128).max)) - supplyInterest_;
                            }
                        }
                        /// @inheritdoc IERC4626
                        function maxMint(address) public view virtual override returns (uint256) {
                            return convertToShares(maxDeposit(address(0)));
                        }
                        /// @inheritdoc IERC4626
                        function maxWithdraw(address owner_) public view virtual override returns (uint256) {
                            uint256 maxWithdrawableAtLiquidity_ = _getLiquidityWithdrawable();
                            uint256 ownerBalance_ = convertToAssets(balanceOf(owner_));
                            return maxWithdrawableAtLiquidity_ < ownerBalance_ ? maxWithdrawableAtLiquidity_ : ownerBalance_;
                        }
                        /// @inheritdoc IERC4626
                        function maxRedeem(address owner_) public view virtual override returns (uint256) {
                            uint256 maxWithdrawableAtLiquidity_ = convertToShares(_getLiquidityWithdrawable());
                            uint256 ownerBalance_ = balanceOf(owner_);
                            return maxWithdrawableAtLiquidity_ < ownerBalance_ ? maxWithdrawableAtLiquidity_ : ownerBalance_;
                        }
                        /// @inheritdoc IFToken
                        function minDeposit() public view returns (uint256) {
                            uint256 minBigMathRounding_ = 1 <<
                                (LIQUIDITY.readFromStorage(LIQUIDITY_TOTAL_AMOUNTS_SLOT) & LiquidityCalcs.DEFAULT_EXPONENT_MASK); // 1 << total supply exponent
                            uint256 previewMint_ = previewMint(1); // rounds up
                            return minBigMathRounding_ > previewMint_ ? minBigMathRounding_ : previewMint_;
                        }
                    }
                    /// @notice fToken admin related methods. fToken admins are Lending Factory auths. Possible actions are
                    /// updating rewards, funding rewards, and rescuing any stuck funds (fToken contract itself never holds any funds).
                    abstract contract fTokenAdmin is fTokenCore, fTokenViews {
                        /// @dev checks if `msg.sender` is an allowed auth at LendingFactory. internal method instead of modifier
                        ///      to reduce bytecode size.
                        function _checkIsLendingFactoryAuth() internal view {
                            if (!LENDING_FACTORY.isAuth(msg.sender)) {
                                revert FluidLendingError(ErrorTypes.fToken__Unauthorized);
                            }
                        }
                        /// @inheritdoc IFTokenAdmin
                        function updateRewards(IFluidLendingRewardsRateModel rewardsRateModel_) external {
                            _checkIsLendingFactoryAuth();
                            // @dev no check for address zero needed here, as that is actually explicitly checked where _rewardsRateModel
                            // is used. In fact it is beneficial to set _rewardsRateModel to address zero when there are no rewards.
                            // apply current rewards rate before updating to new one
                            updateRates();
                            _rewardsRateModel = rewardsRateModel_;
                            // set flag _rewardsActive
                            _rewardsActive = address(rewardsRateModel_) != address(0);
                            emit LogUpdateRewards(rewardsRateModel_);
                        }
                        /// @inheritdoc IFTokenAdmin
                        function rebalance() external payable virtual nonReentrant returns (uint256 assets_) {
                            if (msg.sender != _rebalancer) {
                                revert FluidLendingError(ErrorTypes.fToken__NotRebalancer);
                            }
                            if (msg.value > 0) {
                                revert FluidLendingError(ErrorTypes.fToken__NotNativeUnderlying);
                            }
                            // calculating difference in assets. if liquidity balance is bigger it'll throw which is an expected behaviour
                            assets_ = totalAssets() - _getLiquidityBalance();
                            // send funds to Liquidity protocol
                            uint256 liquidityExchangePrice_ = _depositToLiquidity(assets_, abi.encode(msg.sender));
                            // update the exchange prices, always updating on storage
                            _updateRates(liquidityExchangePrice_, true);
                            // no shares are minted when funding fToken contract for rewards
                            emit LogRebalance(assets_);
                        }
                        /// @inheritdoc IFTokenAdmin
                        function updateRebalancer(address newRebalancer_) public validAddress(newRebalancer_) {
                            _checkIsLendingFactoryAuth();
                            _rebalancer = newRebalancer_;
                            emit LogUpdateRebalancer(newRebalancer_);
                        }
                        /// @inheritdoc IFTokenAdmin
                        function updateRates() public returns (uint256 tokenExchangePrice_, uint256 liquidityExchangePrice_) {
                            liquidityExchangePrice_ = _getLiquidityExchangePrice();
                            tokenExchangePrice_ = _updateRates(liquidityExchangePrice_, true);
                        }
                        /// @inheritdoc IFTokenAdmin
                        //
                        // @dev this contract never holds any funds:
                        // -> deposited funds are directly sent to Liquidity.
                        // -> rewards are also stored at Liquidity.
                        function rescueFunds(address token_) external virtual nonReentrant {
                            _checkIsLendingFactoryAuth();
                            SafeTransfer.safeTransfer(address(token_), address(LIQUIDITY), IERC20(token_).balanceOf(address(this)));
                            emit LogRescueFunds(token_);
                        }
                    }
                    /// @notice fToken public executable actions: deposit, mint, mithdraw and redeem.
                    /// All actions are optionally also available with an additional param to limit the maximum slippage, e.g. maximum
                    /// assets used for minting x amount of shares.
                    abstract contract fTokenActions is fTokenCore, fTokenViews {
                        /// @dev reverts if `amount_` is < `minAmountOut_`. Used to reduce bytecode size.
                        function _revertIfBelowMinAmountOut(uint256 amount_, uint256 minAmountOut_) internal pure {
                            if (amount_ < minAmountOut_) {
                                revert FluidLendingError(ErrorTypes.fToken__MinAmountOut);
                            }
                        }
                        /// @dev reverts if `amount_` is > `maxAmount_`. Used to reduce bytecode size.
                        function _revertIfAboveMaxAmount(uint256 amount_, uint256 maxAmount_) internal pure {
                            if (amount_ > maxAmount_) {
                                revert FluidLendingError(ErrorTypes.fToken__MaxAmount);
                            }
                        }
                        /*//////////////////////////////////////////////////////////////
                                                    DEPOSIT
                        //////////////////////////////////////////////////////////////*/
                        /// @inheritdoc IERC4626
                        /// @notice If `assets_` equals uint256.max then the whole balance of `msg.sender` is deposited.
                        ///         `assets_` must at least be `minDeposit()` amount; reverts `fToken__DepositInsignificant()` if not.
                        ///         Recommended to use `deposit()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return shares_ actually minted shares
                        function deposit(
                            uint256 assets_,
                            address receiver_
                        ) public virtual override nonReentrant returns (uint256 shares_) {
                            if (assets_ == type(uint256).max) {
                                assets_ = ASSET.balanceOf(msg.sender);
                            }
                            // @dev transfer of tokens from `msg.sender` to liquidity contract happens via `liquidityCallback`
                            shares_ = _executeDeposit(assets_, receiver_, abi.encode(msg.sender));
                        }
                        /// @notice same as {fToken-deposit} but with an additional setting for minimum output amount.
                        /// reverts with `fToken__MinAmountOut()` if `minAmountOut_` of shares is not reached
                        function deposit(uint256 assets_, address receiver_, uint256 minAmountOut_) external returns (uint256 shares_) {
                            shares_ = deposit(assets_, receiver_);
                            _revertIfBelowMinAmountOut(shares_, minAmountOut_);
                        }
                        /*//////////////////////////////////////////////////////////////
                                                       MINT 
                        //////////////////////////////////////////////////////////////*/
                        /// @inheritdoc IERC4626
                        /// @notice If `shares_` equals uint256.max then the whole balance of `msg.sender` is deposited.
                        ///         `shares_` must at least be `minMint()` amount; reverts `fToken__DepositInsignificant()` if not.
                        ///         Note there might be tiny inaccuracies between requested `shares_` and actually received shares amount.
                        ///         Recommended to use `deposit()` over mint because it is more gas efficient and less likely to revert.
                        ///         Recommended to use `mint()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return assets_ deposited assets amount
                        function mint(uint256 shares_, address receiver_) public virtual override nonReentrant returns (uint256 assets_) {
                            if (shares_ == type(uint256).max) {
                                assets_ = ASSET.balanceOf(msg.sender);
                            } else {
                                // No need to check for rounding error, previewMint rounds up.
                                assets_ = previewMint(shares_);
                            }
                            // @dev transfer of tokens from `msg.sender` to liquidity contract happens via `liquidityCallback`
                            _executeDeposit(assets_, receiver_, abi.encode(msg.sender));
                        }
                        /// @notice same as {fToken-mint} but with an additional setting for maximum assets input amount.
                        /// reverts with `fToken__MaxAmount()` if `maxAssets_` of assets is surpassed to mint `shares_`.
                        function mint(uint256 shares_, address receiver_, uint256 maxAssets_) external returns (uint256 assets_) {
                            assets_ = mint(shares_, receiver_);
                            _revertIfAboveMaxAmount(assets_, maxAssets_);
                        }
                        /*//////////////////////////////////////////////////////////////
                                                    WITHDRAW
                        //////////////////////////////////////////////////////////////*/
                        /// @inheritdoc IERC4626
                        /// @notice If `assets_` equals uint256.max then the whole fToken balance of `owner_` is withdrawn. This does not
                        ///         consider withdrawal limit at Liquidity so best to check with `maxWithdraw()` before.
                        ///         Note there might be tiny inaccuracies between requested `assets_` and actually received assets amount.
                        ///         Recommended to use `withdraw()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return shares_ burned shares
                        function withdraw(
                            uint256 assets_,
                            address receiver_,
                            address owner_
                        ) public virtual override nonReentrant returns (uint256 shares_) {
                            if (assets_ == type(uint256).max) {
                                assets_ = previewRedeem(balanceOf(owner_));
                            }
                            shares_ = _executeWithdraw(assets_, receiver_, owner_);
                            if (msg.sender != owner_) {
                                _spendAllowance(owner_, msg.sender, shares_);
                            }
                        }
                        /// @notice same as {fToken-withdraw} but with an additional setting for maximum shares burned.
                        /// reverts with `fToken__MaxAmount()` if `maxSharesBurn_` of shares burned is surpassed.
                        function withdraw(
                            uint256 assets_,
                            address receiver_,
                            address owner_,
                            uint256 maxSharesBurn_
                        ) external returns (uint256 shares_) {
                            shares_ = withdraw(assets_, receiver_, owner_);
                            _revertIfAboveMaxAmount(shares_, maxSharesBurn_);
                        }
                        /*//////////////////////////////////////////////////////////////
                                                    REDEEM
                        //////////////////////////////////////////////////////////////*/
                        /// @inheritdoc IERC4626
                        /// @notice If `shares_` equals uint256.max then the whole balance of `owner_` is withdrawn.This does not
                        ///         consider withdrawal limit at Liquidity so best to check with `maxRedeem()` before.
                        ///         Recommended to use `withdraw()` over redeem because it is more gas efficient and can set specific amount.
                        ///         Recommended to use `redeem()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return assets_ withdrawn assets amount
                        function redeem(
                            uint256 shares_,
                            address receiver_,
                            address owner_
                        ) public virtual override nonReentrant returns (uint256 assets_) {
                            if (shares_ == type(uint256).max) {
                                shares_ = balanceOf(owner_);
                            }
                            assets_ = previewRedeem(shares_);
                            uint256 burnedShares_ = _executeWithdraw(assets_, receiver_, owner_);
                            if (msg.sender != owner_) {
                                _spendAllowance(owner_, msg.sender, burnedShares_);
                            }
                        }
                        /// @notice same as {fToken-redeem} but with an additional setting for minimum output amount.
                        /// reverts with `fToken__MinAmountOut()` if `minAmountOut_` of assets is not reached.
                        function redeem(
                            uint256 shares_,
                            address receiver_,
                            address owner_,
                            uint256 minAmountOut_
                        ) external returns (uint256 assets_) {
                            assets_ = redeem(shares_, receiver_, owner_);
                            _revertIfBelowMinAmountOut(assets_, minAmountOut_);
                        }
                    }
                    /// @notice fTokens support EIP-2612 permit approvals via signature so this contract implements
                    /// withdrawals (withdraw / redeem) with signature used for approval of the fToken shares.
                    abstract contract fTokenEIP2612Withdrawals is fTokenActions {
                        /// @dev creates `sharesToPermit_` allowance for `owner_` via EIP2612 `deadline_` and `signature_`
                        function _allowViaPermitEIP2612(
                            address owner_,
                            uint256 sharesToPermit_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) internal {
                            (uint8 v_, bytes32 r_, bytes32 s_) = _splitSignature(signature_);
                            // spender = msg.sender
                            permit(owner_, msg.sender, sharesToPermit_, deadline_, v_, r_, s_);
                        }
                        /// @notice withdraw amount of `assets_` with ERC-2612 permit signature for fToken approval.
                        /// `owner_` signs ERC-2612 permit `signature_` to give allowance of fTokens to `msg.sender`.
                        /// Note there might be tiny inaccuracies between requested `assets_` and actually received assets amount.
                        /// allowance via signature (`sharesToPermit_`) should cover `previewWithdraw(assets_)` plus a little buffer to avoid revert.
                        /// Inherent trust assumption that `msg.sender` will set `receiver_` and `maxSharesBurn_` as `owner_` intends
                        /// (which is always the case when giving allowance to some spender).
                        /// @param sharesToPermit_ shares amount to use for EIP2612 permit(). Should cover `previewWithdraw(assets_)` + small buffer.
                        /// @param assets_ amount of assets to withdraw
                        /// @param receiver_ receiver of withdrawn assets
                        /// @param owner_ owner to withdraw from (must be signature signer)
                        /// @param maxSharesBurn_ maximum accepted amount of shares burned
                        /// @param deadline_ deadline for signature validity
                        /// @param signature_  packed signature of signing the EIP712 hash for ERC-2612 permit
                        /// @return shares_ burned shares amount
                        function withdrawWithSignature(
                            uint256 sharesToPermit_,
                            uint256 assets_,
                            address receiver_,
                            address owner_,
                            uint256 maxSharesBurn_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) external virtual nonReentrant returns (uint256 shares_) {
                            if (msg.sender == owner_) {
                                // no sense in operating with permit if msg.sender is owner. should call normal `withdraw()` instead.
                                revert FluidLendingError(ErrorTypes.fToken__PermitFromOwnerCall);
                            }
                            // create allowance through signature_
                            _allowViaPermitEIP2612(owner_, sharesToPermit_, deadline_, signature_);
                            // execute withdraw to get shares_ to spend amount
                            shares_ = _executeWithdraw(assets_, receiver_, owner_);
                            _revertIfAboveMaxAmount(shares_, maxSharesBurn_);
                            _spendAllowance(owner_, msg.sender, shares_);
                        }
                        /// @notice redeem amount of `shares_` with ERC-2612 permit signature for fToken approval.
                        /// `owner_` signs ERC-2612 permit `signature_` to give allowance of fTokens to `msg.sender`.
                        /// Note there might be tiny inaccuracies between requested `shares_` to redeem and actually burned shares.
                        /// allowance via signature must cover `shares_` plus a tiny buffer.
                        /// Inherent trust assumption that `msg.sender` will set `receiver_` and `minAmountOut_` as `owner_` intends
                        ///       (which is always the case when giving allowance to some spender).
                        /// Recommended to use `withdraw()` over redeem because it is more gas efficient and can set specific amount.
                        /// @param shares_ amount of shares to redeem
                        /// @param receiver_ receiver of withdrawn assets
                        /// @param owner_ owner to withdraw from (must be signature signer)
                        /// @param minAmountOut_ minimum accepted amount of assets withdrawn
                        /// @param deadline_ deadline for signature validity
                        /// @param signature_  packed signature of signing the EIP712 hash for ERC-2612 permit
                        /// @return assets_ withdrawn assets amount
                        function redeemWithSignature(
                            uint256 shares_,
                            address receiver_,
                            address owner_,
                            uint256 minAmountOut_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) external virtual nonReentrant returns (uint256 assets_) {
                            if (msg.sender == owner_) {
                                // no sense in operating with permit if msg.sender is owner. should call normal `redeem()` instead.
                                revert FluidLendingError(ErrorTypes.fToken__PermitFromOwnerCall);
                            }
                            assets_ = previewRedeem(shares_);
                            _revertIfBelowMinAmountOut(assets_, minAmountOut_);
                            // create allowance through signature_
                            _allowViaPermitEIP2612(owner_, shares_, deadline_, signature_);
                            // execute withdraw to get actual shares to spend amount
                            uint256 sharesToSpend_ = _executeWithdraw(assets_, receiver_, owner_);
                            _spendAllowance(owner_, msg.sender, sharesToSpend_);
                        }
                    }
                    /// @notice implements fTokens support for deposit / mint via EIP-2612 permit.
                    /// @dev methods revert if underlying asset does not support EIP-2612.
                    abstract contract fTokenEIP2612Deposits is fTokenActions {
                        /// @notice deposit `assets_` amount with EIP-2612 Permit2 signature for underlying asset approval.
                        ///         IMPORTANT: This will revert if the underlying `asset()` does not support EIP-2612.
                        ///         reverts with `fToken__MinAmountOut()` if `minAmountOut_` of shares is not reached.
                        ///         `assets_` must at least be `minDeposit()` amount; reverts `fToken__DepositInsignificant()` if not.
                        /// @param assets_ amount of assets to deposit
                        /// @param receiver_ receiver of minted fToken shares
                        /// @param minAmountOut_ minimum accepted amount of shares minted
                        /// @param deadline_ deadline for signature validity
                        /// @param signature_  packed signature of signing the EIP712 hash for EIP-2612 Permit
                        /// @return shares_ amount of minted shares
                        function depositWithSignatureEIP2612(
                            uint256 assets_,
                            address receiver_,
                            uint256 minAmountOut_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) external returns (uint256 shares_) {
                            // create allowance through signature_ and spend it
                            (uint8 v_, bytes32 r_, bytes32 s_) = _splitSignature(signature_);
                            // EIP-2612 permit for underlying asset from owner (msg.sender) to spender (this contract)
                            IERC20Permit(address(ASSET)).permit(msg.sender, address(this), assets_, deadline_, v_, r_, s_);
                            // deposit() includes nonReentrant modifier which is enough to have from this point forward
                            shares_ = deposit(assets_, receiver_);
                            _revertIfBelowMinAmountOut(shares_, minAmountOut_);
                        }
                        /// @notice mint amount of `shares_` with EIP-2612 Permit signature for underlying asset approval.
                        ///         IMPORTANT: This will revert if the underlying `asset()` does not support EIP-2612.
                        ///         Signature should approve a little bit more than expected assets amount (`previewMint()`) to avoid reverts.
                        ///         `shares_` must at least be `minMint()` amount; reverts with `fToken__DepositInsignificant()` if not.
                        ///         Note there might be tiny inaccuracies between requested `shares_` and actually received shares amount.
                        ///         Recommended to use `deposit()` over mint because it is more gas efficient and less likely to revert.
                        /// @param shares_ amount of shares to mint
                        /// @param receiver_ receiver of minted fToken shares
                        /// @param maxAssets_ maximum accepted amount of assets used as input to mint `shares_`
                        /// @param deadline_ deadline for signature validity
                        /// @param signature_  packed signature of signing the EIP712 hash for EIP-2612 Permit
                        /// @return assets_ deposited assets amount
                        function mintWithSignatureEIP2612(
                            uint256 shares_,
                            address receiver_,
                            uint256 maxAssets_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) external returns (uint256 assets_) {
                            assets_ = previewMint(shares_);
                            // create allowance through signature_ and spend it
                            (uint8 v_, bytes32 r_, bytes32 s_) = _splitSignature(signature_);
                            // EIP-2612 permit for underlying asset from owner (msg.sender) to spender (this contract)
                            IERC20Permit(address(ASSET)).permit(msg.sender, address(this), assets_, deadline_, v_, r_, s_);
                            // mint() includes nonReentrant modifier which is enough to have from this point forward
                            assets_ = mint(shares_, receiver_);
                            _revertIfAboveMaxAmount(assets_, maxAssets_);
                        }
                    }
                    /// @notice implements fTokens support for deposit / mint via Permit2 signature.
                    abstract contract fTokenPermit2Deposits is fTokenActions {
                        /// @inheritdoc IFToken
                        function depositWithSignature(
                            uint256 assets_,
                            address receiver_,
                            uint256 minAmountOut_,
                            IAllowanceTransfer.PermitSingle calldata permit_,
                            bytes calldata signature_
                        ) external nonReentrant returns (uint256 shares_) {
                            // give allowance to address(this) via Permit2 signature -> to spend allowance in LiquidityCallback
                            // to transfer funds directly from msg.sender to liquidity
                            PERMIT2.permit(
                                // owner - Who signed the permit and also holds the tokens
                                // @dev Note if this is modified to not be msg.sender, extra steps would be needed for security!
                                // the caller could use this signature and deposit to the balance of receiver_, which could be set to any address,
                                // because it is not included in the signature. Use permitWitnessTransferFrom in that case. Same for `minAmountOut_`.
                                msg.sender,
                                permit_, // permit message
                                signature_ // packed signature of signing the EIP712 hash of `permit_`
                            );
                            // @dev transfer of tokens from `msg.sender` to liquidity contract happens via `liquidityCallback`
                            shares_ = _executeDeposit(assets_, receiver_, abi.encode(true, msg.sender));
                            _revertIfBelowMinAmountOut(shares_, minAmountOut_);
                        }
                        /// @inheritdoc IFToken
                        function mintWithSignature(
                            uint256 shares_,
                            address receiver_,
                            uint256 maxAssets_,
                            IAllowanceTransfer.PermitSingle calldata permit_,
                            bytes calldata signature_
                        ) external nonReentrant returns (uint256 assets_) {
                            assets_ = previewMint(shares_);
                            _revertIfAboveMaxAmount(assets_, maxAssets_);
                            // give allowance to address(this) via Permit2 PermitSingle. to spend allowance in LiquidityCallback
                            // to transfer funds directly from msg.sender to liquidity
                            PERMIT2.permit(
                                // owner - Who signed the permit and also holds the tokens
                                // @dev Note if this is modified to not be msg.sender, extra steps would be needed for security!
                                // the caller could use this signature and deposit to the balance of receiver_, which could be set to any address,
                                // because it is not included in the signature. Use permitWitnessTransferFrom in that case. Same for `minAmountOut_`.
                                msg.sender,
                                permit_, // permit message
                                signature_ // packed signature of signing the EIP712 hash of `permit_`
                            );
                            // @dev transfer of tokens from `msg.sender` to liquidity contract happens via `liquidityCallback`
                            _executeDeposit(assets_, receiver_, abi.encode(true, msg.sender));
                        }
                    }
                    /// @title Fluid fToken (Lending with interest)
                    /// @notice fToken is a token that can be used to supply liquidity to the Fluid Liquidity pool and earn interest for doing so.
                    /// The fToken is backed by the underlying balance and can be redeemed for the underlying token at any time.
                    /// The interest is earned via Fluid Liquidity, e.g. because borrowers pay a borrow rate on it. In addition, fTokens may also
                    /// have active rewards going on that count towards the earned yield for fToken holders.
                    /// @dev The fToken implements the ERC20 and ERC4626 standard, which means it can be transferred, minted and burned.
                    /// The fToken supports EIP-2612 permit approvals via signature.
                    /// The fToken implements withdrawals via EIP-2612 permits and deposits with Permit2 or EIP-2612 (if underlying supports it) signatures.
                    /// fTokens are not upgradeable.
                    /// @dev For view methods / accessing data, use the "LendingResolver" periphery contract.
                    contract fToken is fTokenAdmin, fTokenActions, fTokenEIP2612Withdrawals, fTokenPermit2Deposits, fTokenEIP2612Deposits {
                        /// @param liquidity_ liquidity contract address
                        /// @param lendingFactory_ lending factory contract address
                        /// @param asset_ underlying token address
                        constructor(
                            IFluidLiquidity liquidity_,
                            IFluidLendingFactory lendingFactory_,
                            IERC20 asset_
                        ) Variables(liquidity_, lendingFactory_, asset_) {
                            // set initial values for _liquidityExchangePrice, _tokenExchangePrice and _lastUpdateTimestamp
                            _liquidityExchangePrice = uint64(_getLiquidityExchangePrice());
                            _tokenExchangePrice = uint64(EXCHANGE_PRICES_PRECISION);
                            _lastUpdateTimestamp = uint40(block.timestamp);
                        }
                        /// @inheritdoc IERC20Metadata
                        function decimals() public view virtual override(ERC20, IERC20Metadata) returns (uint8) {
                            return DECIMALS;
                        }
                        /// @inheritdoc IFToken
                        function liquidityCallback(address token_, uint256 amount_, bytes calldata data_) external virtual override {
                            if (msg.sender != address(LIQUIDITY) || token_ != address(ASSET) || _status != REENTRANCY_ENTERED) {
                                // caller must be liquidity, token must match, and reentrancy status must be REENTRANCY_ENTERED
                                revert FluidLendingError(ErrorTypes.fToken__Unauthorized);
                            }
                            // callback data can be a) an address only b) an address + transfer via permit2 flag set to true
                            // for a) length will be 32, for b) length is 64
                            if (data_.length == 32) {
                                address from_ = abi.decode(data_, (address));
                                // transfer `amount_` from `from_` (original deposit msg.sender) to liquidity contract
                                SafeTransfer.safeTransferFrom(address(ASSET), from_, address(LIQUIDITY), amount_);
                            } else {
                                (bool isPermit2_, address from_) = abi.decode(data_, (bool, address));
                                if (!isPermit2_) {
                                    // unexepcted liquidity callback data
                                    revert FluidLendingError(ErrorTypes.fToken__InvalidParams);
                                }
                                // transfer `amount_` from `from_` (original deposit msg.sender) to liquidity contract via PERMIT2
                                PERMIT2.transferFrom(from_, address(LIQUIDITY), uint160(amount_), address(ASSET));
                            }
                        }
                    }
                    // SPDX-License-Identifier: BUSL-1.1
                    pragma solidity 0.8.21;
                    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import { ERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
                    import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
                    import { IAllowanceTransfer } from "../interfaces/permit2/iAllowanceTransfer.sol";
                    import { LiquiditySlotsLink } from "../../../libraries/liquiditySlotsLink.sol";
                    import { IFToken } from "../interfaces/iFToken.sol";
                    import { IAllowanceTransfer } from "../interfaces/permit2/iAllowanceTransfer.sol";
                    import { IFluidLendingRewardsRateModel  } from "../interfaces/iLendingRewardsRateModel.sol";
                    import { IFluidLendingFactory } from "../interfaces/iLendingFactory.sol";
                    import { IFluidLiquidity } from "../../../liquidity/interfaces/iLiquidity.sol";
                    import { ErrorTypes } from "../errorTypes.sol";
                    import { Error } from "../error.sol";
                    abstract contract Constants {
                        /// @dev permit2 contract, deployed to same address on EVM networks, see https://github.com/Uniswap/permit2
                        IAllowanceTransfer internal constant PERMIT2 = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3);
                        /// @dev precision for exchange prices
                        uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
                        /// @dev Ignoring leap years
                        uint256 internal constant SECONDS_PER_YEAR = 365 days;
                        /// @dev max allowed reward rate is 50%
                        uint256 internal constant MAX_REWARDS_RATE = 50 * 1e12; // 50%;
                        /// @dev address of the Liquidity contract.
                        IFluidLiquidity internal immutable LIQUIDITY;
                        /// @dev address of the Lending factory contract.
                        IFluidLendingFactory internal immutable LENDING_FACTORY;
                        /// @dev address of the underlying asset contract.
                        IERC20 internal immutable ASSET;
                        /// @dev number of decimals for the fToken, same as ASSET
                        uint8 internal immutable DECIMALS;
                        /// @dev slot ids in Liquidity contract for underlying token.
                        /// Helps in low gas fetch from liquidity contract by skipping delegate call with `readFromStorage`
                        bytes32 internal immutable LIQUIDITY_EXCHANGE_PRICES_SLOT;
                        bytes32 internal immutable LIQUIDITY_TOTAL_AMOUNTS_SLOT;
                        bytes32 internal immutable LIQUIDITY_USER_SUPPLY_SLOT;
                        /// @param liquidity_ liquidity contract address
                        /// @param lendingFactory_ lending factory contract address
                        /// @param asset_ underlying token address
                        constructor(IFluidLiquidity liquidity_, IFluidLendingFactory lendingFactory_, IERC20 asset_) {
                            DECIMALS = IERC20Metadata(address(asset_)).decimals();
                            ASSET = asset_;
                            LIQUIDITY = liquidity_;
                            LENDING_FACTORY = lendingFactory_;
                            LIQUIDITY_EXCHANGE_PRICES_SLOT = LiquiditySlotsLink.calculateMappingStorageSlot(
                                LiquiditySlotsLink.LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT,
                                _getLiquiditySlotLinksAsset()
                            );
                            LIQUIDITY_TOTAL_AMOUNTS_SLOT = LiquiditySlotsLink.calculateMappingStorageSlot(
                                LiquiditySlotsLink.LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT,
                                _getLiquiditySlotLinksAsset()
                            );
                            LIQUIDITY_USER_SUPPLY_SLOT = LiquiditySlotsLink.calculateDoubleMappingStorageSlot(
                                LiquiditySlotsLink.LIQUIDITY_USER_SUPPLY_DOUBLE_MAPPING_SLOT,
                                address(this),
                                _getLiquiditySlotLinksAsset()
                            );
                        }
                        /// @dev gets asset address for liquidity slot links, extracted to separate method so it can be overridden if needed
                        function _getLiquiditySlotLinksAsset() internal view virtual returns (address) {
                            return address(ASSET);
                        }
                    }
                    abstract contract Variables is ERC20, ERC20Permit, Error, Constants, IFToken {
                        /// @dev prefix for token name. fToken will append the underlying asset name
                        string private constant TOKEN_NAME_PREFIX = "Fluid ";
                        /// @dev prefix for token symbol. fToken will append the underlying asset symbol
                        string private constant TOKEN_SYMBOL_PREFIX = "f";
                        // ------------ storage variables from inherited contracts come before vars here --------
                        // _________ ERC20 _______________
                        // ----------------------- slot 0 ---------------------------
                        // mapping(address => uint256) private _balances;
                        // ----------------------- slot 1 ---------------------------
                        // mapping(address => mapping(address => uint256)) private _allowances;
                        // ----------------------- slot 2 ---------------------------
                        // uint256 private _totalSupply;
                        // ----------------------- slot 3 ---------------------------
                        // string private _name;
                        // ----------------------- slot 4 ---------------------------
                        // string private _symbol;
                        // _________ ERC20Permit _______________
                        // ----------------------- slot 5 ---------------------------
                        // mapping(address => Counters.Counter) private _nonces;
                        // ----------------------- slot 6 ---------------------------
                        // bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
                        // ----------------------- slot 7 ---------------------------
                        /// @dev address of the LendingRewardsRateModel.
                        IFluidLendingRewardsRateModel  internal _rewardsRateModel;
                        // -> 12 bytes empty
                        uint96 private __placeholder_gap;
                        // ----------------------- slot 8 ---------------------------
                        // optimized to put all storage variables where a SSTORE happens on actions in the same storage slot
                        /// @dev exchange price for the underlying assset in the liquidity protocol (without rewards)
                        uint64 internal _liquidityExchangePrice; // in 1e12 -> (max value 18_446_744,073709551615)
                        /// @dev exchange price between fToken and the underlying assset (with rewards)
                        uint64 internal _tokenExchangePrice; // in 1e12 -> (max value 18_446_744,073709551615)
                        /// @dev timestamp when exchange prices were updated the last time
                        uint40 internal _lastUpdateTimestamp;
                        /// @dev status for reentrancy guard
                        uint8 internal _status;
                        /// @dev flag to signal if rewards are active without having to read slot 6
                        bool internal _rewardsActive;
                        // 72 bits empty (9 bytes)
                        // ----------------------- slot 9 ---------------------------
                        /// @dev rebalancer address allowed to call `rebalance()` and source for funding rewards (ReserveContract).
                        address internal _rebalancer;
                        /*//////////////////////////////////////////////////////////////
                                              CONSTRUCTOR
                        //////////////////////////////////////////////////////////////*/
                        /// @param liquidity_ liquidity contract address
                        /// @param lendingFactory_ lending factory contract address
                        /// @param asset_ underlying token address
                        constructor(
                            IFluidLiquidity liquidity_,
                            IFluidLendingFactory lendingFactory_,
                            IERC20 asset_
                        )
                            validAddress(address(liquidity_))
                            validAddress(address(lendingFactory_))
                            validAddress(address(asset_))
                            Constants(liquidity_, lendingFactory_, asset_)
                            ERC20(
                                string(abi.encodePacked(TOKEN_NAME_PREFIX, IERC20Metadata(address(asset_)).name())),
                                string(abi.encodePacked(TOKEN_SYMBOL_PREFIX, IERC20Metadata(address(asset_)).symbol()))
                            )
                            ERC20Permit(string(abi.encodePacked(TOKEN_NAME_PREFIX, IERC20Metadata(address(asset_)).name())))
                        {}
                        /// @dev checks that address is not the zero address, reverts if so. Calling the method in the modifier reduces
                        /// bytecode size as modifiers are inlined into bytecode
                        function _checkValidAddress(address value_) internal pure {
                            if (value_ == address(0)) {
                                revert FluidLendingError(ErrorTypes.fToken__InvalidParams);
                            }
                        }
                        /// @dev validates that an address is not the zero address
                        modifier validAddress(address value_) {
                            _checkValidAddress(value_);
                            _;
                        }
                    }
                    //SPDX-License-Identifier: MIT
                    pragma solidity 0.8.21;
                    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
                    import { IAllowanceTransfer } from "./permit2/iAllowanceTransfer.sol";
                    import { IFluidLendingRewardsRateModel } from "./iLendingRewardsRateModel.sol";
                    import { IFluidLendingFactory } from "./iLendingFactory.sol";
                    import { IFluidLiquidity } from "../../../liquidity/interfaces/iLiquidity.sol";
                    interface IFTokenAdmin {
                        /// @notice updates the rewards rate model contract.
                        ///         Only callable by LendingFactory auths.
                        /// @param rewardsRateModel_  the new rewards rate model contract address.
                        ///                           can be set to address(0) to set no rewards (to save gas)
                        function updateRewards(IFluidLendingRewardsRateModel rewardsRateModel_) external;
                        /// @notice Balances out the difference between fToken supply at Liquidity vs totalAssets().
                        ///         Deposits underlying from rebalancer address into Liquidity but doesn't mint any shares
                        ///         -> thus making deposit available as rewards.
                        ///         Only callable by rebalancer.
                        /// @return assets_ amount deposited to Liquidity
                        function rebalance() external payable returns (uint256 assets_);
                        /// @notice gets the liquidity exchange price of the underlying asset, calculates the updated exchange price (with reward rates)
                        ///         and writes those values to storage.
                        ///         Callable by anyone.
                        /// @return tokenExchangePrice_ exchange price of fToken share to underlying asset
                        /// @return liquidityExchangePrice_ exchange price at Liquidity for the underlying asset
                        function updateRates() external returns (uint256 tokenExchangePrice_, uint256 liquidityExchangePrice_);
                        /// @notice sends any potentially stuck funds to Liquidity contract. Only callable by LendingFactory auths.
                        function rescueFunds(address token_) external;
                        /// @notice Updates the rebalancer address (ReserveContract). Only callable by LendingFactory auths.
                        function updateRebalancer(address rebalancer_) external;
                    }
                    interface IFToken is IERC4626, IFTokenAdmin {
                        /// @notice returns minimum amount required for deposit (rounded up)
                        function minDeposit() external view returns (uint256);
                        /// @notice returns config, rewards and exchange prices data in a single view method.
                        /// @return liquidity_ address of the Liquidity contract.
                        /// @return lendingFactory_ address of the Lending factory contract.
                        /// @return lendingRewardsRateModel_ address of the rewards rate model contract. changeable by LendingFactory auths.
                        /// @return permit2_ address of the Permit2 contract used for deposits / mint with signature
                        /// @return rebalancer_ address of the rebalancer allowed to execute `rebalance()`
                        /// @return rewardsActive_ true if rewards are currently active
                        /// @return liquidityBalance_ current Liquidity supply balance of `address(this)` for the underyling asset
                        /// @return liquidityExchangePrice_ (updated) exchange price for the underlying assset in the liquidity protocol (without rewards)
                        /// @return tokenExchangePrice_ (updated) exchange price between fToken and the underlying assset (with rewards)
                        function getData()
                            external
                            view
                            returns (
                                IFluidLiquidity liquidity_,
                                IFluidLendingFactory lendingFactory_,
                                IFluidLendingRewardsRateModel lendingRewardsRateModel_,
                                IAllowanceTransfer permit2_,
                                address rebalancer_,
                                bool rewardsActive_,
                                uint256 liquidityBalance_,
                                uint256 liquidityExchangePrice_,
                                uint256 tokenExchangePrice_
                            );
                        /// @notice transfers `amount_` of `token_` to liquidity. Only callable by liquidity contract.
                        /// @dev this callback is used to optimize gas consumption (reducing necessary token transfers).
                        function liquidityCallback(address token_, uint256 amount_, bytes calldata data_) external;
                        /// @notice deposit `assets_` amount with Permit2 signature for underlying asset approval.
                        ///         reverts with `fToken__MinAmountOut()` if `minAmountOut_` of shares is not reached.
                        ///         `assets_` must at least be `minDeposit()` amount; reverts otherwise.
                        /// @param assets_ amount of assets to deposit
                        /// @param receiver_ receiver of minted fToken shares
                        /// @param minAmountOut_ minimum accepted amount of shares minted
                        /// @param permit_ Permit2 permit message
                        /// @param signature_  packed signature of signing the EIP712 hash of `permit_`
                        /// @return shares_ amount of minted shares
                        function depositWithSignature(
                            uint256 assets_,
                            address receiver_,
                            uint256 minAmountOut_,
                            IAllowanceTransfer.PermitSingle calldata permit_,
                            bytes calldata signature_
                        ) external returns (uint256 shares_);
                        /// @notice mint amount of `shares_` with Permit2 signature for underlying asset approval.
                        ///         Signature should approve a little bit more than expected assets amount (`previewMint()`) to avoid reverts.
                        ///         `shares_` must at least be `minMint()` amount; reverts otherwise.
                        ///         Note there might be tiny inaccuracies between requested `shares_` and actually received shares amount.
                        ///         Recommended to use `deposit()` over mint because it is more gas efficient and less likely to revert.
                        /// @param shares_ amount of shares to mint
                        /// @param receiver_ receiver of minted fToken shares
                        /// @param maxAssets_ maximum accepted amount of assets used as input to mint `shares_`
                        /// @param permit_ Permit2 permit message
                        /// @param signature_  packed signature of signing the EIP712 hash of `permit_`
                        /// @return assets_ deposited assets amount
                        function mintWithSignature(
                            uint256 shares_,
                            address receiver_,
                            uint256 maxAssets_,
                            IAllowanceTransfer.PermitSingle calldata permit_,
                            bytes calldata signature_
                        ) external returns (uint256 assets_);
                    }
                    interface IFTokenNativeUnderlying is IFToken {
                        /// @notice address that is mapped to the chain native token at Liquidity
                        function NATIVE_TOKEN_ADDRESS() external view returns (address);
                        /// @notice deposits `msg.value` amount of native token for `receiver_`.
                        ///         `msg.value` must be at least `minDeposit()` amount; reverts otherwise.
                        ///         Recommended to use `depositNative()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return shares_ actually minted shares
                        function depositNative(address receiver_) external payable returns (uint256 shares_);
                        /// @notice same as {depositNative} but with an additional setting for minimum output amount.
                        /// reverts with `fToken__MinAmountOut()` if `minAmountOut_` of shares is not reached
                        function depositNative(address receiver_, uint256 minAmountOut_) external payable returns (uint256 shares_);
                        /// @notice mints `shares_` for `receiver_`, paying with underlying native token.
                        ///         `shares_` must at least be `minMint()` amount; reverts otherwise.
                        ///         `shares_` set to type(uint256).max not supported.
                        ///         Note there might be tiny inaccuracies between requested `shares_` and actually received shares amount.
                        ///         Recommended to use `depositNative()` over mint because it is more gas efficient and less likely to revert.
                        ///         Recommended to use `mintNative()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return assets_ deposited assets amount
                        function mintNative(uint256 shares_, address receiver_) external payable returns (uint256 assets_);
                        /// @notice same as {mintNative} but with an additional setting for minimum output amount.
                        /// reverts with `fToken__MaxAmount()` if `maxAssets_` of assets is surpassed to mint `shares_`.
                        function mintNative(
                            uint256 shares_,
                            address receiver_,
                            uint256 maxAssets_
                        ) external payable returns (uint256 assets_);
                        /// @notice withdraws `assets_` amount in native underlying to `receiver_`, burning shares of `owner_`.
                        ///         If `assets_` equals uint256.max then the whole fToken balance of `owner_` is withdrawn.This does not
                        ///         consider withdrawal limit at liquidity so best to check with `maxWithdraw()` before.
                        ///         Note there might be tiny inaccuracies between requested `assets_` and actually received assets amount.
                        ///         Recommended to use `withdrawNative()` with a `maxSharesBurn_` param instead to set acceptable limit.
                        /// @return shares_ burned shares
                        function withdrawNative(uint256 assets_, address receiver_, address owner_) external returns (uint256 shares_);
                        /// @notice same as {withdrawNative} but with an additional setting for minimum output amount.
                        /// reverts with `fToken__MaxAmount()` if `maxSharesBurn_` of shares burned is surpassed.
                        function withdrawNative(
                            uint256 assets_,
                            address receiver_,
                            address owner_,
                            uint256 maxSharesBurn_
                        ) external returns (uint256 shares_);
                        /// @notice redeems `shares_` to native underlying to `receiver_`, burning shares of `owner_`.
                        ///         If `shares_` equals uint256.max then the whole balance of `owner_` is withdrawn.This does not
                        ///         consider withdrawal limit at liquidity so best to check with `maxRedeem()` before.
                        ///         Recommended to use `withdrawNative()` over redeem because it is more gas efficient and can set specific amount.
                        ///         Recommended to use `redeemNative()` with a `minAmountOut_` param instead to set acceptable limit.
                        /// @return assets_ withdrawn assets amount
                        function redeemNative(uint256 shares_, address receiver_, address owner_) external returns (uint256 assets_);
                        /// @notice same as {redeemNative} but with an additional setting for minimum output amount.
                        /// reverts with `fToken__MinAmountOut()` if `minAmountOut_` of assets is not reached.
                        function redeemNative(
                            uint256 shares_,
                            address receiver_,
                            address owner_,
                            uint256 minAmountOut_
                        ) external returns (uint256 assets_);
                        /// @notice withdraw amount of `assets_` in native token with ERC-2612 permit signature for fToken approval.
                        /// `owner_` signs ERC-2612 permit `signature_` to give allowance of fTokens to `msg.sender`.
                        /// Note there might be tiny inaccuracies between requested `assets_` and actually received assets amount.
                        /// allowance via signature should cover `previewWithdraw(assets_)` plus a little buffer to avoid revert.
                        /// Inherent trust assumption that `msg.sender` will set `receiver_` and `minAmountOut_` as `owner_` intends
                        /// (which is always the case when giving allowance to some spender).
                        /// @param sharesToPermit_ shares amount to use for EIP2612 permit(). Should cover `previewWithdraw(assets_)` + small buffer.
                        /// @param assets_ amount of assets to withdraw
                        /// @param receiver_ receiver of withdrawn assets
                        /// @param owner_ owner to withdraw from (must be signature signer)
                        /// @param maxSharesBurn_ maximum accepted amount of shares burned
                        /// @param deadline_ deadline for signature validity
                        /// @param signature_  packed signature of signing the EIP712 hash for ERC-2612 permit
                        /// @return shares_ burned shares amount
                        function withdrawWithSignatureNative(
                            uint256 sharesToPermit_,
                            uint256 assets_,
                            address receiver_,
                            address owner_,
                            uint256 maxSharesBurn_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) external returns (uint256 shares_);
                        /// @notice redeem amount of `shares_` as native token with ERC-2612 permit signature for fToken approval.
                        /// `owner_` signs ERC-2612 permit `signature_` to give allowance of fTokens to `msg.sender`.
                        /// Note there might be tiny inaccuracies between requested `shares_` to redeem and actually burned shares.
                        /// allowance via signature must cover `shares_` plus a tiny buffer.
                        /// Inherent trust assumption that `msg.sender` will set `receiver_` and `minAmountOut_` as `owner_` intends
                        ///       (which is always the case when giving allowance to some spender).
                        /// Recommended to use `withdrawNative()` over redeem because it is more gas efficient and can set specific amount.
                        /// @param shares_ amount of shares to redeem
                        /// @param receiver_ receiver of withdrawn assets
                        /// @param owner_ owner to withdraw from (must be signature signer)
                        /// @param minAmountOut_ minimum accepted amount of assets withdrawn
                        /// @param deadline_ deadline for signature validity
                        /// @param signature_  packed signature of signing the EIP712 hash for ERC-2612 permit
                        /// @return assets_ withdrawn assets amount
                        function redeemWithSignatureNative(
                            uint256 shares_,
                            address receiver_,
                            address owner_,
                            uint256 minAmountOut_,
                            uint256 deadline_,
                            bytes calldata signature_
                        ) external returns (uint256 assets_);
                    }
                    //SPDX-License-Identifier: MIT
                    pragma solidity 0.8.21;
                    import { IFluidLiquidity } from "../../../liquidity/interfaces/iLiquidity.sol";
                    interface IFluidLendingFactoryAdmin {
                        /// @notice reads if a certain `auth_` address is an allowed auth or not. Owner is auth by default.
                        function isAuth(address auth_) external view returns (bool);
                        /// @notice              Sets an address as allowed auth or not. Only callable by owner.
                        /// @param auth_         address to set auth value for
                        /// @param allowed_      bool flag for whether address is allowed as auth or not
                        function setAuth(address auth_, bool allowed_) external;
                        /// @notice reads if a certain `deployer_` address is an allowed deployer or not. Owner is deployer by default.
                        function isDeployer(address deployer_) external view returns (bool);
                        /// @notice              Sets an address as allowed deployer or not. Only callable by owner.
                        /// @param deployer_     address to set deployer value for
                        /// @param allowed_      bool flag for whether address is allowed as deployer or not
                        function setDeployer(address deployer_, bool allowed_) external;
                        /// @notice              Sets the `creationCode_` bytecode for a certain `fTokenType_`. Only callable by auths.
                        /// @param fTokenType_   the fToken Type used to refer the creation code
                        /// @param creationCode_ contract creation code. can be set to bytes(0) to remove a previously available `fTokenType_`
                        function setFTokenCreationCode(string memory fTokenType_, bytes calldata creationCode_) external;
                        /// @notice creates token for `asset_` for a lending protocol with interest. Only callable by deployers.
                        /// @param  asset_              address of the asset
                        /// @param  fTokenType_         type of fToken:
                        /// - if it's the native token, it should use `NativeUnderlying`
                        /// - otherwise it should use `fToken`
                        /// - could be more types available, check `fTokenTypes()`
                        /// @param  isNativeUnderlying_ flag to signal fToken type that uses native underlying at Liquidity
                        /// @return token_              address of the created token
                        function createToken(
                            address asset_,
                            string calldata fTokenType_,
                            bool isNativeUnderlying_
                        ) external returns (address token_);
                    }
                    interface IFluidLendingFactory is IFluidLendingFactoryAdmin {
                        /// @notice list of all created tokens
                        function allTokens() external view returns (address[] memory);
                        /// @notice list of all fToken types that can be deployed
                        function fTokenTypes() external view returns (string[] memory);
                        /// @notice returns the creation code for a certain `fTokenType_`
                        function fTokenCreationCode(string memory fTokenType_) external view returns (bytes memory);
                        /// @notice address of the Liquidity contract.
                        function LIQUIDITY() external view returns (IFluidLiquidity);
                        /// @notice computes deterministic token address for `asset_` for a lending protocol
                        /// @param  asset_      address of the asset
                        /// @param  fTokenType_         type of fToken:
                        /// - if it's the native token, it should use `NativeUnderlying`
                        /// - otherwise it should use `fToken`
                        /// - could be more types available, check `fTokenTypes()`
                        /// @return token_      detemrinistic address of the computed token
                        function computeToken(address asset_, string calldata fTokenType_) external view returns (address token_);
                    }
                    //SPDX-License-Identifier: MIT
                    pragma solidity 0.8.21;
                    interface IFluidLendingRewardsRateModel {
                        /// @notice Calculates the current rewards rate (APR)
                        /// @param totalAssets_ amount of assets in the lending
                        /// @return rate_ rewards rate percentage per year with 1e12 RATE_PRECISION, e.g. 1e12 = 1%, 1e14 = 100%
                        /// @return ended_ flag to signal that rewards have ended (always 0 going forward)
                        /// @return startTime_ start time of rewards to compare against last update timestamp
                        function getRate(uint256 totalAssets_) external view returns (uint256 rate_, bool ended_, uint256 startTime_);
                        /// @notice Returns config constants for rewards rate model
                        function getConfig()
                            external
                            view
                            returns (
                                uint256 duration_,
                                uint256 startTime_,
                                uint256 endTime_,
                                uint256 startTvl_,
                                uint256 maxRate_,
                                uint256 rewardAmount_,
                                address initiator_
                            );
                    }
                    // SPDX-License-Identifier: MIT
                    pragma solidity 0.8.21;
                    /// @title AllowanceTransfer
                    /// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
                    /// @dev Requires user's token approval on the Permit2 contract
                    /// from https://github.com/Uniswap/permit2/blob/main/src/interfaces/ISignatureTransfer.sol.
                    /// Copyright (c) 2022 Uniswap Labs
                    interface IAllowanceTransfer {
                        function DOMAIN_SEPARATOR() external view returns (bytes32);
                        /// @notice Thrown when an allowance on a token has expired.
                        /// @param deadline The timestamp at which the allowed amount is no longer valid
                        error AllowanceExpired(uint256 deadline);
                        /// @notice Thrown when an allowance on a token has been depleted.
                        /// @param amount The maximum amount allowed
                        error InsufficientAllowance(uint256 amount);
                        /// @notice Thrown when too many nonces are invalidated.
                        error ExcessiveInvalidation();
                        /// @notice Emits an event when the owner successfully invalidates an ordered nonce.
                        event NonceInvalidation(
                            address indexed owner,
                            address indexed token,
                            address indexed spender,
                            uint48 newNonce,
                            uint48 oldNonce
                        );
                        /// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
                        event Approval(
                            address indexed owner,
                            address indexed token,
                            address indexed spender,
                            uint160 amount,
                            uint48 expiration
                        );
                        /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
                        event Permit(
                            address indexed owner,
                            address indexed token,
                            address indexed spender,
                            uint160 amount,
                            uint48 expiration,
                            uint48 nonce
                        );
                        /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
                        event Lockdown(address indexed owner, address token, address spender);
                        /// @notice The permit data for a token
                        struct PermitDetails {
                            // ERC20 token address
                            address token;
                            // the maximum amount allowed to spend
                            uint160 amount;
                            // timestamp at which a spender's token allowances become invalid
                            uint48 expiration;
                            // an incrementing value indexed per owner,token,and spender for each signature
                            uint48 nonce;
                        }
                        /// @notice The permit message signed for a single token allownce
                        struct PermitSingle {
                            // the permit data for a single token alownce
                            PermitDetails details;
                            // address permissioned on the allowed tokens
                            address spender;
                            // deadline on the permit signature
                            uint256 sigDeadline;
                        }
                        /// @notice The permit message signed for multiple token allowances
                        struct PermitBatch {
                            // the permit data for multiple token allowances
                            PermitDetails[] details;
                            // address permissioned on the allowed tokens
                            address spender;
                            // deadline on the permit signature
                            uint256 sigDeadline;
                        }
                        /// @notice The saved permissions
                        /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
                        /// @dev Setting amount to type(uint160).max sets an unlimited approval
                        struct PackedAllowance {
                            // amount allowed
                            uint160 amount;
                            // permission expiry
                            uint48 expiration;
                            // an incrementing value indexed per owner,token,and spender for each signature
                            uint48 nonce;
                        }
                        /// @notice A token spender pair.
                        struct TokenSpenderPair {
                            // the token the spender is approved
                            address token;
                            // the spender address
                            address spender;
                        }
                        /// @notice Details for a token transfer.
                        struct AllowanceTransferDetails {
                            // the owner of the token
                            address from;
                            // the recipient of the token
                            address to;
                            // the amount of the token
                            uint160 amount;
                            // the token to be transferred
                            address token;
                        }
                        /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
                        /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
                        /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
                        function allowance(
                            address user,
                            address token,
                            address spender
                        ) external view returns (uint160 amount, uint48 expiration, uint48 nonce);
                        /// @notice Approves the spender to use up to amount of the specified token up until the expiration
                        /// @param token The token to approve
                        /// @param spender The spender address to approve
                        /// @param amount The approved amount of the token
                        /// @param expiration The timestamp at which the approval is no longer valid
                        /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
                        /// @dev Setting amount to type(uint160).max sets an unlimited approval
                        function approve(address token, address spender, uint160 amount, uint48 expiration) external;
                        /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
                        /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
                        /// @param owner The owner of the tokens being approved
                        /// @param permitSingle Data signed over by the owner specifying the terms of approval
                        /// @param signature The owner's signature over the permit data
                        function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
                        /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
                        /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
                        /// @param owner The owner of the tokens being approved
                        /// @param permitBatch Data signed over by the owner specifying the terms of approval
                        /// @param signature The owner's signature over the permit data
                        function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;
                        /// @notice Transfer approved tokens from one address to another
                        /// @param from The address to transfer from
                        /// @param to The address of the recipient
                        /// @param amount The amount of the token to transfer
                        /// @param token The token address to transfer
                        /// @dev Requires the from address to have approved at least the desired amount
                        /// of tokens to msg.sender.
                        function transferFrom(address from, address to, uint160 amount, address token) external;
                        /// @notice Transfer approved tokens in a batch
                        /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
                        /// @dev Requires the from addresses to have approved at least the desired amount
                        /// of tokens to msg.sender.
                        function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
                        /// @notice Enables performing a "lockdown" of the sender's Permit2 identity
                        /// by batch revoking approvals
                        /// @param approvals Array of approvals to revoke.
                        function lockdown(TokenSpenderPair[] calldata approvals) external;
                        /// @notice Invalidate nonces for a given (token, spender) pair
                        /// @param token The token to invalidate nonces for
                        /// @param spender The spender to invalidate nonces for
                        /// @param newNonce The new nonce to set. Invalidates all nonces less than it.
                        /// @dev Can't invalidate more than 2**16 nonces per transaction.
                        function invalidateNonces(address token, address spender, uint48 newNonce) external;
                    }
                    // 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 4 of 5: 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 5 of 5: 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))
                            }
                        }
                    }