ETH Price: $2,525.27 (-0.97%)

Transaction Decoder

Block:
22824629 at Jul-01-2025 01:16:59 PM +UTC
Transaction Fee:
0.001088686558607472 ETH $2.75
Gas Used:
171,098 Gas / 6.362941464 Gwei

Emitted Events:

0 WBTC.Approval( owner=[Receiver] 0xa01637355a4e1a19a3dd9a4fdd5b96c527572fce, spender=FluidVaultT1, value=40000000 )
1 WBTC.Transfer( from=[Receiver] 0xa01637355a4e1a19a3dd9a4fdd5b96c527572fce, to=FluidLiquidityProxy, value=40000000 )
2 FluidLiquidityProxy.0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15( 0x4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15, 0x0000000000000000000000003a0b7c8840d74d39552ef53f586dd8c3d1234c40, 0x0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599, 0000000000000000000000000000000000000000000000000000000002625a00, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000009aa3f04d6000000000000000000000011532563fe00, 00000000000000076b23522a3800000754dea18501a18d37dc01e58383e8008d )
3 FluidVaultT1.LogOperate( user_=[Receiver] 0xa01637355a4e1a19a3dd9a4fdd5b96c527572fce, nftId_=3902, colAmt_=40000000, debtAmt_=0, to_=[Receiver] 0xa01637355a4e1a19a3dd9a4fdd5b96c527572fce )

Account State Difference:

  Address   Before After State Difference Code
0x2260FAC5...93bc2C599
(quasarbuilder)
8.858513224007710984 Eth8.858855420007710984 Eth0.000342196
0x3A0b7c88...3d1234C40
(Fluid: Vault WBTC - USDT)
0x52Aa8994...360F4e497
(Fluid: Liquidity)
0xA0163735...527572fce
0.292760256551744514 Eth
Nonce: 72
0.291671569993137042 Eth
Nonce: 74
0.001088686558607472From: 0 To: 22892026855592066050609947431602401211538835161166308139

Execution Trace

