ETH Price: $3,455.62 (-0.90%)

Contract

0x3996464c0fCCa8183e13ea5E5e74375e2c8744Dd
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Operate214821412024-12-25 21:22:116 mins ago1735161731IN
0x3996464c...e2c8744Dd
0 ETH0.000874855.29622851
Operate214821112024-12-25 21:16:1112 mins ago1735161371IN
0x3996464c...e2c8744Dd
0 ETH0.000903015.8
Operate214820722024-12-25 21:08:1120 mins ago1735160891IN
0x3996464c...e2c8744Dd
0 ETH0.001182177.15543285
Operate214820652024-12-25 21:06:4722 mins ago1735160807IN
0x3996464c...e2c8744Dd
0 ETH0.001109987.7
Operate214820322024-12-25 21:00:1128 mins ago1735160411IN
0x3996464c...e2c8744Dd
0 ETH0.000863525.22635075
Operate214820272024-12-25 20:59:1129 mins ago1735160351IN
0x3996464c...e2c8744Dd
0 ETH0.000809985.20243159
Operate214819992024-12-25 20:53:3535 mins ago1735160015IN
0x3996464c...e2c8744Dd
0 ETH0.000978715.92393379
Operate214819932024-12-25 20:52:2336 mins ago1735159943IN
0x3996464c...e2c8744Dd
0 ETH0.000860036.2
Operate214819672024-12-25 20:47:1141 mins ago1735159631IN
0x3996464c...e2c8744Dd
0 ETH0.000814815.5009885
Operate214819592024-12-25 20:45:3543 mins ago1735159535IN
0x3996464c...e2c8744Dd
0 ETH0.000818385.9
Operate214814332024-12-25 18:59:472 hrs ago1735153187IN
0x3996464c...e2c8744Dd
0 ETH0.000885414.95462995
Operate214814272024-12-25 18:58:352 hrs ago1735153115IN
0x3996464c...e2c8744Dd
0 ETH0.000633194.89752298
Operate214813682024-12-25 18:46:472 hrs ago1735152407IN
0x3996464c...e2c8744Dd
0 ETH0.000897935.18246117
Operate214813592024-12-25 18:44:592 hrs ago1735152299IN
0x3996464c...e2c8744Dd
0 ETH0.000718995.08177319
Operate214813032024-12-25 18:33:472 hrs ago1735151627IN
0x3996464c...e2c8744Dd
0 ETH0.000991316
Operate214812972024-12-25 18:32:352 hrs ago1735151555IN
0x3996464c...e2c8744Dd
0 ETH0.000790235.48177037
Operate214812722024-12-25 18:27:353 hrs ago1735151255IN
0x3996464c...e2c8744Dd
0 ETH0.001057396.40056891
Operate214812672024-12-25 18:26:353 hrs ago1735151195IN
0x3996464c...e2c8744Dd
0 ETH0.000919726.63271099
Operate214812382024-12-25 18:20:473 hrs ago1735150847IN
0x3996464c...e2c8744Dd
0 ETH0.001066156.45302993
Operate214812242024-12-25 18:17:593 hrs ago1735150679IN
0x3996464c...e2c8744Dd
0 ETH0.000894186.2
Operate214812002024-12-25 18:13:113 hrs ago1735150391IN
0x3996464c...e2c8744Dd
0 ETH0.001024256.2
Operate214811902024-12-25 18:11:113 hrs ago1735150271IN
0x3996464c...e2c8744Dd
0 ETH0.000836146.02746276
Operate214811292024-12-25 17:58:473 hrs ago1735149527IN
0x3996464c...e2c8744Dd
0 ETH0.000971775.9
Operate214811172024-12-25 17:56:233 hrs ago1735149383IN
0x3996464c...e2c8744Dd
0 ETH0.000804435.6018974
Operate214810812024-12-25 17:48:593 hrs ago1735148939IN
0x3996464c...e2c8744Dd
0 ETH0.001108356.71004935
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block
From
To
200564492024-06-09 19:46:59199 days ago1717962419  Contract Creation0 ETH
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x0C8C77B7...0E3DD6cB3
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
FluidVaultT1

Compiler Version
v0.8.21+commit.d9974bed

Optimization Enabled:
Yes with 10000000 runs

Other Settings:
paris EvmVersion
File 1 of 25 : main.sol
// 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)
            }
        }
    }
}

File 2 of 25 : IERC721Enumerable.sol
// 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);
}

File 3 of 25 : IERC721.sol
// 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);
}

File 4 of 25 : IERC165.sol
// 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);
}

File 5 of 25 : iProxy.sol
// 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_);
}

File 6 of 25 : bigMathMinified.sol
// 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)
            }
        }
    }
}

File 7 of 25 : bigMathVault.sol
// 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
        }
    }
}

File 8 of 25 : errorTypes.sol
// 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;
}

File 9 of 25 : liquidityCalcs.sol
// 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);
    }
}

File 10 of 25 : liquiditySlotsLink.sol
// 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_));
    }
}

File 11 of 25 : safeTransfer.sol
// 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);
        }
    }
}

File 12 of 25 : storageRead.sol
// 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
        }
    }
}

File 13 of 25 : tickMath.sol
// 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)
            }
        }
    }
}

File 14 of 25 : structs.sol
// 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;
    }
}

File 15 of 25 : iLiquidity.sol
//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 {}

File 16 of 25 : fluidOracle.sol
// 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_);
}

File 17 of 25 : iFluidOracle.sol
// 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_);
}

File 18 of 25 : error.sol
// 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);
}

File 19 of 25 : errorTypes.sol
// 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;
}

File 20 of 25 : iVaultFactory.sol
//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_);
}

File 21 of 25 : variables.sol
// 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;
}

File 22 of 25 : constantVariables.sol
// 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;
    }
}

File 23 of 25 : events.sol
// 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_);
}

File 24 of 25 : helpers.sol
// 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_) {}
}

File 25 of 25 : structs.sol
// 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;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 10000000
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"components":[{"internalType":"address","name":"liquidity","type":"address"},{"internalType":"address","name":"factory","type":"address"},{"internalType":"address","name":"adminImplementation","type":"address"},{"internalType":"address","name":"secondaryImplementation","type":"address"},{"internalType":"address","name":"supplyToken","type":"address"},{"internalType":"address","name":"borrowToken","type":"address"},{"internalType":"uint8","name":"supplyDecimals","type":"uint8"},{"internalType":"uint8","name":"borrowDecimals","type":"uint8"},{"internalType":"uint256","name":"vaultId","type":"uint256"},{"internalType":"bytes32","name":"liquiditySupplyExchangePriceSlot","type":"bytes32"},{"internalType":"bytes32","name":"liquidityBorrowExchangePriceSlot","type":"bytes32"},{"internalType":"bytes32","name":"liquidityUserSupplySlot","type":"bytes32"},{"internalType":"bytes32","name":"liquidityUserBorrowSlot","type":"bytes32"}],"internalType":"struct Structs.ConstantViews","name":"constants_","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"colLiquidated","type":"uint256"},{"internalType":"uint256","name":"debtLiquidated","type":"uint256"}],"name":"FluidLiquidateResult","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorId_","type":"uint256"}],"name":"FluidLiquidityCalcsError","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorId_","type":"uint256"}],"name":"FluidSafeTransferError","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorId_","type":"uint256"}],"name":"FluidVaultError","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"colAbsorbedRaw_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"debtAbsorbedRaw_","type":"uint256"}],"name":"LogAbsorb","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"liquidator_","type":"address"},{"indexed":false,"internalType":"uint256","name":"colAmt_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"debtAmt_","type":"uint256"},{"indexed":false,"internalType":"address","name":"to_","type":"address"}],"name":"LogLiquidate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user_","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftId_","type":"uint256"},{"indexed":false,"internalType":"int256","name":"colAmt_","type":"int256"},{"indexed":false,"internalType":"int256","name":"debtAmt_","type":"int256"},{"indexed":false,"internalType":"address","name":"to_","type":"address"}],"name":"LogOperate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"colAmt_","type":"int256"},{"indexed":false,"internalType":"int256","name":"debtAmt_","type":"int256"}],"name":"LogRebalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"supplyExPrice_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowExPrice_","type":"uint256"}],"name":"LogUpdateExchangePrice","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"LIQUIDITY","outputs":[{"internalType":"contract IFluidLiquidity","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAULT_FACTORY","outputs":[{"internalType":"contract IFluidVaultFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAULT_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"constantsView","outputs":[{"components":[{"internalType":"address","name":"liquidity","type":"address"},{"internalType":"address","name":"factory","type":"address"},{"internalType":"address","name":"adminImplementation","type":"address"},{"internalType":"address","name":"secondaryImplementation","type":"address"},{"internalType":"address","name":"supplyToken","type":"address"},{"internalType":"address","name":"borrowToken","type":"address"},{"internalType":"uint8","name":"supplyDecimals","type":"uint8"},{"internalType":"uint8","name":"borrowDecimals","type":"uint8"},{"internalType":"uint256","name":"vaultId","type":"uint256"},{"internalType":"bytes32","name":"liquiditySupplyExchangePriceSlot","type":"bytes32"},{"internalType":"bytes32","name":"liquidityBorrowExchangePriceSlot","type":"bytes32"},{"internalType":"bytes32","name":"liquidityUserSupplySlot","type":"bytes32"},{"internalType":"bytes32","name":"liquidityUserBorrowSlot","type":"bytes32"}],"internalType":"struct Structs.ConstantViews","name":"constantsView_","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"positionTick_","type":"int256"},{"internalType":"uint256","name":"positionTickId_","type":"uint256"},{"internalType":"uint256","name":"positionRawDebt_","type":"uint256"},{"internalType":"uint256","name":"tickData_","type":"uint256"}],"name":"fetchLatestPosition","outputs":[{"internalType":"int256","name":"","type":"int256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"positionRawCol_","type":"uint256"},{"internalType":"uint256","name":"branchId_","type":"uint256"},{"internalType":"uint256","name":"branchData_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"debtAmt_","type":"uint256"},{"internalType":"uint256","name":"colPerUnitDebt_","type":"uint256"},{"internalType":"address","name":"to_","type":"address"},{"internalType":"bool","name":"absorb_","type":"bool"}],"name":"liquidate","outputs":[{"internalType":"uint256","name":"actualDebtAmt_","type":"uint256"},{"internalType":"uint256","name":"actualColAmt_","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"bytes","name":"data_","type":"bytes"}],"name":"liquidityCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nftId_","type":"uint256"},{"internalType":"int256","name":"newCol_","type":"int256"},{"internalType":"int256","name":"newDebt_","type":"int256"},{"internalType":"address","name":"to_","type":"address"}],"name":"operate","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"int256","name":"","type":"int256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"slot_","type":"bytes32"}],"name":"readFromStorage","outputs":[{"internalType":"uint256","name":"result_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rebalance","outputs":[{"internalType":"int256","name":"supplyAmt_","type":"int256"},{"internalType":"int256","name":"borrowAmt_","type":"int256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"vaultVariables2_","type":"uint256"}],"name":"updateExchangePrices","outputs":[{"internalType":"uint256","name":"liqSupplyExPrice_","type":"uint256"},{"internalType":"uint256","name":"liqBorrowExPrice_","type":"uint256"},{"internalType":"uint256","name":"vaultSupplyExPrice_","type":"uint256"},{"internalType":"uint256","name":"vaultBorrowExPrice_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"updateExchangePricesOnStorage","outputs":[{"internalType":"uint256","name":"liqSupplyExPrice_","type":"uint256"},{"internalType":"uint256","name":"liqBorrowExPrice_","type":"uint256"},{"internalType":"uint256","name":"vaultSupplyExPrice_","type":"uint256"},{"internalType":"uint256","name":"vaultBorrowExPrice_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

Deployed Bytecode

0x6080604052600436106100c75760003560e01c8063540acabc11610074578063ad2075011161004e578063ad207501146104ad578063b5c736e4146104cd578063b7791bf2146104ec576100c7565b8063540acabc1461043b5780637d7c2a1c1461047d5780638433ea221461049a576100c7565b8063103f2907116100a5578063103f29071461036657806322348cc7146103bf5780632861c7d114610407576100c7565b806302161887146102de578063032d22761461031857806309f0d8cb14610346575b3480156100d357600080fd5b506040517f4502d0630000000000000000000000000000000000000000000000000000000081523360048201527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d73ffffffffffffffffffffffffffffffffffffffff1690634502d06390602401602060405180830381865afa15801561015e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061018291906156a1565b8061023c57506040517fe04c8e5d0000000000000000000000000000000000000000000000000000000081523060048201523360248201527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d73ffffffffffffffffffffffffffffffffffffffff169063e04c8e5d90604401602060405180830381865afa158015610218573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023c91906156a1565b61027b576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792560048201526024015b60405180910390fd5b6102dc7f0000000000000000000000005ddf07980add152d518ae463269e1a97e93ee1a96000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061072492505050565b005b3480156102ea57600080fd5b506102f3610770565b6040805194855260208501939093529183015260608201526080015b60405180910390f35b61032b6103263660046156e0565b610864565b6040805193845260208401929092529082015260600161030f565b34801561035257600080fd5b506102f3610361366004615721565b6123d0565b34801561037257600080fd5b5061039a7f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d81565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161030f565b3480156103cb57600080fd5b506103df6103da36600461573a565b612668565b604080519586526020860194909452928401919091526060830152608082015260a00161030f565b34801561041357600080fd5b5061039a7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49781565b34801561044757600080fd5b5061046f7f000000000000000000000000000000000000000000000000000000000000001181565b60405190815260200161030f565b610485612928565b6040805192835260208301919091520161030f565b6104856104a836600461576c565b6129a8565b3480156104b957600080fd5b506102dc6104c83660046157ab565b614238565b3480156104d957600080fd5b5061046f6104e8366004615721565b5490565b3480156104f857600080fd5b50604080516101a08101825273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497811682527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d811660208301527f0000000000000000000000005ddf07980add152d518ae463269e1a97e93ee1a98116828401527f000000000000000000000000bd6c8ee7cb9a13333e043c4d5fcf3ac125f7f890811660608301527f0000000000000000000000009d39a5de30e57443bff2a8307a4256c8797a3497811660808301527f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb481660a082015260ff7f0000000000000000000000000000000000000000000000000000000000000012811660c08301527f00000000000000000000000000000000000000000000000000000000000000061660e08201527f00000000000000000000000000000000000000000000000000000000000000116101008201527f0a3b3d8eb1cc89e32f35e4b1c89771e7e070343a177ab8a5d6271eed4367e86b6101208201527fa8e1248eddf82e10c0adc6c737b6d8da17674abf51801ea5a4549f41c2dfdf216101408201527fa830b00c9550a3abe8c767f3441f9524cb2d775719adee23ec3c6c9502686e436101608201527f3814bdb2fa4289a8f2911f0e20438793d8202ee8570005f429590cf40acc58f7610180820152905161030f9190615834565b6060600080835160208501865af43d6040519250601f19601f6020830101168301604052808352806000602085013e81156001810361076757816000803e816000fd5b50505092915050565b6000806000806107816001546123d0565b9296509094509250905067ffffffffffffffff8411806107a8575067ffffffffffffffff83115b806107ba575067ffffffffffffffff82115b806107cc575067ffffffffffffffff81115b15610807576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792d6004820152602401610272565b604083811b8517608084901b1760c083901b17600855517fcde545703e0372175cadfff811d67c32910c3dcb33199679b3271c4106afdf9a906108569084908490918252602082015260400190565b60405180910390a190919293565b600080548190819060018116820361088257600181176000556108b8565b6040517f60121cca0000000000000000000000000000000000000000000000000000000081526179196004820152602401610272565b861580156108c4575085155b806109045750861580159061090457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd8f087138015610904575061271087125b806109445750851580159061094457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd8f086138015610944575061271086125b1561097f576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791a6004820152602401610272565b7f0000000000000000000000009d39a5de30e57443bff2a8307a4256c8797a349773ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1480156109d85750600087135b15610a1f57348714610a1a576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791b6004820152602401610272565b610ab8565b3415610ab8577f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff1673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee148015610a7e5750600086125b610ab8576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791b6004820152602401610272565b610b38604051806102000160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b600154610100820152600282901c620fffff166000808215610b805782600116600114610b71576207ffff600184901c16600003610ba2565b6207ffff600184901c16610ba2565b7f80000000000000000000000000000000000000000000000000000000000000005b61014085015260008c9003610cd4577f800000000000000000000000000000000000000000000000000000000000000060c08501526040517f94bf804d0000000000000000000000000000000000000000000000000000000081527f000000000000000000000000000000000000000000000000000000000000001160048201523360248201527f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d73ffffffffffffffffffffffffffffffffffffffff16906394bf804d906044016020604051808303816000875af1158015610c89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cad9190615983565b9b50847a040000000000000000000000000000000000000000000000000000019450610ef3565b60008b1280610ce3575060008a135b8015610db457506040517f6352211e000000000000000000000000000000000000000000000000000000008152600481018d905233907f000000000000000000000000324c5dc1fc42c7a4d43d92df1eba58a54d13bf2d73ffffffffffffffffffffffffffffffffffffffff1690636352211e90602401602060405180830381865afa158015610d77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9b919061599c565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610def576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791c6004820152602401610272565b60008c8152600360205260408120549350839003610e3d576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179236004820152602401610272565b66ffffffffffffff603584901c811660ff602d86901c81169190911b6060870152607585901c909116606d85901c9182161b60a086015267ffffffffffffffff16915060018084169003610eb6577f800000000000000000000000000000000000000000000000000000000000000060c0850152610ef3565b82600216600214610ed3576207ffff600284901c16600003610ede565b6207ffff600284901c165b60c0850152601583901c62ffffff1660e08501525b7f80000000000000000000000000000000000000000000000000000000000000008460c0015113156111e157602d9290921c60ff16918215610f3e5760608401516001841b01610f47565b83606001516001015b9250606083610f5c8660c0015160020b61432b565b610f6691906159e8565b610f72911c60016159ff565b608085015260c0840151600090815260056020526040902054925060018084161480610faa57508360e0015162ffffff600185901c16115b156110d257610fc78460c001518560e00151866080015186612668565b6101c089015260608801919091526080870182905260c087019290925260a0860151919350111561109b5767ffffffffffffffff6034856101c00151901c16925060ff8316600884901c901b92508360800151836110259190615a12565b9250606483101561103557606492505b60346110468460386008600161458d565b6101c086015160008581526007602052604090207ffffffffffffffffffffffffffffffffffff0000000000000000fffffffffffff9091169190921b17905560a08401516080850180519190910390526111d9565b83608001518460a00151600a546110b291906159ff565b6110bc9190615a12565b600a5560006080850181905260608501526111d9565b67ffffffffffffffff601984901c16915081600003611121576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791d6004820152602401610272565b60ff8216600883901c901b915081846080015110611140576000611148565b836080015182035b915061271082101561118f57600091508361014001518460c001510361118057611177846101400151866146b6565b61014086015294505b61118f8460c001516000614898565b60196111a08360386008600061458d565b60c08601516000908152600560205260409020911b6301ffffff851617905560a08401516080850180516111d5908390615a12565b9052505b600060a08501525b60c08401516040850152606084015184526080840151602085015261010084015161120b906123d0565b6101a08801526101808701525061016085015260008b13156112b15761018084015161123c64e8d4a510008d6159e8565b6112469190615a54565b8460600181815161125791906159ff565b90525060608401516fffffffffffffffffffffffffffffffff10156112ac576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792f6004820152602401610272565b611408565b60008b1215611408577fffffffffffffffffffffffffffffffff800000000000000000000000000000008b1315611364576101808401516001906112fa64e8d4a510008e615a68565b6113049190615ab4565b61130e9190615b1c565b90508360600151816000031115611355576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179266004820152602401610272565b60608401805182019052611408565b7f80000000000000000000000000000000000000000000000000000000000000008b036113d25764e8d4a5100084610180015185606001516113a691906159e8565b6113b09190615a54565b6113b990615b43565b6113c4906001615b7b565b600060608601529a50611408565b6040517f60121cca00000000000000000000000000000000000000000000000000000000815261792f6004820152602401610272565b60008a13156114d8576101a084015161142664e8d4a510008c6159e8565b6114309190615a54565b61143b9060016159ff565b92506127106103ff6052866101000151901c168461145991906159e8565b6114639190615a54565b61146d90846159ff565b8460800181815161147e91906159ff565b90525060808401516fffffffffffffffffffffffffffffffff10156114d3576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792f6004820152602401610272565b6115f8565b60008a12156115f8577fffffffffffffffffffffffffffffffff800000000000000000000000000000008a131561158d576101a084015161151e64e8d4a510008c615a68565b6115289190615ab4565b611533906001615b7b565b9050806000039050836080015181111561157d576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179276004820152602401610272565b60808401805182900390526115f8565b7f80000000000000000000000000000000000000000000000000000000000000008a036113d257600164e8d4a51000856101a0015186608001516115d191906159e8565b6115db9190615a54565b6115e490615b43565b6115ee9190615b1c565b6000608086015299505b8a158015611604575089155b1561163f576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791a6004820152602401610272565b6080840151156118b4576116778460600151633b9aca008660800151633b9aca01028161166e5761166e615a25565b0460010161490b565b60a0880152608087015260e086015260c085015260008a12156116e95783602001518460a0015185608001516116ad9190615a12565b11156116e9576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179346004820152602401610272565b60008b1380156116f7575089155b1561179c578351602085015161171b906c01000000000000000000000000906159e8565b6117259190615a54565b84606001516c010000000000000000000000008660a00151876080015161174c9190615a12565b61175691906159e8565b6117609190615a54565b111561179c576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179346004820152602401610272565b8361014001518460c00151126118af5760008460c00151126117c95760018460c00151901b6001176117d5565b60c084015160000360011b5b92508460021660000361181257600283901b857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000161794506118db565b6001603486811c633fffffff16919091016000818152600760205260409020780fffffffc00000000000000000000000000000000000000000609089901b167afffff000000000000000000000000000000000000000000000000060c28a901b161790557ffffffffffffffffffffffffffffffffffffffffffffc00000000000000000000909616600285901b17601687901b179086901b179491505b6118db565b7f800000000000000000000000000000000000000000000000000000000000000060c08501525b60008b12806118ea575060008a135b15611b155773ffffffffffffffffffffffffffffffffffffffff891661190e573398505b6000846080015111801561195d57508360c00151846040015113158061195d5750633b9aca008460200151633b9aca01028161194c5761194c615a25565b046001018460a00151856080015103115b15611b15576060846101000151901c73ffffffffffffffffffffffffffffffffffffffff16638e7bfbc06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156119b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119da9190615983565b9250760a70c3c40a64e6c51999090b65f67d9240000000000000831180611a045750633b9aca0083105b15611a3f576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179396004820152602401610272565b836101a00151846101800151840281611a5a57611a5a615a25565b049250722cd76fe086b93ce2f768a00b22a00000000000831115611a8f57722cd76fe086b93ce2f768a00b22a0000000000092505b6101008401516103e89060201c6103ff1684020491506b033b2e3c9fd0803ce80000006c010000000000000000000000008302049150611ace82614ae8565b5080915050808460c001511315611b15576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791e6004820152602401610272565b7f80000000000000000000000000000000000000000000000000000000000000008460c001511315611b715760008460c0015112611b5e5760018460c00151901b600117611b6a565b60c084015160000360011b5b9250611b76565b600092505b60a0840151606d90611b8d9060386008600061458d565b6060860151911b90602d90611ba79060386008600061458d565b60e0870151911b9060151b600186901b8615611bc4576000611bc7565b60015b60ff1617171717600360008e81526020019081526020016000208190555060008b1215611d7057610100840151603e1c6103ff1692508215611d70576040517fb5c736e40000000000000000000000000000000000000000000000000000000081527fa830b00c9550a3abe8c767f3441f9524cb2d775719adee23ec3c6c9502686e4360048201527f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff169063b5c736e490602401602060405180830381865afa158015611cad573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cd19190615983565b6101e0850181905266ffffffffffffff600982901c1660ff600183901c161b9250611cfc9083614efd565b9050600081138015611d35575083610160015164e8d4a510008c6000030281611d2757611d27615a25565b05816103e885810385020503125b15611d70576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179286004820152602401610272565b60008b1315611eee577f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff1663ad967e1573eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff167f0000000000000000000000009d39a5de30e57443bff2a8307a4256c8797a349773ffffffffffffffffffffffffffffffffffffffff1614611e23576000611e25565b345b7f0000000000000000000000009d39a5de30e57443bff2a8307a4256c8797a34978e600080600033604051602001611e79919073ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6040516020818303038152906040526040518863ffffffff1660e01b8152600401611ea996959493929190615bff565b604080518083038185885af1158015611ec6573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611eeb9190615c58565b50505b60008a12156120bc577fffffffffffffffffffffffff11111111111111111111111111111111111111127f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff1601611fb15789600003925082341115611f7357611f6e33843403614f8f565b611fb6565b82341015611f6e576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791b6004820152602401610272565b600092505b7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff1663ad967e15847f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4860008e60008033604051602001612047919073ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6040516020818303038152906040526040518863ffffffff1660e01b815260040161207796959493929190615bff565b604080518083038185885af1158015612094573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906120b99190615c58565b50505b60008b12156121a157604080516000808252602082019092527f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff169163ad967e15917f0000000000000000000000009d39a5de30e57443bff2a8307a4256c8797a3497918f918e9082906040518763ffffffff1660e01b815260040161215c96959493929190615bff565b60408051808303816000875af115801561217a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061219e9190615c58565b50505b60008a131561228757604080516000808252602082019092527f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff169163ad967e15917f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4891908e9082908f906040518763ffffffff1660e01b815260040161224296959493929190615bff565b60408051808303816000875af1158015612260573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122849190615c58565b50505b67ffffffffffffffff605286901c1692508360000151846060015160ff8516600886901c901b6122b791906159ff565b6122c19190615a12565b925067ffffffffffffffff609286901c16915083602001518460a0015185608001516122ed9190615a12565b61230090600885901c60ff86161b6159ff565b61230a9190615a12565b9150609261231d8360386008600161458d565b901b60526123308560386008600061458d565b901b7ffffffffffffc00000000000000000000000000000003ffffffffffffffffffff8716171760005560408051338152602081018e90529081018c9052606081018b905273ffffffffffffffffffffffffffffffffffffffff8a1660808201527ffef64760e30a41b9d5ba7dd65ff7236a61d89ed8b44c67a29e84db1a67513a1c9060a00160405180910390a150999a98995096979650505050505050565b6008546040517fb5c736e40000000000000000000000000000000000000000000000000000000081527f0a3b3d8eb1cc89e32f35e4b1c89771e7e070343a177ab8a5d6271eed4367e86b60048201526000918291829182916124b49073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497169063b5c736e4906024015b602060405180830381865afa15801561248b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124af9190615983565b614fde565b506040517fb5c736e40000000000000000000000000000000000000000000000000000000081527fa8e1248eddf82e10c0adc6c737b6d8da17674abf51801ea5a4549f41c2dfdf21600482015290955061254e907f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff169063b5c736e49060240161246e565b94505067ffffffffffffffff81811690604083901c168187108061257157508086105b156125ac576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179326004820152602401610272565b61271061ffff8916670de0b6b3a7640000848a670de0b6b3a764000002816125d6576125d6615a25565b040302816125e6576125e6615a25565b04945061271061ffff60108a901c16670de0b6b3a76400008389670de0b6b3a7640000028161261757612617615a25565b0403028161262757612627615a25565b049350670de0b6b3a7640000608084901c67ffffffffffffffff1686820102049450670de0b6b3a764000060c084901c858201020493505050509193509193565b600080808080868180600189901c62ffffff168b90036126ae575050601a87901c633fffffff169250603887901c6603ffffffffffff166001601989901c811614612705565b60008c8152600660205260408120600360028e0181810660550292918491048152602081019190915260400160002054901c600181811c633fffffff169750601f82901c6603ffffffffffff169450908116149150505b60008581526007602052604090205493508015612748577f80000000000000000000000000000000000000000000000000000000000000009b5060009950612914565b836003166002036127b25761276a82607486901c6603ffffffffffff166151ec565b9150600161277a600f60236159ff565b6001901b6127889190615a12565b82146127b25760a69390931c633fffffff1660008181526007602052604090205490945092612748565b83600316600314806127de575060016127cd600f60236159ff565b6001901b6127db9190615a12565b82145b1561280f577f80000000000000000000000000000000000000000000000000000000000000009b5060009950612914565b6128278a607486901c6603ffffffffffff1684615284565b9950606483048a11156128445761271061270f8b02049950612849565b600099505b89156128f0578360041660041461286c576207ffff600385901c16600003612877565b6207ffff600385901c165b9b5060006128878d60020b61432b565b905061271f612710820204633fffffff601687901c81166128a88385615a12565b6128b291906159e8565b6128bc9190615a54565b6128c690826159ff565b6128dd6c010000000000000000000000008e6159e8565b6128e79190615a54565b97505050612914565b7f80000000000000000000000000000000000000000000000000000000000000009b505b8b8a97509750505050945094509450945094565b60008061298c7f000000000000000000000000bd6c8ee7cb9a13333e043c4d5fcf3ac125f7f8906000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061072492505050565b80602001905181019061299f9190615c58565b90939092509050565b6000806129dd6040518060a0016040528060008152602001600081526020016000815260200160008152602001600081525090565b600080549060018216900361088257600181176000557fffffffffffffffffffffffff11111111111111111111111111111111111111127f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff1601612ab857873414158015612a78575061dead73ffffffffffffffffffffffffffffffffffffffff871614155b15612ab3576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179206004820152602401610272565b612af4565b3415612af4576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179206004820152602401610272565b6001548252600281901c620fffff16600003612b40576040517f60121cca00000000000000000000000000000000000000000000000000000000815261791f6004820152602401610272565b8151612b4b906123d0565b90919250909150836060018460800182815250828152505050612bc86040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b612c016040518060c001604052806000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60008060608660000151901c73ffffffffffffffffffffffffffffffffffffffff1663f3190c896040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c57573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c7b9190615983565b9150760a70c3c40a64e6c51999090b65f67d9240000000000000821180612ca0575081155b15612cdb576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179396004820152602401610272565b85608001518660600151830281612cf457612cf4615a25565b049150722cd76fe086b93ce2f768a00b22a00000000000821115612d2957722cd76fe086b93ce2f768a00b22a0000000000091505b81760a70c3c40a64e6c51999090b65f67d924000000000000081612d4f57612d4f615a25565b875191900491506127109060481c6103ff16810182020460608501526b033b2e3c9fd0803ce80000006c010000000000000000000000008302875191900492506103e890602a1c6103ff168302049050612da881614ae8565b50602087015285516103e89060341c6103ff168302049050612dc981614ae8565b506040870152600480861614612deb576207ffff600386901c16600003612df6565b6207ffff600386901c165b60c0850181905260408701511215612f2757612ec77f000000000000000000000000bd6c8ee7cb9a13333e043c4d5fcf3ac125f7f890868860400151604051602401612e4c929190918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f9e3e482100000000000000000000000000000000000000000000000000000000179052610724565b806020019051810190612eda9190615983565b945084600416600414612ef9576207ffff600386901c16600003612f04565b6207ffff600386901c165b60c085015260008c9003612f2757505050600091825550925082915061422f9050565b6127108c1080612f4657506fffffffffffffffffffffffffffffffff8c115b15612f81576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792e6004820152602401610272565b6002851615612f91576002612f94565b60015b60ff1684610100018181525050612fe16040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60c08501518152601686901c633fffffff16808552600090815260076020908152604082205490860181905260741c6603ffffffffffff16606086018190529003613034576603ffffffffc00060608501525b602084015160c41c620fffff169250821561307b5782600116600114613066576207ffff600184901c16600003613071565b6207ffff600184901c165b60808501526130a2565b7f800000000000000000000000000000000000000000000000000000000000000060808501525b866080015164e8d4a510008e02816130bc576130bc615a25565b0480865266ffffffffffffff609a88901c1660ff609289901c161b92506130e7633b9aca0084615a54565b1115613123576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792e6004820152602401610272565b89156131b65760025485516fffffffffffffffffffffffffffffffff8216945060809190911c925083111561319b578451839061316090846159e8565b61316a9190615a54565b60a0860181905261317b9083615a12565b855160808088018290526000885282901b940393841760025591506131b6565b6000600255845183900385526080850183905260a085018290525b86602001518560c001511315613d4557845115613d45576040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a081018290528190680100000000000000009060008960c0015112613232576101008960c001518161322c5761322c615a25565b0561324f565b60016101008a60c001516001018161324c5761324c615a25565b05035b6040820152845161325f9061432b565b604086015261010089015160010361328a57604085015160e08a015260c08901516020820152613391565b61271f856040015161271002816132a3576132a3615a25565b046060860181905260408601510360808601819052602089015160161c633fffffff90811660c0880181905290910260608701519190040160e08a0152845160208c01516001011480156132fb57508460c001516001145b156133915773ffffffffffffffffffffffffffffffffffffffff8f1661dead0361335b576040517fcc36aa440000000000000000000000000000000000000000000000000000000081526000600482018190526024820152604401610272565b6040517f60121cca0000000000000000000000000000000000000000000000000000000081526179356004820152602401610272565b8861010001516001036134015760c0890180516000908152600560205260408082205460608c01518c5194518452919092206301fffffe8316601a9490941b9390931760389190911b1760011790915566ffffffffffffff602182901c1660ff601983901c161b97509550613424565b67ffffffffffffffff60348960200151901c16965060ff8716600888901c901b96505b868960200181815161343691906159ff565b90525060e08901516134556c01000000000000000000000000896159e8565b61345f9190615a54565b8960400181815161347091906159ff565b90525060c0890151602082015114801561348f57508861010001516001145b8061349c57506080810151155b156135cc5780608001516000036134c55760408082015160009081526004602052205460808201525b88610100015160010361351c57604081015160c08a015160009081036101009283020190910160608301819052608083018051821b90911c90819052900361351c5760408082015160009081526004602052908120555b60808101511561354a57600161353582608001516152c9565b604083015161010002010360208201526135cc565b8a602001518160400151610100021215613589577f800000000000000000000000000000000000000000000000000000000000000060208201526135cc565b604080820180517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190819052600090815260046020522054608082015261351c565b806020015188608001511380156135ea57508a602001518860800151135b156136075760808801516101208a015260026101608a0152613643565b8a602001518160200151131561362f5760208101516101208a015260016101608a0152613643565b60208b01516101208a015260036101608a01525b61365489610120015160020b61432b565b60408601526101608901516002036136e85761271f8560400151612710028161367f5761367f615a25565b046060860181905260408087015191909103608087019081526020808b015160a61c633fffffff908116600090815260079092529290205460a08b0181905260161c821660c0880181905290510260608701519190040160a086018190526101408a0152613705565b604085015160a086018190526101408a0152633fffffff60c08601525b6c010000000000000000000000008961014001518a6060015161372891906159e8565b6137329190615a54565b613748906b033b2e3c9fd0803ce8000000615a12565b8960e001518a602001518b610140015161376291906159e8565b61376c9190615a54565b8a6020015161377b9190615a12565b613791906b033b2e3c9fd0803ce80000006159e8565b61379b9190615a54565b93506b033b2e3c9fd0803ce80000008960600151856137ba91906159e8565b6137c49190615a54565b9250838960200151036137df576137dc600185615a12565b93505b8851841015806137f457508861016001516003145b15613b5f576080810151604080830151600090815260046020522055885184106139d457885160608a01519094506b033b2e3c9fd0803ce80000009061383a90866159e8565b6138449190615a54565b92508289604001516138569190615a12565b6c01000000000000000000000000858b602001516138749190615a12565b61387e91906159e8565b6138889190615a54565b965061389387614ae8565b60608701528086526101208a01511380156138b55750633fffffff8560c00151145b156138cd5784516002018552600160c08601526139e7565b8451600101855261016089015160021480156138ee57506101208901518551145b6138f95760006138ff565b8460c001515b9550612710856060015161271f028161391a5761391a615a25565b046040860181905260608601519081900360808701819052908803633fffffff028161394857613948615a25565b0460c086018190521561397957633fffffff8560c00151101561396f578460c0015161397c565b633ffffffe61397c565b60015b60c0860152851580159061399457508460c001518610155b156139cf576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179386004820152602401610272565b6139e7565b6101208901516001908101865260c08601525b60208901516139f68582615a12565b613a0090846159e8565b613a0a9190615a54565b91508389608001818151613a1e91906159ff565b905250602089018051859190613a35908390615a12565b90525060a089018051849190613a4c9083906159ff565b905250604089018051849190613a63908390615a12565b9052506060880151613a75908361537f565b6060890152602089015160641115613abd576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179306004820152602401610272565b8451600013613ad3578451600190811b17613adc565b845160000360011b5b606089015160208b015191975060741b90603490613aff9060386008600161458d565b901b60168760c00151901b600289901b60a6808d60200151901c901b60011717171717600760008a6000015181526020019081526020016000208190555060168860000151901b600287901b6034808d901c901b60021717179950613d40565b885184900389526020890151613b758582615a12565b613b7f90846159e8565b613b899190615a54565b91508389608001818151613b9d91906159ff565b905250602089018051859190613bb4908390615a12565b90525060a089018051849190613bcb9083906159ff565b905250604089018051849190613be2908390615a12565b9052506060880151613bf4908361537f565b6060890152610160890151680100000000000000009250600203613d1957633fffffff60a68960200151901c1696508760a00151955060006603ffffffffffff607488901c1690506000613c558a606001518361542390919063ffffffff16565b6020808c0180518d516000908152600790935260409092207fffffffffffffffffffffffc00000000000000000000000000000000000000000909216607484901b17600217909155998b5298889052506060890181905260c487901c620fffff16978815613cef5788600116600114613cda576207ffff60018a901c16600003613ce5565b6207ffff60018a901c165b60808b0152613d16565b7f800000000000000000000000000000000000000000000000000000000000000060808b01525b50505b61012089015160c08a01526101608901516101008a015261014089015160e08a0152613391565b505050505b64e8d4a5100087608001518660800151613d5f91906159e8565b613d699190615a54565b985064e8d4a5100087606001518660a00151613d8591906159e8565b613d8f9190615a54565b97508c891115613db357613da3898e615a54565b613dad90896159e8565b97508c98505b88600003613df1576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179356004820152602401610272565b8b89613e058a670de0b6b3a76400006159e8565b613e0f9190615a54565b1015613e4b576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179216004820152602401610272565b73ffffffffffffffffffffffffffffffffffffffff8b1661dead03613ea6576040517fcc36aa4400000000000000000000000000000000000000000000000000000000815260048101899052602481018a9052604401610272565b7fffffffffffffffffffffffff11111111111111111111111111111111111111127f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4873ffffffffffffffffffffffffffffffffffffffff1601613f1f5788925034891015613f1a57613f1a338a3403614f8f565b613f24565b600092505b7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49773ffffffffffffffffffffffffffffffffffffffff1663ad967e15847f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4860008d60000360008033604051602001613fb8919073ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6040516020818303038152906040526040518863ffffffff1660e01b8152600401613fe896959493929190615bff565b604080518083038185885af1158015614005573d6000803e3d6000fd5b50505050506040513d601f19601f8201168201806040525081019061402a9190615c58565b505073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e4971663ad967e157f0000000000000000000000009d39a5de30e57443bff2a8307a4256c8797a349760008b8103908f81806040519080825280601f01601f1916602001820160405280156140bc576020820181803683370190505b506040518763ffffffff1660e01b81526004016140de96959493929190615bff565b60408051808303816000875af11580156140fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141209190615c58565b505067ffffffffffffffff605287901c1692508460a0015160ff8416600885901c901b61414d9190615a12565b925067ffffffffffffffff609287901c169150846080015160ff8316600884901c901b61417a9190615a12565b9150609261418d8360386008600161458d565b901b60526141a08560386008600061458d565b7ffffffffffffc00000000000000000000000000000003ffffffffffffffffffff8916911b171760005560408051338152602081018a90529081018a905273ffffffffffffffffffffffffffffffffffffffff8c1660608201527f80fd9cc6b1821f4a510e45ffce6852ea3404807b5d3d833ffa85664408afcb669060800160405180910390a1505050505050505b94509492505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e49716146142ab576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179296004820152602401610272565b6000546001166000036142ee576040517f60121cca00000000000000000000000000000000000000000000000000000000815261792a6004820152602401610272565b614325846142fe83850185615c7c565b7f00000000000000000000000052aa899454998be5b000ad077a46bbe360f4e497866154ce565b50505050565b600060ff82901d80831803617fff81111561434557600080fd5b700100000000000000000000000000000000600182161561437357506fff9dd7de423466c20352b1246ce4856f5b6002821615614392576fff3bd55f4488ad277531fa1c725a66d00260801c5b60048216156143b1576ffe78410fd6498b73cb96a6917f8532590260801c5b60088216156143d0576ffcf2d9987c9be178ad5bfeffaa1232730260801c5b60108216156143ef576ff9ef02c4529258b057769680fc6601b30260801c5b602082161561440e576ff402d288133a85a17784a411f7aba0820260801c5b604082161561442d576fe895615b5beb6386553757b0352bda900260801c5b608082161561444c576fd34f17a00ffa00a8309940a15930391a0260801c5b61010082161561446c576fae6b7961714e20548d88ea5123f9a0ff0260801c5b61020082161561448c576f76d6461f27082d74e0feed3b388c0ca10260801c5b6104008216156144ac576f372a3bfe0745d8b6b19d985d9a8b85bb0260801c5b6108008216156144cc576f0be32cbee48979763cf7247dd7bb539d0260801c5b6110008216156144eb576e8d4f70c9ff4924dac37612d1e2921e0260801c5b612000821615614509576d4e009ae5519380809a02ca7aec770260801c5b614000821615614525576b17c45e641b6e95dee056ff100260801c5b600091507f80000000000000000000000000000000000000000000000000000000000000008416614583577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0463ffffffff81161561458357600191505b60201c0192915050565b600080856fffffffffffffffffffffffffffffffff8111156145b05760809150811c5b67ffffffffffffffff8111156145c8576040918201911c5b63ffffffff8111156145dc576020918201911c5b61ffff8111156145ee576010918201911c5b60ff8111156145ff576008918201911c5b600f811115614610576004918201911c5b6003811115614621576002918201911c5b6001811115614631576001820191505b801561463e576001820191505b508481101561464a5750835b848103905085811c6000821184161561469957600181019050806001871b0361469957506001908101907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86011b5b6001851b82106146a857600080fd5b90931b909201949350505050565b601681901c633fffffff166000818152600760205260408120549091829182600160c483901c811690036146f4575060c581901c6207ffff1661472c565b5060c581901c6207ffff1660008190039061472c57507f80000000000000000000000000000000000000000000000000000000000000005b600061473788615595565b90508082136147465780614748565b815b94507f8000000000000000000000000000000000000000000000000000000000000000850361479b57867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000116965061488a565b8085036148115760008512156147de57600385600003901b877fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc000011617965061488a565b600385901b877fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc000011660041717965061488a565b6000848152600760205260408120557ffffffffffffffffffffffffffffffffffffffffffffc00000000000000000001909616623ffffc60c284901c1617660fffffffc00000609084901c16177fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff840160341b17600217955b869550505050509250929050565b60008083126148ab5761010083056148b6565b600161010084820105035b905061010081028303826148de576000828152600460205260409020546001821b19166148f3565b6000828152600460205260409020546001821b175b60009283526004602052604090922091909155505050565b600080600080612710851015614951576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179336004820152602401610272565b60008661496b6c01000000000000000000000000886159e8565b6149759190615a54565b905061498081614ae8565b60019091019550905061271061271f820204905060606149a088836159e8565b901c92506149ae8684615a12565b600086815260056020526040812054600181901c62ffffff16965091935085158015906149dc575060018216155b15614a1d5766ffffffffffffff602183901c1660ff601984901c161b614a0286826159ff565b915080600003614a1757614a17886001614898565b50614a71565b8515614a5d576000878152600660209081526040808320600360028b0181810486529190935292208054601986901c929093066055029190911b90911790555b856001019550849050614a71876001614898565b612710811015614ab1576040517f60121cca0000000000000000000000000000000000000000000000000000000081526179316004820152602401610272565b6019614ac28260386008600061458d565b6000898152600560205260409020911b600188901b179055509497939650919450925050565b600080630235b88083107473d85bca016a2338b31715f8e13054c005f8b995d384111715614b1557600080fd5b6c010000000000000000000000008310600081614b4b57506c010000000000000000000000006a52b7d2dcc80cd2e40000008502045b8115614b6e57507652b7d2dcc80cd2e40000000000000000000000000000008490045b6f037af932b2affa9738cc6c38ca5278318110614baf57614000841793506f037af932b2affa9738cc6c38ca5278316a52b7d2dcc80cd2e400000082020490505b6d010f7a088a76f267264caa114f0a8110614bec57612000841793506d010f7a088a76f267264caa114f0a6a52b7d2dcc80cd2e400000082020490505b6b95da74f87f839fc2e0dc5bd98110614c2557611000841793506b95da74f87f839fc2e0dc5bd96a52b7d2dcc80cd2e400000082020490505b6b06f55dedafd8491caed5a1b88110614c5e57610800841793506b06f55dedafd8491caed5a1b86a52b7d2dcc80cd2e400000082020490505b6b017fdd10ee11e624491b4cc18110614c9757610400841793506b017fdd10ee11e624491b4cc16a52b7d2dcc80cd2e400000082020490505b6ab23131bf0c30217b0a2c698110614cce57610200841793506ab23131bf0c30217b0a2c696a52b7d2dcc80cd2e400000082020490505b6a79683edcb9280d797aded78110614d0557610100841793506a79683edcb9280d797aded76a52b7d2dcc80cd2e400000082020490505b6a64366e2f9919f0d9b0dc908110614d3b576080841793506a64366e2f9919f0d9b0dc906a52b7d2dcc80cd2e400000082020490505b6a5b0bcda5a78850646b0a818110614d71576040841793506a5b0bcda5a78850646b0a816a52b7d2dcc80cd2e400000082020490505b6a56c840f992c70f959ae8108110614da7576020841793506a56c840f992c70f959ae8106a52b7d2dcc80cd2e400000082020490505b6a54b9cd178695194f9be0a08110614ddd576010841793506a54b9cd178695194f9be0a06a52b7d2dcc80cd2e400000082020490505b6a53b7458aff204b5e65d6818110614e13576008841793506a53b7458aff204b5e65d6816a52b7d2dcc80cd2e400000082020490505b6a53372a2f38c240d689e4008110614e49576004841793506a53372a2f38c240d689e4006a52b7d2dcc80cd2e400000082020490505b6a52f76617a04499e66400008110614e7f576002841793506a52f76617a04499e66400006a52b7d2dcc80cd2e400000082020490505b6a52d79660f3dec355c000008110614eb5576001841793506a52d79660f3dec355c000006a52b7d2dcc80cd2e400000082020490505b81614ecd57806a52b7d2dcc80cd2e400000086020492505b8115614ee9579219926a52d79660f3dec355c000008582020492505b505082811115614ef857600080fd5b915091565b600066ffffffffffffff604984901c1660ff604185901c161b808203614f27576000915050614f89565b612710613fff60a286901c168402046401ffffffff608186901c16420362ffffff60b087901c16614f5882846159e8565b614f629190615a54565b9050808311614f72576000614f76565b8083035b9350508084038381111561076757925050505b92915050565b600080600080600085875af1905080614fd9576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155a6004820152602401610272565b505050565b67ffffffffffffffff605b82901c811690609b83901c16811580615000575080155b1561503c576040517fd50d7512000000000000000000000000000000000000000000000000000000008152620111716004820152602401610272565b61ffff8316603a84901c6401ffffffff16428181039160ea87901c617fff16911480615066575082155b806150715750806001145b1561507e57505050915091565b64496cebb80084840283020484019350617fff60db87901c169250826001036150a957505050915091565b826001166001036150fe5760019290921c91826c7e37be2022c0914b2680000000816150d7576150d7615a25565b049250612710601e87901c613fff166b033b2e3c9fd0803ce800000085010204925061512b565b60019290921c916305f5e100601e87901c613fff166127108501026b033b2e3c9fd0803ce8000000020492505b806001166001036151625760011c61271081016b033b2e3c9fd0803ce800000082028161515a5761515a615a25565b049050615198565b60011c61271081016b033b2e3c9fd0803ce800000082028161518657615186615a25565b046b033b2e3c9fd0803ce80000000390505b760a70c3c40a64e6c51999090b65f67d92400000000000008382026127100261ffff881691900402601087901c613fff16612710030292506801b5a660ea44b8000085840283020485019450505050915091565b6000600f83811c9083901c0281681fffffffffffffffff8211615210576022615213565b60235b91821c919050617fff85811690851601810161400081101561523457600080fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00001617fff81111561527357506603ffffffffffff9250614f89915050565b600f9290921b909117949350505050565b6000617fff8381169083160360818110156152bc57600f83811c821b9085901c8602816152b3576152b3615a25565b049150506152c2565b60009150505b9392505050565b6000816fffffffffffffffffffffffffffffffff8311156152eb5760809150811c5b67ffffffffffffffff811115615303576040918201911c5b63ffffffff811115615317576020918201911c5b61ffff811115615329576010918201911c5b60ff81111561533a576008918201911c5b600f81111561534b576004918201911c5b600381111561535c576002918201911c5b600181111561536c576001820191505b8015615379576001820191505b50919050565b6000600f83901c8202816c03ffffffffffffffffffffffff82116153c6576c01ffffffffffffffffffffffff82116153bf576153ba826152c9565b6153c9565b60626153c9565b60635b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdd617fff87168201810194500191821c919050604083111561541657604083600f84901b0103925061541b565b600080fd5b505092915050565b600080600f83901c7fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000603186901b168161545f5761545f615a25565b0490506000604082901c60011461547757604061547a565b60415b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdd81019290921c91617fff8581166040019250613fdd90871690910101818111156154165703600f9190911b179050614f89565b60006040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8516600482015273ffffffffffffffffffffffffffffffffffffffff841660248201528260448201526020600060648360008a5af13d15601f3d116001600051141617169150508061558e576040517fdee51a8a000000000000000000000000000000000000000000000000000000008152620115596004820152602401610272565b5050505050565b60008060008084126155ab5761010084056155b6565b600161010085820105035b600081815260046020526040812054919350859003610100808502919091010190811b811c91505b81156155fd5760016155ef836152c9565b846101000201039350615688565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8301927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f03600101615672577f80000000000000000000000000000000000000000000000000000000000000009350615688565b60008381526004602052604090205491506155de565b505050919050565b801515811461569e57600080fd5b50565b6000602082840312156156b357600080fd5b81516152c281615690565b73ffffffffffffffffffffffffffffffffffffffff8116811461569e57600080fd5b600080600080608085870312156156f657600080fd5b8435935060208501359250604085013591506060850135615716816156be565b939692955090935050565b60006020828403121561573357600080fd5b5035919050565b6000806000806080858703121561575057600080fd5b5050823594602084013594506040840135936060013592509050565b6000806000806080858703121561578257600080fd5b8435935060208501359250604085013561579b816156be565b9150606085013561571681615690565b600080600080606085870312156157c157600080fd5b84356157cc816156be565b935060208501359250604085013567ffffffffffffffff808211156157f057600080fd5b818701915087601f83011261580457600080fd5b81358181111561581357600080fd5b88602082850101111561582557600080fd5b95989497505060200194505050565b815173ffffffffffffffffffffffffffffffffffffffff1681526101a08101602083015161587a602084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060408301516158a2604084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060608301516158ca606084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060808301516158f2608084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a083015161591a60a084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c083015161592f60c084018260ff169052565b5060e083015161594460e084018260ff169052565b50610100838101519083015261012080840151908301526101408084015190830152610160808401519083015261018092830151929091019190915290565b60006020828403121561599557600080fd5b5051919050565b6000602082840312156159ae57600080fd5b81516152c2816156be565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417614f8957614f896159b9565b80820180821115614f8957614f896159b9565b81810381811115614f8957614f896159b9565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615a6357615a63615a25565b500490565b808202600082127f800000000000000000000000000000000000000000000000000000000000000084141615615aa057615aa06159b9565b8181058314821517614f8957614f896159b9565b600082615ac357615ac3615a25565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615b1757615b176159b9565b500590565b8181036000831280158383131683831282161715615b3c57615b3c6159b9565b5092915050565b60007f80000000000000000000000000000000000000000000000000000000000000008203615b7457615b746159b9565b5060000390565b808201828112600083128015821682158216171561541b5761541b6159b9565b6000815180845260005b81811015615bc157602081850181015186830182015201615ba5565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b600073ffffffffffffffffffffffffffffffffffffffff8089168352876020840152866040840152808616606084015280851660808401525060c060a0830152615c4c60c0830184615b9b565b98975050505050505050565b60008060408385031215615c6b57600080fd5b505080516020909101519092909150565b600060208284031215615c8e57600080fd5b81356152c2816156be56fea2646970667358221220bd1cedb4ef1e580fa4bec1e12a6abad1d1668013e0141254313f07a6b3f9696464736f6c63430008150033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.