0xa01637355a4e1a19a3dd9a4fdd5b96c527572fce.e9ae5c53( )
  • WBTC.approve( _spender=0x3A0b7c8840D74D39552EF53F586dD8c3d1234C40, _value=40000000 ) => ( True )
  • FluidVaultT1.operate( nftId_=3902, newCol_=40000000, newDebt_=0, to_=0xA01637355a4e1a19A3dd9a4Fdd5B96c527572fce ) => ( 3902, 40000000, 0 )
    • FluidLiquidityProxy.readFromStorage( slot_=2F97BAE778107453C9FA4E71A644CD0A05ED4723A03B3B8AF2D1B088C870A29B ) => ( result_=46565950336560551892815358326851313527150782067981758759053 )
    • FluidLiquidityProxy.readFromStorage( slot_=0A7E0E74B40A947DAF7B6DF34C66BE699F819F509940D1BD48C4D99BC5E3353C ) => ( result_=51761689417213918862354799927593851264573522951867501904429 )
    • FluidLiquidityProxy.ad967e15( )
      • FluidLiquidityUserModule.operate( token_=0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, supplyAmount_=40000000, borrowAmount_=0, withdrawTo_=0x0000000000000000000000000000000000000000, borrowTo_=0x0000000000000000000000000000000000000000, callbackData_=0x000000000000000000000000A01637355A4E1A19A3DD9A4FDD5B96C527572FCE ) => ( memVar3_=1007636721824, memVar4_=1019591935303 )
        • WBTC.balanceOf( _owner=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497 ) => ( 32660395736 )
        • FluidVaultT1.liquidityCallback( token_=0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, amount_=40000000, data_=0x000000000000000000000000A01637355A4E1A19A3DD9A4FDD5B96C527572FCE )
          • WBTC.transferFrom( _from=0xA01637355a4e1a19A3dd9a4Fdd5B96c527572fce, _to=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497, _value=40000000 ) => ( True )
          • WBTC.balanceOf( _owner=0x52Aa899454998Be5b000Ad077a46Bbe360F4e497 ) => ( 32700395736 )
            File 1 of 4: WBTC
            pragma solidity 0.4.24;
            
            // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol
            
            /**
             * @title ERC20Basic
             * @dev Simpler version of ERC20 interface
             * See https://github.com/ethereum/EIPs/issues/179
             */
            contract ERC20Basic {
              function totalSupply() public view returns (uint256);
              function balanceOf(address _who) public view returns (uint256);
              function transfer(address _to, uint256 _value) public returns (bool);
              event Transfer(address indexed from, address indexed to, uint256 value);
            }
            
            // File: openzeppelin-solidity/contracts/math/SafeMath.sol
            
            /**
             * @title SafeMath
             * @dev Math operations with safety checks that throw on error
             */
            library SafeMath {
            
              /**
              * @dev Multiplies two numbers, throws on overflow.
              */
              function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
                // Gas optimization: this is cheaper than asserting 'a' not being zero, but the
                // benefit is lost if 'b' is also tested.
                // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                if (_a == 0) {
                  return 0;
                }
            
                c = _a * _b;
                assert(c / _a == _b);
                return c;
              }
            
              /**
              * @dev Integer division of two numbers, truncating the quotient.
              */
              function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
                // assert(_b > 0); // Solidity automatically throws when dividing by 0
                // uint256 c = _a / _b;
                // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
                return _a / _b;
              }
            
              /**
              * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
              */
              function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
                assert(_b <= _a);
                return _a - _b;
              }
            
              /**
              * @dev Adds two numbers, throws on overflow.
              */
              function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
                c = _a + _b;
                assert(c >= _a);
                return c;
              }
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol
            
            /**
             * @title Basic token
             * @dev Basic version of StandardToken, with no allowances.
             */
            contract BasicToken is ERC20Basic {
              using SafeMath for uint256;
            
              mapping(address => uint256) internal balances;
            
              uint256 internal totalSupply_;
            
              /**
              * @dev Total number of tokens in existence
              */
              function totalSupply() public view returns (uint256) {
                return totalSupply_;
              }
            
              /**
              * @dev Transfer token for a specified address
              * @param _to The address to transfer to.
              * @param _value The amount to be transferred.
              */
              function transfer(address _to, uint256 _value) public returns (bool) {
                require(_value <= balances[msg.sender]);
                require(_to != address(0));
            
                balances[msg.sender] = balances[msg.sender].sub(_value);
                balances[_to] = balances[_to].add(_value);
                emit Transfer(msg.sender, _to, _value);
                return true;
              }
            
              /**
              * @dev Gets the balance of the specified address.
              * @param _owner The address to query the the balance of.
              * @return An uint256 representing the amount owned by the passed address.
              */
              function balanceOf(address _owner) public view returns (uint256) {
                return balances[_owner];
              }
            
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
            
            /**
             * @title ERC20 interface
             * @dev see https://github.com/ethereum/EIPs/issues/20
             */
            contract ERC20 is ERC20Basic {
              function allowance(address _owner, address _spender)
                public view returns (uint256);
            
              function transferFrom(address _from, address _to, uint256 _value)
                public returns (bool);
            
              function approve(address _spender, uint256 _value) public returns (bool);
              event Approval(
                address indexed owner,
                address indexed spender,
                uint256 value
              );
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol
            
            /**
             * @title Standard ERC20 token
             *
             * @dev Implementation of the basic standard token.
             * https://github.com/ethereum/EIPs/issues/20
             * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
             */
            contract StandardToken is ERC20, BasicToken {
            
              mapping (address => mapping (address => uint256)) internal allowed;
            
            
              /**
               * @dev Transfer tokens from one address to another
               * @param _from address The address which you want to send tokens from
               * @param _to address The address which you want to transfer to
               * @param _value uint256 the amount of tokens to be transferred
               */
              function transferFrom(
                address _from,
                address _to,
                uint256 _value
              )
                public
                returns (bool)
              {
                require(_value <= balances[_from]);
                require(_value <= allowed[_from][msg.sender]);
                require(_to != address(0));
            
                balances[_from] = balances[_from].sub(_value);
                balances[_to] = balances[_to].add(_value);
                allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
                emit Transfer(_from, _to, _value);
                return true;
              }
            
              /**
               * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
               * 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
               * @param _spender The address which will spend the funds.
               * @param _value The amount of tokens to be spent.
               */
              function approve(address _spender, uint256 _value) public returns (bool) {
                allowed[msg.sender][_spender] = _value;
                emit Approval(msg.sender, _spender, _value);
                return true;
              }
            
              /**
               * @dev Function to check the amount of tokens that an owner allowed to a spender.
               * @param _owner address The address which owns the funds.
               * @param _spender address The address which will spend the funds.
               * @return A uint256 specifying the amount of tokens still available for the spender.
               */
              function allowance(
                address _owner,
                address _spender
               )
                public
                view
                returns (uint256)
              {
                return allowed[_owner][_spender];
              }
            
              /**
               * @dev Increase the amount of tokens that an owner allowed to a spender.
               * approve should be called when allowed[_spender] == 0. To increment
               * allowed value is better to use this function to avoid 2 calls (and wait until
               * the first transaction is mined)
               * From MonolithDAO Token.sol
               * @param _spender The address which will spend the funds.
               * @param _addedValue The amount of tokens to increase the allowance by.
               */
              function increaseApproval(
                address _spender,
                uint256 _addedValue
              )
                public
                returns (bool)
              {
                allowed[msg.sender][_spender] = (
                  allowed[msg.sender][_spender].add(_addedValue));
                emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
                return true;
              }
            
              /**
               * @dev Decrease the amount of tokens that an owner allowed to a spender.
               * approve should be called when allowed[_spender] == 0. To decrement
               * allowed value is better to use this function to avoid 2 calls (and wait until
               * the first transaction is mined)
               * From MonolithDAO Token.sol
               * @param _spender The address which will spend the funds.
               * @param _subtractedValue The amount of tokens to decrease the allowance by.
               */
              function decreaseApproval(
                address _spender,
                uint256 _subtractedValue
              )
                public
                returns (bool)
              {
                uint256 oldValue = allowed[msg.sender][_spender];
                if (_subtractedValue >= oldValue) {
                  allowed[msg.sender][_spender] = 0;
                } else {
                  allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
                }
                emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
                return true;
              }
            
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol
            
            /**
             * @title DetailedERC20 token
             * @dev The decimals are only for visualization purposes.
             * All the operations are done using the smallest and indivisible token unit,
             * just as on Ethereum all the operations are done in wei.
             */
            contract DetailedERC20 is ERC20 {
              string public name;
              string public symbol;
              uint8 public decimals;
            
              constructor(string _name, string _symbol, uint8 _decimals) public {
                name = _name;
                symbol = _symbol;
                decimals = _decimals;
              }
            }
            
            // File: openzeppelin-solidity/contracts/ownership/Ownable.sol
            
            /**
             * @title Ownable
             * @dev The Ownable contract has an owner address, and provides basic authorization control
             * functions, this simplifies the implementation of "user permissions".
             */
            contract Ownable {
              address public owner;
            
            
              event OwnershipRenounced(address indexed previousOwner);
              event OwnershipTransferred(
                address indexed previousOwner,
                address indexed newOwner
              );
            
            
              /**
               * @dev The Ownable constructor sets the original `owner` of the contract to the sender
               * account.
               */
              constructor() public {
                owner = msg.sender;
              }
            
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                require(msg.sender == owner);
                _;
              }
            
              /**
               * @dev Allows the current owner to relinquish control of the contract.
               * @notice Renouncing to ownership will leave the contract without an owner.
               * It will not be possible to call the functions with the `onlyOwner`
               * modifier anymore.
               */
              function renounceOwnership() public onlyOwner {
                emit OwnershipRenounced(owner);
                owner = address(0);
              }
            
              /**
               * @dev Allows the current owner to transfer control of the contract to a newOwner.
               * @param _newOwner The address to transfer ownership to.
               */
              function transferOwnership(address _newOwner) public onlyOwner {
                _transferOwnership(_newOwner);
              }
            
              /**
               * @dev Transfers control of the contract to a newOwner.
               * @param _newOwner The address to transfer ownership to.
               */
              function _transferOwnership(address _newOwner) internal {
                require(_newOwner != address(0));
                emit OwnershipTransferred(owner, _newOwner);
                owner = _newOwner;
              }
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol
            
            /**
             * @title Mintable token
             * @dev Simple ERC20 Token example, with mintable token creation
             * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
             */
            contract MintableToken is StandardToken, Ownable {
              event Mint(address indexed to, uint256 amount);
              event MintFinished();
            
              bool public mintingFinished = false;
            
            
              modifier canMint() {
                require(!mintingFinished);
                _;
              }
            
              modifier hasMintPermission() {
                require(msg.sender == owner);
                _;
              }
            
              /**
               * @dev Function to mint tokens
               * @param _to The address that will receive the minted tokens.
               * @param _amount The amount of tokens to mint.
               * @return A boolean that indicates if the operation was successful.
               */
              function mint(
                address _to,
                uint256 _amount
              )
                public
                hasMintPermission
                canMint
                returns (bool)
              {
                totalSupply_ = totalSupply_.add(_amount);
                balances[_to] = balances[_to].add(_amount);
                emit Mint(_to, _amount);
                emit Transfer(address(0), _to, _amount);
                return true;
              }
            
              /**
               * @dev Function to stop minting new tokens.
               * @return True if the operation was successful.
               */
              function finishMinting() public onlyOwner canMint returns (bool) {
                mintingFinished = true;
                emit MintFinished();
                return true;
              }
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol
            
            /**
             * @title Burnable Token
             * @dev Token that can be irreversibly burned (destroyed).
             */
            contract BurnableToken is BasicToken {
            
              event Burn(address indexed burner, uint256 value);
            
              /**
               * @dev Burns a specific amount of tokens.
               * @param _value The amount of token to be burned.
               */
              function burn(uint256 _value) public {
                _burn(msg.sender, _value);
              }
            
              function _burn(address _who, uint256 _value) internal {
                require(_value <= balances[_who]);
                // no need to require value <= totalSupply, since that would imply the
                // sender's balance is greater than the totalSupply, which *should* be an assertion failure
            
                balances[_who] = balances[_who].sub(_value);
                totalSupply_ = totalSupply_.sub(_value);
                emit Burn(_who, _value);
                emit Transfer(_who, address(0), _value);
              }
            }
            
            // File: openzeppelin-solidity/contracts/lifecycle/Pausable.sol
            
            /**
             * @title Pausable
             * @dev Base contract which allows children to implement an emergency stop mechanism.
             */
            contract Pausable is Ownable {
              event Pause();
              event Unpause();
            
              bool public paused = false;
            
            
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               */
              modifier whenNotPaused() {
                require(!paused);
                _;
              }
            
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               */
              modifier whenPaused() {
                require(paused);
                _;
              }
            
              /**
               * @dev called by the owner to pause, triggers stopped state
               */
              function pause() public onlyOwner whenNotPaused {
                paused = true;
                emit Pause();
              }
            
              /**
               * @dev called by the owner to unpause, returns to normal state
               */
              function unpause() public onlyOwner whenPaused {
                paused = false;
                emit Unpause();
              }
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/PausableToken.sol
            
            /**
             * @title Pausable token
             * @dev StandardToken modified with pausable transfers.
             **/
            contract PausableToken is StandardToken, Pausable {
            
              function transfer(
                address _to,
                uint256 _value
              )
                public
                whenNotPaused
                returns (bool)
              {
                return super.transfer(_to, _value);
              }
            
              function transferFrom(
                address _from,
                address _to,
                uint256 _value
              )
                public
                whenNotPaused
                returns (bool)
              {
                return super.transferFrom(_from, _to, _value);
              }
            
              function approve(
                address _spender,
                uint256 _value
              )
                public
                whenNotPaused
                returns (bool)
              {
                return super.approve(_spender, _value);
              }
            
              function increaseApproval(
                address _spender,
                uint _addedValue
              )
                public
                whenNotPaused
                returns (bool success)
              {
                return super.increaseApproval(_spender, _addedValue);
              }
            
              function decreaseApproval(
                address _spender,
                uint _subtractedValue
              )
                public
                whenNotPaused
                returns (bool success)
              {
                return super.decreaseApproval(_spender, _subtractedValue);
              }
            }
            
            // File: openzeppelin-solidity/contracts/ownership/Claimable.sol
            
            /**
             * @title Claimable
             * @dev Extension for the Ownable contract, where the ownership needs to be claimed.
             * This allows the new owner to accept the transfer.
             */
            contract Claimable is Ownable {
              address public pendingOwner;
            
              /**
               * @dev Modifier throws if called by any account other than the pendingOwner.
               */
              modifier onlyPendingOwner() {
                require(msg.sender == pendingOwner);
                _;
              }
            
              /**
               * @dev Allows the current owner to set the pendingOwner address.
               * @param newOwner The address to transfer ownership to.
               */
              function transferOwnership(address newOwner) public onlyOwner {
                pendingOwner = newOwner;
              }
            
              /**
               * @dev Allows the pendingOwner address to finalize the transfer.
               */
              function claimOwnership() public onlyPendingOwner {
                emit OwnershipTransferred(owner, pendingOwner);
                owner = pendingOwner;
                pendingOwner = address(0);
              }
            }
            
            // File: openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol
            
            /**
             * @title SafeERC20
             * @dev Wrappers around ERC20 operations that throw on failure.
             * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
             * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
             */
            library SafeERC20 {
              function safeTransfer(
                ERC20Basic _token,
                address _to,
                uint256 _value
              )
                internal
              {
                require(_token.transfer(_to, _value));
              }
            
              function safeTransferFrom(
                ERC20 _token,
                address _from,
                address _to,
                uint256 _value
              )
                internal
              {
                require(_token.transferFrom(_from, _to, _value));
              }
            
              function safeApprove(
                ERC20 _token,
                address _spender,
                uint256 _value
              )
                internal
              {
                require(_token.approve(_spender, _value));
              }
            }
            
            // File: openzeppelin-solidity/contracts/ownership/CanReclaimToken.sol
            
            /**
             * @title Contracts that should be able to recover tokens
             * @author SylTi
             * @dev This allow a contract to recover any ERC20 token received in a contract by transferring the balance to the contract owner.
             * This will prevent any accidental loss of tokens.
             */
            contract CanReclaimToken is Ownable {
              using SafeERC20 for ERC20Basic;
            
              /**
               * @dev Reclaim all ERC20Basic compatible tokens
               * @param _token ERC20Basic The address of the token contract
               */
              function reclaimToken(ERC20Basic _token) external onlyOwner {
                uint256 balance = _token.balanceOf(this);
                _token.safeTransfer(owner, balance);
              }
            
            }
            
            // File: contracts/utils/OwnableContract.sol
            
            // empty block is used as this contract just inherits others.
            contract OwnableContract is CanReclaimToken, Claimable { } /* solhint-disable-line no-empty-blocks */
            
            // File: contracts/token/WBTC.sol
            
            contract WBTC is StandardToken, DetailedERC20("Wrapped BTC", "WBTC", 8),
                MintableToken, BurnableToken, PausableToken, OwnableContract {
            
                function burn(uint value) public onlyOwner {
                    super.burn(value);
                }
            
                function finishMinting() public onlyOwner returns (bool) {
                    return false;
                }
            
                function renounceOwnership() public onlyOwner {
                    revert("renouncing ownership is blocked");
                }
            }

            File 2 of 4: 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 4: FluidVaultT1
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
            pragma solidity ^0.8.0;
            import "../IERC721.sol";
            /**
             * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
             * @dev See https://eips.ethereum.org/EIPS/eip-721
             */
            interface IERC721Enumerable is IERC721 {
                /**
                 * @dev Returns the total amount of tokens stored by the contract.
                 */
                function totalSupply() external view returns (uint256);
                /**
                 * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
                 * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
                 */
                function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
                /**
                 * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
                 * Use along with {totalSupply} to enumerate all tokens.
                 */
                function tokenByIndex(uint256 index) external view returns (uint256);
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
            pragma solidity ^0.8.0;
            import "../../utils/introspection/IERC165.sol";
            /**
             * @dev Required interface of an ERC721 compliant contract.
             */
            interface IERC721 is IERC165 {
                /**
                 * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
                 */
                event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
                /**
                 * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
                 */
                event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
                /**
                 * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
                 */
                event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
                /**
                 * @dev Returns the number of tokens in ``owner``'s account.
                 */
                function balanceOf(address owner) external view returns (uint256 balance);
                /**
                 * @dev Returns the owner of the `tokenId` token.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must exist.
                 */
                function ownerOf(uint256 tokenId) external view returns (address owner);
                /**
                 * @dev Safely transfers `tokenId` token from `from` to `to`.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must exist and be owned by `from`.
                 * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
                 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                 *
                 * Emits a {Transfer} event.
                 */
                function safeTransferFrom(
                    address from,
                    address to,
                    uint256 tokenId,
                    bytes calldata data
                ) external;
                /**
                 * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
                 * are aware of the ERC721 protocol to prevent tokens from being forever locked.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must exist and be owned by `from`.
                 * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
                 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                 *
                 * Emits a {Transfer} event.
                 */
                function safeTransferFrom(
                    address from,
                    address to,
                    uint256 tokenId
                ) external;
                /**
                 * @dev Transfers `tokenId` token from `from` to `to`.
                 *
                 * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
                 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
                 * understand this adds an external call which potentially creates a reentrancy vulnerability.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must be owned by `from`.
                 * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
                 *
                 * Emits a {Transfer} event.
                 */
                function transferFrom(
                    address from,
                    address to,
                    uint256 tokenId
                ) external;
                /**
                 * @dev Gives permission to `to` to transfer `tokenId` token to another account.
                 * The approval is cleared when the token is transferred.
                 *
                 * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
                 *
                 * Requirements:
                 *
                 * - The caller must own the token or be an approved operator.
                 * - `tokenId` must exist.
                 *
                 * Emits an {Approval} event.
                 */
                function approve(address to, uint256 tokenId) external;
                /**
                 * @dev Approve or remove `operator` as an operator for the caller.
                 * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
                 *
                 * Requirements:
                 *
                 * - The `operator` cannot be the caller.
                 *
                 * Emits an {ApprovalForAll} event.
                 */
                function setApprovalForAll(address operator, bool _approved) external;
                /**
                 * @dev Returns the account approved for `tokenId` token.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must exist.
                 */
                function getApproved(uint256 tokenId) external view returns (address operator);
                /**
                 * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
                 *
                 * See {setApprovalForAll}
                 */
                function isApprovedForAll(address owner, address operator) external view returns (bool);
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev Interface of the ERC165 standard, as defined in the
             * https://eips.ethereum.org/EIPS/eip-165[EIP].
             *
             * Implementers can declare support of contract interfaces, which can then be
             * queried by others ({ERC165Checker}).
             *
             * For an implementation, see {ERC165}.
             */
            interface IERC165 {
                /**
                 * @dev Returns true if this contract implements the interface defined by
                 * `interfaceId`. See the corresponding
                 * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
                 * to learn more about how these ids are created.
                 *
                 * This function call must use less than 30 000 gas.
                 */
                function supportsInterface(bytes4 interfaceId) external view returns (bool);
            }
            // 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;
            import { BigMathMinified } from "./bigMathMinified.sol";
            /// @title Extended version of BigMathMinified. Implements functions for normal operators (*, /, etc) modified to interact with big numbers.
            /// @notice this is an optimized version mainly created by taking Fluid vault's codebase into consideration so it's use is limited for other cases.
            // 
            // @dev IMPORTANT: for any change here, make sure to uncomment and run the fuzz tests in bigMathVault.t.sol
            library BigMathVault {
                uint private constant COEFFICIENT_SIZE_DEBT_FACTOR = 35;
                uint private constant EXPONENT_SIZE_DEBT_FACTOR = 15;
                uint private constant COEFFICIENT_MAX_DEBT_FACTOR = (1 << COEFFICIENT_SIZE_DEBT_FACTOR) - 1;
                uint private constant EXPONENT_MAX_DEBT_FACTOR = (1 << EXPONENT_SIZE_DEBT_FACTOR) - 1;
                uint private constant DECIMALS_DEBT_FACTOR = 16384;
                uint internal constant MAX_MASK_DEBT_FACTOR = (1 << (COEFFICIENT_SIZE_DEBT_FACTOR + EXPONENT_SIZE_DEBT_FACTOR)) - 1;
                // Having precision as 2**64 on vault
                uint internal constant PRECISION = 64;
                uint internal constant TWO_POWER_64 = 1 << PRECISION;
                // Max bit for 35 bits * 35 bits number will be 70
                // why do we use 69 then here instead of 70
                uint internal constant TWO_POWER_69_MINUS_1 = (1 << 69) - 1;
                uint private constant COEFFICIENT_PLUS_PRECISION = COEFFICIENT_SIZE_DEBT_FACTOR + PRECISION; // 99
                uint private constant COEFFICIENT_PLUS_PRECISION_MINUS_1 = COEFFICIENT_PLUS_PRECISION - 1; // 98
                uint private constant TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1 = (1 << COEFFICIENT_PLUS_PRECISION_MINUS_1) - 1; // (1 << 98) - 1;
                uint private constant TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1_MINUS_1 =
                    (1 << (COEFFICIENT_PLUS_PRECISION_MINUS_1 - 1)) - 1; // (1 << 97) - 1;
                /// @dev multiplies a `normal` number with a `bigNumber1` and then divides by `bigNumber2`.
                /// @dev For vault's use case MUST always:
                ///      - bigNumbers have exponent size 15 bits
                ///      - bigNumbers have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
                ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
                ///      - bigNumber1 (debt factor) always have exponent >= 1 & <= 16384
                ///      - bigNumber2 (connection factor) always have exponent >= 1 & <= 32767 (15 bits)
                ///      - bigNumber2 always >= bigNumber1 (connection factor can never be < base branch debt factor)
                ///      - as a result of previous points, numbers must never be 0
                ///      - normal is positionRawDebt and is always within 10000 and type(int128).max
                /// @return normal * bigNumber1 / bigNumber2
                function mulDivNormal(uint256 normal, uint256 bigNumber1, uint256 bigNumber2) internal pure returns (uint256) {
                    unchecked {
                        // exponent2_ - exponent1_
                        uint netExponent_ = (bigNumber2 & EXPONENT_MAX_DEBT_FACTOR) - (bigNumber1 & EXPONENT_MAX_DEBT_FACTOR);
                        if (netExponent_ < 129) {
                            // (normal * coefficient1_) / (coefficient2_ << netExponent_);
                            return ((normal * (bigNumber1 >> EXPONENT_SIZE_DEBT_FACTOR)) /
                                ((bigNumber2 >> EXPONENT_SIZE_DEBT_FACTOR) << netExponent_));
                        }
                        // else:
                        // biggest possible nominator: type(int128).max * 35bits max      =  5846006549323611672814739330865132078589370433536
                        // smallest possible denominator: 17179869184 << 129 (= 1 << 163) = 11692013098647223345629478661730264157247460343808
                        // -> can only ever be 0
                        return 0;
                    }
                }
                /// @dev multiplies a `bigNumber` with normal `number1` and then divides by `TWO_POWER_64`.
                /// @dev For vault's use case (calculating new branch debt factor after liquidation):
                ///      - number1 is debtFactor, intialized as TWO_POWER_64 and reduced from there, hence it's always <= TWO_POWER_64 and always > 0.
                ///      - bigNumber is branch debt factor, which starts as ((X35 << 15) | (1 << 14)) and reduces from there.
                ///      - bigNumber must have have exponent size 15 bits and be >= 1 & <= 16384
                ///      - bigNumber must have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
                ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
                /// @param bigNumber Coefficient | Exponent.
                /// @param number1 normal number.
                /// @return result bigNumber * number1 / TWO_POWER_64.
                function mulDivBigNumber(uint256 bigNumber, uint256 number1) internal pure returns (uint256 result) {
                    // using unchecked as we are only at 1 place in Vault and it won't overflow there.
                    unchecked {
                        uint256 _resultNumerator = (bigNumber >> EXPONENT_SIZE_DEBT_FACTOR) * number1; // bigNumber coefficient * normal number
                        // 99% chances are that most sig bit should be 64 + 35 - 1 or 64 + 35 - 2
                        // diff = mostSigBit. Can only ever be >= 35 and <= 98
                        uint256 diff = (_resultNumerator > TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1)
                            ? COEFFICIENT_PLUS_PRECISION
                            : (_resultNumerator > TWO_POWER_COEFFICIENT_PLUS_PRECISION_MINUS_1_MINUS_1)
                                ? COEFFICIENT_PLUS_PRECISION_MINUS_1
                                : BigMathMinified.mostSignificantBit(_resultNumerator);
                        // diff = difference in bits to make the _resultNumerator 35 bits again
                        diff = diff - COEFFICIENT_SIZE_DEBT_FACTOR;
                        _resultNumerator = _resultNumerator >> diff;
                        // starting exponent is 16384, so exponent should never get 0 here
                        result = (bigNumber & EXPONENT_MAX_DEBT_FACTOR) + diff;
                        if (result > PRECISION) {
                            result = (_resultNumerator << EXPONENT_SIZE_DEBT_FACTOR) + result - PRECISION; // divides by TWO_POWER_64 by reducing exponent by 64
                        } else {
                            // if number1 is small, e.g. 1e4 and bigNumber is also small e.g. coefficient = 17179869184 & exponent is at 50
                            // then: resultNumerator = 171798691840000, diff most significant bit = 48, ending up with diff = 13
                            // for exponent in result we end up doing: 50 + 13 - 64 -> underflowing exponent.
                            // this should never happen anyway, but if it does better to revert than to continue with unknown effects.
                            revert(); // debt factor should never become a BigNumber with exponent <= 0
                        }
                    }
                }
                /// @dev multiplies a `bigNumber1` with another `bigNumber2`.
                /// @dev For vault's use case (calculating connection factor of merged branches userTickDebtFactor * connectionDebtFactor *... connectionDebtFactor):
                ///      - bigNumbers must have have exponent size 15 bits and be >= 1 & <= 32767
                ///      - bigNumber must have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
                ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
                /// @dev sum of exponents from `bigNumber1` `bigNumber2` should be > 16384.
                /// e.g. res = bigNumber1 * bigNumber2 = [(coe1, exp1) * (coe2, exp2)] >> decimal
                ///          = (coe1*coe2>>overflow, exp1+exp2+overflow-decimal)
                /// @param bigNumber1          BigNumber format with coefficient and exponent.
                /// @param bigNumber2          BigNumber format with coefficient and exponent.
                /// @return                    BigNumber format with coefficient and exponent
                function mulBigNumber(uint256 bigNumber1, uint256 bigNumber2) internal pure returns (uint256) {
                    unchecked {
                        // coefficient1_ * coefficient2_
                        uint resCoefficient_ = (bigNumber1 >> EXPONENT_SIZE_DEBT_FACTOR) *
                            (bigNumber2 >> EXPONENT_SIZE_DEBT_FACTOR);
                        // res coefficient at min can be 17179869184 * 17179869184 =  295147905179352825856 (= 1 << 68; 69th bit as 1)
                        // res coefficient at max can be 34359738367 * 34359738367 = 1180591620648691826689 (X35 * X35 fits in 70 bits)
                        uint overflowLen_ = resCoefficient_ > TWO_POWER_69_MINUS_1
                            ? COEFFICIENT_SIZE_DEBT_FACTOR
                            : COEFFICIENT_SIZE_DEBT_FACTOR - 1;
                        // overflowLen_ is either 34 or 35
                        resCoefficient_ = resCoefficient_ >> overflowLen_;
                        // bigNumber2 is connection factor
                        // exponent1_ + exponent2_ + overflowLen_ - decimals
                        uint resExponent_ = ((bigNumber1 & EXPONENT_MAX_DEBT_FACTOR) +
                            (bigNumber2 & EXPONENT_MAX_DEBT_FACTOR) +
                            overflowLen_);
                        if (resExponent_ < DECIMALS_DEBT_FACTOR) {
                            // for this ever to happen, the debt factors used to calculate connection factors would have to be at extremely
                            // unrealistic values. Like e.g.
                            // branch3 (debt factor X35 << 15 | 16383) got merged into branch2 (debt factor X35 << 15 | 8190)
                            // -> connection factor (divBigNumber): ((coe1<<precision_)/coe2>>overflowLen, exp1+decimal+overflowLen-exp2-precision_) so:
                            // coefficient: (X35<<64)/X35 >> 30 = 17179869184
                            // exponent: 8190+16384+30-16383-64 = 8157.
                            // result: 17179869184 << 15 | 8157
                            // and then branch2 into branch1 (debt factor X35 << 15 | 22). -> connection factor:
                            // coefficient: (X35<<64)/X35 >> 30 = 17179869184
                            // exponent: 22+16384+30-8190-64 = 8182.
                            // result: 17179869184 << 15 | 8182
                            // connection factors sum up (mulBigNumber): (coe1*coe2>>overflow, exp1+exp2+overflow-decimal)
                            // exponent: 8182+8157+35-16384=16374-16384=-10. underflow.
                            // this should never happen anyway, but if it does better to revert than to continue with unknown effects.
                            revert();
                        }
                        resExponent_ = resExponent_ - DECIMALS_DEBT_FACTOR;
                        if (resExponent_ > EXPONENT_MAX_DEBT_FACTOR) {
                            // if resExponent_ is not within limits that means user's got ~100% (something like 99.999999999999...)
                            // this situation will probably never happen and this basically means user's position is ~100% liquidated
                            return MAX_MASK_DEBT_FACTOR;
                        }
                        return ((resCoefficient_ << EXPONENT_SIZE_DEBT_FACTOR) | resExponent_);
                    }
                }
                /// @dev divides a `bigNumber1` by `bigNumber2`.
                /// @dev For vault's use case (calculating connectionFactor_ = baseBranchDebtFactor / currentBranchDebtFactor) bigNumbers MUST always:
                ///      - have exponent size 15 bits and be >= 1 & <= 16384
                ///      - have coefficient size 35 bits and have 35th bit always 1 (when exponent > 0 BigMath numbers have max precision)
                ///        so coefficients must always be in range 17179869184 <= coefficient <= 34359738367.
                ///      - as a result of previous points, numbers must never be 0
                /// e.g. res = bigNumber1 / bigNumber2 = [(coe1, exp1) / (coe2, exp2)] << decimal
                ///          = ((coe1<<precision_)/coe2, exp1+decimal-exp2-precision_)
                /// @param bigNumber1          BigNumber format with coefficient and exponent
                /// @param bigNumber2          BigNumber format with coefficient and exponent
                /// @return                    BigNumber format with coefficient and exponent
                /// Returned connection factor can only ever be >= baseBranchDebtFactor (c = x*100/y with both x,y > 0 & x,y <= 100: c can only ever be >= x)
                function divBigNumber(uint256 bigNumber1, uint256 bigNumber2) internal pure returns (uint256) {
                    unchecked {
                        // (coefficient1_ << PRECISION) / coefficient2_
                        uint256 resCoefficient_ = ((bigNumber1 >> EXPONENT_SIZE_DEBT_FACTOR) << PRECISION) /
                            (bigNumber2 >> EXPONENT_SIZE_DEBT_FACTOR);
                        // nominator at min 17179869184 << 64 = 316912650057057350374175801344. at max 34359738367 << 64 = 633825300095667956674642051072.
                        // so min value resCoefficient_ 9223372037123211264 (64 bits) vs max 36893488146345361408 (fits in 65 bits)
                        // mostSigBit will be PRECISION + 1 or PRECISION
                        uint256 overflowLen_ = ((resCoefficient_ >> PRECISION) == 1) ? (PRECISION + 1) : PRECISION;
                        // Overflow will be PRECISION - COEFFICIENT_SIZE_DEBT_FACTOR or (PRECISION + 1) - COEFFICIENT_SIZE_DEBT_FACTOR
                        // Meaning 64 - 35 = 29 or 65 - 35 = 30
                        overflowLen_ = overflowLen_ - COEFFICIENT_SIZE_DEBT_FACTOR;
                        resCoefficient_ = resCoefficient_ >> overflowLen_;
                        // exponent1_ will always be less than or equal to 16384
                        // exponent2_ will always be less than or equal to 16384
                        // Even if exponent2_ is 0 (not possible) & resExponent_ = DECIMALS_DEBT_FACTOR then also resExponent_ will be less than max limit, so no overflow
                        // result exponent = (exponent1_ + DECIMALS_DEBT_FACTOR + overflowLen_) - (exponent2_ + PRECISION);
                        uint256 resExponent_ = ((bigNumber1 & EXPONENT_MAX_DEBT_FACTOR) + // exponent1_
                            DECIMALS_DEBT_FACTOR + // DECIMALS_DEBT_FACTOR is 100% as it is percentage value
                            overflowLen_); // addition part resExponent_ here min 16414, max 32798
                        // reuse overFlowLen_ variable for subtraction sum of exponent
                        overflowLen_ = (bigNumber2 & EXPONENT_MAX_DEBT_FACTOR) + PRECISION; // subtraction part overflowLen_ here: min 65, max 16448
                        if (resExponent_ > overflowLen_) {
                            resExponent_ = resExponent_ - overflowLen_;
                            return ((resCoefficient_ << EXPONENT_SIZE_DEBT_FACTOR) | resExponent_);
                        }
                        // Can happen if bigNumber1 exponent is < 35 (35+16384+29 = 16448) and bigNumber2 exponent is e.g. max 16384.
                        // this would mean a branch with a normal big debt factor (bigNumber2) is merged into a base branch with an extremely small
                        // debt factor (bigNumber1).
                        // this should never happen anyway, but if it does better to revert than to continue with unknown effects.
                        revert(); // connection factor should never become a BigNumber with exponent <= 0
                    }
                }
            }
            // 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 {
                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;
            /// @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;
            /// @title library that calculates number "tick" and "ratioX96" from this: ratioX96 = (1.0015^tick) * 2^96
            /// @notice this library is used in Fluid Vault protocol for optimiziation.
            /// @dev "tick" supports between -32767 and 32767. "ratioX96" supports between 37075072 and 169307877264527972847801929085841449095838922544595
            library TickMath {
                /// The minimum tick that can be passed in getRatioAtTick. 1.0015**-32767
                int24 internal constant MIN_TICK = -32767;
                /// The maximum tick that can be passed in getRatioAtTick. 1.0015**32767
                int24 internal constant MAX_TICK = 32767;
                uint256 internal constant FACTOR00 = 0x100000000000000000000000000000000;
                uint256 internal constant FACTOR01 = 0xff9dd7de423466c20352b1246ce4856f; // 2^128/1.0015**1 = 339772707859149738855091969477551883631
                uint256 internal constant FACTOR02 = 0xff3bd55f4488ad277531fa1c725a66d0; // 2^128/1.0015**2 = 339263812140938331358054887146831636176
                uint256 internal constant FACTOR03 = 0xfe78410fd6498b73cb96a6917f853259; // 2^128/1.0015**4 = 338248306163758188337119769319392490073
                uint256 internal constant FACTOR04 = 0xfcf2d9987c9be178ad5bfeffaa123273; // 2^128/1.0015**8 = 336226404141693512316971918999264834163
                uint256 internal constant FACTOR05 = 0xf9ef02c4529258b057769680fc6601b3; // 2^128/1.0015**16 = 332218786018727629051611634067491389875
                uint256 internal constant FACTOR06 = 0xf402d288133a85a17784a411f7aba082; // 2^128/1.0015**32 = 324346285652234375371948336458280706178
                uint256 internal constant FACTOR07 = 0xe895615b5beb6386553757b0352bda90; // 2^128/1.0015**64 = 309156521885964218294057947947195947664
                uint256 internal constant FACTOR08 = 0xd34f17a00ffa00a8309940a15930391a; // 2^128/1.0015**128 = 280877777739312896540849703637713172762 
                uint256 internal constant FACTOR09 = 0xae6b7961714e20548d88ea5123f9a0ff; // 2^128/1.0015**256 = 231843708922198649176471782639349113087
                uint256 internal constant FACTOR10 = 0x76d6461f27082d74e0feed3b388c0ca1; // 2^128/1.0015**512 = 157961477267171621126394973980180876449
                uint256 internal constant FACTOR11 = 0x372a3bfe0745d8b6b19d985d9a8b85bb; // 2^128/1.0015**1024 = 73326833024599564193373530205717235131
                uint256 internal constant FACTOR12 = 0x0be32cbee48979763cf7247dd7bb539d; // 2^128/1.0015**2048 = 15801066890623697521348224657638773661
                uint256 internal constant FACTOR13 = 0x8d4f70c9ff4924dac37612d1e2921e;   // 2^128/1.0015**4096 = 733725103481409245883800626999235102
                uint256 internal constant FACTOR14 = 0x4e009ae5519380809a02ca7aec77;     // 2^128/1.0015**8192 = 1582075887005588088019997442108535
                uint256 internal constant FACTOR15 = 0x17c45e641b6e95dee056ff10;         // 2^128/1.0015**16384 = 7355550435635883087458926352
                /// The minimum value that can be returned from getRatioAtTick. Equivalent to getRatioAtTick(MIN_TICK). ~ Equivalent to `(1 << 96) * (1.0015**-32767)`
                uint256 internal constant MIN_RATIOX96 = 37075072;
                /// The maximum value that can be returned from getRatioAtTick. Equivalent to getRatioAtTick(MAX_TICK).
                /// ~ Equivalent to `(1 << 96) * (1.0015**32767)`, rounding etc. leading to minor difference
                uint256 internal constant MAX_RATIOX96 = 169307877264527972847801929085841449095838922544595;
                uint256 internal constant ZERO_TICK_SCALED_RATIO = 0x1000000000000000000000000; // 1 << 96 // 79228162514264337593543950336
                uint256 internal constant _1E26 = 1e26;
                /// @notice ratioX96 = (1.0015^tick) * 2^96
                /// @dev Throws if |tick| > max tick
                /// @param tick The input tick for the above formula
                /// @return ratioX96 ratio = (debt amount/collateral amount)
                function getRatioAtTick(int tick) internal pure returns (uint256 ratioX96) {
                    assembly {
                        let absTick_ := sub(xor(tick, sar(255, tick)), sar(255, tick))
                        if gt(absTick_, MAX_TICK) {
                            revert(0, 0)
                        }
                        let factor_ := FACTOR00
                        if and(absTick_, 0x1) {
                            factor_ := FACTOR01
                        }
                        if and(absTick_, 0x2) {
                            factor_ := shr(128, mul(factor_, FACTOR02))
                        }
                        if and(absTick_, 0x4) {
                            factor_ := shr(128, mul(factor_, FACTOR03))
                        }
                        if and(absTick_, 0x8) {
                            factor_ := shr(128, mul(factor_, FACTOR04))
                        }
                        if and(absTick_, 0x10) {
                            factor_ := shr(128, mul(factor_, FACTOR05))
                        }
                        if and(absTick_, 0x20) {
                            factor_ := shr(128, mul(factor_, FACTOR06))
                        }
                        if and(absTick_, 0x40) {
                            factor_ := shr(128, mul(factor_, FACTOR07))
                        }
                        if and(absTick_, 0x80) {
                            factor_ := shr(128, mul(factor_, FACTOR08))
                        }
                        if and(absTick_, 0x100) {
                            factor_ := shr(128, mul(factor_, FACTOR09))
                        }
                        if and(absTick_, 0x200) {
                            factor_ := shr(128, mul(factor_, FACTOR10))
                        }
                        if and(absTick_, 0x400) {
                            factor_ := shr(128, mul(factor_, FACTOR11))
                        }
                        if and(absTick_, 0x800) {
                            factor_ := shr(128, mul(factor_, FACTOR12))
                        }
                        if and(absTick_, 0x1000) {
                            factor_ := shr(128, mul(factor_, FACTOR13))
                        }
                        if and(absTick_, 0x2000) {
                            factor_ := shr(128, mul(factor_, FACTOR14))
                        }
                        if and(absTick_, 0x4000) {
                            factor_ := shr(128, mul(factor_, FACTOR15))
                        }
                        let precision_ := 0
                        if iszero(and(tick, 0x8000000000000000000000000000000000000000000000000000000000000000)) {
                            factor_ := div(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, factor_)
                            // we round up in the division so getTickAtRatio of the output price is always consistent
                            if mod(factor_, 0x100000000) {
                                precision_ := 1
                            }
                        }
                        ratioX96 := add(shr(32, factor_), precision_)
                    }
                }
                /// @notice ratioX96 = (1.0015^tick) * 2^96
                /// @dev Throws if ratioX96 > max ratio || ratioX96 < min ratio
                /// @param ratioX96 The input ratio; ratio = (debt amount/collateral amount)
                /// @return tick The output tick for the above formula. Returns in round down form. if tick is 123.23 then 123, if tick is -123.23 then returns -124
                /// @return perfectRatioX96 perfect ratio for the above tick
                function getTickAtRatio(uint256 ratioX96) internal pure returns (int tick, uint perfectRatioX96) {
                    assembly {
                        if or(gt(ratioX96, MAX_RATIOX96), lt(ratioX96, MIN_RATIOX96)) {
                            revert(0, 0)
                        }
                        let cond := lt(ratioX96, ZERO_TICK_SCALED_RATIO)
                        let factor_
                        if iszero(cond) {
                            // if ratioX96 >= ZERO_TICK_SCALED_RATIO
                            factor_ := div(mul(ratioX96, _1E26), ZERO_TICK_SCALED_RATIO)
                        }
                        if cond {
                            // ratioX96 < ZERO_TICK_SCALED_RATIO
                            factor_ := div(mul(ZERO_TICK_SCALED_RATIO, _1E26), ratioX96)
                        }
                        // put in https://www.wolframalpha.com/ whole equation: (1.0015^tick) * 2^96 * 10^26 / 79228162514264337593543950336
                        // for tick = 16384
                        // ratioX96 = (1.0015^16384) * 2^96 = 3665252098134783297721995888537077351735
                        // 3665252098134783297721995888537077351735 * 10^26 / 79228162514264337593543950336 =
                        // 4626198540796508716348404308345255985.06131964639489434655721
                        if iszero(lt(factor_, 4626198540796508716348404308345255985)) {
                            tick := or(tick, 0x4000)
                            factor_ := div(mul(factor_, _1E26), 4626198540796508716348404308345255985)
                        }
                        // for tick = 8192
                        // ratioX96 = (1.0015^8192) * 2^96 = 17040868196391020479062776466509865
                        // 17040868196391020479062776466509865 * 10^26 / 79228162514264337593543950336 =
                        // 21508599537851153911767490449162.3037648642153898377655505172
                        if iszero(lt(factor_, 21508599537851153911767490449162)) {
                            tick := or(tick, 0x2000)
                            factor_ := div(mul(factor_, _1E26), 21508599537851153911767490449162)
                        }
                        // for tick = 4096
                        // ratioX96 = (1.0015^4096) * 2^96 = 36743933851015821532611831851150
                        // 36743933851015821532611831851150 * 10^26 / 79228162514264337593543950336 =
                        // 46377364670549310883002866648.9777607649742626173648716941385
                        if iszero(lt(factor_, 46377364670549310883002866649)) {
                            tick := or(tick, 0x1000)
                            factor_ := div(mul(factor_, _1E26), 46377364670549310883002866649)
                        }
                        // for tick = 2048
                        // ratioX96 = (1.0015^2048) * 2^96 = 1706210527034005899209104452335
                        // 1706210527034005899209104452335 * 10^26 / 79228162514264337593543950336 =
                        // 2153540449365864845468344760.06357108484096046743300420319322
                        if iszero(lt(factor_, 2153540449365864845468344760)) {
                            tick := or(tick, 0x800)
                            factor_ := div(mul(factor_, _1E26), 2153540449365864845468344760)
                        }
                        // for tick = 1024
                        // ratioX96 = (1.0015^1024) * 2^96 = 367668226692760093024536487236
                        // 367668226692760093024536487236 * 10^26 / 79228162514264337593543950336 =
                        // 464062544207767844008185024.950588990554136265212906454481127
                        if iszero(lt(factor_, 464062544207767844008185025)) {
                            tick := or(tick, 0x400)
                            factor_ := div(mul(factor_, _1E26), 464062544207767844008185025)
                        }
                        // for tick = 512
                        // ratioX96 = (1.0015^512) * 2^96 = 170674186729409605620119663668
                        // 170674186729409605620119663668 * 10^26 / 79228162514264337593543950336 =
                        // 215421109505955298802281577.031879604792139232258508172947569
                        if iszero(lt(factor_, 215421109505955298802281577)) {
                            tick := or(tick, 0x200)
                            factor_ := div(mul(factor_, _1E26), 215421109505955298802281577)
                        }
                        // for tick = 256
                        // ratioX96 = (1.0015^256) * 2^96 = 116285004205991934861656513301
                        // 116285004205991934861656513301 * 10^26 / 79228162514264337593543950336 =
                        // 146772309890508740607270614.667650899656438875541505058062410
                        if iszero(lt(factor_, 146772309890508740607270615)) {
                            tick := or(tick, 0x100)
                            factor_ := div(mul(factor_, _1E26), 146772309890508740607270615)
                        }
                        // for tick = 128
                        // ratioX96 = (1.0015^128) * 2^96 = 95984619659632141743747099590
                        // 95984619659632141743747099590 * 10^26 / 79228162514264337593543950336 =
                        // 121149622323187099817270416.157248837742741760456796835775887
                        if iszero(lt(factor_, 121149622323187099817270416)) {
                            tick := or(tick, 0x80)
                            factor_ := div(mul(factor_, _1E26), 121149622323187099817270416)
                        }
                        // for tick = 64
                        // ratioX96 = (1.0015^64) * 2^96 = 87204845308406958006717891124
                        // 87204845308406958006717891124 * 10^26 / 79228162514264337593543950336 =
                        // 110067989135437147685980801.568068573422377364214113968609839
                        if iszero(lt(factor_, 110067989135437147685980801)) {
                            tick := or(tick, 0x40)
                            factor_ := div(mul(factor_, _1E26), 110067989135437147685980801)
                        }
                        // for tick = 32
                        // ratioX96 = (1.0015^32) * 2^96 = 83120873769022354029916374475
                        // 83120873769022354029916374475 * 10^26 / 79228162514264337593543950336 =
                        // 104913292358707887270979599.831816586773651266562785765558183
                        if iszero(lt(factor_, 104913292358707887270979600)) {
                            tick := or(tick, 0x20)
                            factor_ := div(mul(factor_, _1E26), 104913292358707887270979600)
                        }
                        // for tick = 16
                        // ratioX96 = (1.0015^16) * 2^96 = 81151180492336368327184716176
                        // 81151180492336368327184716176 * 10^26 / 79228162514264337593543950336 =
                        // 102427189924701091191840927.762844039579442328381455567932128
                        if iszero(lt(factor_, 102427189924701091191840928)) {
                            tick := or(tick, 0x10)
                            factor_ := div(mul(factor_, _1E26), 102427189924701091191840928)
                        }
                        // for tick = 8
                        // ratioX96 = (1.0015^8) * 2^96 = 80183906840906820640659903620
                        // 80183906840906820640659903620 * 10^26 / 79228162514264337593543950336 =
                        // 101206318935480056907421312.890625
                        if iszero(lt(factor_, 101206318935480056907421313)) {
                            tick := or(tick, 0x8)
                            factor_ := div(mul(factor_, _1E26), 101206318935480056907421313)
                        }
                        // for tick = 4
                        // ratioX96 = (1.0015^4) * 2^96 = 79704602139525152702959747603
                        // 79704602139525152702959747603 * 10^26 / 79228162514264337593543950336 =
                        // 100601351350506250000000000
                        if iszero(lt(factor_, 100601351350506250000000000)) {
                            tick := or(tick, 0x4)
                            factor_ := div(mul(factor_, _1E26), 100601351350506250000000000)
                        }
                        // for tick = 2
                        // ratioX96 = (1.0015^2) * 2^96 = 79466025265172787701084167660
                        // 79466025265172787701084167660 * 10^26 / 79228162514264337593543950336 =
                        // 100300225000000000000000000
                        if iszero(lt(factor_, 100300225000000000000000000)) {
                            tick := or(tick, 0x2)
                            factor_ := div(mul(factor_, _1E26), 100300225000000000000000000)
                        }
                        // for tick = 1
                        // ratioX96 = (1.0015^1) * 2^96 = 79347004758035734099934266261
                        // 79347004758035734099934266261 * 10^26 / 79228162514264337593543950336 =
                        // 100150000000000000000000000
                        if iszero(lt(factor_, 100150000000000000000000000)) {
                            tick := or(tick, 0x1)
                            factor_ := div(mul(factor_, _1E26), 100150000000000000000000000)
                        }
                        if iszero(cond) {
                            // if ratioX96 >= ZERO_TICK_SCALED_RATIO
                            perfectRatioX96 := div(mul(ratioX96, _1E26), factor_)
                        }
                        if cond {
                            // ratioX96 < ZERO_TICK_SCALED_RATIO
                            tick := not(tick)
                            perfectRatioX96 := div(mul(ratioX96, factor_), 100150000000000000000000000)
                        }
                        // perfect ratio should always be <= ratioX96
                        // not sure if it can ever be bigger but better to have extra checks
                        if gt(perfectRatioX96, ratioX96) {
                            revert(0, 0)
                        }
                    }
                }
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            abstract contract Structs {
                struct AddressBool {
                    address addr;
                    bool value;
                }
                struct AddressUint256 {
                    address addr;
                    uint256 value;
                }
                /// @notice struct to set borrow rate data for version 1
                struct RateDataV1Params {
                    ///
                    /// @param token for rate data
                    address token;
                    ///
                    /// @param kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
                    /// utilization below kink usually means slow increase in rate, once utilization is above kink borrow rate increases fast
                    uint256 kink;
                    ///
                    /// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
                    /// i.e. constant minimum borrow rate
                    /// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
                    uint256 rateAtUtilizationZero;
                    ///
                    /// @param rateAtUtilizationKink borrow rate when utilization is at kink. in 1e2: 100% = 10_000; 1% = 100
                    /// e.g. when rate should be 7% at kink then rateAtUtilizationKink would be 700
                    uint256 rateAtUtilizationKink;
                    ///
                    /// @param rateAtUtilizationMax borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
                    /// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
                    uint256 rateAtUtilizationMax;
                }
                /// @notice struct to set borrow rate data for version 2
                struct RateDataV2Params {
                    ///
                    /// @param token for rate data
                    address token;
                    ///
                    /// @param kink1 first kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
                    /// utilization below kink 1 usually means slow increase in rate, once utilization is above kink 1 borrow rate increases faster
                    uint256 kink1;
                    ///
                    /// @param kink2 second kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
                    /// utilization below kink 2 usually means slow / medium increase in rate, once utilization is above kink 2 borrow rate increases fast
                    uint256 kink2;
                    ///
                    /// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
                    /// i.e. constant minimum borrow rate
                    /// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
                    uint256 rateAtUtilizationZero;
                    ///
                    /// @param rateAtUtilizationKink1 desired borrow rate when utilization is at first kink. in 1e2: 100% = 10_000; 1% = 100
                    /// e.g. when rate should be 7% at first kink then rateAtUtilizationKink would be 700
                    uint256 rateAtUtilizationKink1;
                    ///
                    /// @param rateAtUtilizationKink2 desired borrow rate when utilization is at second kink. in 1e2: 100% = 10_000; 1% = 100
                    /// e.g. when rate should be 7% at second kink then rateAtUtilizationKink would be 1_200
                    uint256 rateAtUtilizationKink2;
                    ///
                    /// @param rateAtUtilizationMax desired borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
                    /// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
                    uint256 rateAtUtilizationMax;
                }
                /// @notice struct to set token config
                struct TokenConfig {
                    ///
                    /// @param token address
                    address token;
                    ///
                    /// @param fee charges on borrower's interest. in 1e2: 100% = 10_000; 1% = 100
                    uint256 fee;
                    ///
                    /// @param threshold on when to update the storage slot. in 1e2: 100% = 10_000; 1% = 100
                    uint256 threshold;
                    ///
                    /// @param maxUtilization maximum allowed utilization. in 1e2: 100% = 10_000; 1% = 100
                    ///                       set to 100% to disable and have default limit of 100% (avoiding SLOAD).
                    uint256 maxUtilization;
                }
                /// @notice struct to set user supply & withdrawal config
                struct UserSupplyConfig {
                    ///
                    /// @param user address
                    address user;
                    ///
                    /// @param token address
                    address token;
                    ///
                    /// @param mode: 0 = without interest. 1 = with interest
                    uint8 mode;
                    ///
                    /// @param expandPercent withdrawal limit expand percent. in 1e2: 100% = 10_000; 1% = 100
                    /// Also used to calculate rate at which withdrawal limit should decrease (instant).
                    uint256 expandPercent;
                    ///
                    /// @param expandDuration withdrawal limit expand duration in seconds.
                    /// used to calculate rate together with expandPercent
                    uint256 expandDuration;
                    ///
                    /// @param baseWithdrawalLimit base limit, below this, user can withdraw the entire amount.
                    /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
                    /// with interest -> raw, without interest -> normal
                    uint256 baseWithdrawalLimit;
                }
                /// @notice struct to set user borrow & payback config
                struct UserBorrowConfig {
                    ///
                    /// @param user address
                    address user;
                    ///
                    /// @param token address
                    address token;
                    ///
                    /// @param mode: 0 = without interest. 1 = with interest
                    uint8 mode;
                    ///
                    /// @param expandPercent debt limit expand percent. in 1e2: 100% = 10_000; 1% = 100
                    /// Also used to calculate rate at which debt limit should decrease (instant).
                    uint256 expandPercent;
                    ///
                    /// @param expandDuration debt limit expand duration in seconds.
                    /// used to calculate rate together with expandPercent
                    uint256 expandDuration;
                    ///
                    /// @param baseDebtCeiling base borrow limit. until here, borrow limit remains as baseDebtCeiling
                    /// (user can borrow until this point at once without stepped expansion). Above this, automated limit comes in place.
                    /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
                    /// with interest -> raw, without interest -> normal
                    uint256 baseDebtCeiling;
                    ///
                    /// @param maxDebtCeiling max borrow ceiling, maximum amount the user can borrow.
                    /// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
                    /// with interest -> raw, without interest -> normal
                    uint256 maxDebtCeiling;
                }
            }
            //SPDX-License-Identifier: MIT
            pragma solidity 0.8.21;
            import { IProxy } from "../../infiniteProxy/interfaces/iProxy.sol";
            import { Structs as AdminModuleStructs } from "../adminModule/structs.sol";
            interface IFluidLiquidityAdmin {
                /// @notice adds/removes auths. Auths generally could be contracts which can have restricted actions defined on contract.
                ///         auths can be helpful in reducing governance overhead where it's not needed.
                /// @param authsStatus_ array of structs setting allowed status for an address.
                ///                     status true => add auth, false => remove auth
                function updateAuths(AdminModuleStructs.AddressBool[] calldata authsStatus_) external;
                /// @notice adds/removes guardians. Only callable by Governance.
                /// @param guardiansStatus_ array of structs setting allowed status for an address.
                ///                         status true => add guardian, false => remove guardian
                function updateGuardians(AdminModuleStructs.AddressBool[] calldata guardiansStatus_) external;
                /// @notice changes the revenue collector address (contract that is sent revenue). Only callable by Governance.
                /// @param revenueCollector_  new revenue collector address
                function updateRevenueCollector(address revenueCollector_) external;
                /// @notice changes current status, e.g. for pausing or unpausing all user operations. Only callable by Auths.
                /// @param newStatus_ new status
                ///        status = 2 -> pause, status = 1 -> resume.
                function changeStatus(uint256 newStatus_) external;
                /// @notice                  update tokens rate data version 1. Only callable by Auths.
                /// @param tokensRateData_   array of RateDataV1Params with rate data to set for each token
                function updateRateDataV1s(AdminModuleStructs.RateDataV1Params[] calldata tokensRateData_) external;
                /// @notice                  update tokens rate data version 2. Only callable by Auths.
                /// @param tokensRateData_   array of RateDataV2Params with rate data to set for each token
                function updateRateDataV2s(AdminModuleStructs.RateDataV2Params[] calldata tokensRateData_) external;
                /// @notice updates token configs: fee charge on borrowers interest & storage update utilization threshold.
                ///         Only callable by Auths.
                /// @param tokenConfigs_ contains token address, fee & utilization threshold
                function updateTokenConfigs(AdminModuleStructs.TokenConfig[] calldata tokenConfigs_) external;
                /// @notice updates user classes: 0 is for new protocols, 1 is for established protocols.
                ///         Only callable by Auths.
                /// @param userClasses_ struct array of uint256 value to assign for each user address
                function updateUserClasses(AdminModuleStructs.AddressUint256[] calldata userClasses_) external;
                /// @notice sets user supply configs per token basis. Eg: with interest or interest-free and automated limits.
                ///         Only callable by Auths.
                /// @param userSupplyConfigs_ struct array containing user supply config, see `UserSupplyConfig` struct for more info
                function updateUserSupplyConfigs(AdminModuleStructs.UserSupplyConfig[] memory userSupplyConfigs_) external;
                /// @notice 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;
            import { IFluidOracle } from "./interfaces/iFluidOracle.sol";
            /// @title   FluidOracle
            /// @notice  Base contract that any Fluid Oracle must implement
            abstract contract FluidOracle is IFluidOracle {
                /// @inheritdoc IFluidOracle
                function getExchangeRate() external view virtual returns (uint256 exchangeRate_);
                /// @inheritdoc IFluidOracle
                function getExchangeRateOperate() external view virtual returns (uint256 exchangeRate_);
                /// @inheritdoc IFluidOracle
                function getExchangeRateLiquidate() external view virtual returns (uint256 exchangeRate_);
            }
            // SPDX-License-Identifier: MIT
            pragma solidity 0.8.21;
            interface IFluidOracle {
                /// @dev Deprecated. Use `getExchangeRateOperate()` and `getExchangeRateLiquidate()` instead. Only implemented for
                ///      backwards compatibility.
                function getExchangeRate() external view returns (uint256 exchangeRate_);
                /// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for operates
                function getExchangeRateOperate() external view returns (uint256 exchangeRate_);
                /// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for liquidations
                function getExchangeRateLiquidate() external view returns (uint256 exchangeRate_);
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            contract Error {
                error FluidVaultError(uint256 errorId_);
                /// @notice used to simulate liquidation to find the maximum liquidatable amounts
                error FluidLiquidateResult(uint256 colLiquidated, uint256 debtLiquidated);
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            library ErrorTypes {
                /***********************************|
                |           Vault Factory           | 
                |__________________________________*/
                uint256 internal constant VaultFactory__InvalidOperation = 30001;
                uint256 internal constant VaultFactory__Unauthorized = 30002;
                uint256 internal constant VaultFactory__SameTokenNotAllowed = 30003;
                uint256 internal constant VaultFactory__InvalidParams = 30004;
                uint256 internal constant VaultFactory__InvalidVault = 30005;
                uint256 internal constant VaultFactory__InvalidVaultAddress = 30006;
                uint256 internal constant VaultFactory__OnlyDelegateCallAllowed = 30007;
                /***********************************|
                |            VaultT1                | 
                |__________________________________*/
                /// @notice thrown at reentrancy
                uint256 internal constant VaultT1__AlreadyEntered = 31001;
                /// @notice thrown when user sends deposit & borrow amount as 0
                uint256 internal constant VaultT1__InvalidOperateAmount = 31002;
                /// @notice thrown when msg.value is not in sync with native token deposit or payback
                uint256 internal constant VaultT1__InvalidMsgValueOperate = 31003;
                /// @notice thrown when msg.sender is not the owner of the vault
                uint256 internal constant VaultT1__NotAnOwner = 31004;
                /// @notice thrown when user's position does not exist. Sending the wrong index from the frontend
                uint256 internal constant VaultT1__TickIsEmpty = 31005;
                /// @notice thrown when the user's position is above CF and the user tries to make it more risky by trying to withdraw or borrow
                uint256 internal constant VaultT1__PositionAboveCF = 31006;
                /// @notice thrown when the top tick is not initialized. Happens if the vault is totally new or all the user's left
                uint256 internal constant VaultT1__TopTickDoesNotExist = 31007;
                /// @notice thrown when msg.value in liquidate is not in sync payback
                uint256 internal constant VaultT1__InvalidMsgValueLiquidate = 31008;
                /// @notice thrown when slippage is more on liquidation than what the liquidator sent
                uint256 internal constant VaultT1__ExcessSlippageLiquidation = 31009;
                /// @notice thrown when msg.sender is not the rebalancer/reserve contract
                uint256 internal constant VaultT1__NotRebalancer = 31010;
                /// @notice thrown when NFT of one vault interacts with the NFT of other vault
                uint256 internal constant VaultT1__NftNotOfThisVault = 31011;
                /// @notice thrown when the token is not initialized on the liquidity contract
                uint256 internal constant VaultT1__TokenNotInitialized = 31012;
                /// @notice thrown when admin updates fallback if a non-auth calls vault
                uint256 internal constant VaultT1__NotAnAuth = 31013;
                /// @notice thrown in operate when user tries to witdhraw more collateral than deposited
                uint256 internal constant VaultT1__ExcessCollateralWithdrawal = 31014;
                /// @notice thrown in operate when user tries to payback more debt than borrowed
                uint256 internal constant VaultT1__ExcessDebtPayback = 31015;
                /// @notice thrown when user try to withdrawal more than operate's withdrawal limit
                uint256 internal constant VaultT1__WithdrawMoreThanOperateLimit = 31016;
                /// @notice thrown when caller of liquidityCallback is not Liquidity
                uint256 internal constant VaultT1__InvalidLiquidityCallbackAddress = 31017;
                /// @notice thrown when reentrancy is not already on
                uint256 internal constant VaultT1__NotEntered = 31018;
                /// @notice thrown when someone directly calls secondary implementation contract
                uint256 internal constant VaultT1__OnlyDelegateCallAllowed = 31019;
                /// @notice thrown when the safeTransferFrom for a token amount failed
                uint256 internal constant VaultT1__TransferFromFailed = 31020;
                /// @notice thrown when exchange price overflows while updating on storage
                uint256 internal constant VaultT1__ExchangePriceOverFlow = 31021;
                /// @notice thrown when debt to liquidate amt is sent wrong
                uint256 internal constant VaultT1__InvalidLiquidationAmt = 31022;
                /// @notice thrown when user debt or collateral goes above 2**128 or below -2**128
                uint256 internal constant VaultT1__UserCollateralDebtExceed = 31023;
                /// @notice thrown if on liquidation branch debt becomes lower than 100
                uint256 internal constant VaultT1__BranchDebtTooLow = 31024;
                /// @notice thrown when tick's debt is less than 10000
                uint256 internal constant VaultT1__TickDebtTooLow = 31025;
                /// @notice thrown when the received new liquidity exchange price is of unexpected value (< than the old one)
                uint256 internal constant VaultT1__LiquidityExchangePriceUnexpected = 31026;
                /// @notice thrown when user's debt is less than 10000
                uint256 internal constant VaultT1__UserDebtTooLow = 31027;
                /// @notice thrown when on only payback and only deposit the ratio of position increases
                uint256 internal constant VaultT1__InvalidPaybackOrDeposit = 31028;
                /// @notice thrown when liquidation just happens of a single partial or when there's nothing to liquidate
                uint256 internal constant VaultT1__InvalidLiquidation = 31029;
                /// @notice thrown when msg.value is sent wrong in rebalance
                uint256 internal constant VaultT1__InvalidMsgValueInRebalance = 31030;
                /// @notice thrown when nothing rebalanced
                uint256 internal constant VaultT1__NothingToRebalance = 31031;
                /// @notice thrown on unforseen liquidation scenarios. Might never come in use.
                uint256 internal constant VaultT1__LiquidationReverts = 31032;
                /// @notice thrown when oracle price is > 1e54
                uint256 internal constant VaultT1__InvalidOraclePrice = 31033;
                /***********************************|
                |              ERC721               | 
                |__________________________________*/
                uint256 internal constant ERC721__InvalidParams = 32001;
                uint256 internal constant ERC721__Unauthorized = 32002;
                uint256 internal constant ERC721__InvalidOperation = 32003;
                uint256 internal constant ERC721__UnsafeRecipient = 32004;
                uint256 internal constant ERC721__OutOfBoundsIndex = 32005;
                /***********************************|
                |            Vault Admin            | 
                |__________________________________*/
                /// @notice thrown when admin tries to setup invalid value which are crossing limits
                uint256 internal constant VaultT1Admin__ValueAboveLimit = 33001;
                /// @notice when someone directly calls admin implementation contract
                uint256 internal constant VaultT1Admin__OnlyDelegateCallAllowed = 33002;
                /// @notice thrown when auth sends NFT ID as 0 while collecting dust debt
                uint256 internal constant VaultT1Admin__NftIdShouldBeNonZero = 33003;
                /// @notice thrown when trying to collect dust debt of NFT which is not of this vault
                uint256 internal constant VaultT1Admin__NftNotOfThisVault = 33004;
                /// @notice thrown when dust debt of NFT is 0, meaning nothing to collect
                uint256 internal constant VaultT1Admin__DustDebtIsZero = 33005;
                /// @notice thrown when final debt after liquidation is not 0, meaning position 100% liquidated
                uint256 internal constant VaultT1Admin__FinalDebtShouldBeZero = 33006;
                /// @notice thrown when NFT is not liquidated state
                uint256 internal constant VaultT1Admin__NftNotLiquidated = 33007;
                /// @notice thrown when total absorbed dust debt is 0
                uint256 internal constant VaultT1Admin__AbsorbedDustDebtIsZero = 33008;
                /// @notice thrown when address is set as 0
                uint256 internal constant VaultT1Admin__AddressZeroNotAllowed = 33009;
                /***********************************|
                |            Vault Rewards          | 
                |__________________________________*/
                uint256 internal constant VaultRewards__Unauthorized = 34001;
                uint256 internal constant VaultRewards__AddressZero = 34002;
                uint256 internal constant VaultRewards__InvalidParams = 34003;
                uint256 internal constant VaultRewards__NewMagnifierSameAsOldMagnifier = 34004;
                uint256 internal constant VaultRewards__NotTheInitiator = 34005;
                uint256 internal constant VaultRewards__AlreadyStarted = 34006;
                uint256 internal constant VaultRewards__RewardsNotStartedOrEnded = 34007;
            }
            //SPDX-License-Identifier: MIT
            pragma solidity 0.8.21;
            import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
            interface IFluidVaultFactory is IERC721Enumerable {
                /// @notice Minting an NFT Vault for the user
                function mint(uint256 vaultId_, address user_) external returns (uint256 tokenId_);
                /// @notice returns owner of Vault which is also an NFT
                function ownerOf(uint256 tokenId) external view returns (address owner);
                /// @notice Global auth is auth for all vaults
                function isGlobalAuth(address auth_) external view returns (bool);
                /// @notice Vault auth is auth for a specific vault
                function isVaultAuth(address vault_, address auth_) external view returns (bool);
                /// @notice Total vaults deployed.
                function totalVaults() external view returns (uint256);
                /// @notice Compute vaultAddress
                function getVaultAddress(uint256 vaultId) external view returns (address);
                /// @notice read uint256 `result_` for a storage `slot_` key
                function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            contract Variables {
                /***********************************|
                |         Storage Variables         |
                |__________________________________*/
                /// note: in all variables. For tick >= 0 are represented with bit as 1, tick < 0 are represented with bit as 0
                /// note: read all the variables through storageRead.sol
                /// note: vaultVariables contains vault variables which need regular updates through transactions
                /// First 1 bit => 0 => re-entrancy. If 0 then allow transaction to go, else throw.
                /// Next 1 bit => 1 => Is the current active branch liquidated? If true then check the branch's minima tick before creating a new position
                /// If the new tick is greater than minima tick then initialize a new branch, make that as current branch & do proper linking
                /// Next 1 bit => 2 => sign of topmost tick (0 -> negative; 1 -> positive)
                /// Next 19 bits => 3-21 => absolute value of topmost tick
                /// Next 30 bits => 22-51 => current branch ID
                /// Next 30 bits => 52-81 => total branch ID
                /// Next 64 bits => 82-145 => Total supply
                /// Next 64 bits => 146-209 => Total borrow
                /// Next 32 bits => 210-241 => Total positions
                uint256 internal vaultVariables;
                /// note: vaultVariables2 contains variables which do not update on every transaction. So mainly admin/auth set amount
                /// First 16 bits => 0-15 => supply rate magnifier; 10000 = 1x (Here 16 bits should be more than enough)
                /// Next 16 bits => 16-31 => borrow rate magnifier; 10000 = 1x (Here 16 bits should be more than enough)
                /// Next 10 bits => 32-41 => collateral factor. 800 = 0.8 = 80% (max precision of 0.1%)
                /// Next 10 bits => 42-51 => liquidation Threshold. 900 = 0.9 = 90% (max precision of 0.1%)
                /// Next 10 bits => 52-61 => liquidation Max Limit. 950 = 0.95 = 95% (max precision of 0.1%) (above this 100% liquidation can happen)
                /// Next 10 bits => 62-71 => withdraw gap. 100 = 0.1 = 10%. (max precision of 0.1%) (max 7 bits can also suffice for the requirement here of 0.1% to 10%). Needed to save some limits on withdrawals so liquidate can work seamlessly.
                /// Next 10 bits => 72-81 => liquidation penalty. 100 = 0.01 = 1%. (max precision of 0.01%) (max liquidation penantly can be 10.23%). Applies when tick is in between liquidation Threshold & liquidation Max Limit.
                /// Next 10 bits => 82-91 => borrow fee. 100 = 0.01 = 1%. (max precision of 0.01%) (max borrow fee can be 10.23%). Fees on borrow.
                /// Next 4  bits => 92-95 => empty
                /// Next 160 bits => 96-255 => Oracle address
                uint256 internal vaultVariables2;
                /// note: stores absorbed liquidity
                /// First 128 bits raw debt amount
                /// last 128 bits raw col amount
                uint256 internal absorbedLiquidity;
                /// position index => position data uint
                /// if the entire variable is 0 (meaning not initialized) at the start that means no position at all
                /// First 1 bit => 0 => position type (0 => borrow position; 1 => supply position)
                /// Next 1 bit => 1 => sign of user's tick (0 => negative; 1 => positive)
                /// Next 19 bits => 2-20 => absolute value of user's tick
                /// Next 24 bits => 21-44 => user's tick's id
                /// Below we are storing user's collateral & not debt, because the position can also be only collateral with no tick but it can never be only debt
                /// Next 64 bits => 45-108 => user's supply amount. Debt will be calculated through supply & ratio.
                /// Next 64 bits => 109-172 => user's dust debt amount. User's net debt = total debt - dust amount. Total debt is calculated through supply & ratio
                /// User won't pay any extra interest on dust debt & hence we will not show it as a debt on UI. For user's there's no dust.
                mapping(uint256 => uint256) internal positionData;
                /// Tick has debt only keeps data of non liquidated positions. liquidated tick's data stays in branch itself
                /// tick parent => uint (represents bool for 256 children)
                /// parent of (i)th tick:-
                /// if (i>=0) (i / 256);
                /// else ((i + 1) / 256) - 1
                /// first bit of the variable is the smallest tick & last bit is the biggest tick of that slot
                mapping(int256 => uint256) internal tickHasDebt;
                /// mapping tickId => tickData
                /// Tick related data. Total debt & other things
                /// First bit => 0 => If 1 then liquidated else not liquidated
                /// Next 24 bits => 1-24 => Total IDs. ID should start from 1.
                /// If not liquidated:
                /// Next 64 bits => 25-88 => raw debt
                /// If liquidated
                /// The below 3 things are of last ID. This is to be updated when user creates a new position
                /// Next 1 bit => 25 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated (100% liquidated)
                /// Next 30 bits => 26-55 => branch ID where this tick got liquidated
                /// Next 50 bits => 56-105 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
                mapping(int256 => uint256) internal tickData;
                /// tick id => previous tick id liquidation data. ID starts from 1
                /// One tick ID contains 3 IDs of 80 bits in it, holding liquidation data of previously active but liquidated ticks
                /// 81 bits data below
                /// #### First 85 bits ####
                /// 1st bit => 0 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
                /// Next 30 bits => 1-30 => branch ID where this tick got liquidated
                /// Next 50 bits => 31-80 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
                /// #### Second 85 bits ####
                /// 85th bit => 85 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
                /// Next 30 bits => 86-115 => branch ID where this tick got liquidated
                /// Next 50 bits => 116-165 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
                /// #### Third 85 bits ####
                /// 170th bit => 170 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
                /// Next 30 bits => 171-200 => branch ID where this tick got liquidated
                /// Next 50 bits => 201-250 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
                mapping(int256 => mapping(uint256 => uint256)) internal tickId;
                /// mapping branchId => branchData
                /// First 2 bits => 0-1 => if 0 then not liquidated, if 1 then liquidated, if 2 then merged, if 3 then closed
                /// merged means the branch is merged into it's base branch
                /// closed means all the users are 100% liquidated
                /// Next 1 bit => 2 => minima tick sign of this branch. Will only be there if any liquidation happened.
                /// Next 19 bits => 3-21 => minima tick of this branch. Will only be there if any liquidation happened.
                /// Next 30 bits => 22-51 => Partials of minima tick of branch this is connected to. 0 if master branch.
                /// Next 64 bits => 52-115 Debt liquidity at this branch. Similar to last's top tick data. Remaining debt will move here from tickData after first liquidation
                /// If not merged
                /// Next 50 bits => 116-165 => Debt factor or of this branch. (35 bits coefficient | 15 bits expansion)
                /// If merged
                /// Next 50 bits => 116-165 => Connection/adjustment debt factor of this branch with the next branch.
                /// If closed
                /// Next 50 bits => 116-165 => Debt factor as 0. As all the user's positions are now fully gone
                /// following values are present always again (merged / not merged / closed)
                /// Next 30 bits => 166-195 => Branch's ID with which this branch is connected. If 0 then that means this is the master branch
                /// Next 1 bit => 196 => sign of minima tick of branch this is connected to. 0 if master branch.
                /// Next 19 bits => 197-215 => minima tick of branch this is connected to. 0 if master branch.
                mapping(uint256 => uint256) internal branchData;
                /// Exchange prices are in 1e12
                /// First 64 bits => 0-63 => Liquidity's collateral token supply exchange price
                /// First 64 bits => 64-127 => Liquidity's debt token borrow exchange price
                /// First 64 bits => 128-191 => Vault's collateral token supply exchange price
                /// First 64 bits => 192-255 => Vault's debt token borrow exchange price
                uint256 internal rates;
                /// address of rebalancer
                address internal rebalancer;
                uint256 internal absorbedDustDebt;
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            import { IFluidVaultFactory } from "../../interfaces/iVaultFactory.sol";
            import { IFluidLiquidity } from "../../../../liquidity/interfaces/iLiquidity.sol";
            import { StorageRead } from "../../../../libraries/storageRead.sol";
            import { Structs } from "./structs.sol";
            interface TokenInterface {
                function decimals() external view returns (uint8);
            }
            contract ConstantVariables is StorageRead, Structs {
                /***********************************|
                |        Constant Variables         |
                |__________________________________*/
                address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                /// @dev collateral token address
                address internal immutable SUPPLY_TOKEN;
                /// @dev borrow token address
                address internal immutable BORROW_TOKEN;
                /// @dev Token decimals. For example wETH is 18 decimals
                uint8 internal immutable SUPPLY_DECIMALS;
                /// @dev Token decimals. For example USDC is 6 decimals
                uint8 internal immutable BORROW_DECIMALS;
                /// @dev VaultT1 AdminModule implemenation address
                address internal immutable ADMIN_IMPLEMENTATION;
                /// @dev VaultT1 Secondary implemenation (main2.sol) address
                address internal immutable SECONDARY_IMPLEMENTATION;
                /// @dev liquidity proxy contract address
                IFluidLiquidity public immutable LIQUIDITY;
                /// @dev vault factory contract address
                IFluidVaultFactory public immutable VAULT_FACTORY;
                uint public immutable VAULT_ID;
                uint internal constant X8 = 0xff;
                uint internal constant X10 = 0x3ff;
                uint internal constant X16 = 0xffff;
                uint internal constant X19 = 0x7ffff;
                uint internal constant X20 = 0xfffff;
                uint internal constant X24 = 0xffffff;
                uint internal constant X25 = 0x1ffffff;
                uint internal constant X30 = 0x3fffffff;
                uint internal constant X35 = 0x7ffffffff;
                uint internal constant X50 = 0x3ffffffffffff;
                uint internal constant X64 = 0xffffffffffffffff;
                uint internal constant X96 = 0xffffffffffffffffffffffff;
                uint internal constant X128 = 0xffffffffffffffffffffffffffffffff;
                uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
                /// @dev slot ids in Liquidity contract. Helps in low gas fetch from liquidity contract by skipping delegate call
                bytes32 internal immutable LIQUIDITY_SUPPLY_EXCHANGE_PRICE_SLOT;
                bytes32 internal immutable LIQUIDITY_BORROW_EXCHANGE_PRICE_SLOT;
                bytes32 internal immutable LIQUIDITY_USER_SUPPLY_SLOT;
                bytes32 internal immutable LIQUIDITY_USER_BORROW_SLOT;
                /// @notice returns all Vault constants
                function constantsView() external view returns (ConstantViews memory constantsView_) {
                    constantsView_.liquidity = address(LIQUIDITY);
                    constantsView_.factory = address(VAULT_FACTORY);
                    constantsView_.adminImplementation = ADMIN_IMPLEMENTATION;
                    constantsView_.secondaryImplementation = SECONDARY_IMPLEMENTATION;
                    constantsView_.supplyToken = SUPPLY_TOKEN;
                    constantsView_.borrowToken = BORROW_TOKEN;
                    constantsView_.supplyDecimals = SUPPLY_DECIMALS;
                    constantsView_.borrowDecimals = BORROW_DECIMALS;
                    constantsView_.vaultId = VAULT_ID;
                    constantsView_.liquiditySupplyExchangePriceSlot = LIQUIDITY_SUPPLY_EXCHANGE_PRICE_SLOT;
                    constantsView_.liquidityBorrowExchangePriceSlot = LIQUIDITY_BORROW_EXCHANGE_PRICE_SLOT;
                    constantsView_.liquidityUserSupplySlot = LIQUIDITY_USER_SUPPLY_SLOT;
                    constantsView_.liquidityUserBorrowSlot = LIQUIDITY_USER_BORROW_SLOT;
                }
                constructor(ConstantViews memory constants_) {
                    LIQUIDITY = IFluidLiquidity(constants_.liquidity);
                    VAULT_FACTORY = IFluidVaultFactory(constants_.factory);
                    VAULT_ID = constants_.vaultId;
                    SUPPLY_TOKEN = constants_.supplyToken;
                    BORROW_TOKEN = constants_.borrowToken;
                    SUPPLY_DECIMALS = constants_.supplyDecimals;
                    BORROW_DECIMALS = constants_.borrowDecimals;
                    // @dev those slots are calculated in the deploymentLogics / VaultFactory
                    LIQUIDITY_SUPPLY_EXCHANGE_PRICE_SLOT = constants_.liquiditySupplyExchangePriceSlot;
                    LIQUIDITY_BORROW_EXCHANGE_PRICE_SLOT = constants_.liquidityBorrowExchangePriceSlot;
                    LIQUIDITY_USER_SUPPLY_SLOT = constants_.liquidityUserSupplySlot;
                    LIQUIDITY_USER_BORROW_SLOT = constants_.liquidityUserBorrowSlot;
                    ADMIN_IMPLEMENTATION = constants_.adminImplementation;
                    SECONDARY_IMPLEMENTATION = constants_.secondaryImplementation;
                }
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            contract Events {
                /// @notice emitted when an operate() method is executed that changes collateral (`colAmt_`) / debt (debtAmt_`)
                /// amount for a `user_` position with `nftId_`. Receiver of any funds is the address `to_`.
                event LogOperate(address user_, uint256 nftId_, int256 colAmt_, int256 debtAmt_, address to_);
                /// @notice emitted when the exchange prices are updated in storage.
                event LogUpdateExchangePrice(uint256 supplyExPrice_, uint256 borrowExPrice_);
                /// @notice emitted when a liquidation has been executed.
                event LogLiquidate(address liquidator_, uint256 colAmt_, uint256 debtAmt_, address to_);
                /// @notice emitted when `absorb()` was executed to absorb bad debt.
                event LogAbsorb(uint colAbsorbedRaw_, uint debtAbsorbedRaw_);
                /// @notice emitted when a `rebalance()` has been executed, balancing out total supply / borrow between Vault
                /// and Fluid Liquidity pools.
                /// if `colAmt_` is positive then loss, meaning transfer from rebalancer address to vault and deposit.
                /// if `colAmt_` is negative then profit, meaning withdrawn from vault and sent to rebalancer address.
                /// if `debtAmt_` is positive then profit, meaning borrow from vault and sent to rebalancer address.
                /// if `debtAmt_` is negative then loss, meaning transfer from rebalancer address to vault and payback.
                event LogRebalance(int colAmt_, int debtAmt_);
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            import { Variables } from "../common/variables.sol";
            import { ConstantVariables } from "./constantVariables.sol";
            import { Events } from "./events.sol";
            import { TickMath } from "../../../../libraries/tickMath.sol";
            import { BigMathMinified } from "../../../../libraries/bigMathMinified.sol";
            import { BigMathVault } from "../../../../libraries/bigMathVault.sol";
            import { LiquidityCalcs } from "../../../../libraries/liquidityCalcs.sol";
            import { ErrorTypes } from "../../errorTypes.sol";
            import { Error } from "../../error.sol";
            /// @dev Fluid vault protocol helper methods. Mostly used for `operate()` and `liquidate()` methods of CoreModule.
            abstract contract Helpers is Variables, ConstantVariables, Events, Error {
                using BigMathMinified for uint256;
                using BigMathVault for uint256;
                /// @notice Calculates new vault exchange prices. Does not update values in storage.
                /// @param vaultVariables2_ exactly same as vaultVariables2 from storage
                /// @return liqSupplyExPrice_ latest liquidity's supply token supply exchange price
                /// @return liqBorrowExPrice_ latest liquidity's borrow token borrow exchange price
                /// @return vaultSupplyExPrice_ latest vault's supply token exchange price
                /// @return vaultBorrowExPrice_ latest vault's borrow token exchange price
                function updateExchangePrices(
                    uint256 vaultVariables2_
                )
                    public
                    view
                    returns (
                        uint256 liqSupplyExPrice_,
                        uint256 liqBorrowExPrice_,
                        uint256 vaultSupplyExPrice_,
                        uint256 vaultBorrowExPrice_
                    )
                {
                    // Fetching last stored rates
                    uint rates_ = rates;
                    (liqSupplyExPrice_, ) = LiquidityCalcs.calcExchangePrices(
                        LIQUIDITY.readFromStorage(LIQUIDITY_SUPPLY_EXCHANGE_PRICE_SLOT)
                    );
                    (, liqBorrowExPrice_) = LiquidityCalcs.calcExchangePrices(
                        LIQUIDITY.readFromStorage(LIQUIDITY_BORROW_EXCHANGE_PRICE_SLOT)
                    );
                    uint256 oldLiqSupplyExPrice_ = (rates_ & X64);
                    uint256 oldLiqBorrowExPrice_ = ((rates_ >> 64) & X64);
                    if (liqSupplyExPrice_ < oldLiqSupplyExPrice_ || liqBorrowExPrice_ < oldLiqBorrowExPrice_) {
                        // new liquidity exchange price is < than the old one. liquidity exchange price should only ever increase.
                        // If not, something went wrong and avoid proceeding with unknown outcome.
                        revert FluidVaultError(ErrorTypes.VaultT1__LiquidityExchangePriceUnexpected);
                    }
                    // liquidity Exchange Prices always increases in next block. Hence substraction with old will never be negative
                    // uint64 * 1e18 is the max the number that could be
                    unchecked {
                        // Calculating increase in supply exchange price w.r.t last stored liquidity's exchange price
                        // vaultSupplyExPrice_ => supplyIncreaseInPercent_
                        vaultSupplyExPrice_ = ((((liqSupplyExPrice_ * 1e18) / oldLiqSupplyExPrice_) - 1e18) *
                            (vaultVariables2_ & X16)) / 10000; // supply rate magnifier
                        // Calculating increase in borrow exchange price w.r.t last stored liquidity's exchange price
                        // vaultBorrowExPrice_ => borrowIncreaseInPercent_
                        vaultBorrowExPrice_ = ((((liqBorrowExPrice_ * 1e18) / oldLiqBorrowExPrice_) - 1e18) *
                            ((vaultVariables2_ >> 16) & X16)) / 10000; // borrow rate magnifier
                        // It's extremely hard the exchange prices to overflow even in 100 years but if it does it's not an
                        // issue here as we are not updating on storage
                        // (rates_ >> 128) & X64) -> last stored vault's supply token exchange price
                        vaultSupplyExPrice_ = (((rates_ >> 128) & X64) * (1e18 + vaultSupplyExPrice_)) / 1e18;
                        // (rates_ >> 192) -> last stored vault's borrow token exchange price (no need to mask with & X64 as it is anyway max 64 bits)
                        vaultBorrowExPrice_ = ((rates_ >> 192) * (1e18 + vaultBorrowExPrice_)) / 1e18;
                    }
                }
                /// note admin module is also calling this function self call
                /// @dev updating exchange price on storage. Only need to update on storage when changing supply or borrow magnifier
                function updateExchangePricesOnStorage()
                    public
                    returns (
                        uint256 liqSupplyExPrice_,
                        uint256 liqBorrowExPrice_,
                        uint256 vaultSupplyExPrice_,
                        uint256 vaultBorrowExPrice_
                    )
                {
                    (liqSupplyExPrice_, liqBorrowExPrice_, vaultSupplyExPrice_, vaultBorrowExPrice_) = updateExchangePrices(
                        vaultVariables2
                    );
                    if (
                        liqSupplyExPrice_ > X64 || liqBorrowExPrice_ > X64 || vaultSupplyExPrice_ > X64 || vaultBorrowExPrice_ > X64
                    ) {
                        revert FluidVaultError(ErrorTypes.VaultT1__ExchangePriceOverFlow);
                    }
                    // Updating in storage
                    rates =
                        liqSupplyExPrice_ |
                        (liqBorrowExPrice_ << 64) |
                        (vaultSupplyExPrice_ << 128) |
                        (vaultBorrowExPrice_ << 192);
                    emit LogUpdateExchangePrice(vaultSupplyExPrice_, vaultBorrowExPrice_);
                }
                /// @dev fetches new user's position after liquidation. The new liquidated position's debt is decreased by 0.01%
                /// to make sure that branch's liquidity never becomes 0 as if it would have gotten 0 then there will be multiple cases that we would need to tackle.
                /// @param positionTick_ position's tick when it was last updated through operate
                /// @param positionTickId_ position's tick Id. This stores the debt factor and branch to make the first connection
                /// @param positionRawDebt_ position's raw debt when it was last updated through operate
                /// @param tickData_ position's tick's tickData just for minor comparison to know if data is moved to tick Id or is still in tick data
                /// @return final tick position after all the liquidation
                /// @return final debt of position after all the liquidation
                /// @return positionRawCol_ final collateral of position after all the liquidation
                /// @return branchId_ final branch's ID where the position is at currently
                /// @return branchData_ final branch's data where the position is at currently
                function fetchLatestPosition(
                    int256 positionTick_,
                    uint256 positionTickId_,
                    uint256 positionRawDebt_,
                    uint256 tickData_
                )
                    public
                    view
                    returns (
                        int256, // positionTick_
                        uint256, // positionRawDebt_
                        uint256 positionRawCol_,
                        uint256 branchId_,
                        uint256 branchData_
                    )
                {
                    uint256 initialPositionRawDebt_ = positionRawDebt_;
                    uint256 connectionFactor_;
                    bool isFullyLiquidated_;
                    // Checking if tick's total ID = user's tick ID
                    if (((tickData_ >> 1) & X24) == positionTickId_) {
                        // fetching from tick data itself
                        isFullyLiquidated_ = ((tickData_ >> 25) & 1) == 1;
                        branchId_ = (tickData_ >> 26) & X30;
                        connectionFactor_ = (tickData_ >> 56) & X50;
                    } else {
                        {
                            uint256 tickLiquidationData_;
                            unchecked {
                                // Fetching tick's liquidation data. One variable contains data of 3 IDs. Tick Id mapping is starting from 1.
                                tickLiquidationData_ =
                                    tickId[positionTick_][(positionTickId_ + 2) / 3] >>
                                    (((positionTickId_ + 2) % 3) * 85);
                            }
                            isFullyLiquidated_ = (tickLiquidationData_ & 1) == 1;
                            branchId_ = (tickLiquidationData_ >> 1) & X30;
                            connectionFactor_ = (tickLiquidationData_ >> 31) & X50;
                        }
                    }
                    // data of branch
                    branchData_ = branchData[branchId_];
                    if (isFullyLiquidated_) {
                        positionTick_ = type(int).min;
                        positionRawDebt_ = 0;
                    } else {
                        // Below information about connection debt factor
                        // If branch is merged, Connection debt factor is used to multiply in order to get perfect liquidation of user
                        // For example: Considering user was at the top.
                        // In first branch, the user liquidated to debt factor 0.5 and then branch got merged (branching starting from 1)
                        // In second branch, it got liquidated to 0.4 but when the above branch merged the debt factor on this branch was 0.6
                        // Meaning on 1st branch, user got liquidated by 50% & on 2nd by 33.33%. So a total of 66.6%.
                        // What we will set a connection factor will be 0.6/0.5 = 1.2
                        // So now to get user's position, this is what we'll do:
                        // finalDebt = (0.4 / (1 * 1.2)) * debtBeforeLiquidation
                        // 0.4 is current active branch's minima debt factor
                        // 1 is debt factor from where user started
                        // 1.2 is connection factor which we found out through 0.6 / 0.5
                        while ((branchData_ & 3) == 2) {
                            // If true then the branch is merged
                            // userTickDebtFactor * connectionDebtFactor *... connectionDebtFactor aka adjustmentDebtFactor
                            connectionFactor_ = connectionFactor_.mulBigNumber(((branchData_ >> 116) & X50));
                            if (connectionFactor_ == BigMathVault.MAX_MASK_DEBT_FACTOR) break; // user ~100% liquidated
                            // Note we don't need updated branch data in case of 100% liquidated so saving gas for fetching it
                            // Fetching new branch data
                            branchId_ = (branchData_ >> 166) & X30; // Link to base branch of current branch
                            branchData_ = branchData[branchId_];
                        }
                        // When the while loop breaks meaning the branch now has minima Debt Factor or is a closed branch;
                        if (((branchData_ & 3) == 3) || (connectionFactor_ == BigMathVault.MAX_MASK_DEBT_FACTOR)) {
                            // Branch got closed (or user liquidated ~100%). Hence make the user's position 0
                            // Rare cases to get into this situation
                            // Branch can get close often but once closed it's tricky that some user might come iterating through there
                            // If a user comes then that user will be very mini user like some cents probably
                            positionTick_ = type(int).min;
                            positionRawDebt_ = 0;
                        } else {
                            // If branch is not merged, the main branch it's connected to then it'll have minima debt factor
                            // position debt = debt * base branch minimaDebtFactor / connectionFactor
                            positionRawDebt_ = positionRawDebt_.mulDivNormal(
                                (branchData_ >> 116) & X50, // minimaDebtFactor
                                connectionFactor_
                            );
                            unchecked {
                                // Reducing user's liquidity by 0.01% if user got liquidated.
                                // As this will make sure that the branch always have some debt even if all liquidated user left
                                // This saves a lot more logics & consideration on Operate function
                                // if we don't do this then we have to add logics related to closing the branch and factor connections accordingly.
                                if (positionRawDebt_ > (initialPositionRawDebt_ / 100)) {
                                    positionRawDebt_ = (positionRawDebt_ * 9999) / 10000;
                                } else {
                                    // if user debt reduced by more than 99% in liquidation then making user as fully liquidated
                                    positionRawDebt_ = 0;
                                }
                            }
                            {
                                if (positionRawDebt_ > 0) {
                                    // positionTick_ -> read minima tick of branch
                                    unchecked {
                                        positionTick_ = branchData_ & 4 == 4
                                            ? int((branchData_ >> 3) & X19)
                                            : -int((branchData_ >> 3) & X19);
                                    }
                                    // Calculating user's collateral
                                    uint256 ratioAtTick_ = TickMath.getRatioAtTick(int24(positionTick_));
                                    uint256 ratioOneLess_;
                                    unchecked {
                                        ratioOneLess_ = (ratioAtTick_ * 10000) / 10015;
                                    }
                                    // formula below for better readability:
                                    // length = ratioAtTick_ - ratioOneLess_
                                    // ratio = ratioOneLess_ + (length * positionPartials_) / X30
                                    // positionRawCol_ = (positionRawDebt_ * (1 << 96)) / ratio_
                                    positionRawCol_ =
                                        (positionRawDebt_ * TickMath.ZERO_TICK_SCALED_RATIO) /
                                        (ratioOneLess_ + ((ratioAtTick_ - ratioOneLess_) * ((branchData_ >> 22) & X30)) / X30);
                                } else {
                                    positionTick_ = type(int).min;
                                }
                            }
                        }
                    }
                    return (positionTick_, positionRawDebt_, positionRawCol_, branchId_, branchData_);
                }
                /// @dev sets `tick_` as having debt or no debt in storage `tickHasDebt` depending on `addOrRemove_`
                /// @param tick_ tick to add or remove from tickHasDebt
                /// @param addOrRemove_ if true then add else remove
                function _updateTickHasDebt(int tick_, bool addOrRemove_) internal {
                    // Positive mapID_ starts from 0 & above and negative starts below 0.
                    // tick 0 to 255 will have mapId_ as 0 while tick -256 to -1 will have mapId_ as -1.
                    unchecked {
                        int mapId_ = tick_ < 0 ? ((tick_ + 1) / 256) - 1 : tick_ / 256;
                        // in case of removing:
                        // (tick == 255) tickHasDebt[mapId_] - 1 << 255
                        // (tick == 0) tickHasDebt[mapId_] - 1 << 0
                        // (tick == -1) tickHasDebt[mapId_] - 1 << 255
                        // (tick == -256) tickHasDebt[mapId_] - 1 << 0
                        // in case of adding:
                        // (tick == 255) tickHasDebt[mapId_] - 1 << 255
                        // (tick == 0) tickHasDebt[mapId_] - 1 << 0
                        // (tick == -1) tickHasDebt[mapId_] - 1 << 255
                        // (tick == -256) tickHasDebt[mapId_] - 1 << 0
                        uint position_ = uint(tick_ - (mapId_ * 256));
                        tickHasDebt[mapId_] = addOrRemove_
                            ? tickHasDebt[mapId_] | (1 << position_)
                            : tickHasDebt[mapId_] & ~(1 << position_);
                    }
                }
                /// @dev gets next perfect top tick (tick which is not liquidated)
                /// @param topTick_ current top tick which will no longer be top tick
                /// @return nextTick_ next top tick which will become the new top tick
                function _fetchNextTopTick(int topTick_) internal view returns (int nextTick_) {
                    int mapId_;
                    uint tickHasDebt_;
                    unchecked {
                        mapId_ = topTick_ < 0 ? ((topTick_ + 1) / 256) - 1 : topTick_ / 256;
                        uint bitsToRemove_ = uint(-topTick_ + (mapId_ * 256 + 256));
                        // Removing current top tick from tickHasDebt
                        tickHasDebt_ = (tickHasDebt[mapId_] << bitsToRemove_) >> bitsToRemove_;
                        // For last user remaining in vault there could be a lot of iterations in the while loop.
                        // Chances of this to happen is extremely low (like ~0%)
                        while (true) {
                            if (tickHasDebt_ > 0) {
                                nextTick_ = mapId_ * 256 + int(tickHasDebt_.mostSignificantBit()) - 1;
                                break;
                            }
                            // Reducing mapId_ by 1 in every loop; if it reaches to -129 then no filled tick exist, meaning it's the last tick
                            if (--mapId_ == -129) {
                                nextTick_ = type(int).min;
                                break;
                            }
                            tickHasDebt_ = tickHasDebt[mapId_];
                        }
                    }
                }
                /// @dev adding debt to a particular tick
                /// @param totalColRaw_ total raw collateral of position
                /// @param netDebtRaw_ net raw debt (total debt - dust debt)
                /// @return tick_ tick where the debt is being added
                /// @return tickId_ tick current id
                /// @return userRawDebt_ user's total raw debt
                /// @return rawDust_ dust debt used for adjustment
                function _addDebtToTickWrite(
                    uint256 totalColRaw_,
                    uint256 netDebtRaw_ // debtRaw - dust
                ) internal returns (int256 tick_, uint256 tickId_, uint256 userRawDebt_, uint256 rawDust_) {
                    if (netDebtRaw_ < 10000) {
                        // thrown if user's debt is too low
                        revert FluidVaultError(ErrorTypes.VaultT1__UserDebtTooLow);
                    }
                    // tick_ & ratio_ returned from library is round down. Hence increasing it by 1 and increasing ratio by 1 tick.
                    uint ratio_ = (netDebtRaw_ * TickMath.ZERO_TICK_SCALED_RATIO) / totalColRaw_;
                    (tick_, ratio_) = TickMath.getTickAtRatio(ratio_);
                    unchecked {
                        ++tick_;
                        ratio_ = (ratio_ * 10015) / 10000;
                    }
                    userRawDebt_ = (ratio_ * totalColRaw_) >> 96;
                    rawDust_ = userRawDebt_ - netDebtRaw_;
                    // Current state of tick
                    uint256 tickData_ = tickData[tick_];
                    tickId_ = (tickData_ >> 1) & X24;
                    uint tickNewDebt_;
                    if (tickId_ > 0 && tickData_ & 1 == 0) {
                        // Current debt in the tick
                        uint256 tickExistingRawDebt_ = (tickData_ >> 25) & X64;
                        tickExistingRawDebt_ = (tickExistingRawDebt_ >> 8) << (tickExistingRawDebt_ & X8);
                        // Tick's already initialized and not liquidated. Hence simply add the debt
                        tickNewDebt_ = tickExistingRawDebt_ + userRawDebt_;
                        if (tickExistingRawDebt_ == 0) {
                            // Adding tick into tickHasDebt
                            _updateTickHasDebt(tick_, true);
                        }
                    } else {
                        // Liquidation happened or tick getting initialized for the very first time.
                        if (tickId_ > 0) {
                            // Meaning a liquidation happened. Hence move the data to tickID
                            unchecked {
                                uint tickMap_ = (tickId_ + 2) / 3;
                                // Adding 2 in ID so we can get right mapping ID. For example for ID 1, 2 & 3 mapping should be 1 and so on..
                                // For example shift for id 1 should be 0, for id 2 should be 85, for id 3 it should be 170 and so on..
                                tickId[tick_][tickMap_] =
                                    tickId[tick_][tickMap_] |
                                    ((tickData_ >> 25) << (((tickId_ + 2) % 3) * 85));
                            }
                        }
                        // Increasing total ID by one
                        unchecked {
                            ++tickId_;
                        }
                        tickNewDebt_ = userRawDebt_;
                        // Adding tick into tickHasDebt
                        _updateTickHasDebt(tick_, true);
                    }
                    if (tickNewDebt_ < 10000) {
                        // thrown if tick's debt/liquidity is too low
                        revert FluidVaultError(ErrorTypes.VaultT1__TickDebtTooLow);
                    }
                    tickData[tick_] = (tickId_ << 1) | (tickNewDebt_.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 25);
                }
                /// @dev sets new top tick. If it comes to this function then that means current top tick is perfect tick.
                /// if next top tick is liquidated then unitializes the current non liquidated branch and make the liquidated branch as current branch
                /// @param topTick_ current top tick
                /// @param vaultVariables_ vaultVariables of storage but with newer updates
                /// @return newVaultVariables_ newVaultVariables_ updated vault variable internally to this function
                /// @return newTopTick_ new top tick
                function _setNewTopTick(
                    int topTick_,
                    uint vaultVariables_
                ) internal returns (uint newVaultVariables_, int newTopTick_) {
                    // This function considers that the current top tick was not liquidated
                    // Overall flow of function:
                    // if new top tick liquidated (aka base branch's minima tick) -> Close the current branch and make base branch as current branch
                    // if new top tick not liquidated -> update things in current branch.
                    // if new top tick is not liquidated and same tick exist in base branch then tick is considered as not liquidated.
                    uint branchId_ = (vaultVariables_ >> 22) & X30; // branch id of current branch
                    uint256 branchData_ = branchData[branchId_];
                    int256 baseBranchMinimaTick_;
                    if ((branchData_ >> 196) & 1 == 1) {
                        baseBranchMinimaTick_ = int((branchData_ >> 197) & X19);
                    } else {
                        unchecked {
                            baseBranchMinimaTick_ = -int((branchData_ >> 197) & X19);
                        }
                        if (baseBranchMinimaTick_ == 0) {
                            // meaning the current branch is the master branch
                            baseBranchMinimaTick_ = type(int).min;
                        }
                    }
                    // Returns type(int).min if no top tick exist
                    int nextTopTickNotLiquidated_ = _fetchNextTopTick(topTick_);
                    newTopTick_ = baseBranchMinimaTick_ > nextTopTickNotLiquidated_
                        ? baseBranchMinimaTick_
                        : nextTopTickNotLiquidated_;
                    if (newTopTick_ == type(int).min) {
                        // if this happens that means this was the last user of the vault :(
                        vaultVariables_ = vaultVariables_ & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00001;
                    } else if (newTopTick_ == nextTopTickNotLiquidated_) {
                        // New top tick exist in current non liquidated branch
                        if (newTopTick_ < 0) {
                            unchecked {
                                vaultVariables_ =
                                    (vaultVariables_ & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00001) |
                                    (uint(-newTopTick_) << 3);
                            }
                        } else {
                            vaultVariables_ =
                                (vaultVariables_ & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00001) |
                                4 | // setting top tick as positive
                                (uint(newTopTick_) << 3);
                        }
                    } else {
                        // if this happens that means base branch exists & is the next top tick
                        // Remove current non liquidated branch as active.
                        // Not deleting here as it's going to get initialize again whenever a new top tick comes
                        branchData[branchId_] = 0;
                        // Inserting liquidated branch's minima tick
                        unchecked {
                            vaultVariables_ =
                                (vaultVariables_ & 0xfffffffffffffffffffffffffffffffffffffffffffc00000000000000000001) |
                                2 | // Setting top tick as liquidated
                                (((branchData_ >> 196) & X20) << 2) | // new current top tick = base branch minima tick
                                (((branchData_ >> 166) & X30) << 22) | // new current branch id = base branch id
                                ((branchId_ - 1) << 52); // reduce total branch id by 1
                        }
                    }
                    newVaultVariables_ = vaultVariables_;
                }
                constructor(ConstantViews memory constants_) ConstantVariables(constants_) {}
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            import { IFluidOracle } from "../../../../oracle/fluidOracle.sol";
            import { TickMath } from "../../../../libraries/tickMath.sol";
            import { BigMathMinified } from "../../../../libraries/bigMathMinified.sol";
            import { BigMathVault } from "../../../../libraries/bigMathVault.sol";
            import { LiquidityCalcs } from "../../../../libraries/liquidityCalcs.sol";
            import { SafeTransfer } from "../../../../libraries/safeTransfer.sol";
            import { Helpers } from "./helpers.sol";
            import { LiquiditySlotsLink } from "../../../../libraries/liquiditySlotsLink.sol";
            import { ErrorTypes } from "../../errorTypes.sol";
            /// @notice Fluid "VaultT1" (Vault Type 1). Fluid vault protocol main contract.
            ///         Fluid Vault protocol is a borrow / lending protocol, allowing users to create collateral / borrow positions.
            ///         All funds are deposited into / borrowed from Fluid Liquidity layer.
            ///         Positions are represented through NFTs minted by the VaultFactory.
            ///         Deployed by "VaultFactory" and linked together with VaultT1 AdminModule `ADMIN_IMPLEMENTATION` and
            ///         FluidVaultT1Secondary (main2.sol) `SECONDARY_IMPLEMENTATION`.
            ///         AdminModule & FluidVaultT1Secondary methods are delegateCalled, if the msg.sender has the required authorization.
            ///         This contract links to an Oracle, which is used to assess collateral / debt value. Oracles implement the
            ///         "FluidOracle" base contract and return the price in 1e27 precision.
            /// @dev    For view methods / accessing data, use the "VaultResolver" periphery contract.
            //
            // vaults can only be deployed for tokens that are listed at Liquidity (constructor reverts otherwise
            // if either the exchange price for the supply token or the borrow token is still not set at Liquidity).
            contract FluidVaultT1 is Helpers {
                using BigMathMinified for uint256;
                using BigMathVault for uint256;
                /// @dev Single function which handles supply, withdraw, borrow & payback
                /// @param nftId_ NFT ID for interaction. If 0 then create new NFT/position.
                /// @param newCol_ new collateral. If positive then deposit, if negative then withdraw, if 0 then do nohing
                /// @param newDebt_ new debt. If positive then borrow, if negative then payback, if 0 then do nohing
                /// @param to_ address where withdraw or borrow should go. If address(0) then msg.sender
                /// @return nftId_ if 0 then this returns the newly created NFT Id else returns the same NFT ID
                /// @return newCol_ final supply amount. Mainly if max withdraw using type(int).min then this is useful to get perfect amount else remain same as newCol_
                /// @return newDebt_ final borrow amount. Mainly if max payback using type(int).min then this is useful to get perfect amount else remain same as newDebt_
                function operate(
                    uint256 nftId_, // if 0 then new position
                    int256 newCol_, // if negative then withdraw
                    int256 newDebt_, // if negative then payback
                    address to_ // address at which the borrow & withdraw amount should go to. If address(0) then it'll go to msg.sender
                )
                    public
                    payable
                    returns (
                        uint256, // nftId_
                        int256, // final supply amount. if - then withdraw
                        int256 // final borrow amount. if - then payback
                    )
                {
                    uint256 vaultVariables_ = vaultVariables;
                    // re-entrancy check
                    if (vaultVariables_ & 1 == 0) {
                        // Updating on storage
                        vaultVariables = vaultVariables_ | 1;
                    } else {
                        revert FluidVaultError(ErrorTypes.VaultT1__AlreadyEntered);
                    }
                    if (
                        (newCol_ == 0 && newDebt_ == 0) ||
                        // withdrawal or deposit cannot be too small
                        ((newCol_ != 0) && (newCol_ > -10000 && newCol_ < 10000)) ||
                        // borrow or payback cannot be too small
                        ((newDebt_ != 0) && (newDebt_ > -10000 && newDebt_ < 10000))
                    ) {
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidOperateAmount);
                    }
                    // Check msg.value aligns with input amounts if supply or borrow token is native token.
                    // Note that it's not possible for a vault to have both supply token and borrow token as native token.
                    if (SUPPLY_TOKEN == NATIVE_TOKEN && newCol_ > 0) {
                        if (uint(newCol_) != msg.value) {
                            revert FluidVaultError(ErrorTypes.VaultT1__InvalidMsgValueOperate);
                        }
                    } else if (msg.value > 0) {
                        if (!(BORROW_TOKEN == NATIVE_TOKEN && newDebt_ < 0)) {
                            // msg.value sent along for withdraw, borrow, or non-native token operations
                            revert FluidVaultError(ErrorTypes.VaultT1__InvalidMsgValueOperate);
                        }
                    }
                    OperateMemoryVars memory o_;
                    // Temporary variables used as helpers at many places
                    uint256 temp_;
                    uint256 temp2_;
                    int256 temp3_;
                    o_.vaultVariables2 = vaultVariables2;
                    temp_ = (vaultVariables_ >> 2) & X20;
                    unchecked {
                        o_.topTick = (temp_ == 0) ? type(int).min : ((temp_ & 1) == 1)
                            ? int((temp_ >> 1) & X19)
                            : -int((temp_ >> 1) & X19);
                    }
                    {
                        // Fetching user's position
                        if (nftId_ == 0) {
                            // creating new position.
                            o_.tick = type(int).min;
                            // minting new NFT vault for user.
                            nftId_ = VAULT_FACTORY.mint(VAULT_ID, msg.sender);
                            // Adding 1 in total positions. Total positions cannot exceed 32bits as NFT minting checks for that
                            unchecked {
                                vaultVariables_ = vaultVariables_ + (1 << 210);
                            }
                        } else {
                            // Updating existing position
                            // checking owner only in case of withdraw or borrow
                            if ((newCol_ < 0 || newDebt_ > 0) && (VAULT_FACTORY.ownerOf(nftId_) != msg.sender)) {
                                revert FluidVaultError(ErrorTypes.VaultT1__NotAnOwner);
                            }
                            // temp_ => user's position data
                            temp_ = positionData[nftId_];
                            if (temp_ == 0) {
                                revert FluidVaultError(ErrorTypes.VaultT1__NftNotOfThisVault);
                            }
                            // temp2_ => user's supply amount
                            temp2_ = (temp_ >> 45) & X64;
                            // Converting big number into normal number
                            o_.colRaw = (temp2_ >> 8) << (temp2_ & X8);
                            // temp2_ => user's  dust debt amount
                            temp2_ = (temp_ >> 109) & X64;
                            // Converting big number into normal number
                            o_.dustDebtRaw = (temp2_ >> 8) << (temp2_ & X8);
                            // 1 is supply & 0 is borrow
                            if (temp_ & 1 == 1) {
                                // only supply position (has no debt)
                                o_.tick = type(int).min;
                            } else {
                                // borrow position (has collateral & debt)
                                unchecked {
                                    o_.tick = temp_ & 2 == 2 ? int((temp_ >> 2) & X19) : -int((temp_ >> 2) & X19);
                                }
                                o_.tickId = (temp_ >> 21) & X24;
                            }
                        }
                    }
                    // Get latest updated Position's debt & supply (if position is with debt -> not new / supply position)
                    if (o_.tick > type(int).min) {
                        // if entering this if statement then temp_ here will always be user's position data
                        // extracting collateral exponent
                        temp_ = (temp_ >> 45) & X8;
                        // if exponent is > 0 then rounding up the collateral just for calculating debt
                        unchecked {
                            temp_ = temp_ == 0 ? (o_.colRaw + 1) : o_.colRaw + (1 << temp_);
                        }
                        // fetch current debt
                        o_.debtRaw = ((TickMath.getRatioAtTick(int24(o_.tick)) * temp_) >> 96) + 1;
                        // Tick data from user's tick
                        temp_ = tickData[o_.tick];
                        // Checking if tick is liquidated (first bit 1) OR if the total IDs of tick is greater than user's tick ID
                        if (((temp_ & 1) == 1) || (((temp_ >> 1) & X24) > o_.tickId)) {
                            // User got liquidated
                            (
                                // returns the position of the user if the user got liquidated.
                                o_.tick,
                                o_.debtRaw,
                                o_.colRaw,
                                temp2_, // final branchId from liquidation where position exist right now
                                o_.branchData
                            ) = fetchLatestPosition(o_.tick, o_.tickId, o_.debtRaw, temp_);
                            if (o_.debtRaw > o_.dustDebtRaw) {
                                // temp_ => branch's Debt
                                temp_ = (o_.branchData >> 52) & X64;
                                temp_ = (temp_ >> 8) << (temp_ & X8);
                                // o_.debtRaw should always be < branch's Debt (temp_).
                                // Taking margin (0.01%) in fetchLatestPosition to make sure it's always less
                                temp_ -= o_.debtRaw;
                                if (temp_ < 100) {
                                    // explicitly making sure that branch debt/liquidity doesn't get super low.
                                    temp_ = 100;
                                }
                                // Inserting updated branch's debt
                                branchData[temp2_] =
                                    (o_.branchData & 0xfffffffffffffffffffffffffffffffffff0000000000000000fffffffffffff) |
                                    (temp_.toBigNumber(56, 8, BigMathMinified.ROUND_UP) << 52);
                                unchecked {
                                    // Converted positionRawDebt_ in net position debt
                                    o_.debtRaw -= o_.dustDebtRaw;
                                }
                            } else {
                                // Liquidated 100% or almost 100%
                                // absorbing dust debt
                                absorbedDustDebt = absorbedDustDebt + o_.dustDebtRaw - o_.debtRaw;
                                o_.debtRaw = 0;
                                o_.colRaw = 0;
                            }
                        } else {
                            // User didn't got liquidated
                            // Removing user's debt from tick data
                            // temp2_ => debt in tick
                            temp2_ = (temp_ >> 25) & X64;
                            // below require can fail when a user liquidity is extremely low (talking about way less than even $1)
                            // adding require meaning this vault user won't be able to interact unless someone makes the liquidity in tick as non 0.
                            // reason of adding is the tick has already removed from everywhere. Can removing it again break something? Better to simply remove that case entirely
                            if (temp2_ == 0) {
                                revert FluidVaultError(ErrorTypes.VaultT1__TickIsEmpty);
                            }
                            // Converting big number into normal number
                            temp2_ = (temp2_ >> 8) << (temp2_ & X8);
                            // debtInTick (temp2_) < debtToRemove (o_.debtRaw) that means minor precision error. Hence make the debtInTick as 0.
                            // The precision error can be caused with Bigmath library limiting the precision to 2**56.
                            unchecked {
                                temp2_ = o_.debtRaw < temp2_ ? temp2_ - o_.debtRaw : 0;
                            }
                            if (temp2_ < 10000) {
                                temp2_ = 0;
                                // if debt becomes 0 then remove from tick has debt
                                if (o_.tick == o_.topTick) {
                                    // if tick is top tick then current top tick is perfect tick -> fetch & set new top tick
                                    // Updating new top tick in vaultVariables_ and topTick_
                                    (vaultVariables_, o_.topTick) = _setNewTopTick(o_.topTick, vaultVariables_);
                                }
                                // Removing from tickHasDebt
                                _updateTickHasDebt(o_.tick, false);
                            }
                            tickData[o_.tick] = (temp_ & X25) | (temp2_.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 25);
                            // Converted positionRawDebt_ in net position debt
                            o_.debtRaw -= o_.dustDebtRaw;
                        }
                        o_.dustDebtRaw = 0;
                    }
                    // Setting the current tick into old tick as the position tick is going to change now.
                    o_.oldTick = o_.tick;
                    o_.oldColRaw = o_.colRaw;
                    o_.oldNetDebtRaw = o_.debtRaw;
                    {
                        (o_.liquidityExPrice, , o_.supplyExPrice, o_.borrowExPrice) = updateExchangePrices(o_.vaultVariables2);
                        {
                            // supply or withdraw
                            if (newCol_ > 0) {
                                // supply new col, rounding down
                                o_.colRaw += (uint256(newCol_) * EXCHANGE_PRICES_PRECISION) / o_.supplyExPrice;
                                // final user's collateral should not be above 2**128 bits
                                if (o_.colRaw > X128) {
                                    revert FluidVaultError(ErrorTypes.VaultT1__UserCollateralDebtExceed);
                                }
                            } else if (newCol_ < 0) {
                                // if withdraw equals type(int).min then max withdraw
                                if (newCol_ > type(int128).min) {
                                    // partial withdraw, rounding up removing extra wei from collateral
                                    temp3_ = ((newCol_ * int(EXCHANGE_PRICES_PRECISION)) / int256(o_.supplyExPrice)) - 1;
                                    unchecked {
                                        if (uint256(-temp3_) > o_.colRaw) {
                                            revert FluidVaultError(ErrorTypes.VaultT1__ExcessCollateralWithdrawal);
                                        }
                                        o_.colRaw -= uint256(-temp3_);
                                    }
                                } else if (newCol_ == type(int).min) {
                                    // max withdraw, rounding up:
                                    // adding +1 to negative withdrawAmount newCol_ for safe rounding (reducing withdraw)
                                    newCol_ = -(int256((o_.colRaw * o_.supplyExPrice) / EXCHANGE_PRICES_PRECISION)) + 1;
                                    o_.colRaw = 0;
                                } else {
                                    revert FluidVaultError(ErrorTypes.VaultT1__UserCollateralDebtExceed);
                                }
                            }
                        }
                        {
                            // borrow or payback
                            if (newDebt_ > 0) {
                                // borrow new debt, rounding up adding extra wei in debt
                                temp_ = ((uint(newDebt_) * EXCHANGE_PRICES_PRECISION) / o_.borrowExPrice) + 1;
                                // if borrow fee is 0 then it'll become temp_ + 0.
                                // Only adding fee in o_.debtRaw and not in newDebt_ as newDebt_ is debt that needs to be borrowed from Liquidity
                                // as we have added fee in debtRaw hence it will get added in user's position & vault's total borrow.
                                // It can be collected with rebalance function.
                                o_.debtRaw += temp_ + (temp_ * ((o_.vaultVariables2 >> 82) & X10)) / 10000;
                                // final user's debt should not be above 2**128 bits
                                if (o_.debtRaw > X128) {
                                    revert FluidVaultError(ErrorTypes.VaultT1__UserCollateralDebtExceed);
                                }
                            } else if (newDebt_ < 0) {
                                // if payback equals type(int).min then max payback
                                if (newDebt_ > type(int128).min) {
                                    // partial payback.
                                    // temp3_ => newDebt_ in raw terms, safe rounding up negative amount to rounding reduce payback
                                    temp3_ = (newDebt_ * int256(EXCHANGE_PRICES_PRECISION)) / int256(o_.borrowExPrice) + 1;
                                    unchecked {
                                        temp3_ = -temp3_;
                                        if (uint256(temp3_) > o_.debtRaw) {
                                            revert FluidVaultError(ErrorTypes.VaultT1__ExcessDebtPayback);
                                        }
                                        o_.debtRaw -= uint256(temp3_);
                                    }
                                } else if (newDebt_ == type(int).min) {
                                    // max payback, rounding up amount that will be transferred in to pay back full debt:
                                    // subtracting -1 of negative debtAmount newDebt_ for safe rounding (increasing payback)
                                    newDebt_ = -(int256((o_.debtRaw * o_.borrowExPrice) / EXCHANGE_PRICES_PRECISION)) - 1;
                                    o_.debtRaw = 0;
                                } else {
                                    revert FluidVaultError(ErrorTypes.VaultT1__UserCollateralDebtExceed);
                                }
                            }
                        }
                    }
                    // if position has no collateral or debt and user sends type(int).min for withdraw and payback then this results in 0
                    // there's is no issue if it stays 0 but better to throw here to avoid checking for potential issues if there could be
                    if (newCol_ == 0 && newDebt_ == 0) {
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidOperateAmount);
                    }
                    // Assign new tick
                    if (o_.debtRaw > 0) {
                        // updating tickHasDebt in the below function if required
                        // o_.debtRaw here is updated to new debt raw incl. dust debt (not net debt)
                        unchecked {
                            (o_.tick, o_.tickId, o_.debtRaw, o_.dustDebtRaw) = _addDebtToTickWrite(
                                o_.colRaw,
                                ((o_.debtRaw * 1000000001) / 1000000000) + 1
                            );
                        }
                        if (newDebt_ < 0) {
                            // anyone can payback debt of any position
                            // hence, explicitly checking the debt should decrease
                            if ((o_.debtRaw - o_.dustDebtRaw) > o_.oldNetDebtRaw) {
                                revert FluidVaultError(ErrorTypes.VaultT1__InvalidPaybackOrDeposit);
                            }
                        }
                        if ((newCol_ > 0) && (newDebt_ == 0)) {
                            // anyone can deposit collateral in any position
                            // Hence, explicitly checking that new ratio should be less than old ratio
                            if (
                                (((o_.debtRaw - o_.dustDebtRaw) * TickMath.ZERO_TICK_SCALED_RATIO) / o_.colRaw) >
                                ((o_.oldNetDebtRaw * TickMath.ZERO_TICK_SCALED_RATIO) / o_.oldColRaw)
                            ) {
                                revert FluidVaultError(ErrorTypes.VaultT1__InvalidPaybackOrDeposit);
                            }
                        }
                        if (o_.tick >= o_.topTick) {
                            // Updating topTick in storage
                            // temp_ => tick to insert in vault variables
                            unchecked {
                                temp_ = o_.tick < 0 ? uint(-o_.tick) << 1 : (uint(o_.tick) << 1) | 1;
                            }
                            if (vaultVariables_ & 2 == 0) {
                                // Current branch not liquidated. Hence, just update top tick
                                vaultVariables_ =
                                    (vaultVariables_ & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000) |
                                    (temp_ << 2);
                            } else {
                                // Current branch liquidated
                                // Initialize a new branch
                                // temp2_ => totalBranchId_
                                unchecked {
                                    temp2_ = ((vaultVariables_ >> 52) & X30) + 1; // would take 34 years to overflow if a new branch is created every second
                                }
                                // Connecting new active branch with current active branch which is now base branch
                                // Current top tick is now base branch's minima tick
                                branchData[temp2_] =
                                    (((vaultVariables_ >> 22) & X30) << 166) | // current branch id set as base branch id
                                    (((vaultVariables_ >> 2) & X20) << 196); // current top tick set as base branch minima tick
                                // Updating new vault variables in memory with new branch
                                vaultVariables_ =
                                    (vaultVariables_ & 0xfffffffffffffffffffffffffffffffffffffffffffc00000000000000000000) |
                                    (temp_ << 2) | // new top tick
                                    (temp2_ << 22) | // new branch id
                                    (temp2_ << 52); // total branch ids
                            }
                        }
                    } else {
                        // debtRaw_ remains 0 in this situation
                        // This kind of position will not have any tick. Meaning it'll be a supply position.
                        o_.tick = type(int).min;
                    }
                    {
                        if (newCol_ < 0 || newDebt_ > 0) {
                            // withdraw or borrow
                            if (to_ == address(0)) {
                                to_ = msg.sender;
                            }
                            unchecked {
                                // if debt is greater than 0 & transaction includes borrow or withdraw (incl. combinations such as deposit + borrow etc.)
                                // -> check collateral factor
                                // calc for net debt can be unchecked as o_.dustDebtRaw can not be > o_.debtRaw:
                                // o_.dustDebtRaw is the result of o_.debtRaw - x where x > 0 see _addDebtToTickWrite()
                                // Only fetch oracle if position is getting riskier or if borrowing is involved
                                // if user is withdrawing and paying back in the same transaction such that the final ratio
                                // is lower than initial then as well no need to check oracle aka user is doing payback & withdraw or deleverage
                                if (o_.debtRaw > 0 && (
                                        o_.oldTick <= o_.tick ||
                                        (o_.debtRaw - o_.dustDebtRaw) > (((o_.oldNetDebtRaw * 1000000001) / 1000000000) + 1)
                                    )
                                ) {
                                    // Oracle returns price at 100% ratio.
                                    // converting oracle 160 bits into oracle address
                                    // temp_ => debt price w.r.t to col in 1e27
                                    temp_ = IFluidOracle(address(uint160(o_.vaultVariables2 >> 96))).getExchangeRateOperate();
                                    // Note if price would come back as 0 `getTickAtRatio` will fail
                                    // reverting if oracle price is too high or lower than 1e9 to avoid precision issues
                                    if (temp_ > 1e54 || temp_ < 1e9) {
                                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidOraclePrice);
                                    }
                                    // Converting price in terms of raw amounts
                                    temp_ = (temp_ * o_.supplyExPrice) / o_.borrowExPrice;
                                    // capping oracle pricing to 1e45 (#487RGF783GF: id reference for other similar cases in codebase)
                                    // This means we are restricting collateral price to never go above 1e45
                                    // Above 1e45 precisions gets too low for calculations
                                    // This can will never happen for all good token pairs (for example, WBTC/DAI pair when WBTC price is $1M, oracle price will come as 1e43)
                                    // Restricting oracle price doesn't pose any risk to protocol as we are capping collateral price, meaning if price is above 1e45
                                    // user is simply not able to borrow more
                                    if (temp_ > 1e45) {
                                        temp_ = 1e45;
                                    }
                                    // temp2_ => ratio at CF. CF is in 3 decimals. 900 = 90%
                                    temp2_ = ((temp_ * ((o_.vaultVariables2 >> 32) & X10)) / 1000);
                                    // Price from oracle is in 1e27 decimals. Converting it into (1 << 96) decimals
                                    temp2_ = ((temp2_ * TickMath.ZERO_TICK_SCALED_RATIO) / 1e27);
                                    // temp3_ => tickAtCF_
                                    (temp3_, ) = TickMath.getTickAtRatio(temp2_);
                                    if (o_.tick > temp3_) {
                                        // Above CF, user should only be allowed to reduce ratio either by paying debt or by depositing more collateral
                                        // Not comparing collateral as user can potentially use safe/deleverage to reduce tick & debt.
                                        // On use of safe/deleverage, collateral will decrease but debt will decrease as well making the overall position safer.
                                        revert FluidVaultError(ErrorTypes.VaultT1__PositionAboveCF);
                                    }
                                }
                            }
                        }
                    }
                    {
                        // Updating user's new position on storage
                        // temp_ => tick to insert as user position tick
                        if (o_.tick > type(int).min) {
                            unchecked {
                                temp_ = o_.tick < 0 ? (uint(-o_.tick) << 1) : ((uint(o_.tick) << 1) | 1);
                            }
                        } else {
                            // if positionTick_ = type(int).min OR positionRawDebt_ == 0 then that means it's only supply position
                            // (for case of positionRawDebt_ == 0, tick is set to type(int).min further up)
                            temp_ = 0;
                        }
                        positionData[nftId_] =
                            ((temp_ == 0) ? 1 : 0) | // setting if supply only position (1) or not (first bit)
                            (temp_ << 1) |
                            (o_.tickId << 21) |
                            (o_.colRaw.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 45) |
                            // dust debt is rounded down because user debt = debt - dustDebt. rounding up would mean we reduce user debt
                            (o_.dustDebtRaw.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 109);
                    }
                    // Withdrawal gap to make sure there's always liquidity for liquidation
                    // For example if withdrawal allowance is 15% on liquidity then we can limit operate's withdrawal allowance to 10%
                    // this will allow liquidate function to get extra 5% buffer for potential liquidations.
                    if (newCol_ < 0) {
                        // extracting withdrawal gap which is in 0.1% precision.
                        temp_ = (o_.vaultVariables2 >> 62) & X10;
                        if (temp_ > 0) {
                            // fetching user's supply slot data
                            o_.userSupplyLiquidityData = LIQUIDITY.readFromStorage(LIQUIDITY_USER_SUPPLY_SLOT);
                            // converting current user's supply from big number to normal
                            temp2_ = (o_.userSupplyLiquidityData >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64;
                            temp2_ = (temp2_ >> 8) << (temp2_ & X8);
                            // fetching liquidity's withdrawal limit
                            temp3_ = int(LiquidityCalcs.calcWithdrawalLimitBeforeOperate(o_.userSupplyLiquidityData, temp2_));
                            // max the number could go is vault's supply * 1000. Overflowing is almost impossible.
                            unchecked {
                                // (liquidityUserSupply - withdrawalGap - liquidityWithdrawaLimit) should be less than user's withdrawal
                                if (
                                    (temp3_ > 0) &&
                                    (((int(temp2_ * (1000 - temp_)) / 1000)) - temp3_) <
                                    (((-newCol_) * int(EXCHANGE_PRICES_PRECISION)) / int(o_.liquidityExPrice))
                                ) {
                                    revert FluidVaultError(ErrorTypes.VaultT1__WithdrawMoreThanOperateLimit);
                                }
                            }
                        }
                    }
                    {
                        // execute actions at Liquidity: deposit & payback is first and then withdraw & borrow
                        if (newCol_ > 0) {
                            // deposit
                            LIQUIDITY.operate{ value: SUPPLY_TOKEN == NATIVE_TOKEN ? msg.value : 0 }(
                                SUPPLY_TOKEN,
                                newCol_,
                                0,
                                address(0),
                                address(0),
                                abi.encode(msg.sender)
                            );
                        }
                        if (newDebt_ < 0) {
                            if (BORROW_TOKEN == NATIVE_TOKEN) {
                                unchecked {
                                    temp_ = uint(-newDebt_);
                                    if (msg.value > temp_) {
                                        SafeTransfer.safeTransferNative(msg.sender, msg.value - temp_);
                                    } else if (msg.value < temp_) {
                                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidMsgValueOperate);
                                    }
                                }
                            } else {
                                temp_ = 0;
                            }
                            // payback
                            LIQUIDITY.operate{ value: temp_ }(
                                BORROW_TOKEN,
                                0,
                                newDebt_,
                                address(0),
                                address(0),
                                abi.encode(msg.sender)
                            );
                        }
                        if (newCol_ < 0) {
                            // withdraw
                            LIQUIDITY.operate(SUPPLY_TOKEN, newCol_, 0, to_, address(0), new bytes(0));
                        }
                        if (newDebt_ > 0) {
                            // borrow
                            LIQUIDITY.operate(BORROW_TOKEN, 0, newDebt_, address(0), to_, new bytes(0));
                        }
                    }
                    {
                        // Updating vault variables on storage
                        // Calculating new total collateral & total debt.
                        temp_ = (vaultVariables_ >> 82) & X64;
                        temp_ = ((temp_ >> 8) << (temp_ & X8)) + o_.colRaw - o_.oldColRaw;
                        temp2_ = (vaultVariables_ >> 146) & X64;
                        temp2_ = ((temp2_ >> 8) << (temp2_ & X8)) + (o_.debtRaw - o_.dustDebtRaw) - o_.oldNetDebtRaw;
                        // Updating vault variables on storage. This will also reentrancy 0 back again
                        // Converting total supply & total borrow in 64 bits (56 | 8) bignumber
                        vaultVariables =
                            (vaultVariables_ & 0xfffffffffffc00000000000000000000000000000003ffffffffffffffffffff) |
                            (temp_.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 82) | // total supply
                            (temp2_.toBigNumber(56, 8, BigMathMinified.ROUND_UP) << 146); // total borrow
                    }
                    emit LogOperate(msg.sender, nftId_, newCol_, newDebt_, to_);
                    return (nftId_, newCol_, newDebt_);
                }
                /// @dev allows to liquidate all bad debt of all users at once. Liquidator can also liquidate partially any amount they want.
                /// @param debtAmt_ total debt to liquidate (aka debt token to swap into collateral token)
                /// @param colPerUnitDebt_ minimum collateral token per unit of debt in 1e18 decimals
                /// @param to_ address at which collateral token should go to.
                ///            If dead address (0x000000000000000000000000000000000000dEaD) then reverts with custom error "FluidLiquidateResult"
                ///            returning the actual collateral and actual debt liquidated. Useful to find max liquidatable amounts via try / catch.
                /// @param absorb_ if true then liquidate from absorbed first
                /// @return actualDebtAmt_ if liquidator sends debtAmt_ more than debt remaining to liquidate then actualDebtAmt_ changes from debtAmt_ else remains same
                /// @return actualColAmt_ total liquidated collateral which liquidator will get
                function liquidate(
                    uint256 debtAmt_,
                    uint256 colPerUnitDebt_, // min collateral needed per unit of debt in 1e18
                    address to_,
                    bool absorb_
                ) public payable returns (uint actualDebtAmt_, uint actualColAmt_) {
                    LiquidateMemoryVars memory memoryVars_;
                    uint vaultVariables_ = vaultVariables;
                    // ############# turning re-entrancy bit on #############
                    if (vaultVariables_ & 1 == 0) {
                        // Updating on storage
                        vaultVariables = vaultVariables_ | 1;
                    } else {
                        revert FluidVaultError(ErrorTypes.VaultT1__AlreadyEntered);
                    }
                    if (BORROW_TOKEN == NATIVE_TOKEN) {
                        if ((msg.value != debtAmt_) && (to_ != 0x000000000000000000000000000000000000dEaD)) {
                            revert FluidVaultError(ErrorTypes.VaultT1__InvalidMsgValueLiquidate);
                        }
                    } else if (msg.value > 0) {
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidMsgValueLiquidate);
                    }
                    memoryVars_.vaultVariables2 = vaultVariables2;
                    if (((vaultVariables_ >> 2) & X20) == 0) {
                        revert FluidVaultError(ErrorTypes.VaultT1__TopTickDoesNotExist);
                    }
                    // Below are exchange prices of vaults
                    (, , memoryVars_.supplyExPrice, memoryVars_.borrowExPrice) = updateExchangePrices(memoryVars_.vaultVariables2);
                    CurrentLiquidity memory currentData_;
                    BranchData memory branch_;
                    // Temporary holder variables, used many times for different small things
                    uint temp_;
                    uint temp2_;
                    {
                        // ############# Oracle related stuff #############
                        // Col price w.r.t debt. For example: 1 ETH = 1000 DAI
                        // temp_ -> debtPerCol
                        temp_ = IFluidOracle(address(uint160(memoryVars_.vaultVariables2 >> 96))).getExchangeRateLiquidate(); // Price in 27 decimals
                        // not reverting if oracle price is lower than 1e9 as it can pause potential liquidation in this edge case situations
                        if (temp_ > 1e54 || temp_ == 0) {
                            revert FluidVaultError(ErrorTypes.VaultT1__InvalidOraclePrice);
                        }
                        unchecked {
                            // temp_ -> debtPerCol Converting in terms of raw amount
                            temp_ = (temp_ * memoryVars_.supplyExPrice) / memoryVars_.borrowExPrice;
                            // capping oracle pricing to 1e45
                            // Reason mentioned at (search: #487RGF783GF)
                            if (temp_ > 1e45) {
                                temp_ = 1e45;
                            }
                            // temp2_ -> Raw colPerDebt_ in 27 decimals
                            temp2_ = 1e54 / temp_;
                            // temp2_ can never be > 1e54
                            // Oracle price should never be > 1e54
                            // Liquidation penalty in 4 decimals (1e2 = 1%) (max: 10.23%) -> (vaultVariables2_ >> 72) & X10
                            currentData_.colPerDebt = (temp2_ * (10000 + ((memoryVars_.vaultVariables2 >> 72) & X10))) / 10000;
                            // get liquidiation tick (tick at liquidation threshold ratio)
                            // Liquidation threshold in 3 decimals (900 = 90%) -> (vaultVariables2_ >> 42) & X10
                            // Dividing by 1e27 to convert temp_ into normal number
                            temp_ = ((temp_ * TickMath.ZERO_TICK_SCALED_RATIO) / 1e27);
                            // temp2_ -> liquidationRatio_
                            temp2_ = (temp_ * ((memoryVars_.vaultVariables2 >> 42) & X10)) / 1000;
                        }
                        (memoryVars_.liquidationTick, ) = TickMath.getTickAtRatio(temp2_);
                        // get liquidiation max limit tick (tick at liquidation max limit ratio)
                        // Max limit in 3 decimals (900 = 90%) -> (vaultVariables2_ >> 52) & X10
                        // temp2_ -> maxRatio_
                        unchecked {
                            temp2_ = (temp_ * ((memoryVars_.vaultVariables2 >> 52) & X10)) / 1000;
                        }
                        (memoryVars_.maxTick, ) = TickMath.getTickAtRatio(temp2_);
                    }
                    // extracting top tick as top tick will be the current tick
                    unchecked {
                        currentData_.tick = (vaultVariables_ & 4) == 4
                            ? int256((vaultVariables_ >> 3) & X19)
                            : -int256((vaultVariables_ >> 3) & X19);
                    }
                    if (currentData_.tick > memoryVars_.maxTick) {
                        // absorbing all the debt above maxTick if available
                        vaultVariables_ = (abi.decode(_spell(SECONDARY_IMPLEMENTATION, abi.encodeWithSignature("absorb(uint256,int256)", vaultVariables_, memoryVars_.maxTick)), (uint256)));
                        // updating current tick to new topTick after absorb
                        unchecked {
                            currentData_.tick = (vaultVariables_ & 4) == 4
                                ? int256((vaultVariables_ >> 3) & X19)
                                : -int256((vaultVariables_ >> 3) & X19);
                        }
                        if (debtAmt_ == 0) {
                            // updating vault variables on storage as the transaction was for only absorb
                            vaultVariables = vaultVariables_;
                            return (0, 0);
                        }
                    }
                    if (debtAmt_ < 10000 || debtAmt_ > X128) {
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidLiquidationAmt);
                    }
                    // setting up status if top tick is liquidated or not
                    currentData_.tickStatus = vaultVariables_ & 2 == 0 ? 1 : 2;
                    // Tick info is mainly used as a place holder to store temporary tick related data
                    // (it can be current or ref using same memory variable)
                    TickData memory tickInfo_;
                    tickInfo_.tick = currentData_.tick;
                    {
                        // ############# Setting current branch in memory #############
                        // Updating branch related data
                        branch_.id = (vaultVariables_ >> 22) & X30;
                        branch_.data = branchData[branch_.id];
                        branch_.debtFactor = (branch_.data >> 116) & X50;
                        if (branch_.debtFactor == 0) {
                            // Initializing branch debt factor. 35 | 15 bit number. Where full 35 bits and 15th bit is occupied.
                            // Making the total number as (2**35 - 1) << 2**14.
                            // note: initial debt factor can be any number.
                            branch_.debtFactor = ((X35 << 15) | (1 << 14));
                        }
                        // fetching base branch's minima tick. if 0 that means it's a master branch
                        temp_ = (branch_.data >> 196) & X20;
                        if (temp_ > 0) {
                            unchecked {
                                branch_.minimaTick = (temp_ & 1) == 1 ? int256((temp_ >> 1) & X19) : -int256((temp_ >> 1) & X19);
                            }
                        } else {
                            branch_.minimaTick = type(int).min;
                        }
                    }
                    // debtAmt_ should be less than 2**128 & EXCHANGE_PRICES_PRECISION is 1e12
                    unchecked {
                        currentData_.debtRemaining = (debtAmt_ * EXCHANGE_PRICES_PRECISION) / memoryVars_.borrowExPrice;
                    }
                    // extracting total debt
                    temp2_ = (vaultVariables_ >> 146) & X64;
                    temp2_ = ((temp2_ >> 8) << (temp2_ & X8));
                    if ((temp2_ / 1e9) > currentData_.debtRemaining) {
                        // if liquidation amount is less than 1e9 of total debt then revert
                        // so if total debt is $1B then minimum liquidation limit = $1
                        // so if total debt is $1T then minimum liquidation limit = $1000
                        // partials precision is slightlty above 1e9 so this will make sure that on every liquidation atleast 1 partial gets liquidated
                        // not sure if it can result in any issue but restricting amount further more to remove very low amount scenarios totally
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidLiquidationAmt);
                    }
                    if (absorb_) {
                        temp_ = absorbedLiquidity;
                        // temp2_ -> absorbed col
                        temp2_ = (temp_ >> 128) & X128;
                        // temp_ -> absorbed debt
                        temp_ = temp_ & X128;
                        if (temp_ > currentData_.debtRemaining) {
                            // Removing collateral in equal proportion as debt
                            currentData_.totalColLiq = ((temp2_ * currentData_.debtRemaining) / temp_);
                            temp2_ -= currentData_.totalColLiq;
                            // Removing debt
                            currentData_.totalDebtLiq = currentData_.debtRemaining;
                            unchecked {
                                temp_ -= currentData_.debtRemaining;
                            }
                            currentData_.debtRemaining = 0;
                            // updating on storage
                            absorbedLiquidity = temp_ | (temp2_ << 128);
                        } else {
                            // updating on storage
                            absorbedLiquidity = 0;
                            unchecked {
                                currentData_.debtRemaining -= temp_;
                            }
                            currentData_.totalDebtLiq = temp_;
                            currentData_.totalColLiq = temp2_;
                        }
                    }
                    // current tick should be greater than liquidationTick and it cannot be greater than maxTick as absorb will run
                    if (currentData_.tick > memoryVars_.liquidationTick) {
                        if (currentData_.debtRemaining > 0) {
                            // Stores liquidated debt & collateral in each loop
                            uint debtLiquidated_;
                            uint colLiquidated_;
                            uint debtFactor_ = BigMathVault.TWO_POWER_64;
                            TickHasDebt memory tickHasDebt_;
                            unchecked {
                                tickHasDebt_.mapId = (currentData_.tick < 0)
                                    ? (((currentData_.tick + 1) / 256) - 1)
                                    : (currentData_.tick / 256);
                            }
                            tickInfo_.ratio = TickMath.getRatioAtTick(tickInfo_.tick);
                            if (currentData_.tickStatus == 1) {
                                // top tick is not liquidated. Hence it's a perfect tick.
                                currentData_.ratio = tickInfo_.ratio;
                                // if current tick in liquidation is a perfect tick then it is also the next tick that has debt.
                                tickHasDebt_.nextTick = currentData_.tick;
                            } else {
                                // top tick is liquidated. Hence it has partials.
                                // next tick that has debt liquidity will have to be fetched from tickHasDebt
                                unchecked {
                                    tickInfo_.ratioOneLess = (tickInfo_.ratio * 10000) / 10015;
                                    tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
                                    tickInfo_.partials = (branch_.data >> 22) & X30;
                                    currentData_.ratio = tickInfo_.ratioOneLess + ((tickInfo_.length * tickInfo_.partials) / X30);
                                    
                                    if ((memoryVars_.liquidationTick + 1) == tickInfo_.tick && (tickInfo_.partials == 1)) {
                                        if (to_ == 0x000000000000000000000000000000000000dEaD) {
                                            // revert with liquidated amounts if to_ address is the dead address.
                                            // this can be used in a resolver to find the max liquidatable amounts.
                                            revert FluidLiquidateResult(0, 0);
                                        }
                                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidLiquidation);
                                    }
                                }
                            }
                            while (true) {
                                if (currentData_.tickStatus == 1) {
                                    // not liquidated -> Getting the debt from tick data itself
                                    temp2_ = tickData[currentData_.tick];
                                    // temp_ => tick debt
                                    temp_ = (temp2_ >> 25) & X64;
                                    // Converting big number into normal number
                                    temp_ = (temp_ >> 8) << (temp_ & X8);
                                    // Updating tickData on storage with removing debt & adding connection to branch
                                    tickData[currentData_.tick] =
                                        1 | // set tick as liquidated
                                        (temp2_ & 0x1fffffe) | // set same total tick ids
                                        (branch_.id << 26) | // branch id where this tick got liquidated
                                        (branch_.debtFactor << 56);
                                } else {
                                    // already liquidated -> Get the debt from branch data in big number
                                    // temp_ => tick debt
                                    temp_ = (branch_.data >> 52) & X64;
                                    // Converting big number into normal number
                                    temp_ = (temp_ >> 8) << (temp_ & X8);
                                    // Branch is getting updated over the end
                                }
                                // Adding new debt into active debt for liquidation
                                currentData_.debt += temp_;
                                // Adding new col into active col for liquidation
                                // Ratio is in 2**96 decimals hence multiplying debt with 2**96 to get proper collateral
                                currentData_.col += (temp_ * TickMath.ZERO_TICK_SCALED_RATIO) / currentData_.ratio;
                                if (
                                    (tickHasDebt_.nextTick == currentData_.tick && currentData_.tickStatus == 1) ||
                                    tickHasDebt_.tickHasDebt == 0
                                ) {
                                    // Fetching next perfect tick with liquidity
                                    // tickHasDebt_.tickHasDebt == 0 will only happen in the first while loop
                                    // in the very first perfect tick liquidation it'll be 0
                                    if (tickHasDebt_.tickHasDebt == 0) {
                                        tickHasDebt_.tickHasDebt = tickHasDebt[tickHasDebt_.mapId];
                                    }
                                    // in 1st loop tickStatus can be 2. Meaning not a perfect current tick
                                    if (currentData_.tickStatus == 1) {
                                        unchecked {
                                            tickHasDebt_.bitsToRemove = uint(-currentData_.tick + (tickHasDebt_.mapId * 256 + 256));
                                        }
                                        // Removing current top tick from tickHasDebt
                                        tickHasDebt_.tickHasDebt =
                                            (tickHasDebt_.tickHasDebt << tickHasDebt_.bitsToRemove) >>
                                            tickHasDebt_.bitsToRemove;
                                        // Updating in storage if tickHasDebt becomes 0.
                                        if (tickHasDebt_.tickHasDebt == 0) {
                                            tickHasDebt[tickHasDebt_.mapId] = 0;
                                        }
                                    }
                                    // For last user remaining in vault there could be a lot of while loop.
                                    // Chances of this to happen is extremely low (like ~0%)
                                    while (true) {
                                        if (tickHasDebt_.tickHasDebt > 0) {
                                            unchecked {
                                                tickHasDebt_.nextTick =
                                                    tickHasDebt_.mapId *
                                                    256 +
                                                    int(tickHasDebt_.tickHasDebt.mostSignificantBit()) -
                                                    1;
                                            }
                                            break;
                                        }
                                        // tickHasDebt_.tickHasDebt == 0. Checking if minimum tick of this mapID is less than liquidationTick_
                                        // if true that means now the next tick is not needed as liquidation gets over minimum at liquidationTick_
                                        unchecked {
                                            if ((tickHasDebt_.mapId * 256) < memoryVars_.liquidationTick) {
                                                tickHasDebt_.nextTick = type(int).min;
                                                break;
                                            }
                                            // Fetching next tick has debt by decreasing tickHasDebt_.mapId first
                                            tickHasDebt_.tickHasDebt = tickHasDebt[--tickHasDebt_.mapId];
                                        }
                                    }
                                }
                                // Fetching refTick. refTick is the biggest tick of these 3:
                                // 1. Next tick with liquidity (from tickHasDebt)
                                // 2. Minima tick of current branch
                                // 3. Liquidation threshold tick
                                {
                                    // Setting currentData_.refTick & currentData_.refTickStatus
                                    if (
                                        branch_.minimaTick > tickHasDebt_.nextTick &&
                                        branch_.minimaTick > memoryVars_.liquidationTick
                                    ) {
                                        // next tick will be of base branch (merge)
                                        currentData_.refTick = branch_.minimaTick;
                                        currentData_.refTickStatus = 2;
                                    } else if (tickHasDebt_.nextTick > memoryVars_.liquidationTick) {
                                        // next tick will be next tick from perfect tick
                                        currentData_.refTick = tickHasDebt_.nextTick;
                                        currentData_.refTickStatus = 1;
                                    } else {
                                        // next tick is threshold tick
                                        currentData_.refTick = memoryVars_.liquidationTick;
                                        currentData_.refTickStatus = 3; // leads to end of liquidation loop
                                    }
                                }
                                // using tickInfo variable again for ref tick as we don't have the need for it any more
                                tickInfo_.ratio = TickMath.getRatioAtTick(int24(currentData_.refTick));
                                if (currentData_.refTickStatus == 2) {
                                    // merge current branch with base branch
                                    unchecked {
                                        tickInfo_.ratioOneLess = (tickInfo_.ratio * 10000) / 10015;
                                        tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
                                        // Fetching base branch data to get the base branch's partial
                                        branch_.baseBranchData = branchData[((branch_.data >> 166) & X30)];
                                        tickInfo_.partials = (branch_.baseBranchData >> 22) & X30;
                                        tickInfo_.currentRatio =
                                            tickInfo_.ratioOneLess +
                                            ((tickInfo_.length * tickInfo_.partials) / X30);
                                        currentData_.refRatio = tickInfo_.currentRatio;
                                    }
                                } else {
                                    // refTickStatus can only be 1 (next tick from perfect tick) or 3 (liquidation threshold tick)
                                    tickInfo_.currentRatio = tickInfo_.ratio;
                                    currentData_.refRatio = tickInfo_.ratio;
                                    tickInfo_.partials = X30;
                                }
                                // Formula: (debt_ - x) / (col_ - (x * colPerDebt_)) = ratioEnd_
                                // x = ((ratioEnd_ * col) - debt_) / ((colPerDebt_ * ratioEnd_) - 1)
                                // x is debtToLiquidate_
                                // col_ = debt_ / ratioStart_ -> (currentData_.debt / currentData_.ratio)
                                // ratioEnd_ is currentData_.refRatio
                                //
                                // Calculation results of numerator & denominator is always negative
                                // which will cancel out to give positive output in the end so we can safely cast to uint.
                                // for nominator:
                                // ratioStart can only be >= ratioEnd so first part can only be reducing currentData_.debt leading to
                                // currentData_.debt reduced - currentData_.debt original * 1e27 -> can only be a negative number
                                // for denominator:
                                // currentData_.colPerDebt and currentData_.refRatio are inversely proportional to each other.
                                // the maximum value they can ever be is ~9.97e26 which is the 0.3% away from 100% because liquidation
                                // threshold + liquidation penalty can never be > 99.7%. This can also be verified by going back from
                                // min / max ratio values further up where we fetch oracle price etc.
                                // as optimization we can inverse nominator and denominator subtraction to directly get a positive number.
                                debtLiquidated_ =
                                    // nominator
                                    ((currentData_.debt - (currentData_.refRatio * currentData_.debt) / currentData_.ratio) *
                                        1e27) /
                                    // denominator
                                    (1e27 - ((currentData_.colPerDebt * currentData_.refRatio) / TickMath.ZERO_TICK_SCALED_RATIO));
                                colLiquidated_ = (debtLiquidated_ * currentData_.colPerDebt) / 1e27;
                                if (currentData_.debt == debtLiquidated_) {
                                    debtLiquidated_ -= 1;
                                }
                                if (debtLiquidated_ >= currentData_.debtRemaining || currentData_.refTickStatus == 3) {
                                    // End of liquidation as full amount to liquidate or liquidation threshold tick has been reached;
                                    // Updating tickHasDebt on storage.
                                    tickHasDebt[tickHasDebt_.mapId] = tickHasDebt_.tickHasDebt;
                                    if (debtLiquidated_ >= currentData_.debtRemaining) {
                                        // Liquidation ended between currentTick & refTick.
                                        // Not all of liquidatable debt is actually liquidated -> recalculate
                                        debtLiquidated_ = currentData_.debtRemaining;
                                        colLiquidated_ = (debtLiquidated_ * currentData_.colPerDebt) / 1e27;
                                        // Liquidating to debt. temp_ => final ratio after liquidation
                                        // liquidatable debt - debtLiquidated / liquidatable col - colLiquidated
                                        temp_ =
                                            ((currentData_.debt - debtLiquidated_) * TickMath.ZERO_TICK_SCALED_RATIO) /
                                            (currentData_.col - colLiquidated_);
                                        // Fetching tick of where liquidation ended
                                        (tickInfo_.tick, tickInfo_.ratioOneLess) = TickMath.getTickAtRatio(temp_);
                                        if ((tickInfo_.tick < currentData_.refTick) && (tickInfo_.partials == X30)) {
                                            // this situation might never happen
                                            // if this happens then there might be some very edge case precision of few weis which is returning 1 tick less
                                            // if the above were to ever happen then tickInfo_.tick only be currentData_.refTick - 1
                                            // in this case the partial will be very very near to full (X30)
                                            // increasing tick by 2 and making partial as 1 which is basically very very near to currentData_.refTick
                                            unchecked {
                                                tickInfo_.tick += 2;
                                            }
                                            tickInfo_.partials = 1;
                                        } else {
                                            unchecked {
                                                // Increasing tick by 1 as final ratio will probably be a partial
                                                ++tickInfo_.tick;
                                                // if ref tick is old liquidated tick then storing partials in temp2_
                                                // tickInfo_.partials contains partial of branch which is the current ref tick
                                                temp2_ = (currentData_.refTickStatus == 2 && tickInfo_.tick == currentData_.refTick) ? tickInfo_.partials : 0;
                                                tickInfo_.ratio = (tickInfo_.ratioOneLess * 10015) / 10000;
                                                tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
                                                tickInfo_.partials = ((temp_ - tickInfo_.ratioOneLess) * X30) / tickInfo_.length;
                                                // Taking edge cases where partial comes as 0 or X30 meaning perfect tick.
                                                // Hence, increasing or reducing it by 1 as liquidation tick cannot be perfect tick.
                                                tickInfo_.partials = tickInfo_.partials == 0 ? 1 : tickInfo_.partials >= X30
                                                    ? X30 - 1
                                                    : tickInfo_.partials;
                                            }
                                            if (temp2_ > 0 && temp2_ >= tickInfo_.partials) {
                                                // if refTick is liquidated tick and hence contains partials then checking that
                                                // current liquidation tick's partial should not be less than last liquidation refTick
                                                // not sure if this is even possible to happen but adding checks to avoid it fully
                                                // if it reverts here then next liquidation on next block should go through fine
                                                revert FluidVaultError(ErrorTypes.VaultT1__LiquidationReverts);
                                            }
                                        }
                                    } else {
                                        // End in liquidation threshold.
                                        // finalRatio_ = currentData_.refRatio;
                                        // Increasing liquidation threshold tick by 1 partial. With 1 partial it'll reach to the next tick.
                                        // Ratio change will be negligible. Doing this as liquidation threshold tick can also be a perfect non-liquidated tick.
                                        unchecked {
                                            tickInfo_.tick = currentData_.refTick + 1;
                                        }
                                        // Making partial as 1 so it doesn't stay perfect tick
                                        tickInfo_.partials = 1;
                                        // length is not needed as only partials are written to storage
                                    }
                                    // debtFactor = debtFactor * (liquidatableDebt - debtLiquidated) / liquidatableDebt
                                    // -> debtFactor * leftOverDebt / liquidatableDebt
                                    debtFactor_ = (debtFactor_ * (currentData_.debt - debtLiquidated_)) / currentData_.debt;
                                    currentData_.totalDebtLiq += debtLiquidated_;
                                    currentData_.debt -= debtLiquidated_; // currentData_.debt => leftOverDebt after debtLiquidated_
                                    currentData_.totalColLiq += colLiquidated_;
                                    currentData_.col -= colLiquidated_; // currentData_.col => leftOverCol after colLiquidated_
                                    // Updating branch's debt factor & write to storage as liquidation is over
                                    branch_.debtFactor = branch_.debtFactor.mulDivBigNumber(debtFactor_);
                                    if (currentData_.debt < 100) {
                                        // this can happen when someone tries to create a dust tick
                                        revert FluidVaultError(ErrorTypes.VaultT1__BranchDebtTooLow);
                                    }
                                    unchecked {
                                        // Tick to insert
                                        temp2_ = tickInfo_.tick < 0
                                            ? (uint(-tickInfo_.tick) << 1)
                                            : ((uint(tickInfo_.tick) << 1) | 1);
                                    }
                                    // Updating Branch data with debt factor, debt, partials, minima tick & assigning is liquidated
                                    branchData[branch_.id] =
                                        ((branch_.data >> 166) << 166) |
                                        1 | // set as liquidated
                                        (temp2_ << 2) | // minima tick of branch
                                        (tickInfo_.partials << 22) |
                                        (currentData_.debt.toBigNumber(56, 8, BigMathMinified.ROUND_UP) << 52) | // branch debt
                                        (branch_.debtFactor << 116);
                                    // Updating vault variables with current branch & tick
                                    vaultVariables_ =
                                        ((vaultVariables_ >> 52) << 52) |
                                        2 | // set as liquidated
                                        (temp2_ << 2) | // top tick
                                        (branch_.id << 22);
                                    break;
                                }
                                unchecked {
                                    // debtLiquidated_ >= currentData_.debtRemaining leads to loop break in if statement above
                                    // so this can be unchecked
                                    currentData_.debtRemaining -= debtLiquidated_;
                                }
                                // debtFactor = debtFactor * (liquidatableDebt - debtLiquidated) / liquidatableDebt
                                // -> debtFactor * leftOverDebt / liquidatableDebt
                                debtFactor_ = (debtFactor_ * (currentData_.debt - debtLiquidated_)) / currentData_.debt;
                                currentData_.totalDebtLiq += debtLiquidated_;
                                currentData_.debt -= debtLiquidated_;
                                currentData_.totalColLiq += colLiquidated_;
                                currentData_.col -= colLiquidated_;
                                // updating branch's debt factor
                                branch_.debtFactor = branch_.debtFactor.mulDivBigNumber(debtFactor_);
                                // Setting debt factor as 1 << 64 again
                                debtFactor_ = BigMathVault.TWO_POWER_64;
                                if (currentData_.refTickStatus == 2) {
                                    // ref tick is base branch's minima hence merging current branch to base branch
                                    // and making base branch as current branch.
                                    // read base branch related data
                                    temp_ = (branch_.data >> 166) & X30; // temp_ -> base branch id
                                    temp2_ = branch_.baseBranchData;
                                    {
                                        uint newBranchDebtFactor_ = (temp2_ >> 116) & X50;
                                        // connectionFactor_ = baseBranchDebtFactor / currentBranchDebtFactor
                                        uint connectionFactor_ = newBranchDebtFactor_.divBigNumber(branch_.debtFactor);
                                        // Updating current branch in storage
                                        branchData[branch_.id] =
                                            ((branch_.data >> 166) << 166) | // deleting debt / partials / minima tick
                                            2 | // setting as merged
                                            (connectionFactor_ << 116); // set new connectionFactor
                                        // Storing base branch in memory
                                        // Updating branch ID to base branch ID
                                        branch_.id = temp_;
                                        // Updating branch data with base branch data
                                        branch_.data = temp2_;
                                        // Remove next branch connection from base branch
                                        branch_.debtFactor = newBranchDebtFactor_;
                                        // temp_ => minima tick of base branch
                                        temp_ = (temp2_ >> 196) & X20;
                                        if (temp_ > 0) {
                                            unchecked {
                                                branch_.minimaTick = (temp_ & 1) == 1
                                                    ? int256((temp_ >> 1) & X19)
                                                    : -int256((temp_ >> 1) & X19);
                                            }
                                        } else {
                                            branch_.minimaTick = type(int).min;
                                        }
                                    }
                                }
                                // Making refTick as currentTick
                                currentData_.tick = currentData_.refTick;
                                currentData_.tickStatus = currentData_.refTickStatus;
                                currentData_.ratio = currentData_.refRatio;
                            }
                        }
                    }
                    // calculating net token amounts using exchange price
                    actualDebtAmt_ = (currentData_.totalDebtLiq * memoryVars_.borrowExPrice) / EXCHANGE_PRICES_PRECISION;
                    actualColAmt_ = (currentData_.totalColLiq * memoryVars_.supplyExPrice) / EXCHANGE_PRICES_PRECISION;
                    // Chances of this to happen are in few wei
                    if (actualDebtAmt_ > debtAmt_) {
                        // calc new actualColAmt_ via ratio.
                        actualColAmt_ = actualColAmt_ * (debtAmt_ / actualDebtAmt_);
                        actualDebtAmt_ = debtAmt_;
                    }
                    if (actualDebtAmt_ == 0) {
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidLiquidation);
                    }
                    if (((actualColAmt_ * 1e18) / actualDebtAmt_) < colPerUnitDebt_) {
                        revert FluidVaultError(ErrorTypes.VaultT1__ExcessSlippageLiquidation);
                    }
                    if (to_ == 0x000000000000000000000000000000000000dEaD) {
                        // revert with liquidated amounts if to_ address is the dead address.
                        // this can be used in a resolver to find the max liquidatable amounts.
                        revert FluidLiquidateResult(actualColAmt_, actualDebtAmt_);
                    }
                    // payback at Liquidity
                    if (BORROW_TOKEN == NATIVE_TOKEN) {
                        temp_ = actualDebtAmt_;
                        if (actualDebtAmt_ < msg.value) {
                            unchecked {
                                // subtraction can be unchecked because of if check above
                                SafeTransfer.safeTransferNative(msg.sender, msg.value - actualDebtAmt_);
                            }
                        }
                        // else if actualDebtAmt_ > msg.value not possible as actualDebtAmt_ can maximally be debtAmt_ and
                        // msg.value == debtAmt_ is checked in the beginning of function.
                    } else {
                        temp_ = 0;
                    }
                    unchecked {
                        // payback at liquidity
                        LIQUIDITY.operate{ value: temp_ }(
                            BORROW_TOKEN,
                            0,
                            -int(actualDebtAmt_),
                            address(0),
                            address(0),
                            abi.encode(msg.sender)
                        );
                        // withdraw at liquidity
                        LIQUIDITY.operate(SUPPLY_TOKEN, -int(actualColAmt_), 0, to_, address(0), new bytes(0));
                    }
                    // Calculating new total collateral & total debt.
                    // temp_ -> total supply
                    temp_ = (vaultVariables_ >> 82) & X64;
                    temp_ = ((temp_ >> 8) << (temp_ & X8)) - currentData_.totalColLiq;
                    // temp2_ -> total borrow
                    temp2_ = (vaultVariables_ >> 146) & X64;
                    temp2_ = ((temp2_ >> 8) << (temp2_ & X8)) - currentData_.totalDebtLiq;
                    // Updating vault variables on storage
                    // Converting total supply & total borrow in 64 bits (56 | 8) bignumber
                    vaultVariables =
                        (vaultVariables_ & 0xfffffffffffc00000000000000000000000000000003ffffffffffffffffffff) |
                        (temp_.toBigNumber(56, 8, BigMathMinified.ROUND_DOWN) << 82) | // total supply
                        (temp2_.toBigNumber(56, 8, BigMathMinified.ROUND_UP) << 146); // total borrow
                    emit LogLiquidate(msg.sender, actualColAmt_, actualDebtAmt_, to_);
                }
                /// @dev Checks total supply of vault's in Liquidity Layer & Vault contract and rebalance it accordingly
                /// if vault supply is more than Liquidity Layer then deposit difference through reserve/rebalance contract
                /// if vault supply is less than Liquidity Layer then withdraw difference to reserve/rebalance contract
                /// if vault borrow is more than Liquidity Layer then borrow difference to reserve/rebalance contract
                /// if vault borrow is less than Liquidity Layer then payback difference through reserve/rebalance contract
                function rebalance() external payable returns (int supplyAmt_, int borrowAmt_) {
                    (supplyAmt_, borrowAmt_) = abi.decode(_spell(SECONDARY_IMPLEMENTATION, msg.data), (int, int));
                }
                /// @dev liquidity callback for cheaper token transfers in case of deposit or payback.
                /// only callable by Liquidity during an operation.
                function liquidityCallback(address token_, uint amount_, bytes calldata data_) external {
                    if (msg.sender != address(LIQUIDITY))
                        revert FluidVaultError(ErrorTypes.VaultT1__InvalidLiquidityCallbackAddress);
                    if (vaultVariables & 1 == 0) revert FluidVaultError(ErrorTypes.VaultT1__NotEntered);
                    SafeTransfer.safeTransferFrom(token_, abi.decode(data_, (address)), address(LIQUIDITY), amount_);
                }
                constructor(ConstantViews memory constants_) Helpers(constants_) {
                    // Note that vaults are deployed by VaultFactory so we somewhat trust the values being passed in
                    // Setting branch in vault.
                    vaultVariables = (vaultVariables) | (1 << 22) | (1 << 52);
                    uint liqSupplyExchangePrice_ = (LIQUIDITY.readFromStorage(LIQUIDITY_SUPPLY_EXCHANGE_PRICE_SLOT) >>
                        LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) & X64;
                    uint liqBorrowExchangePrice_ = (LIQUIDITY.readFromStorage(LIQUIDITY_BORROW_EXCHANGE_PRICE_SLOT) >>
                        LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) & X64;
                    if (
                        liqSupplyExchangePrice_ < EXCHANGE_PRICES_PRECISION || liqBorrowExchangePrice_ < EXCHANGE_PRICES_PRECISION
                    ) {
                        revert FluidVaultError(ErrorTypes.VaultT1__TokenNotInitialized);
                    }
                    // Updating initial rates in storage
                    rates =
                        liqSupplyExchangePrice_ |
                        (liqBorrowExchangePrice_ << 64) |
                        (EXCHANGE_PRICES_PRECISION << 128) |
                        (EXCHANGE_PRICES_PRECISION << 192);
                }
                fallback() external {
                    if (!(VAULT_FACTORY.isGlobalAuth(msg.sender) || VAULT_FACTORY.isVaultAuth(address(this), msg.sender))) {
                        revert FluidVaultError(ErrorTypes.VaultT1__NotAnAuth);
                    }
                    // 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
                    _spell(ADMIN_IMPLEMENTATION, msg.data);
                }
                function _spell(address target_, bytes memory data_) private returns (bytes memory response_) {
                    assembly {
                        let succeeded := delegatecall(gas(), target_, add(data_, 0x20), mload(data_), 0, 0)
                        let size := returndatasize()
                        response_ := mload(0x40)
                        mstore(0x40, add(response_, and(add(add(size, 0x20), 0x1f), not(0x1f))))
                        mstore(response_, size)
                        returndatacopy(add(response_, 0x20), 0, size)
                        switch iszero(succeeded)
                        case 1 {
                            // throw if delegatecall failed
                            returndatacopy(0x00, 0x00, size)
                            revert(0x00, size)
                        }
                    }
                }
            }
            // SPDX-License-Identifier: BUSL-1.1
            pragma solidity 0.8.21;
            contract Structs {
                // structs are used to mitigate Stack too deep errors
                struct OperateMemoryVars {
                    // ## User's position before update ##
                    uint oldColRaw;
                    uint oldNetDebtRaw; // total debt - dust debt
                    int oldTick;
                    // ## User's position after update ##
                    uint colRaw;
                    uint debtRaw;
                    uint dustDebtRaw;
                    int tick;
                    uint tickId;
                    // others
                    uint256 vaultVariables2;
                    uint256 branchId;
                    int256 topTick;
                    uint liquidityExPrice;
                    uint supplyExPrice;
                    uint borrowExPrice;
                    uint branchData;
                    // user's supply slot data in liquidity
                    uint userSupplyLiquidityData;
                }
                struct BranchData {
                    uint id;
                    uint data;
                    uint ratio;
                    uint debtFactor;
                    int minimaTick;
                    uint baseBranchData;
                }
                struct TickData {
                    int tick;
                    uint data;
                    uint ratio;
                    uint ratioOneLess;
                    uint length;
                    uint currentRatio; // current tick is ratio with partials.
                    uint partials;
                }
                // note: All the below token amounts are in raw form.
                struct CurrentLiquidity {
                    uint256 debtRemaining; // Debt remaining to liquidate
                    uint256 debt; // Current liquidatable debt before reaching next check point
                    uint256 col; // Calculate using debt & ratioCurrent
                    uint256 colPerDebt; // How much collateral to liquidate per unit of Debt
                    uint256 totalDebtLiq; // Total debt liquidated till now
                    uint256 totalColLiq; // Total collateral liquidated till now
                    int tick; // Current tick to liquidate
                    uint ratio; // Current ratio to liquidate
                    uint tickStatus; // if 1 then it's a perfect tick, if 2 that means it's a liquidated tick
                    int refTick; // ref tick to liquidate
                    uint refRatio; // ratio at ref tick
                    uint refTickStatus; // if 1 then it's a perfect tick, if 2 that means it's a liquidated tick, if 3 that means it's a liquidation threshold
                }
                struct TickHasDebt {
                    int tick; // current tick
                    int nextTick; // next tick with liquidity
                    int mapId; // mapping ID of tickHasDebt
                    uint bitsToRemove; // liquidity to remove till tick_ so we can search for next tick
                    uint tickHasDebt; // getting tickHasDebt_ from tickHasDebt[mapId_]
                    uint mostSigBit; // most significant bit in tickHasDebt_ to get the next tick
                }
                struct LiquidateMemoryVars {
                    uint256 vaultVariables2;
                    int liquidationTick;
                    int maxTick;
                    uint256 supplyExPrice;
                    uint256 borrowExPrice;
                }
                struct AbsorbMemoryVariables {
                    uint256 debtAbsorbed;
                    uint256 colAbsorbed;
                    int256 startingTick;
                    uint256 mostSigBit;
                }
                struct ConstantViews {
                    address liquidity;
                    address factory;
                    address adminImplementation;
                    address secondaryImplementation;
                    address supplyToken;
                    address borrowToken;
                    uint8 supplyDecimals;
                    uint8 borrowDecimals;
                    uint vaultId;
                    bytes32 liquiditySupplyExchangePriceSlot;
                    bytes32 liquidityBorrowExchangePriceSlot;
                    bytes32 liquidityUserSupplySlot;
                    bytes32 liquidityUserBorrowSlot;
                }
                struct RebalanceMemoryVariables {
                    uint256 liqSupplyExPrice;
                    uint256 liqBorrowExPrice;
                    uint256 vaultSupplyExPrice;
                    uint256 vaultBorrowExPrice;
                }
            }
            

            File 4 of 4: 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))
                    }
                }
            }