ETH Price: $3,339.85 (+0.85%)
 

Overview

ETH Balance

0 ETH

ETH Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

1 Token Transfer found.

Parent Transaction Hash Block From To
View All Internal Transactions

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
InfinityPoolsQuoter

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 300 runs

Other Settings:
shanghai EvmVersion
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {IInfinityPoolFactory} from "src/interfaces/IInfinityPoolFactory.sol";
import {IInfinityPoolPaymentCallback} from "src/interfaces/IInfinityPoolPaymentCallback.sol";
import {IInfinityPool} from "src/interfaces/IInfinityPool.sol";
import {Spot} from "src/libraries/external/Spot.sol";
import {fromUint256, Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {OptQuad} from "src/types/Optional/OptQuad.sol";
import {OPT_QUAD_NONE} from "src/Constants.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {BytesLib} from "src/periphery/libraries/BytesLib.sol";

//Get estimated amountOut decoded from the custom error message
contract InfinityPoolsQuoter is IInfinityPoolPaymentCallback {
    error PoolDoesNotExist();

    function infinityPoolPaymentCallback(int256 amount0, int256 amount1, bytes calldata data) external {}

    constructor() {}

    function getAmountsOut(IInfinityPoolFactory factory, address tokenIn, address tokenOut, int256 splits, uint256 amountIn, uint256)
        external
        returns (uint256 amountOut, Spot.SpotSwapParams memory spotSwapParams)
    {
        IInfinityPool pool = IInfinityPool(factory.getPool(tokenIn, tokenOut, splits));
        if (address(pool) == address(0)) revert PoolDoesNotExist();

        (address token0,,) = pool.getPoolInfo();

        Quad tenToPowerTokenDecimals = fromUint256(10 ** IERC20Metadata(tokenIn).decimals());

        spotSwapParams = Spot.SpotSwapParams({
            shove: fromUint256(amountIn) / tenToPowerTokenDecimals,
            ofToken: token0 != tokenIn,
            limitPrice: OPT_QUAD_NONE,
            remainingAmount: OPT_QUAD_NONE
        });

        (int256 amount0, int256 amount1) = quoteSwap(pool, spotSwapParams);
        amountOut = token0!=tokenIn? uint256(-amount0):uint256(-amount1);
    }

    function quoteSwap(IInfinityPool pool, Spot.SpotSwapParams memory spotSwapParams) public returns (int256 amount0, int256 amount1) {
        try pool.swap(spotSwapParams, address(this), "0x1") {} //pass non zero data to trigger custom error revert
        catch (bytes memory reason) {
            ( amount0,  amount1) = abi.decode(BytesLib.slice(reason, 4, reason.length - 4), (int256, int256)); //ignore the first 4 bytes

        }
    }

    function getAmountsIn(IInfinityPoolFactory factory, address tokenIn, address tokenOut, int256 splits, uint256 amountOut, uint256)
        external
        returns (uint256 amountIn, Spot.SpotSwapParams memory spotSwapParams)
    {
        IInfinityPool pool = IInfinityPool(factory.getPool(tokenIn, tokenOut, splits));
        if (address(pool) == address(0)) revert PoolDoesNotExist();

        (address token0,,) = pool.getPoolInfo();

        Quad tenToPowerTokenDecimals = fromUint256(10 ** IERC20Metadata(tokenOut).decimals());

        spotSwapParams = Spot.SpotSwapParams({
            shove: fromUint256(amountOut).neg() / tenToPowerTokenDecimals,
            ofToken: token0 != tokenOut,
            limitPrice: OPT_QUAD_NONE,
            remainingAmount: OPT_QUAD_NONE
        });

      
        (int256 amount0, int256 amount1) = quoteSwap(pool, spotSwapParams);
        amountIn= token0!=tokenOut? uint256(amount0):uint256(amount1);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

interface IInfinityPoolFactory {
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);
    event PoolCreated(address indexed token0, address indexed token1, int256 splits, address pool, uint8 decimals0, uint8 decimals1);

    function owner() external view returns (address);
    function getPool(address tokenA, address tokenB, int256 splits) external view returns (address pool);
    function getPool(address tokenA, address tokenB) external view returns (address pool);

    function createPool(address tokenA, address tokenB, int256 splits) external returns (address pool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

interface IInfinityPoolPaymentCallback {
    function infinityPoolPaymentCallback(int256 amount0, int256 amount1, bytes calldata data) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "src/types/ABDKMathQuad/Quad.sol";
import {NewLoan} from "src/libraries/external/NewLoan.sol";
import {Spot} from "src/libraries/external/Spot.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {Structs} from "src/libraries/external/Structs.sol";
import {SwapperInternal} from "src/libraries/external/SwapperInternal.sol";

interface IInfinityPool {
    enum Action {
        SWAP,
        NEW_LOAN,
        UNWIND,
        REFLOW,
        RESET,
        POUR,
        DRAIN,
        COLLECT,
        TAP
    }

    event PoolInitialized(Quad startPrice, Quad quadVar0, Quad twapSpread, Quad poolDate);

    event Borrowed(
        address indexed user,
        uint256 indexed swapperNum,
        Quad[] owedPotential,
        int256 startBin,
        int256 strikeBin,
        Quad tokenMix,
        Quad lockinEnd,
        OptInt256 deadEra,
        bool token,
        OptInt256 twapUntil
    );

    event LiquidityAdded(
        address indexed user,
        int256 startTub,
        int256 stopTub,
        Quad liquidity,
        uint256 indexed lpNum,
        int256 amount0,
        int256 amount1,
        int256 earnEra,
        Quad poolDate
    );

    event LiquidityTapped(uint256 indexed lpNum, Quad poolDate);

    event LiquidityDrained(uint256 indexed lpNum, address indexed receiver, int256 amount0, int256 amount1, Quad poolDate);

    event LiquidityCollected(uint256 indexed lpNum, address indexed receiver, int256 amount0, int256 amount1, Quad poolDate);

    event SwapperCreated(uint256 id, Quad poolDate, Quad tokenMixParam, SwapperInternal.Info info);

    event SwapperReset(
        uint256 indexed swapperId,
        OptInt256 deadEra,
        Quad tokenMix,
        bool fixedToken,
        OptInt256 twapUntil,
        address receiver,
        int256 amount0,
        int256 amount1,
        Quad poolDate
    );

    event SwapperReflow(
        uint256 indexed swapperId,
        Quad tokenMix,
        bool fixedToken,
        OptInt256 twapUntil,
        address receiver,
        int256 amount0,
        int256 amount1,
        Quad poolDate
    );

    event SwapperUnwind(uint256 indexed swapperId, address receiver, Quad poolDate);

    error InvalidLPNumber();

    function getPoolPriceInfo() external returns (Structs.PoolPriceInfo memory);
    function getPoolInfo() external view returns (address, address, int256);

    function newLoan(NewLoan.NewLoanParams memory params, bytes calldata data) external returns (uint256, int256, int256);
    function swap(Spot.SpotSwapParams memory params, address receiver, bytes calldata data) external returns (int256 amount0, int256 amount1);

    function unwind(uint256 swapperId, address receiver, bytes calldata data) external returns (int256 amount0, int256 amount1);
    function reflow(uint256 swapperId, Quad tokenMix, bool fixedToken, OptInt256 twapUntil, address receiver, bytes calldata data)
        external
        returns (int256 amount0, int256 amount1);
    function reset(uint256 swapperId, OptInt256 deadEra, Quad tokenMix, bool fixedToken, OptInt256 twapUntil, address receiver, bytes calldata data)
        external
        returns (int256, int256);
    function getPourQuantities(int256 startTub, int256 stopTub, Quad liquidity) external returns (int256, int256);
    function pour(int256 startTub, int256 stopTub, Quad liquidity, bytes calldata data) external returns (uint256, int256, int256, int256);

    function getLpCount() external view returns (uint256);
    function getLiquidityPosition(uint256 lpNum) external returns (Structs.LiquidityPosition memory);
    function drain(uint256 lpNum, address receiver, bytes calldata data) external returns (int256, int256);
    function collect(uint256 lpNum, address receiver, bytes calldata data) external returns (int256, int256);
    function tap(uint256 lpNum) external;
    function advance() external;

    function doActions(Action[] calldata actions, bytes[] calldata actionDatas, address receiver, bytes calldata data)
        external
        returns (int256 amount0, int256 amount1);

    function getSwappersCount() external view returns (uint256);

    function getBinLiquids(uint256 startBin, uint256 stopBin) external returns (Quad[] memory);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {Quad, POSITIVE_ZERO, POSITIVE_ONE, fromInt256} from "src/types/ABDKMathQuad/Quad.sol";
import {OptQuad, toOptQuad} from "src/types/Optional/OptQuad.sol";
import {max, min, exp, log, abs} from "src/types/ABDKMathQuad/Math.sol";
import {Z, I} from "src/Constants.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {BucketRolling} from "src/libraries/internal/BucketRolling.sol";
import {EachPayoff} from "src/libraries/internal/EachPayoff.sol";
import {liquid, mid, sqrtStrike, exFee, gamma, spotFee, BINS, O, PoolHelper} from "src/libraries/helpers/PoolHelper.sol";
import {PiecewiseGrowthNew} from "src/libraries/internal/DeadlineJumps.sol";

library Spot {
    using GapStagedFrame for GapStagedFrame.Info;
    using GrowthSplitFrame for GrowthSplitFrame.Info;
    using JumpyFallback for JumpyFallback.Info;
    using BucketRolling for BucketRolling.Info;
    using EachPayoff for PoolState;
    using PoolHelper for PoolState;

    error InvalidTickBin(int256 tickBin);
    error InvalidPushAmount();

    function updateFees(PoolState storage self, bool isUp) internal {
        self.fees[0].move(self, isUp);
        self.fees[1].move(self, isUp);
    }

    function _uptick(PoolState storage self) internal {
        if (self.tickBin >= ((BINS(self.splits) - 1))) revert InvalidTickBin(self.tickBin);

        self.joinStaged.flush(self, self.tickBin + 1);
        updateFees(self, true);
        self.tickBin = (self.tickBin + 1);
        self.binFrac = POSITIVE_ZERO;
    }

    function _downtick(PoolState storage self) internal {
        if (self.tickBin <= (0)) revert InvalidTickBin(self.tickBin);
        self.joinStaged.flush(self, self.tickBin - 1);
        updateFees(self, false);
        self.tickBin = (self.tickBin - 1);
        self.binFrac = POSITIVE_ONE;
    }

    function onSlip(PoolState storage pool, Quad qMove) public {
        if (qMove >= POSITIVE_ZERO) pool.dayMove.add(pool, qMove);
    }

    struct SpotSwapParams {
        Quad shove;
        bool ofToken; //token of push amount (true if token1, false if token0)
        OptQuad limitPrice;
        OptQuad remainingAmount; // used if user wants to have this much of remainingAmount relative to last action
    }

    event SpotSwapEvent(SpotSwapParams params, address receiver, UserPay.Info swapped);

    struct SwapLocalVars {
        bool pushing;
        Quad maxTokens;
        Quad input;
        Quad output;
        Quad varMove;
        bool pullToken;
        UserPay.Info transfer;
        Quad lastPrice;
        Quad liquid;
        Quad outFactor;
        Quad qEnd;
        Quad sweepPrice;
        Quad qNew;
        Quad qStep;
        Quad endPrice;
        Quad inFactor;
        Quad stepInput;
        Quad sweepInput;
        Quad qNew_;
        Quad qStep_;
        Quad stepOutput;
        Quad charged;
        Quad gained;
        OptQuad limitPrinv;
        Quad lastPrinv;
        Quad sweepPrinv;
        Quad endPrinv;
    }

    function swap(PoolState storage pool, Quad shove, bool token, OptQuad limitPrice) public returns (UserPay.Info memory) {
        SwapLocalVars memory vars;
        vars.pushing = shove > O;
        vars.maxTokens = abs(shove);
        vars.input = O;
        vars.output = O;
        vars.varMove = O;
        vars.pullToken = vars.pushing ? !token : token;
        if (!vars.pullToken) {
            vars.lastPrice = exp(pool.logBin() * (fromInt256(pool.tickBin - pool.BINS() / 2) + pool.binFrac + pool.halfspread));
            while ((limitPrice.isDefined() ? vars.lastPrice < limitPrice.get() : true) && (vars.pushing ? vars.input : vars.output) < vars.maxTokens)
            {
                if (pool.binFrac == fromInt256(1)) {
                    if (pool.tickBin == pool.BINS() - 1) break;
                    pool.uptick();
                }
                vars.liquid = pool.liquid();
                vars.outFactor = pool.epsilon_() / pool.sqrtMid() * vars.liquid;
                vars.qEnd = vars.pushing || vars.liquid <= O
                    ? fromInt256(1)
                    : min(pool.binFrac + (vars.maxTokens - vars.output) / vars.outFactor, fromInt256(1));
                vars.sweepPrice =
                    exp(pool.logBin() * (fromInt256(pool.tickBin - pool.BINS() / 2) + fromInt256(2) * vars.qEnd - pool.binFrac + pool.halfspread));
                if (vars.liquid > O) {
                    vars.endPrice = limitPrice.isDefined() ? min(limitPrice.get(), vars.sweepPrice) : vars.sweepPrice;
                    vars.inFactor = vars.outFactor / (fromInt256(2) * pool.logBin());
                    vars.stepInput = vars.inFactor * (vars.endPrice - vars.lastPrice);
                    vars.sweepInput = vars.input + vars.stepInput;
                    if (vars.pushing && vars.sweepInput > vars.maxTokens) {
                        vars.stepInput = vars.maxTokens - vars.input;
                        vars.lastPrice = vars.lastPrice + vars.stepInput / vars.inFactor;
                        vars.input = vars.maxTokens;
                    } else {
                        vars.lastPrice = vars.endPrice;
                        vars.input = vars.sweepInput;
                    }
                    vars.qNew_ = vars.lastPrice == vars.sweepPrice
                        ? vars.qEnd
                        : min(
                            (log(vars.lastPrice) / pool.logBin() - (fromInt256(pool.tickBin - pool.BINS() / 2) + pool.halfspread) + pool.binFrac)
                                / fromInt256(2),
                            fromInt256(1)
                        );
                    vars.qStep_ = vars.qNew_ - pool.binFrac;
                    vars.stepOutput = vars.outFactor * vars.qStep_;
                    if (!vars.pushing && vars.qEnd < fromInt256(1) && vars.endPrice == vars.sweepPrice) vars.output = vars.maxTokens;
                    else vars.output = vars.output + vars.stepOutput;
                    vars.charged = vars.stepInput - vars.stepOutput * pool.mid();
                    vars.gained = vars.charged / vars.liquid;
                    spotFee_(pool, !vars.pullToken, (fromInt256(1) - pool.used.nowAt(pool, pool.tickBin) * pool.deflator) * vars.gained, vars.charged);
                    vars.varMove = vars.varMove + fromInt256(4) * vars.gained / pool.sqrtMid();
                    (vars.qNew, vars.qStep) = (vars.qNew_, vars.qStep_);
                } else {
                    vars.lastPrice = vars.sweepPrice;
                    (vars.qNew, vars.qStep) = (fromInt256(1), fromInt256(1) - pool.binFrac);
                }
                pool.halfspread = pool.halfspread + vars.qStep;
                pool.binFrac = vars.qNew;
            }
            vars.transfer = UserPay.Info(-vars.output, vars.input);
        } else {
            vars.limitPrinv = limitPrice.isDefined() ? toOptQuad(fromInt256(1) / limitPrice.get()) : limitPrice;
            vars.lastPrinv = exp(-pool.logBin() * (fromInt256(pool.tickBin - pool.BINS() / 2) + pool.binFrac - pool.halfspread));
            while (
                (vars.limitPrinv.isDefined() ? vars.lastPrinv < vars.limitPrinv.get() : true)
                    && (vars.pushing ? vars.input : vars.output) < vars.maxTokens
            ) {
                if (pool.binFrac == fromInt256(0)) {
                    if (pool.tickBin == 0) break;
                    pool.downtick();
                }
                vars.liquid = pool.liquid();
                vars.outFactor = pool.epsilon_() * pool.sqrtMid() * vars.liquid;
                vars.qEnd = vars.pushing || vars.liquid <= O ? fromInt256(0) : max(pool.binFrac - (vars.maxTokens - vars.output) / vars.outFactor, O);
                vars.sweepPrinv =
                    exp(-pool.logBin() * (fromInt256(pool.tickBin - pool.BINS() / 2) + fromInt256(2) * vars.qEnd - pool.binFrac - pool.halfspread));
                if (vars.liquid > O) {
                    vars.endPrinv = vars.limitPrinv.isDefined() ? min(vars.limitPrinv.get(), vars.sweepPrinv) : vars.sweepPrinv;
                    vars.inFactor = vars.outFactor / (fromInt256(2) * pool.logBin());
                    vars.stepInput = vars.inFactor * (vars.endPrinv - vars.lastPrinv);
                    vars.sweepInput = vars.input + vars.stepInput;
                    if (vars.pushing && vars.sweepInput > vars.maxTokens) {
                        vars.stepInput = vars.maxTokens - vars.input;
                        vars.lastPrinv = vars.lastPrinv + vars.stepInput / vars.inFactor;
                        vars.input = vars.maxTokens;
                    } else {
                        vars.lastPrinv = vars.endPrinv;
                        vars.input = vars.sweepInput;
                    }
                    vars.qNew_ = vars.lastPrinv == vars.sweepPrinv
                        ? vars.qEnd
                        : max(
                            (log(vars.lastPrinv) / (-pool.logBin()) - (fromInt256(pool.tickBin - pool.BINS() / 2) - pool.halfspread) + pool.binFrac)
                                / fromInt256(2),
                            O
                        );
                    vars.qStep_ = pool.binFrac - vars.qNew_;
                    vars.stepOutput = vars.outFactor * vars.qStep_;
                    if (!vars.pushing && vars.qEnd > fromInt256(0) && vars.endPrinv == vars.sweepPrinv) vars.output = vars.maxTokens;
                    else vars.output = vars.output + vars.stepOutput;
                    vars.charged = vars.stepInput - vars.stepOutput / pool.mid();
                    vars.gained = vars.charged / vars.liquid;
                    spotFee_(pool, !vars.pullToken, (fromInt256(1) - pool.used.nowAt(pool, pool.tickBin) * pool.deflator) * vars.gained, vars.charged);
                    vars.varMove = vars.varMove + fromInt256(4) * vars.gained * pool.sqrtMid();
                    (vars.qNew, vars.qStep) = (vars.qNew_, vars.qStep_);
                } else {
                    vars.lastPrinv = vars.sweepPrinv;
                    (vars.qNew, vars.qStep) = (fromInt256(0), pool.binFrac);
                }
                pool.halfspread = pool.halfspread + vars.qStep;
                pool.binFrac = vars.qNew;
            }
            vars.transfer = UserPay.Info(vars.input, -vars.output);
        }
        pool.dayMove.add(pool, vars.varMove);
        return vars.transfer;
    }

    function spotFee_(PoolState storage pool, bool inToken, Quad accrual, Quad) public {
        PiecewiseGrowthNew.Info storage fee = pool.fees[inToken ? 1 : 0].live(pool.splits);
        fee.accrued = fee.accrued + accrual;
    }

    function swap(PoolState storage pool, SpotSwapParams memory params) external returns (UserPay.Info memory) {
        if (params.shove.isNaN()) revert InvalidPushAmount();
        return swap(pool, params.shove, params.ofToken, params.limitPrice);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "./ValueType.sol";
import "./Constants.sol";
import "./Casting.sol";

function isZero(Quad x) pure returns (bool) {
    return x == POSITIVE_ZERO || x == NEGATIVE_ZERO;
}

function isPositive(Quad x) pure returns (bool) {
    return x > POSITIVE_ZERO;
}

function isNonnegative(Quad x) pure returns (bool) {
    return x >= NEGATIVE_ZERO;
}

function isNegative(Quad x) pure returns (bool) {
    return x < NEGATIVE_ZERO;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "src/types/ABDKMathQuad/Quad.sol";

type OptQuad is bytes16;

using {isDefined, get} for OptQuad global;

error OptQuadValueIsUndefined();

function isDefined(OptQuad x) pure returns (bool) {
    return !Quad.wrap(OptQuad.unwrap(x)).isNaN();
}

function get(OptQuad x) pure returns (Quad result) {
    if (!isDefined(x)) revert OptQuadValueIsUndefined();
    result = Quad.wrap(OptQuad.unwrap(x));
}

function toOptQuad(Quad x) pure returns (OptQuad result) {
    result = OptQuad.wrap(Quad.unwrap(x));
}

File 8 of 52 : Constants.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "./types/ABDKMathQuad/Quad.sol";
import {OPT_INT256_NONE, OptInt256, wrap} from "./types/Optional/OptInt256.sol";
import {OptQuad,toOptQuad} from "./types/Optional/OptQuad.sol";

int8 constant JUMPS = 13;
int8 constant JUMP_NONE = type(int8).max;
uint256 constant MIN_SPLITS = 12;
uint256 constant MAX_SPLITS = 19;
uint256 constant TICK_SUB_SLITS = MAX_SPLITS - MIN_SPLITS + 1;

int256 constant TUBS = int256(1 << MIN_SPLITS);

int256 constant DEADERA_NONE = OPT_INT256_NONE;

uint128 constant LOG_WORD_SIZE = 7;

int128 constant WORD_SIZE = int128(int128(1) << LOG_WORD_SIZE);

bool constant Z = false;
bool constant I = true;

int256 constant EPOCH = 1735740000;

OptInt256 constant NEVER_AGO = OptInt256.wrap(-1 << 30);
//precalulated values
// Step 0: 0.9996616064962437
// Step 1: 0.9993233275026507
// Step 2: 0.9986471128909702
// Step 3: 0.9972960560854701
// Step 4: 0.9945994234836332
// Step 5: 0.9892280131939755
// Step 6: 0.9785720620877001
// Step 7: 0.9576032806985737
// Step 8: 0.9170040432046712
// Step 9: 0.8408964152537145
// Step 10: 0.7071067811865476
// Step 11: 0.5
// Step 12: 0.25

Quad constant DEFLATOR_STEP_0 = Quad.wrap(bytes16(0x3ffeffd3a565efb64eabbbe3a5146f15));
Quad constant DEFLATOR_STEP_1 = Quad.wrap(bytes16(0x3ffeffa74ea381efc217a773f15c025f));
Quad constant DEFLATOR_STEP_2 = Quad.wrap(bytes16(0x3ffeff4eaca4391b5da33e743691f72a));
Quad constant DEFLATOR_STEP_3 = Quad.wrap(bytes16(0x3ffefe9d96b2a23d914a6037442fde32));
Quad constant DEFLATOR_STEP_4 = Quad.wrap(bytes16(0x3ffefd3c22b8f71f10975ba4b32bcf3a));
Quad constant DEFLATOR_STEP_5 = Quad.wrap(bytes16(0x3ffefa7c1819e90d82e90a7e74b263c2));
Quad constant DEFLATOR_STEP_6 = Quad.wrap(bytes16(0x3ffef50765b6e4540674f84b762862bb));
Quad constant DEFLATOR_STEP_7 = Quad.wrap(bytes16(0x3ffeea4afa2a490d9858f73a18f5db30));
Quad constant DEFLATOR_STEP_8 = Quad.wrap(bytes16(0x3ffed5818dcfba48725da05aeb66e0dd));
Quad constant DEFLATOR_STEP_9 = Quad.wrap(bytes16(0x3ffeae89f995ad3ad5e8734d1773205a));
Quad constant DEFLATOR_STEP_10 = Quad.wrap(bytes16(0x3ffe6a09e667f3bcc908b2fb1366ea95));
Quad constant DEFLATOR_STEP_11 = Quad.wrap(bytes16(0x3ffe0000000000000000000000000000));
Quad constant DEFLATOR_STEP_12 = Quad.wrap(bytes16(0x3ffd0000000000000000000000000000));

//np.log(np.float128(2))
Quad constant LAMBDA = Quad.wrap(bytes16(0x3ffe62e42fefa39ef35793c7673007e5));

Quad constant LOG1PPC = Quad.wrap(0x3ff8460d6ccca3676b71e159f1d244a4);

// 	final val minRate = 1e-3 / 365.2425 / ln2 // 0.1% annually
Quad constant MIN_RATE = Quad.wrap(0x3fed0913e12d7aa29793a679b84dd45f);

Quad constant LN2 = Quad.wrap(bytes16(0x3ffe62e42fefa39ef35793c7673007e6));

Quad constant TWAP_SPREAD_DEFAULT = Quad.wrap(bytes16(0x3ff1a36e2eb1c432ca57a786c226809d));

// PERIODIC_APPROX = [
//     (0.4706553526183245, -0.05091131334578034, -0.0732538714640282, -0.007292900708078191),
//     (0.5925716471806282, -0.060926965128963895, -0.09017229010013399, -0.009913238478984947)]

Quad constant PERIODIC_APPROX_CONSTANT_0 = Quad.wrap(bytes16(0x3ffde1f37a0cbb71e000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_1 = Quad.wrap(bytes16(0xbffaa110c33a210dd000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_2 = Quad.wrap(bytes16(0xbffb2c0c4063e4eba000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_3 = Quad.wrap(bytes16(0xbff7ddf29208bf6f8000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_4 = Quad.wrap(bytes16(0x3ffe2f658d0a5af4c000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_5 = Quad.wrap(bytes16(0xbffaf31d1b558cc20000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_6 = Quad.wrap(bytes16(0xbffb71587fcfc49bb000000000000000));
Quad constant PERIODIC_APPROX_CONSTANT_7 = Quad.wrap(bytes16(0xbff844d6458847bbc000000000000000));

Quad constant NAN = Quad.wrap(bytes16(0x7fff0000000000000000000000000001));
OptQuad constant OPT_QUAD_NONE = OptQuad.wrap(Quad.unwrap(NAN));

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: UNLICENSED

/*
 * @title Solidity Bytes Arrays Utils
 * @author Gonçalo Sá <[email protected]>
 *
 * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
 *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
 */
pragma solidity ^0.8.20;

library BytesLib {
    function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
        require(_length + 31 >= _length, "slice_overflow");
        require(_start + _length >= _start, "slice_overflow");
        require(_bytes.length >= _start + _length, "slice_outOfBounds");

        bytes memory tempBytes;

        assembly {
            switch iszero(_length)
            case 0 {
                // Get a location of some free memory and store it in tempBytes as
                // Solidity does for memory variables.
                tempBytes := mload(0x40)

                // The first word of the slice result is potentially a partial
                // word read from the original array. To read it, we calculate
                // the length of that partial word and start copying that many
                // bytes into the array. The first word we copy will start with
                // data we don't care about, but the last `lengthmod` bytes will
                // land at the beginning of the contents of the new array. When
                // we're done copying, we overwrite the full first word with
                // the actual length of the slice.
                let lengthmod := and(_length, 31)

                // The multiplication in the next line is necessary
                // because when slicing multiples of 32 bytes (lengthmod == 0)
                // the following copy loop was copying the origin's length
                // and then ending prematurely not copying everything it should.
                let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
                let end := add(mc, _length)

                for {
                    // The multiplication in the next line has the same exact purpose
                    // as the one above.
                    let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
                } lt(mc, end) {
                    mc := add(mc, 0x20)
                    cc := add(cc, 0x20)
                } { mstore(mc, mload(cc)) }

                mstore(tempBytes, _length)

                //update free-memory pointer
                //allocating the array padded to 32 bytes like the compiler does now
                mstore(0x40, and(add(mc, 31), not(31)))
            }
            //if we want a zero-length slice let's just return a zero-length array
            default {
                tempBytes := mload(0x40)
                //zero out the 32 bytes slice we are about to return
                //we need to do it because Solidity does not garbage collect
                mstore(tempBytes, 0)

                mstore(0x40, add(tempBytes, 0x20))
            }
        }

        return tempBytes;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {
    Quad,
    fromInt256,
    POSITIVE_ZERO,
    HALF,
    POSITIVE_ONE,
    POSITIVE_TWO,
    POSITIVE_FOUR,
    POSITIVE_EIGHT,
    POSITIVE_NINE,
    POSITIVE_INFINITY,
    POSITIVE_EIGHT_OVER_NINE,
    POSITIVE_ONE_OVER_FOUR,
    wrap
} from "src/types/ABDKMathQuad/Quad.sol";
import {sqrt, exp} from "src/types/ABDKMathQuad/Math.sol";
import "src/types/ABDKMathQuad/MathExtended.sol" as MathExtended;
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {
    Z,
    I,
    LAMBDA,
    MIN_RATE,
    PERIODIC_APPROX_CONSTANT_0,
    PERIODIC_APPROX_CONSTANT_1,
    PERIODIC_APPROX_CONSTANT_2,
    PERIODIC_APPROX_CONSTANT_3,
    PERIODIC_APPROX_CONSTANT_4,
    PERIODIC_APPROX_CONSTANT_5,
    PERIODIC_APPROX_CONSTANT_6,
    PERIODIC_APPROX_CONSTANT_7
} from "src/Constants.sol";
import {DeadlineJumps, DeadlineSet} from "src/libraries/internal/DeadlineJumps.sol";
import {Swapper} from "src/libraries/external/Swapper.sol";
import {SwapperInternal} from "src/libraries/external/SwapperInternal.sol";
import {BINS, binStrike, sqrtStrike, logBin, fluidAt, fracPrice} from "src/libraries/helpers/PoolHelper.sol";
import {BucketRolling} from "src/libraries/internal/BucketRolling.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";

using {MathExtended.expm1} for Quad;

library NewLoan {
    using BucketRolling for BucketRolling.Info;
    using JumpyFallback for JumpyFallback.Info;
    using Swapper for SwapperInternal.Info;
    using SwapperInternal for SwapperInternal.Info;

    error UnavailableLockInPeriod(Quad lockingEnd);
    error UnsupportedLockInPeriod(Quad lockingEnd);
    error InvalidOwedPotential();
    error InvalidBinRange(int256 startBin, int256 stopBin);
    error InvalidLendToken();
    error UnalignableStrikeOutside(int256 strikeBin, int256 startBin, int256 stopBin);
    error UnalignedStrike(int256 strikeBin, int256 startBin);
    error StrikeAtBinZeroNotAllowed();
    error LiquidityRangeContainsPrice(Quad fracPrice);
    error NaNIsNotAllowed();

    function tilter(int256 splits, int256 bin, Quad strike) internal pure returns (Quad) {
        return sqrtStrike(splits, bin) - strike / sqrtStrike(splits, bin);
    }

    function quadVar(PoolState storage pool) internal view returns (Quad) {
        return pool.dayMove.sum(pool) * pool.move2Var;
    }

    function max(Quad x, Quad y) internal pure returns (Quad) {
        return x > y ? x : y;
    }

    struct ComputeLentAtIndexLocalVars {
        Quad inflator;
        Quad rate;
        Quad logm;
        Quad theta;
        Quad cr;
        int256 bin;
        Quad oldUsed;
        Quad minted;
        Quad virtualOwed;
        Quad square;
        Quad newUsed;
    }

    struct LockingEndLocalVars {
        Quad logw;
        Quad geoMean;
        Quad shiftMnot1;
        Quad shiftMis1;
        bool canSkip;
    }

    struct ComputeLentAtIndexParams {
        uint256 index;
        Quad owedPotentialAtIndex;
        int256 startBin;
        Quad lockinEnd;
        Quad logm;
        Quad deltaLogm;
        Quad q;
    }

    function computeLentAtIndex(PoolState storage pool, ComputeLentAtIndexParams memory params, LockingEndLocalVars memory lockingEndVars)
        private
        returns (Quad)
    {
        ComputeLentAtIndexLocalVars memory vars;

        vars.inflator = (POSITIVE_ONE / pool.deflator);

        vars.rate = POSITIVE_ONE / (POSITIVE_TWO * params.q * ((params.q - HALF) * params.logm).exp() - POSITIVE_ONE);

        if ((params.lockinEnd <= POSITIVE_ONE)) {
            lockingEndVars.logw = params.logm;

            if ((params.lockinEnd > POSITIVE_ZERO)) {
                if (!lockingEndVars.canSkip) {
                    lockingEndVars.geoMean = ((params.lockinEnd * quadVar(pool))).sqrt();
                    lockingEndVars.shiftMnot1 = (
                        PERIODIC_APPROX_CONSTANT_0 + (PERIODIC_APPROX_CONSTANT_1 * lockingEndVars.geoMean)
                            + (PERIODIC_APPROX_CONSTANT_2 + quadVar(pool) * PERIODIC_APPROX_CONSTANT_3) * params.lockinEnd
                    ) * lockingEndVars.geoMean;
                    lockingEndVars.shiftMis1 = (
                        PERIODIC_APPROX_CONSTANT_4 + (PERIODIC_APPROX_CONSTANT_5 * lockingEndVars.geoMean)
                            + (PERIODIC_APPROX_CONSTANT_6 + quadVar(pool) * PERIODIC_APPROX_CONSTANT_7) * params.lockinEnd
                    ) * lockingEndVars.geoMean;
                    lockingEndVars.canSkip = true;
                }
                lockingEndVars.logw = max((params.logm + lockingEndVars.shiftMnot1), lockingEndVars.shiftMis1);
                if (!((lockingEndVars.logw > POSITIVE_ZERO))) revert UnavailableLockInPeriod(params.lockinEnd);
            }

            vars.rate = POSITIVE_ONE
                / (
                    ((params.q + HALF) * ((params.q - HALF) * lockingEndVars.logw).expm1())
                        - ((params.q - HALF) * (-(params.q + HALF) * lockingEndVars.logw).expm1())
                );
        } else {
            // params.lockinEnd != POSITIVE_INFINITY does not work!!
            if ((params.lockinEnd.unwrap() != POSITIVE_INFINITY.unwrap())) revert UnsupportedLockInPeriod(params.lockinEnd);
        }
        vars.rate = max(vars.rate, MIN_RATE);
        params.logm = (params.logm + params.deltaLogm);
        vars.theta = (POSITIVE_ONE + (POSITIVE_EIGHT_OVER_NINE * vars.rate));
        vars.cr = (vars.rate / POSITIVE_NINE);
        vars.bin = (params.startBin + int256(params.index));
        vars.oldUsed = (pool.used.nowAt(pool.era, pool.splits, vars.bin) * pool.deflator);
        vars.minted = fluidAt(pool.minted, vars.bin, pool.splits);
        vars.virtualOwed = (((vars.theta + (vars.cr / (POSITIVE_ONE - vars.oldUsed))) * vars.oldUsed) + (params.owedPotentialAtIndex / vars.minted));
        vars.square = ((vars.virtualOwed - vars.theta) + vars.cr);
        vars.newUsed = (
            (((vars.virtualOwed + POSITIVE_ONE) + vars.rate) - (((vars.square * vars.square) + ((POSITIVE_FOUR * vars.cr) * vars.theta))).sqrt())
                / (POSITIVE_TWO * vars.theta)
        );
        return (((vars.newUsed - vars.oldUsed) * vars.minted) * vars.inflator);
    }

    struct NewLoanParams {
        Quad[] owedPotential;
        int256 startBin;
        int256 strikeBin;
        Quad tokenMix;
        Quad lockinEnd;
        OptInt256 deadEra;
        bool token;
        OptInt256 twapUntil;
    }

    struct NewLoanLocalVars {
        int256 stopBin;
        bool lendToken;
        Quad offset;
        Quad q;
        Quad logm;
        Quad deltaLogm;
        Quad inflator;
        Quad[] lent;
        SwapperInternal.Info swapper;
    }

    function newLoan(PoolState storage pool, NewLoanParams memory params) external returns (UserPay.Info memory) {
        if (params.tokenMix.isNaN() && params.lockinEnd.isNaN()) revert NaNIsNotAllowed();
        NewLoanLocalVars memory vars;

        if (!((params.owedPotential.length > 0) && (params.lockinEnd >= POSITIVE_ZERO))) revert InvalidOwedPotential();
        for (int256 i = 0; (i < int256(params.owedPotential.length)); i = (i + 1)) {
            if (((params.owedPotential[uint256(i)]) < POSITIVE_ZERO)) revert InvalidOwedPotential();
        }
        vars.stopBin = (params.startBin + int256(params.owedPotential.length));
        if (!((0 <= params.startBin) && (vars.stopBin <= BINS(pool.splits)))) revert InvalidBinRange(params.startBin, vars.stopBin);
        vars.lendToken = Z;
        if ((vars.stopBin <= pool.tickBin)) {
            vars.lendToken = I;
        } else {
            if ((params.startBin > pool.tickBin)) vars.lendToken = Z;
            else revert LiquidityRangeContainsPrice(fracPrice(pool.splits, pool.tickBin, pool.binFrac));
        }

        if ((vars.stopBin > (params.startBin + 1))) {
            if (!(params.startBin < params.strikeBin && ((params.strikeBin < (vars.stopBin - 1))))) {
                revert UnalignableStrikeOutside(params.strikeBin, params.startBin, vars.stopBin);
            }
            for (int256 index = 0; (index < int256(params.owedPotential.length)); index = (index + 1)) {
                vars.offset = (
                    vars.offset
                        + (
                            params.owedPotential[uint256(index)]
                                * tilter(pool.splits, (params.startBin + index), binStrike(pool.splits, params.strikeBin))
                        )
                );
            }
            if ((vars.offset > POSITIVE_ZERO)) {
                params.owedPotential[0] =
                    (params.owedPotential[0] + (vars.offset / -tilter(pool.splits, (params.startBin), binStrike(pool.splits, params.strikeBin))));
            } else {
                if ((vars.offset < POSITIVE_ZERO)) {
                    uint256 i = (params.owedPotential.length - 1);
                    params.owedPotential[i] =
                        params.owedPotential[i] + (-vars.offset / tilter(pool.splits, vars.stopBin - 1, binStrike(pool.splits, params.strikeBin)));
                }
            }
        } else {
            if ((params.strikeBin != params.startBin)) revert UnalignedStrike(params.strikeBin, params.startBin);
            if ((params.strikeBin == 0)) revert StrikeAtBinZeroNotAllowed();
        }
        vars.q = (((POSITIVE_ONE_OVER_FOUR) + ((POSITIVE_TWO * LAMBDA / quadVar(pool))))).sqrt();
        vars.logm = logBin(pool.splits) * ((fromInt256((params.startBin - pool.tickBin)).abs()) - HALF);
        if ((vars.lendToken == Z)) {
            vars.deltaLogm = logBin(pool.splits);
        } else {
            if ((vars.lendToken == I)) vars.deltaLogm = -logBin(pool.splits);
            else revert InvalidLendToken();
        }

        vars.lent = new Quad[](params.owedPotential.length);

        LockingEndLocalVars memory lockingEndVars;
        ComputeLentAtIndexParams memory computeLentAtIndexParams =
            ComputeLentAtIndexParams(0, params.owedPotential[0], params.startBin, params.lockinEnd, vars.logm, vars.deltaLogm, vars.q);
        for (uint256 index = 0; (index < params.owedPotential.length); index = (index + 1)) {
            computeLentAtIndexParams.index = index;
            computeLentAtIndexParams.owedPotentialAtIndex = params.owedPotential[index];
            vars.lent[index] = computeLentAtIndex(pool, computeLentAtIndexParams, lockingEndVars);
        }
        SwapperInternal.Info memory swapperInfo = SwapperInternal.Info({
            owner: msg.sender,
            startBin: int32(params.startBin),
            strikeBin: int32(params.strikeBin),
            tokenMix: params.tokenMix,
            unlockDate: pool.date + params.lockinEnd,
            deadEra: params.deadEra,
            token: params.token,
            twapUntil: params.twapUntil,
            owed: params.owedPotential,
            lent: vars.lent,
            minted: new Quad[](params.owedPotential.length),
            oweLimit: POSITIVE_ZERO,
            lentCapacity0: POSITIVE_ZERO,
            lentCapacity1: POSITIVE_ZERO
        });
        pool.swappers.push(SwapperInternal.init(swapperInfo, pool));

        SwapperInternal.Info storage swapper = pool.swappers[pool.swappers.length - 1];
        swapper.created(pool);

        return (
            UserPay.Info(
                (swapper.backing(pool, Z) - (vars.lendToken == Z ? (swapper.lentCapacity0 * pool.deflator) : POSITIVE_ZERO)),
                (swapper.backing(pool, I) - (vars.lendToken == I ? (swapper.lentCapacity1 * pool.deflator) : POSITIVE_ZERO))
            )
        );
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

type OptInt256 is int256;

using {isDefined, get} for OptInt256 global;

using {neq as !=} for OptInt256 global;

error OptInt256ValueIsUndefined();

int256 constant OPT_INT256_NONE = type(int256).min;

function isDefined(OptInt256 x) pure returns (bool) {
    return OptInt256.unwrap(x) != OPT_INT256_NONE;
}

function neq(OptInt256 x, OptInt256 y) pure returns (bool) {
    return OptInt256.unwrap(x) != OptInt256.unwrap(y);
}

function get(OptInt256 x) pure returns (int256 result) {
    if (!isDefined(x)) revert OptInt256ValueIsUndefined();
    result = OptInt256.unwrap(x);
}

function wrap(int256 x) pure returns (OptInt256 result) {
    result = OptInt256.wrap(x);
}

File 13 of 52 : Structs.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {LP} from "./LP.sol";
import {Quad} from "src/types/ABDKMathQuad/Quad.sol";

library Structs {
    int8 constant LP_STATE_OPENED = 1; // not earning yet
    int8 constant LP_STATE_ACTIVE = 2; // earning
    int8 constant LP_STATE_CLOSED = 3;

    struct PoolPriceInfo {
        int256 splits;
        int256 tickBin;
        Quad binFrac;
        Quad quadvar;
        Quad poolDate;
    }

    struct LiquidityInfo {
        int256 startTub;
        int256 stopTub;
        uint256 chainId;
        uint256 blockNumber;
        bytes32 blockHash;
        uint256 blockTimestamp;
        int256 splits;
        int256 tickBin;
        Quad binFrac;
        Quad poolDate;
        TubLiquidityInfo[] perTubInfos;
    }

    // per-tub
    struct TubLiquidityInfo {
        int256 tub;
        Quad accrued0;
        Quad accrued1;
        Quad liquidity;
        Quad utilization;
    }

    struct LiquidityPosition {
        uint256 lpNum;
        address token0;
        address token1;
        int256 lowerEdge; // correspond to lowerPrice
        int256 upperEdge; // correspond to upperPrice
        int256 earnEra;
        // N.B. amounts and fees are in token native unit
        int256 lockedAmount0;
        int256 lockedAmount1;
        int256 availableAmount0;
        int256 availableAmount1;
        int256 unclaimedFees0;
        int256 unclaimedFees1;
        int8 state; // LP_STATE_OPENED, LP_STATE_ACTIVE, LP_STATE_CLOSED
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, fromInt256, POSITIVE_ZERO, POSITIVE_ONE, fromUint256} from "src/types/ABDKMathQuad/Quad.sol";
import {OptInt256, wrap} from "src/types/Optional/OptInt256.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {Z, I, LAMBDA, JUMPS, LN2, NEVER_AGO} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve, DropsGroup} from "src/libraries/internal/DeadlineJumps.sol";
import {sqrtStrike, fluidAt, eraDate} from "src/libraries/helpers/PoolHelper.sol";
import {max} from "src/types/ABDKMathQuad/Math.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {DropFaberTotals} from "src/libraries/internal/DropFaberTotals.sol";
import {JumpyAnchorFaber} from "src/libraries/internal/JumpyAnchorFaber.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";
import {AnyPayoff} from "src/libraries/internal/Payoff.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {max as maxInt32} from "src/libraries/internal/Utils.sol";
import {NettingGrowth} from "src/libraries/internal/NettingGrowth.sol";
import {isInfinity} from "src/types/ABDKMathQuad/Helpers.sol";

library SwapperInternal {
    using SafeCast for int256;
    using GapStagedFrame for GapStagedFrame.Info;
    using GrowthSplitFrame for GrowthSplitFrame.Info;
    using JumpyFallback for JumpyFallback.Info;
    using DropFaberTotals for DropFaberTotals.Info;
    using JumpyAnchorFaber for JumpyAnchorFaber.Info;
    using DeadlineSet for DeadlineSet.Info;
    using DeadlineJumps for DeadlineSet.Info;
    using NettingGrowth for NettingGrowth.Info;

    struct Info {
        OptInt256 twapUntil;
        OptInt256 deadEra;
        Quad tokenMix;
        Quad unlockDate;
        Quad oweLimit;
        Quad lentCapacity0;
        Quad lentCapacity1;
        // The owed liquidity is tracked in the Swapper, as lent + interests
        // This quantity is set at creation time and never changes during the swapper lifecycle
        Quad[] owed;
        // The lent liquidity is tracked in the Swapper in inflated units
        // This quantity is set at creation time and never changes during the swapper lifecycle
        Quad[] lent;
        Quad[] minted;
        int32 startBin;
        int32 strikeBin;
        address owner;
        bool token;
    }

    error SwapperExpired(int256 era, int256 deadEra);
    error DeadlineDoesNotCoverLockInDate(int256 deadEra, Quad unlockDate);
    error InvalidTokenMixFraction(Quad tokenMix);
    error TWAPEndsBeforeDeadline(int256 depegEra, int256 deadEra);
    error FixedTokenExceedsCapacity(bool token, Quad tokenMix, Quad capacity);

    function init(Info memory self, PoolState storage pool) external view returns (Info memory) {
        {
            // Inflator is used to record quantities that are independent of the current time
            // To convert them back in quantities at a certain time t, they have to be multiplied by the deflator corresponding at that t time
            Quad inflator = (POSITIVE_ONE / pool.deflator);
            Quad sum0;
            Quad sum1;
            for (uint256 i; i < self.owed.length; i++) {
                // The liquidity tracked in the Swapper is independent of time and therefore it is multiplied by the inflator before being stored
                // The actual owed liquidity at a certain point in time is affected by a time decay factor
                self.owed[i] = self.owed[i] * inflator;
                // Sum of base token and quote token corresponding to the owed liquidity inflated for each bin
                sum0 = sum0 + self.owed[i] / sqrtStrike(pool.splits, self.startBin + int256(i));
                sum1 = sum1 + self.owed[i] * sqrtStrike(pool.splits, self.startBin + int256(i));
            }
            // Total owed liquidity in inflated units
            // This is key to compute many quantities at a certain point in time
            self.oweLimit = (sum0 * sum1).sqrt();
        }
        {
            Quad sum0;
            Quad sum1;
            for (uint256 i; i < self.lent.length; i++) {
                // Sum of base token and quote token corresponding to the lent liquidity inflated for each bin
                // There is no need to multiply this by the inflator because `lent` is already inflated at the time it is computed
                sum0 = sum0 + self.lent[i] / sqrtStrike(pool.splits, self.startBin + int256(i));
                sum1 = sum1 + self.lent[i] * sqrtStrike(pool.splits, self.startBin + int256(i));
            }

            // For lent, no need to multiply by the inflator since it is already inflated
            // These represent the lent capacity in token0 and token1 in inflated terms respectively
            self.lentCapacity0 = pool.epsilon * sum0;
            self.lentCapacity1 = pool.epsilon * sum1;
        }

        return self;
    }

    function signed(bool enable, Quad amount) internal pure returns (Quad) {
        return enable ? amount : -amount;
    }

    /**
     * @dev Updates the fees tracker as a result of borrowing liquidity
     *     @param liquidityRatio: The fees here are expressed as a ratio between the fees in liquidity units (computed as owed - lent)  and the minted liquidity in the specific bin (kind of), so it is a pure number (not liquidity units)
     *     @param bin: The bin for which the fees are updated
     *     @param deadEra: The era at which the fees are due
     */
    function feeCreateOne(PoolState storage pool, Quad liquidityRatio, int256 bin, OptInt256 deadEra) internal {
        pool.joinStaged.flush(pool, bin);

        // The fees are not tracked as tokens directly, to convert these quantities into actual tokens need to multiply by liquidity
        Quad[] memory amounts = new Quad[](1);
        amounts[0] = pool.epsilon * liquidityRatio / sqrtStrike(pool.splits, bin);
        pool.fees[0].create(pool, pool.splits, bin, amounts, deadEra);

        amounts[0] = pool.epsilon * liquidityRatio * sqrtStrike(pool.splits, bin);
        pool.fees[1].create(pool, pool.splits, bin, amounts, deadEra);
    }

    /**
     * @dev Computes the backing of the swapper when the TWAP is not active, either because not defined or expired
     *     @dev This is basically a subfunction of the `backing()` function
     */
    function noTwap(Info memory self, PoolState storage pool, bool token, Quad tokenMix) internal view returns (Quad) {
        if (self.deadEra.isDefined()) {
            // Swapper expires case
            // NOTE: As the pool date gets closer and closer to the Swapper deadEra, the `expiryFraction` gets closer and closer to 1 resulting in the returned token `Z` and token `I` converging to the related lentCapcity values (that are in inflated terms) and weighted by the tokenMix and finally deflated by the current pool deflator
            // NOTE: So in this branch the backing of a certain token is a decreasing function of time and is zero when the Swapper expires
            Quad expiryFraction = (pool.date - eraDate(self.deadEra.get())).exp2();
            if (token == Z) {
                return (
                    (POSITIVE_ONE - expiryFraction) * pool.epsilon * self.oweLimit / sqrtStrike(pool.splits, self.strikeBin)
                        + expiryFraction * self.lentCapacity0
                ) * (POSITIVE_ONE - tokenMix) * pool.deflator;
            }
            // if (token == I)
            else {
                return (
                    (POSITIVE_ONE - expiryFraction) * pool.epsilon * self.oweLimit * sqrtStrike(pool.splits, self.strikeBin)
                        + expiryFraction * self.lentCapacity1
                ) * tokenMix * pool.deflator;
            }
        } else {
            // Swapper does not expire case
            // NOTE: In this branch, the dependency on time is just given by the pool deflator
            // Fixed term loan, no TWAP
            if (token == Z) return pool.epsilon * self.oweLimit * (POSITIVE_ONE - tokenMix) / sqrtStrike(pool.splits, self.strikeBin) * pool.deflator;
            // if (token == I)
            else return pool.epsilon * self.oweLimit * tokenMix * sqrtStrike(pool.splits, self.strikeBin) * pool.deflator;
        }
    }

    struct EndTokenMixLocalVars {
        Quad twapDeflator;
        Quad capacity;
        Quad deadDeflator;
        Quad ratio;
    }

    /**
     * @dev Computes the token mix when the TWAP has ended
     */
    function endTokenMix(Info storage self, PoolState storage pool) internal view returns (Quad res) {
        EndTokenMixLocalVars memory vars;

        if (!self.twapUntil.isDefined()) {
            // Trivial case when no TWAP is defined
            res = self.tokenMix;
        } else {
            // Case when the TWAP is defined: at the time the TWAP ends the exposure is still `tokenMix` since this is what the TWAP is for, but as soon as it expires it changes
            // Token the TWAP offers fixed exposure to
            bool fixedToken = self.token;
            // Era at which the TWAP ends for the swapper
            int256 depegEra = self.twapUntil.get();
            // Deflator at the time the TWAP ends
            vars.twapDeflator = pool.deflator * (pool.date - eraDate(depegEra)).exp2();

            // Computing `vars.capacity` which is the capacity of the swapper in token0 or token1 depending on `fixedToken` value at the time the TWAP ends
            if (self.deadEra.isDefined()) {
                // If the Swapper expires at some point in the future
                if (self.deadEra.get() == depegEra) {
                    // If the time when the TWAP end time is the same as the time when the swapper expires then the capacity is just the lent liquidity at the time the TWAP ends converted into `fixedToken`
                    vars.capacity = vars.twapDeflator * (fixedToken == Z ? self.lentCapacity0 : self.lentCapacity1);
                } else {
                    // If the time when the TWAP ends is different from the time when the swapper expires
                    // Compute the deflator when the swapper expires
                    vars.deadDeflator = pool.deflator * (pool.date - eraDate(self.deadEra.get())).exp2();

                    // The capacity for 0 < depegEra < deadEra case is the sum of 2 components
                    // For the depegEra < t < deadEra period, the capacity depends on the deadDeflator only
                    // For the 0 < t < depegEra period, the capacity depends on the twapDeflator only and therefore the capacity due to deadDeflator is subtracted in this case since it is already factored in the other case
                    vars.capacity = self.oweLimit * (vars.twapDeflator - vars.deadDeflator)
                        * (
                            fixedToken == Z
                                ? pool.epsilon / sqrtStrike(pool.splits, self.strikeBin)
                                : pool.epsilon * sqrtStrike(pool.splits, self.strikeBin)
                        ) + vars.deadDeflator * (fixedToken == Z ? self.lentCapacity0 : self.lentCapacity1);
                }
            } else {
                // If the Swapper never expires
                // The capacity is just the owed liquidity limit at the time the TWAP ends converted into `fixedToken`
                vars.capacity = self.oweLimit * vars.twapDeflator
                    * (fixedToken == Z ? pool.epsilon / sqrtStrike(pool.splits, self.strikeBin) : pool.epsilon * sqrtStrike(pool.splits, self.strikeBin));
            }
            // NOTE: In the validate, it is checked that the tokenMix is <= the capacity of the swapper and therefore the ratio is always between 0 and 1
            vars.ratio = (self.tokenMix / vars.capacity);
            res = (fixedToken == I)
                ? ((vars.ratio > POSITIVE_ONE) ? POSITIVE_ONE : vars.ratio)
                : ((POSITIVE_ONE - vars.ratio) < POSITIVE_ZERO ? POSITIVE_ZERO : (POSITIVE_ONE - vars.ratio));
        }
        return res;
    }

    struct BackingLocalVars {
        bool fixedToken;
        int256 depegEra;
        Quad twapLeft;
        Quad twapDeflator;
        Quad deadDeflator;
        Quad capacity0;
        Quad capacity1;
        Quad owedMean;
        Quad towardToken;
    }

    /**
     * @dev Computes the amount of `token` is required to back the swapper
     *     @dev It is used in functions that change the state of the swapper to recompute the amounts the caller has to pay to back the swapper
     */
    function backing(Info storage self, PoolState storage pool, bool token) internal view returns (Quad) {
        BackingLocalVars memory vars;

        Quad res;

        if (!self.twapUntil.isDefined()) {
            // No TWAP Case, the calculation is performed in `noTwap()` function
            return noTwap(self, pool, token, self.tokenMix);
        } else {
            // TWAP Defined Case
            vars.fixedToken = self.token;
            vars.depegEra = self.twapUntil.get();
            if (pool.era >= vars.depegEra) {
                // TWAP Expired Case, the calculation is performed in `noTwap()` function
                return noTwap(self, pool, token, endTokenMix(self, pool));
            } else {
                // TWAP Active Case
                if (token == vars.fixedToken) {
                    // DeltaT for which the TWAP is going to remain active
                    vars.twapLeft = eraDate(vars.depegEra) - pool.date;
                    res = self.tokenMix * (POSITIVE_ONE + (pool.twapSpread * LAMBDA * vars.twapLeft));
                } else {
                    if (self.deadEra.isDefined()) {
                        // Swapper Expires Case
                        int256 deadEra = self.deadEra.get();
                        vars.twapDeflator = pool.deflator * (pool.date - eraDate(vars.depegEra)).exp2();

                        if (vars.depegEra == deadEra) {
                            vars.capacity0 = vars.twapDeflator * self.lentCapacity0;
                            vars.capacity1 = vars.twapDeflator * self.lentCapacity1;
                        } else {
                            // Case deadEra > depegEra
                            vars.deadDeflator = pool.deflator * (pool.date - eraDate(deadEra)).exp2();

                            // The extra tokens to cover the depegEra < t < deadEra depend on the `oweLimit` deflated by deflator related to the DeltaT = deadEra - depegEra
                            // NOTE: This is guaranteed to be positive since 2^(date - depeg) > 2^(date - dead) because depeg < dead in this logic branch
                            vars.owedMean = ((vars.twapDeflator - vars.deadDeflator) * pool.epsilon * self.oweLimit);

                            // In general, the liquidity is
                            // - converted to token0 by dividing for sqrtStrike(swapper.strikeBin)
                            // - converted to token1 by multiplying for sqrtStrike(swapper.strikeBin)
                            // These capacities are calculated using the full `oweMean` so they represent the max capacity for a token and will be used as normalization factors in the rest of the logic of this branch
                            vars.capacity0 = (vars.owedMean / sqrtStrike(pool.splits, self.strikeBin)) + (vars.deadDeflator * self.lentCapacity0);
                            vars.capacity1 = (vars.owedMean * sqrtStrike(pool.splits, self.strikeBin)) + (vars.deadDeflator * self.lentCapacity1);
                        }

                        res = (token == Z)
                            ? (((pool.deflator - vars.twapDeflator) * pool.epsilon * self.oweLimit) / sqrtStrike(pool.splits, self.strikeBin))
                                + vars.capacity0 * (POSITIVE_ONE - self.tokenMix / vars.capacity1)
                            : (((pool.deflator - vars.twapDeflator) * pool.epsilon * self.oweLimit) * sqrtStrike(pool.splits, self.strikeBin))
                                + vars.capacity1 * (POSITIVE_ONE - self.tokenMix / vars.capacity0);
                    } else {
                        // Swapper does not expire case

                        // In general, the liquidity is
                        // - converted to token0 by dividing for sqrtStrike(swapper.strikeBin)
                        // - converted to token1 by multiplying for sqrtStrike(swapper.strikeBin)
                        vars.towardToken =
                            (token == Z) ? (POSITIVE_ONE / sqrtStrike(pool.splits, self.strikeBin)) : sqrtStrike(pool.splits, self.strikeBin);

                        res = (((pool.epsilon * self.oweLimit) * pool.deflator) - (self.tokenMix * vars.towardToken)) * vars.towardToken;
                    }
                }
            }
        }
        return res;
    }

    function feeCreate(Info storage self, PoolState storage pool, Quad[] memory liquidityRatio) internal {
        if (!self.deadEra.isDefined()) {
            for (uint256 index = 0; index < liquidityRatio.length; index++) {
                pool.joinStaged.flush(pool, self.startBin + int256(index));
            }
        }
        Quad[] memory interest = new Quad[](liquidityRatio.length);
        for (uint256 index = 0; index < liquidityRatio.length; index++) {
            interest[index] = pool.epsilon * liquidityRatio[index] / sqrtStrike(pool.splits, self.startBin + int256(index));
        }
        pool.fees[0].create(pool, pool.splits, self.startBin, interest, self.deadEra);
        for (uint256 index = 0; index < liquidityRatio.length; index++) {
            interest[index] = pool.epsilon * liquidityRatio[index] * sqrtStrike(pool.splits, self.startBin + int256(index));
        }
        pool.fees[1].create(pool, pool.splits, self.startBin, interest, self.deadEra);
    }

    /**
     * @dev Updates the liquidity lending accounting in the pool and updates the corresponding fees.
     *     @param enable: If true, the function updates the pool accounting and the fees. If false, it undoes the operations done by the function when enable = true
     *     For the operation undoing to work correctly, the function must be called with the same state as when enable = true so this is why `self.minted` is not updated when enable = false
     */
    function borrow(Info storage self, PoolState storage pool, bool enable) public {
        int256 scale = pool.splits;
        // Looping over all the bins the liquidity has been borrowed from
        Quad[] memory amounts = new Quad[](self.owed.length);
        for (int256 index = 0; (index < int256(self.owed.length)); index = (index + 1)) {
            // Current bin
            int256 bin = (self.startBin + index);

            // Minted is a normalization factor applied to the borrowed and lent liquidity for the fees and flows calculations
            // For doing new operations only, update the locally recorded snapshot of the minted liquidity in the pool for the current bin
            // For undoing operations, which is the case for `enable = false`, the minted liquidity is not updated
            // Minted tracks the highest historical value of each bin, separately, seen when `borrow()` is called
            if (enable) self.minted[index.toUint256()] = max(self.minted[index.toUint256()], fluidAt(pool.minted, bin, pool.splits));

            // Tracking the new lent amount for the current bin
            // If positive, it tracks the amount of liquidity lent and expiring at deadEra
            pool.lent.createOne(pool.era, pool.splits, signed(enable, self.lent[index.toUint256()]), bin, self.deadEra);
            pool.used.createOne(
                pool.era, pool.splits, signed(enable, self.lent[index.toUint256()] / self.minted[index.toUint256()]), bin, self.deadEra
            );

            pool.owed.createOne(pool.era, pool.splits, signed(enable, self.owed[index.toUint256()]), bin, self.deadEra);
            amounts[index.toUint256()] = signed(enable, self.owed[index.toUint256()] - self.lent[index.toUint256()]) / self.minted[index.toUint256()];
        }
        // Creating new fees resulting from delta between the owed and lent liquidity normalized over the current minted
        feeCreate(self, pool, amounts);

        // Creating flows for lent liquidity for token0 and token1 re-entering the pool at the deadEra
        for (uint256 index = 0; index < amounts.length; index++) {
            amounts[index] = signed(enable, pool.epsilon * self.lent[index] / sqrtStrike(pool.splits, self.startBin + int256(index)));
        }
        pool.lentEnd[0].expire(pool.era, amounts, scale, self.startBin, self.deadEra);

        for (uint256 index = 0; index < amounts.length; index++) {
            amounts[index] = signed(enable, pool.epsilon * self.lent[index] * sqrtStrike(pool.splits, self.startBin + int256(index)));
        }
        pool.lentEnd[1].expire(pool.era, amounts, scale, self.startBin, self.deadEra);
        for (uint256 index = 0; index < amounts.length; index++) {
            amounts[index] = signed(enable, -self.owed[index] / sqrtStrike(pool.splits, self.startBin + int256(index)));
        }
        pool.flowHat[0].create(pool, amounts, scale, self.startBin, self.deadEra);
        for (uint256 index = 0; index < amounts.length; index++) {
            amounts[index] = signed(enable, -self.owed[index] * sqrtStrike(pool.splits, self.startBin + int256(index)));
        }
        pool.flowHat[1].create(pool, amounts, scale, self.startBin, self.deadEra);
    }

    /**
     * @dev Swapper Expired Check
     */
    function isExpired(Info storage self, PoolState storage pool) internal view returns (bool) {
        bool result = false;

        if (self.deadEra.isDefined()) {
            // The Swapper is expired if the current era is greater than or equal to the dead era
            if (pool.era >= (self.deadEra.get())) result = true;
            else result = false;
        } else {
            result = false;
        }
        return result;
    }

    function checkAlive(Info storage self, PoolState storage pool) public view {
        if (isExpired(self, pool)) revert SwapperExpired(pool.era, self.deadEra.get());
    }

    /**
     * @dev This function is used to calculate the flow of the swapper
     *     @dev There are 2 quantites for each token that are computed here: the payoff and the flows of tokens back into the system
     *     @dev From a time perspective,
     *     - the flows of tokens back into the system only depend on the Swapper expiration era and the
     *     - the payoff depends on the Swapper expiration era and the TWAP expiration era, if they do not coincide each payoff will be composed of 2 parts: one for the 0 < t < depegEra and one for the depegEra < t < deadEra
     */
    function flow(Info storage self, PoolState storage pool, bool enable) public {
        // The token mix after the TWAP if any, has expired
        Quad finalTokenMix = endTokenMix(self, pool);

        // The flows when there is no TWAP, are just the `oweLimit` split according to the `finalTokenMix` so `flow0` and `flow1` will be used in the banches of the logic regarding TWAP expired and no TWAP
        // The flow of liquidity that will become token0
        Quad flow0 = self.oweLimit * (POSITIVE_ONE - finalTokenMix);
        // The flow of liquidity that will become token1
        Quad flow1 = self.oweLimit * finalTokenMix;

        // Payoff Creation, the expiration time needs to be taken into account
        if (self.twapUntil.isDefined() && self.twapUntil.get() > pool.era) {
            // TWAP is active i.e. defined and not expired yet

            // Computing the payoff related to the TWAP, expiring at `depegEra`
            bool fixedToken = self.token;
            int256 depegEra = self.twapUntil.get();
            // If the desired exposure is to token0, then the full `oweLimit` will become token1
            if (fixedToken == Z) AnyPayoff.payoffCreate(pool, I, signed(enable, self.oweLimit), self.strikeBin, wrap(depegEra));
            // If the desired exposure is to token1, then the full `oweLimit` will become token0
            else if (fixedToken == I) AnyPayoff.payoffCreate(pool, Z, signed(enable, self.oweLimit), self.strikeBin, wrap(depegEra));

            pool.netting[(fixedToken == true ? 1 : 0)].create(pool, signed(enable, self.tokenMix), depegEra);
            if (self.deadEra != wrap(depegEra)) {
                // If the swapper matures after the TWAP has expired, the payoff has to be extended for the depegEra < t < deadEra interval using the `flow0` and `flow1` quantities previously calculated
                AnyPayoff.payoffExtend(pool, Z, signed(enable, flow0), self.strikeBin, wrap(depegEra), self.deadEra);
                AnyPayoff.payoffExtend(pool, I, signed(enable, flow1), self.strikeBin, wrap(depegEra), self.deadEra);
            }
        } else {
            // If no TWAP or it is expired, then the payoffs are just a function of `flow0` and `flow1` respectively, expiring at `deadEra` i.e. when the swapper expires
            AnyPayoff.payoffCreate(pool, Z, signed(enable, flow0), self.strikeBin, self.deadEra);
            AnyPayoff.payoffCreate(pool, I, signed(enable, flow1), self.strikeBin, self.deadEra);
        }

        if (self.deadEra.isDefined()) {
            // Since `self.lentCapacity0` is the max possible amount of token0 "ratio" (i.e. this quantity has to be multiplied by liquidity to become a token quantity) then the actual amount is the `(POSITIVE_ONE - finalTokenMix)` share
            // Since `self.lentCapacity1` is the max possible amount of token1 "ratio" (i.e. this quantity has to be multiplied by liquidity to become a token quantity) then the actual amount is the `(finalTokenMix)` share
            pool.expire[0].create(pool.era, signed(enable, self.lentCapacity0 * (POSITIVE_ONE - finalTokenMix)), self.deadEra);
            pool.expire[1].create(pool.era, signed(enable, self.lentCapacity1 * finalTokenMix), self.deadEra);
        }
    }

    /**
     * @dev Ignore
     */
    function maturing(Info storage self, PoolState storage pool, bool token) internal view returns (Quad) {
        return (
            flowing(self, token)
                * ((token == Z) ? (pool.epsilon / sqrtStrike(pool.splits, self.strikeBin)) : (pool.epsilon * sqrtStrike(pool.splits, self.strikeBin)))
        );
    }

    /**
     * @dev Ignore
     */
    function flowing(Info storage self, bool token) internal view returns (Quad) {
        Quad res = POSITIVE_ZERO;

        if (!(self.twapUntil.isDefined())) {
            res = (self.oweLimit * ((token == Z) ? (POSITIVE_ONE - self.tokenMix) : self.tokenMix));
        } else {
            bool fixedToken = self.token;

            if ((token == fixedToken)) res = POSITIVE_ZERO;
            else res = self.oweLimit;
        }
        return res;
    }

    struct ValidateLocalVars {
        int256 depegEra;
        Quad twapDeflator;
        bool fixedToken;
        Quad capacity;
        Quad deadDeflator;
        Quad owedMean;
    }

    function validate(Info storage self, PoolState storage pool) public {
        ValidateLocalVars memory vars;

        if (self.deadEra.isDefined() && ((eraDate(self.deadEra.get())) < self.unlockDate)) {
            revert DeadlineDoesNotCoverLockInDate(self.deadEra.get(), self.unlockDate);
        }

        checkAlive(self, pool);

        if (self.tokenMix < POSITIVE_ZERO) revert InvalidTokenMixFraction(self.tokenMix);

        if ((!self.twapUntil.isDefined())) {
            if (!(self.tokenMix <= POSITIVE_ONE)) revert InvalidTokenMixFraction(self.tokenMix);
        } else {
            vars.depegEra = self.twapUntil.get();
            if (self.deadEra.isDefined() && (self.deadEra.get() < vars.depegEra)) {
                // The TWAP end time should be before the Swapper expiration time
                revert TWAPEndsBeforeDeadline(vars.depegEra, self.deadEra.get());
            }

            vars.twapDeflator = pool.deflator * (pool.date - eraDate(vars.depegEra)).exp2();
            vars.fixedToken = self.token;

            // Computing the `vars.capacity` to validate the new `tokenMix` value
            if (self.deadEra.isDefined()) {
                // Swapper expiring at some date
                Quad lentCapacity = vars.fixedToken == Z ? self.lentCapacity0 : self.lentCapacity1;
                if (vars.depegEra == self.deadEra.get()) {
                    // Case where the TWAP and the Swapper end at the same time
                    // In this case, the capacity is just the lent liquidity deflated at the time the TWAP ends
                    vars.capacity = vars.twapDeflator * lentCapacity;
                } else {
                    // Case where the TWAP and the Swapper end at different times
                    //		val deadDeflator = deflator * exp(ln2 * (date - eraDate(deadEra)))
                    //								val owedMean = (twapDeflator - deadDeflator) * ε * oweLimit
                    //								(fixedToken match { case Z => owedMean / sqrtStrike; case I => owedMean * sqrtStrike }) + deadDeflator * lentCapacity

                    // Deflator when the Swapper ends
                    vars.deadDeflator = pool.deflator * (pool.date - eraDate(self.deadEra.get())).exp2();
                    vars.owedMean = (vars.twapDeflator - vars.deadDeflator) * pool.epsilon * self.oweLimit;
                    vars.capacity = vars.fixedToken == Z
                        ? (vars.owedMean / sqrtStrike(pool.splits, self.strikeBin))
                        : (vars.owedMean * sqrtStrike(pool.splits, self.strikeBin));
                    vars.capacity = vars.capacity + vars.deadDeflator * lentCapacity;
                }
            } else {
                vars.capacity = vars.fixedToken == Z
                    ? (pool.epsilon / sqrtStrike(pool.splits, self.strikeBin))
                    : (pool.epsilon * sqrtStrike(pool.splits, self.strikeBin));
                vars.capacity = vars.capacity * self.oweLimit * vars.twapDeflator;
            }
            if (isInfinity(self.tokenMix)) self.tokenMix = vars.capacity;
            else if ((self.tokenMix > vars.capacity)) revert FixedTokenExceedsCapacity(vars.fixedToken, self.tokenMix, vars.capacity);
        }
    }
}

File 15 of 52 : IInfinityPoolState.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";

import {JumpyAnchorFaber} from "src/libraries/internal/JumpyAnchorFaber.sol";

import {BoxcarTubFrame} from "src/libraries/internal/BoxcarTubFrame.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {DropFaberTotals} from "src/libraries/internal/DropFaberTotals.sol";
import {DeadlineJumps, DropsGroup, DeadlineSet} from "src/libraries/internal/DeadlineJumps.sol";
import {EraBoxcarMidSum} from "src/libraries/internal/EraBoxcarMidSum.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {BucketRolling} from "src/libraries/internal/BucketRolling.sol";
import {LP} from "src/libraries/external/LP.sol";
import {DeadlineFlag} from "src/libraries/internal/DeadlineFlag.sol";
import {SwapperInternal} from "src/libraries/external/Swapper.sol";
import {NettingGrowth} from "src/libraries/internal/NettingGrowth.sol";
import {Capper} from "src/libraries/internal/Capper.sol";
import {SparseFloat} from "src/libraries/internal/SparseFloat.sol";

interface IInfinityPoolState {}

struct PoolState {
    // NOTE: The order and type of the variables is important for the storage layout and the consequent gas costs so the criteria are
    // the order should go from the largest to the smallest types
    // mappings and dynamic array are storage pointers in the struct
    uint256 lpCount;
    int32 era;
    int32 tickBin;
    int32 splits;
    // All the Quads are 16 bytes so every pair of them takes 1 EVM Slot
    //cache decimals to avoid recompute everytime
    Quad tenToPowerDecimals0;
    Quad tenToPowerDecimals1;
    Quad fee;
    Quad epsilon;
    Quad move2Var;
    //time
    Quad date;
    Quad deflator;
    Quad entryDeflator;
    Quad binFrac;
    Quad surplus0;
    Quad surplus1;
    Quad twapSpread;
    Quad halfspread;
    // Each Address is 20 bytes, so can't be packed effectively unless there are 12 bytes types, but this is not the case here
    address factory;
    address token0;
    address token1;
    bool isPoolInitialized;
    //liquidity
    BoxcarTubFrame.Info minted;
    JumpyFallback.Info lent;
    DropFaberTotals.Info[2] lentEnd;
    JumpyFallback.Info used; // Liquidity utilisation ratio, inflated
    JumpyFallback.Info owed; // ω liquidity, inflated
    GapStagedFrame.Info joinStaged;
    BucketRolling.Info dayMove;
    DeadlineSet.Info[2] expire; //dropsGroup
    //resets
    // The `pool.resets` is like DeadlineJumps but instead of tracking Quad value associated to deadlines, it tracks boolean values associated to deadlines
    DeadlineFlag.Info resets;
    JumpyAnchorFaber.Info[2] flowHat;
    EraBoxcarMidSum.Info[2][2] flowDot;
    GrowthSplitFrame.Info[2] fees;
    BoxcarTubFrame.Info offRamp;
    NettingGrowth.Info[2] netting;
    SparseFloat.Info[2] priceRun;
    SparseFloat.Info[2] reserveRun;
    LP.Info[] lps;
    SwapperInternal.Info[] swappers;
    // No data packing for mapping
    mapping(bytes32 => Capper.Info) capper; // Keyed by (tick, token) hashed
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {ABDKMathQuad} from "../../../lib/abdk-libraries-solidity/ABDKMathQuad.sol";
import {wrap} from "./Helpers.sol";
import {Quad} from "./ValueType.sol";
import {fromUint256, fromInt256, intoUint256, intoInt256} from "./Casting.sol";
import {POSITIVE_ONE, NEGATIVE_ONE, POSITIVE_ZERO, HALF, POSITIVE_TWO, NEGATIVE_ZERO, POSITIVE_INFINITY, NEGATIVE_INFINITY} from "./Constants.sol";

function ceil(Quad x) pure returns (int256 res) {
    res = intoInt256(x);
    if (x > fromInt256(res)) res = res + 1;
}

function sqrt(Quad x) pure returns (Quad) {
    return wrap(ABDKMathQuad.sqrt(x.unwrap()));
}

function exp2(Quad x) pure returns (Quad) {
    return wrap(ABDKMathQuad.pow_2(x.unwrap()));
}

function exp(Quad x) pure returns (Quad) {
    return wrap(ABDKMathQuad.exp(x.unwrap()));
}

function log2(Quad x) pure returns (Quad res) {
    res = wrap(ABDKMathQuad.log_2(x.unwrap()));
}

function log(Quad x) pure returns (Quad res) {
    res = wrap(ABDKMathQuad.ln(x.unwrap()));
}

function abs(Quad x) pure returns (Quad res) {
    res = wrap(ABDKMathQuad.abs(x.unwrap()));
}

function max(Quad x, Quad y) pure returns (Quad res) {
    res = (x >= y) ? x : y;
}

function min(Quad x, Quad y) pure returns (Quad res) {
    res = (x <= y) ? x : y;
}

function pow(Quad b, Quad e) pure returns (Quad res) {
    res = exp(e * log(b));
}

function floor(Quad x) pure returns (int256 res) {
    if (x == POSITIVE_ZERO || x == NEGATIVE_ZERO) {
        res = 0; // the code below gives the wrong answer for -0
    } else {
        res = intoInt256(x);
        if (x < fromInt256(res)) res = res - 1;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {wrap} from "src/types/Optional/OptInt256.sol";
import {MIN_SPLITS, MAX_SPLITS, DEADERA_NONE} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol";
import {JumpyAnchorFaber} from "src/libraries/internal/JumpyAnchorFaber.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {DailyJumps} from "src/libraries/internal/DailyJumps.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {sqrtStrike, dayEra, eraDay} from "src/libraries/helpers/PoolHelper.sol";

library Gap {
    struct Info {
        int32 start;
        int32 stop;
    }

    function meet(Info storage self, Info memory other) internal {
        self.start = int32(SignedMath.max(self.start, other.start));
        self.stop = int32(SignedMath.min(self.stop, other.stop));
    }

    function top(Info storage self) internal {
        self.start = type(int32).min;
        self.stop = type(int32).max;
    }
}

library GapStagedFrame {
    using DailyJumps for DailyJumps.Info;
    using Gap for Gap.Info;
    using JumpyAnchorFaber for JumpyAnchorFaber.Info;
    using JumpyFallback for JumpyFallback.Info;
    using GrowthSplitFrame for GrowthSplitFrame.Info;
    using SafeCast for int256;
    using SafeCast for uint256;

    int256 constant neverAgo = -1 << 30;

    struct Info {
        DailyJumps.Info[2 << MAX_SPLITS] frame;
        Gap.Info latest;
        Gap.Info[2] gaps;
        int32 gapDay;
    }

    error InvalidNowtReaderApplyArguments();
    error InvalidStageArguments();
    error InvalidLiftReaderApplyArguments();
    error InvalidAddRangeArguments();
    error InavalidStageState();

    function coefAdd(Info storage self, PoolState storage pool, int256 node, Quad amount, int256 tailDay) public {
        self.frame[node.toUint256()].defer(pool, amount, tailDay);
    }

    function nowReader_coefAt(Info storage self, PoolState storage pool, int256 node) public returns (Quad) {
        DailyJumps.Info storage temp = self.frame[node.toUint256()];
        return temp.now(pool);
    }

    function nowReader_Apply(Info storage self, PoolState storage pool, int256 scale, int256 offset) public returns (Quad) {
        if (((offset & (-1 << scale.toUint256())) != 0)) revert InvalidNowtReaderApplyArguments();

        Quad total = nowReader_coefAt(self, pool, 1);

        int256 node = ((1 << scale.toUint256()).toInt256() + offset);

        while ((node > 1)) {
            total = (total + nowReader_coefAt(self, pool, node));
            node = (node >> 1);
        }

        return total;
    }

    function fresh(Info storage self, PoolState storage pool) public {
        int256 temp = (eraDay(pool.era) - self.gapDay);

        if ((temp == 0)) {
            return;
        } else {
            if ((temp == 1)) {
                int256 idx1 = (eraDay(pool.era) & 1);

                Gap.Info storage idx2 = self.gaps[idx1.toUint256()];

                self.latest.meet(idx2);
                idx2.top();
            } else {
                if ((self.gapDay != neverAgo)) {
                    Gap.Info storage idx0 = self.gaps[0];

                    Gap.Info storage idx1 = self.gaps[1];

                    self.latest.meet(idx0);
                    self.latest.meet(idx1);

                    idx0.top();
                    idx1.top();
                }
            }
        }
        self.gapDay = int32(eraDay(pool.era));
    }

    function liftReader_coefAt(Info storage self, int256 node, int256 day) public view returns (Quad) {
        DailyJumps.Info storage temp = self.frame[node.toUint256()];

        return temp.liftAt(day);
    }

    function _apply(Info storage self, PoolState storage pool, int256 bin) public view returns (DailyJumps.Info storage) {
        int256 idx = ((1 << int256(pool.splits).toUint256()).toInt256() + bin);

        return self.frame[idx.toUint256()];
    }

    function stage(Info storage self, PoolState storage pool, int256 startBin, int256 stopBin, Quad change) public {
        if ((startBin >= stopBin)) revert InvalidStageArguments();

        int256 tailDay = (eraDay(pool.era) + 2);

        fresh(self, pool);
        int256 idx = (eraDay(pool.era) & 1);

        Gap.Info storage gap = self.gaps[idx.toUint256()];

        if ((startBin >= pool.tickBin)) {
            int256 startStaged;
            if ((startBin == pool.tickBin)) startStaged = (pool.tickBin + 1);
            else startStaged = startBin;

            addRange(self, pool, pool.splits, startStaged, stopBin, change, tailDay);
            gap.stop = int32(SignedMath.min(gap.stop, startStaged));
        } else {
            if ((stopBin <= (pool.tickBin + 1))) {
                int256 stopStaged;
                if ((stopBin == (pool.tickBin + 1))) stopStaged = pool.tickBin;
                else stopStaged = stopBin;

                addRange(self, pool, pool.splits, startBin, stopStaged, change, tailDay);
                gap.start = int32(SignedMath.max(gap.start, stopStaged));
            } else {
                addRange(self, pool, pool.splits, startBin, pool.tickBin, change, tailDay);
                gap.start = pool.tickBin;
                addRange(self, pool, pool.splits, (pool.tickBin + 1), stopBin, change, tailDay);
                gap.stop = (pool.tickBin + 1);
            }
        }
        if (gap.start > pool.tickBin || gap.stop <= pool.tickBin) revert InavalidStageState();
    }

    function addRange(Info storage self, PoolState storage pool, int256 scale, int256 startIdx, int256 stopIdx, Quad change, int256 tailDay) public {
        int256 length = (1 << scale.toUint256()).toInt256();

        if ((((startIdx < 0) || (startIdx > stopIdx)) || (stopIdx > length))) revert InvalidAddRangeArguments();

        int256 start = (length + startIdx);

        int256 stop = (length + stopIdx);

        while ((start != stop)) {
            int256 temp = (start & 1);

            if ((temp != 0)) {
                coefAdd(self, pool, start, change, tailDay);
                start = (start + 1);
            }

            if (((stop & 1) != 0)) {
                stop = (stop - 1);
                coefAdd(self, pool, stop, change, tailDay);
            }

            start = (start >> 1);
            stop = (stop >> 1);
        }
    }

    function flush(Info storage self, PoolState storage pool, int256 bin) public {
        fresh(self, pool);
        GrowthSplitFrame.Info storage fees0 = pool.fees[0];

        GrowthSplitFrame.Info storage fees1 = pool.fees[1];

        Quad yieldFlow0 = (fees0.tailAt(pool, bin) / pool.epsilon);

        Quad yieldFlow1 = (fees1.tailAt(pool, bin) / pool.epsilon);

        Quad yieldRatio = (yieldFlow0 * sqrtStrike(pool.splits, bin));

        if (((bin < self.latest.start) || (bin >= self.latest.stop))) {
            Quad staged = nowReader_Apply(self, pool, pool.splits, bin);

            JumpyAnchorFaber.Info storage flowHat0 = pool.flowHat[0];

            JumpyAnchorFaber.Info storage flowHat1 = pool.flowHat[1];

            flowHat0.lateCreate(pool.era, (yieldFlow0.neg() * staged), pool.splits, bin, wrap((DEADERA_NONE)));
            flowHat1.lateCreate(pool.era, (yieldFlow1.neg() * staged), pool.splits, bin, wrap((DEADERA_NONE)));
            pool.owed.createOne(pool.era, pool.splits, (yieldRatio * staged), bin, wrap((DEADERA_NONE)));
            DailyJumps.Info storage temp = _apply(self, pool, bin);

            temp.begin(pool, staged.neg());
            if ((bin == self.latest.stop)) self.latest.stop = int32(bin + 1);
            else if ((bin + 1 == (self.latest.start))) self.latest.start = int32(bin);
        }

        for (int256 i = 0; (i < 2); i = (i + 1)) {
            flushAtGapIndex(
                self, pool, FlushAtGapIndexParams({i: i, bin: bin, yieldFlow0: yieldFlow0, yieldFlow1: yieldFlow1, yieldRatio: yieldRatio})
            );
        }
    }

    struct FlushAtGapIndexParams {
        int256 i;
        int256 bin;
        Quad yieldFlow0;
        Quad yieldFlow1;
        Quad yieldRatio;
    }

    function flushAtGapIndex(Info storage self, PoolState storage pool, FlushAtGapIndexParams memory params) public {
        int256 deadDay = (eraDay(pool.era) + (2 - params.i));

        Gap.Info storage gap = self.gaps[(deadDay & 1).toUint256()];

        if (((params.bin < gap.start) || (params.bin >= gap.stop))) {
            int256 deadEra = dayEra(deadDay);

            Quad staged = liftReader_Apply(self, deadDay, pool.splits, params.bin);

            JumpyAnchorFaber.Info storage flowHat0 = pool.flowHat[0];

            JumpyAnchorFaber.Info storage flowHat1 = pool.flowHat[1];

            flowHat0.lateExpire(pool.era, (params.yieldFlow0 * staged), pool.splits, params.bin, deadEra);
            flowHat1.lateExpire(pool.era, (params.yieldFlow1 * staged), pool.splits, params.bin, deadEra);
            pool.owed.expireOne(pool.era, pool.splits, (params.yieldRatio.neg() * staged), params.bin, deadEra);
            DailyJumps.Info storage temp = _apply(self, pool, params.bin);

            temp.defer(pool, staged.neg(), deadDay);
            if ((params.bin == gap.stop)) gap.stop = int32(params.bin + 1);
            else if ((params.bin + 1 == (gap.start))) gap.start = int32(params.bin);
        }
    }

    function liftReader_Apply(Info storage self, int256 day, int256 scale, int256 offset) public view returns (Quad) {
        if (((offset & (-1 << scale.toUint256())) != 0)) revert InvalidLiftReaderApplyArguments();

        Quad total = liftReader_coefAt(self, 1, day);

        int256 node = ((1 << scale.toUint256()).toInt256() + offset);

        while ((node > 1)) {
            total = (total + liftReader_coefAt(self, node, day));
            node = (node >> 1);
        }

        return total;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, fromInt256, POSITIVE_ZERO, POSITIVE_TWO} from "src/types/ABDKMathQuad/Quad.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve, PiecewiseGrowthNew, TailedJumps, DropsGroup} from "src/libraries/internal/DeadlineJumps.sol";
import {OptInt256, wrap} from "src/types/Optional/OptInt256.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

import {MAX_SPLITS, MIN_SPLITS} from "src/Constants.sol";

library GrowthSplitFrame {
    using SafeCast for int256;
    using SafeCast for uint256;
    using PiecewiseGrowthNew for PiecewiseGrowthNew.Info;
    using DeadlineJumps for DeadlineSet.Info;
    using DropsGroup for DeadlineSet.Info;
    using PiecewiseCurve for DeadlineSet.Info;
    using TailedJumps for TailedJumps.Info;

    struct Info {
        PiecewiseGrowthNew.Info[2 << MIN_SPLITS] large;
        TailedJumps.Info[1 << MAX_SPLITS] small;
        int32 activeLeaf;
        bool below;
    }

    error InvalidSumTotalArguments();
    error InvalidAccruedRangeArguments();
    error InvalidAreaArguments();
    error InvalidAddArguments();
    error InvalidSumRangeArguments();
    error InvalidSubLeftSumArguments();

    function init(Info storage self, PoolState storage pool, int256 initBin, bool below) public {
        self.activeLeaf = int32(int256(1 << int256(pool.splits).toUint256()) + initBin);
        self.below = below;
    }

    function live(Info storage self, int256 splits) public view returns (PiecewiseGrowthNew.Info storage) {
        return self.large[int256(self.activeLeaf).toUint256() >> (splits - int256(MIN_SPLITS)).toUint256()];
    }

    function largeAtAccruedReader(Info storage self, int256 poolEra, Quad poolDeflator, int256 node) public returns (Quad) {
        return self.large[node.toUint256()].grow(poolEra, poolDeflator);
    }

    function largeAtTailReader(Info storage self, int256, Quad, int256 node) public view returns (Quad) {
        return self.large[node.toUint256()].tail;
    }

    function largeAtNowReader(Info storage self, PoolState storage pool, int256, Quad, int256 node) public returns (Quad) {
        return self.large[node.toUint256()].deadlineJumps.now(pool.era);
    }

    function sumTotal(
        Info storage self,
        int256 poolEra,
        Quad poolDeflator,
        int256 scale,
        int256 startIdx,
        int256 stopIdx,
        function (Info storage, int256, Quad,int256) returns(Quad) largeAt
    ) internal returns (Quad) {
        if (!(((scale <= int256(MIN_SPLITS) && (0 <= startIdx)) && (stopIdx <= int256(1 << scale.toUint256()))))) revert InvalidSumTotalArguments();

        Quad total;
        int256 start = (int256(1 << scale.toUint256()) + startIdx);
        int256 stop = (int256(1 << scale.toUint256()) + stopIdx);

        while ((start < stop)) {
            if (((start & 1) != 0)) {
                total = (total + largeAt(self, poolEra, poolDeflator, start));
                start = (start + 1);
            }
            if (((stop & 1) != 0)) {
                stop = (stop - 1);
                total = (total + largeAt(self, poolEra, poolDeflator, stop));
            }
            start = (start >> 1);
            stop = (stop >> 1);
        }
        return total;
    }

    function binRate(Info storage self, PoolState storage pool) public returns (Quad) {
        return areaNowReader(self, pool, pool.splits, pool.tickBin);
    }

    function dirtyRate(Info storage self, PoolState storage pool) public returns (Quad) {
        Quad left = subLeftSumNowReader(self, pool, pool.splits, pool.tickBin);
        return self.below ? left : live(self, pool.splits).deadlineJumps.now(pool.era) - left;
    }

    function sumOutside(Info storage self, int256 poolEra, Quad poolDeflator, int256 startTub, int256 stopTub) public returns (Quad) {
        return sumTotal(self, poolEra, poolDeflator, int256(MIN_SPLITS), startTub, stopTub, largeAtAccruedReader);
    }

    function accruedRange(Info storage self, PoolState storage pool, int256 startTub, int256 stopTub) public returns (Quad) {
        if (!((startTub < stopTub))) revert InvalidAccruedRangeArguments();
        int256 activeTub =
            (int256(int256(self.activeLeaf) - int256(1 << int256(pool.splits).toUint256())) >> (int256(pool.splits) - int256(MIN_SPLITS)).toUint256());
        if (activeTub < startTub || ((stopTub <= activeTub))) {
            return sumOutside(self, pool.era, pool.deflator, startTub, stopTub);
        } else {
            return (
                (sumOutside(self, pool.era, pool.deflator, startTub, activeTub) + live(self, pool.splits).accrued)
                    + sumOutside(self, pool.era, pool.deflator, (activeTub + 1), stopTub)
            );
        }
    }

    function smallAtTailReader(Info storage self, int256 node) public view returns (Quad) {
        return self.small[node.toUint256()].tail;
    }

    function smallAtNowReader(Info storage self, PoolState storage pool, int256 node) public returns (Quad) {
        return self.small[node.toUint256()].deadlineJumps.now(pool.era);
    }

    function areaTailReader(Info storage self, int256 scale, int256 offset) public view returns (Quad) {
        if (!(((offset & int256(-1 << scale.toUint256())) == 0))) revert InvalidAreaArguments();
        int256 node = (int256(1 << scale.toUint256()) + offset);
        int256 temp = ((scale > int256(MIN_SPLITS)) ? int256(node >> (scale - int256(MIN_SPLITS)).toUint256()) : node);

        Quad subArea = largeAtTailReader(self, 0, POSITIVE_ZERO, temp);
        for (int256 depth = int256(MIN_SPLITS); (depth < scale); depth = (depth + 1)) {
            Quad nextCoef = smallAtTailReader(self, (int256(1 << depth.toUint256()) + int256(offset >> (scale - depth).toUint256())));
            bool rightChild = ((offset & int256(1 << ((scale - depth).toUint256() - 1))) != 0);
            subArea = (rightChild ? (subArea - nextCoef) : (subArea + nextCoef));
            subArea = (subArea / POSITIVE_TWO);
        }
        return subArea;
    }

    function areaNowReader(Info storage self, PoolState storage pool, int256 scale, int256 offset) public returns (Quad) {
        if (!(((offset & int256(-1 << scale.toUint256())) == 0))) revert InvalidAreaArguments();
        int256 node = (int256(1 << scale.toUint256()) + offset);
        int256 temp = ((scale > int256(MIN_SPLITS)) ? int256(node >> (scale - int256(MIN_SPLITS)).toUint256()) : node);

        Quad subArea = largeAtNowReader(self, pool, 0, POSITIVE_ZERO, temp);
        for (int256 depth = int256(MIN_SPLITS); (depth < scale); depth = (depth + 1)) {
            Quad nextCoef = smallAtNowReader(self, pool, (int256(1 << depth.toUint256()) + int256(offset >> (scale - depth).toUint256())));
            bool rightChild = ((offset & int256(1 << ((scale - depth).toUint256() - 1))) != 0);
            subArea = (rightChild ? (subArea - nextCoef) : (subArea + nextCoef));
            subArea = (subArea / POSITIVE_TWO);
        }
        return subArea;
    }

    function subLeftSumTailReader(Info storage self, int256 scale, int256 stopIdx) public view returns (Quad) {
        if (((stopIdx % int256(1 << (scale - int256(MIN_SPLITS)).toUint256())) == 0)) {
            return POSITIVE_ZERO;
        } else {
            if (!(scale >= int256(MIN_SPLITS) && (((stopIdx & int256(-1 << scale.toUint256())) == 0)))) revert InvalidSubLeftSumArguments();
            Quad subArea = largeAtTailReader(
                self, 0, POSITIVE_ZERO, int256((1 << MIN_SPLITS) + (stopIdx >> (scale - int256(MIN_SPLITS)).toUint256()).toUint256())
            );

            Quad total = POSITIVE_ZERO;
            for (int256 depth = int256(MIN_SPLITS); (depth < scale); depth = (depth + 1)) {
                Quad nextCoef = smallAtTailReader(self, int256((1 << depth.toUint256()) + uint256(stopIdx >> (scale - depth).toUint256())));

                bool rightChild = ((stopIdx & int256(1 << ((scale - depth) - 1).toUint256())) != 0);

                if (rightChild) total = (total + (subArea + nextCoef));
                subArea = (rightChild ? (subArea - nextCoef) : (subArea + nextCoef));
                subArea = (subArea / POSITIVE_TWO);
            }
            return (total / POSITIVE_TWO);
        }
    }

    function subLeftSumNowReader(Info storage self, PoolState storage pool, int256 scale, int256 stopIdx) public returns (Quad) {
        if (((stopIdx % int256(1 << (scale - int256(MIN_SPLITS)).toUint256())) == 0)) {
            return POSITIVE_ZERO;
        } else {
            if (!(scale >= int256(MIN_SPLITS) && (((stopIdx & int256(-1 << scale.toUint256())) == 0)))) revert InvalidSubLeftSumArguments();
            Quad subArea = largeAtNowReader(
                self, pool, 0, POSITIVE_ZERO, int256((1 << MIN_SPLITS) + (stopIdx >> (scale - int256(MIN_SPLITS)).toUint256()).toUint256())
            );

            Quad total = POSITIVE_ZERO;
            for (int256 depth = int256(MIN_SPLITS); (depth < scale); depth = (depth + 1)) {
                Quad nextCoef = smallAtNowReader(self, pool, int256((1 << depth.toUint256()) + uint256(stopIdx >> (scale - depth).toUint256())));

                bool rightChild = ((stopIdx & int256(1 << ((scale - depth) - 1).toUint256())) != 0);

                if (rightChild) total = (total + (subArea + nextCoef));
                subArea = (rightChild ? (subArea - nextCoef) : (subArea + nextCoef));
                subArea = (subArea / POSITIVE_TWO);
            }
            return (total / POSITIVE_TWO);
        }
    }

    function sumRangeTailReader(Info storage self, PoolState storage pool, int256 scale, int256 startIdx, int256 stopIdx) public returns (Quad) {
        if ((scale <= int256(MIN_SPLITS))) {
            return sumTotal(self, pool.era, pool.deflator, scale, startIdx, stopIdx, largeAtTailReader);
        } else {
            if (!((0 <= startIdx) && (stopIdx <= int256(1 << scale.toUint256())))) revert InvalidSumRangeArguments();
            if (startIdx < stopIdx) {
                Quad coarse = sumTotal(
                    self,
                    pool.era,
                    pool.deflator,
                    int256(MIN_SPLITS),
                    int256(startIdx >> (scale - int256(MIN_SPLITS)).toUint256()),
                    int256(stopIdx >> (scale - int256(MIN_SPLITS)).toUint256()),
                    largeAtTailReader
                );
                return ((coarse - subLeftSumTailReader(self, scale, startIdx)) + subLeftSumTailReader(self, scale, stopIdx));
            } else {
                return POSITIVE_ZERO;
            }
        }
    }

    function sumTail(Info storage self, PoolState storage pool, int256 startTub, int256 stopTub) internal returns (Quad) {
        return sumRangeTailReader(self, pool, pool.splits, startTub, stopTub);
    }

    function tailAt(Info storage self, PoolState storage pool, int256 bin) public view returns (Quad) {
        return areaTailReader(self, pool.splits, bin);
    }

    function move(Info storage self, PoolState storage pool, bool up) public {
        int256 oldNode = int256(self.activeLeaf >> (pool.splits - int256(MIN_SPLITS)).toUint256());

        self.activeLeaf = (up ? (self.activeLeaf + 1) : (self.activeLeaf - 1));
        int256 newNode = int256(self.activeLeaf >> (pool.splits - int256(MIN_SPLITS)).toUint256());

        if ((newNode == oldNode)) return;
        bool thawing = (up == self.below);
        Quad accrued = self.large[oldNode.toUint256()].accrued;

        while (true) {
            if (thawing) self.large[oldNode.toUint256()].markAccrual(pool.era, pool.deflator);
            else self.large[newNode.toUint256()].grow(pool.era, pool.deflator);
            self.large[newNode.toUint256()].atDeflator = POSITIVE_ZERO;
            int256 sibling = oldNode ^ 1;

            oldNode = (oldNode >> 1);
            newNode = (newNode >> 1);
            if ((newNode == oldNode)) return;
            accrued = (accrued + self.large[sibling.toUint256()].grow(pool.era, pool.deflator));
            self.large[oldNode.toUint256()].accrued = accrued;
        }
    }

    function accruing(Info storage self, PoolState storage pool, int256 node, int256 depth) public view returns (bool) {
        int256 active = int256(self.activeLeaf >> (pool.splits - depth).toUint256());
        if (self.below) return (node < active);
        else return (active < node);
    }

    function largeAddCreate(Info storage self, PoolState storage pool, int256 node, Quad amount, int256 depth, OptInt256 deadEra, OptInt256) public {
        self.large[node.toUint256()].create(pool.era, pool.deflator, amount, deadEra, accruing(self, pool, node, depth));
    }

    function smallAddCreate(Info storage self, PoolState storage pool, int256 node, Quad amount, OptInt256 deadEra, OptInt256) public {
        self.small[node.toUint256()].create(pool.era, amount, deadEra);
    }

    struct AddLocalVars {
        Quad[] carry;
        int256 first;
        int256 node;
        int256 depth;
        Quad change;
    }

    function add(
        Info storage self,
        PoolState storage pool,
        int256 scale,
        int256 start,
        Quad[] memory areas,
        OptInt256 aDeadEra,
        OptInt256 newDeadEra,
        function (Info storage, PoolState storage, int256, Quad, int256, OptInt256, OptInt256) internal largeAdd,
        function (Info storage, PoolState storage, int256, Quad, OptInt256, OptInt256) internal smallAdd
    ) internal {
        AddLocalVars memory vars;

        if (!((scale.toUint256() >= MIN_SPLITS && ((start >= 0)) && ((start.toUint256() + areas.length) <= (1 << scale.toUint256()))))) {
            revert InvalidAddArguments();
        }

        vars.carry = new Quad[](scale.toUint256());
        vars.first = ((1 << scale.toUint256()) + start.toUint256()).toInt256();
        vars.node = 0;
        vars.depth = 0;
        vars.change = POSITIVE_ZERO;

        for (int256 index = 0; (index.toUint256() < areas.length); index = (index + 1)) {
            vars.node = (vars.first + index);
            vars.change = areas[index.toUint256()];
            vars.depth = (scale - 1);
            while ((vars.depth.toUint256() >= MIN_SPLITS)) {
                bool leftChild = ((vars.node & 1) == 0);

                vars.node = (vars.node >> 1);
                if (leftChild) {
                    vars.carry[vars.depth.toUint256()] = vars.change;
                    break;
                } else {
                    smallAdd(self, pool, vars.node, (vars.carry[vars.depth.toUint256()] - vars.change), aDeadEra, newDeadEra);
                    vars.change = (vars.change + vars.carry[vars.depth.toUint256()]);
                }
                vars.depth = (vars.depth - 1);
            }

            if ((vars.depth.toUint256() < MIN_SPLITS)) {
                largeAdd(self, pool, vars.node, vars.change, MIN_SPLITS.toInt256(), aDeadEra, newDeadEra);
                while (true) {
                    bool leftChild = ((vars.node & 1) == 0);

                    vars.node = (vars.node >> 1);
                    if (leftChild) {
                        vars.carry[vars.depth.toUint256()] = vars.change;
                        break;
                    } else {
                        vars.change = (vars.change + vars.carry[vars.depth.toUint256()]);
                        if ((vars.node <= 1)) break;

                        largeAdd(self, pool, vars.node, vars.change, vars.depth, aDeadEra, newDeadEra);
                    }
                    vars.depth = (vars.depth - 1);
                }
            }
        }

        if ((vars.depth.toUint256() >= MIN_SPLITS)) {
            smallAdd(self, pool, vars.node, vars.change, aDeadEra, newDeadEra);
            while ((vars.depth.toUint256() > MIN_SPLITS)) {
                vars.depth = (vars.depth - 1);
                bool leftChild = ((vars.node & 1) == 0);

                vars.node = (vars.node >> 1);
                if (leftChild) {
                    smallAdd(self, pool, vars.node, vars.change, aDeadEra, newDeadEra);
                } else {
                    smallAdd(self, pool, vars.node, (vars.carry[vars.depth.toUint256()] - vars.change), aDeadEra, newDeadEra);
                    vars.change = (vars.change + vars.carry[vars.depth.toUint256()]);
                }
            }
        }

        while ((vars.node > 1)) {
            largeAdd(self, pool, vars.node, vars.change, vars.depth, aDeadEra, newDeadEra);
            vars.depth = (vars.depth - 1);
            if (((vars.node & 1) != 0)) vars.change = (vars.change + vars.carry[vars.depth.toUint256()]);

            vars.node = (vars.node >> 1);
        }

        largeAdd(self, pool, 1, vars.change, 0, aDeadEra, newDeadEra);
    }

    function create(Info storage self, PoolState storage pool, int256 scale, int256 start, Quad[] memory areas, OptInt256 deadEra) public {
        add(self, pool, scale, start, areas, deadEra, wrap(0), largeAddCreate, smallAddCreate);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, isPositive, isZero} from "src/types/ABDKMathQuad/Quad.sol";
import {ceil} from "src/types/ABDKMathQuad/Math.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IInfinityPoolPaymentCallback} from "src/interfaces/IInfinityPoolPaymentCallback.sol";

library UserPay {
    // Convention of UserPay.Info from library calls are in human unit
    struct Info {
        Quad token0;
        Quad token1;
    }

    error UserPayToken0Mismatch(int256 expectedToken0, int256 expectedToken1);
    error UserPayToken1Mismatch(int256 expectedToken0, int256 expectedToken1);

    function translateQuantities(Info memory amounts, PoolState storage pool) internal view returns (int256, int256) {
        Quad amount0 = amounts.token0 * pool.tenToPowerDecimals0; // in wei
        Quad amount1 = amounts.token1 * pool.tenToPowerDecimals1; // in wei
        return (round(amount0), round(amount1));
    }

    /**
     *  amount > 0 => user pays
     *  amount < 0 => user receives
     */
    function makeUserPay(Info memory amounts, PoolState storage pool, address to, bytes calldata data)
        public
        returns (int256 expectedToken0, int256 expectedToken1)
    {
        (expectedToken0, expectedToken1) = translateQuantities(amounts, pool);

        if (expectedToken0 < 0) SafeERC20.safeTransfer(IERC20(pool.token0), to, uint256(-expectedToken0));
        if (expectedToken1 < 0) SafeERC20.safeTransfer(IERC20(pool.token1), to, uint256(-expectedToken1));

        if (data.length > 0) {
            uint256 balance0Before = IERC20(pool.token0).balanceOf(address(this));
            uint256 balance1Before = IERC20(pool.token1).balanceOf(address(this));
            IInfinityPoolPaymentCallback(msg.sender).infinityPoolPaymentCallback(expectedToken0, expectedToken1, data);

            if (expectedToken0 > 0 && IERC20(pool.token0).balanceOf(address(this)) < balance0Before + uint256(expectedToken0)) {
                revert UserPayToken0Mismatch(expectedToken0, expectedToken1);
            }

            if (expectedToken1 > 0 && IERC20(pool.token1).balanceOf(address(this)) < balance1Before + uint256(expectedToken1)) {
                revert UserPayToken1Mismatch(expectedToken0, expectedToken1);
            }
        } else {
            if (expectedToken0 > 0) SafeERC20.safeTransferFrom(IERC20(pool.token0), msg.sender, address(this), uint256(expectedToken0));
            if (expectedToken1 > 0) SafeERC20.safeTransferFrom(IERC20(pool.token1), msg.sender, address(this), uint256(expectedToken1));
        }
    }

    function makeUserPay(Info memory amounts, PoolState storage pool, bytes calldata data)
        internal
        returns (int256 expectedToken0, int256 expectedToken1)
    {
        return makeUserPay(amounts, pool, msg.sender, data);
    }

    // always round to +infinity so that user pay more and receive less
    function round(Quad amount) internal pure returns (int256) {
        return ceil(amount);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve} from "src/libraries/internal/DeadlineJumps.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {MAX_SPLITS, MIN_SPLITS, JUMPS} from "src/Constants.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

library DeadlineShaded {
    using DeadlineJumps for DeadlineSet.Info;
    using DeadlineSet for DeadlineSet.Info;
    using PiecewiseCurve for DeadlineSet.Info;

    struct Info {
        DeadlineSet.Info deadlineJumps;
        bool terminal;
    }
}

library JumpyFallback {
    using DeadlineShaded for DeadlineShaded.Info;
    using DeadlineJumps for DeadlineSet.Info;
    using DeadlineSet for DeadlineSet.Info;
    using PiecewiseCurve for DeadlineSet.Info;
    using SafeCast for int256;
    using SafeCast for uint256;

    struct Info {
        DeadlineShaded.Info[1 << MAX_SPLITS] tree;
    }

    error InvalidAtArguments();
    error InvalidEnterArguments();

    function at(
        Info storage self,
        int256 poolEra,
        int256 splits,
        int256 offset,
        function(DeadlineSet.Info storage, int256) internal returns (Quad) read
    ) internal returns (Quad) {
        if (!(((offset & (-1 << splits.toUint256())) == 0))) revert InvalidAtArguments();

        int256 cursor = ((1 << splits.toUint256()).toInt256() + offset);

        while (!(self.tree[cursor.toUint256()].deadlineJumps.touched)) {
            if (((cursor >> MIN_SPLITS) == 1)) return POSITIVE_ZERO;

            cursor = (cursor >> 1);
        }

        DeadlineShaded.Info storage leaf = self.tree[cursor.toUint256()];

        if (leaf.terminal) return read(leaf.deadlineJumps, poolEra);

        bool temp = true;

        while (temp) {
            if (!(((cursor >> MIN_SPLITS) > 1))) revert InvalidAtArguments();

            cursor = (cursor >> 1);
            temp = !(self.tree[cursor.toUint256()].deadlineJumps.touched);
        }

        DeadlineShaded.Info storage trunk = self.tree[cursor.toUint256()];

        if (!trunk.terminal) revert InvalidAtArguments();

        Quad trunkValue = read(trunk.deadlineJumps, poolEra);

        if ((trunk.deadlineJumps.nextJump == JUMPS)) {
            leaf.deadlineJumps.latest = (leaf.deadlineJumps.latest + trunk.deadlineJumps.latest);
            leaf.terminal = true;
            return read(leaf.deadlineJumps, poolEra);
        } else {
            return (trunkValue + read(leaf.deadlineJumps, poolEra));
        }
    }

    function nowAt(Info storage self, int256 poolEra, int256 splits, int256 offset) internal returns (Quad) {
        return at(self, poolEra, splits, offset, PiecewiseCurve.now);
    }

    function nowAt(Info storage self, PoolState storage pool, int256 bin) internal returns (Quad) {
        return nowAt(self, pool.era, pool.splits, bin);
    }

    function dropAt(Info storage self, int256 splits, int256 offset, int256 deadEra) public returns (Quad) {
        return at(self, deadEra, splits, offset, DeadlineSet.dropAt);
    }

    function enter(Info storage self, int256 poolEra, int256 splits, int256 offset) public returns (DeadlineShaded.Info storage) {
        if (!(((offset & (-1 << splits.toUint256())) == 0))) revert InvalidEnterArguments();

        int256 cursor = ((1 << splits.toUint256()).toInt256() + offset);

        DeadlineShaded.Info storage leaf = self.tree[cursor.toUint256()];

        if (leaf.deadlineJumps.touched) return leaf;
        bool temp = true;

        while (temp) {
            cursor = (cursor >> 1);
            if (((cursor >> MIN_SPLITS) == 0)) {
                leaf.terminal = true;
                return leaf;
            }

            temp = !(self.tree[cursor.toUint256()].deadlineJumps.touched);
        }

        DeadlineShaded.Info storage trunk = self.tree[cursor.toUint256()];

        if (trunk.terminal) return leaf;

        while (true) {
            if (!(((cursor >> MIN_SPLITS) > 1))) revert InvalidEnterArguments();

            cursor = (cursor >> 1);
            DeadlineShaded.Info storage base = self.tree[cursor.toUint256()];

            if (base.terminal) {
                trunk.deadlineJumps.latest = (trunk.deadlineJumps.latest + base.deadlineJumps.now(poolEra));
                trunk.terminal = true;
                return leaf;
            }

            if (base.deadlineJumps.touched) revert InvalidEnterArguments();
        }

        return leaf;
    }

    function createOne(Info storage self, int256 poolEra, int256 splits, Quad amount, int256 bin, OptInt256 deadEra) public {
        enter(self, poolEra, splits, bin).deadlineJumps.create(poolEra, amount, deadEra);
    }

    function expireOne(Info storage self, int256 poolEra, int256 splits, Quad amount, int256 bin, int256 deadEra) public {
        enter(self, poolEra, splits, bin).deadlineJumps.expire(poolEra, amount, deadEra);
    }

    function extendOne(Info storage self, int256 poolEra, int256 splits, Quad amount, int256 bin, OptInt256 oldDeadEra, OptInt256 newDeadEra)
        public
    {
        enter(self, poolEra, splits, bin).deadlineJumps.extend(poolEra, amount, oldDeadEra, newDeadEra);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO, POSITIVE_ONE, POSITIVE_EIGHT, fromInt256} from "src/types/ABDKMathQuad/Quad.sol";
import {floor} from "src/types/ABDKMathQuad/Math.sol";
import {floorMod} from "src/libraries/helpers/PoolHelper.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

library BucketRolling {
    struct Info {
        int32 lastBucket;
        Quad[9] ring;
    }

    function init(BucketRolling.Info storage self, PoolState storage pool, Quad initial) internal {
        Quad date = pool.date;
        Quad bucketInitial = initial / POSITIVE_EIGHT;
        for (uint8 i; i < 9; i++) {
            self.ring[i] = bucketInitial;
        }

        self.lastBucket = int32(floor(date * POSITIVE_EIGHT));
        self.ring[uint256(floorMod(self.lastBucket, 9))] = (bucketInitial * (date * POSITIVE_EIGHT - fromInt256(self.lastBucket)));
    }

    function sum(BucketRolling.Info storage self, PoolState storage pool) internal view returns (Quad) {
        int256 bucket = floor((pool.date * POSITIVE_EIGHT));
        Quad fraction = ((pool.date * POSITIVE_EIGHT) - fromInt256(bucket));
        int256 idx = floorMod((bucket + 1), 9);
        Quad _sum = POSITIVE_ZERO;
        for (int256 i = 0; (i < 9); i = (i + 1)) {
            _sum = (_sum + self.ring[uint256(i)]);
        }
        return (_sum - (self.ring[uint256(idx)] * fraction));
    }

    function add(BucketRolling.Info storage self, PoolState storage pool, Quad value) internal {
        int256 bucket = floor((pool.date * POSITIVE_EIGHT));

        if (((bucket - self.lastBucket) >= 9)) {
            for (int256 idx = 0; (idx < 9); idx = (idx + 1)) {
                self.ring[uint256(idx)] = POSITIVE_ZERO;
            }
        } else {
            for (int256 passed = (self.lastBucket + 1); (passed < bucket); passed = (passed + 1)) {
                int256 idx = floorMod(passed, 9);
                self.ring[uint256(idx)] = POSITIVE_ZERO;
            }
        }
        if ((bucket > self.lastBucket)) {
            int256 idx = floorMod(bucket, 9);

            self.ring[uint256(idx)] = value;
            self.lastBucket = int32(bucket);
        } else {
            int256 idx = floorMod(bucket, 9);
            self.ring[uint256(idx)] = (self.ring[uint256(idx)] + value);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO, POSITIVE_ONE, POSITIVE_TWO, fromInt256, HALF} from "src/types/ABDKMathQuad/Quad.sol";
import {ceil} from "src/types/ABDKMathQuad/Math.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {TUBS, JUMPS, JUMP_NONE, WORD_SIZE, Z, I, LAMBDA} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import {Spot} from "../external/Spot.sol";
import {subSplits, sqrtStrike, mid, h, logTick, binLowTick} from "src/libraries/helpers/PoolHelper.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";

import {FloatBits, SparseFloat} from "src/libraries/internal/SparseFloat.sol";
import {Capper} from "src/libraries/internal/Capper.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

library EachPayoff {
    using Capper for Capper.Info;
    using SparseFloat for SparseFloat.Info;
    using SafeCast for int256;
    using SafeCast for uint256;

    function capperBegin(PoolState storage pool, int256 pivotTick, bool side, int128 exponent) public returns (int128) {
        bytes32 key = keccak256(abi.encode(pivotTick, side));
        Capper.Info storage capper = pool.capper[key];
        if (capper.isInitialized) {
            capper.lastDrain = pool.era;
            capper.grow(pool, pivotTick, side);
            return capper.integral.read(exponent).significand;
        } else {
            pool.capper[key].init(pool, pivotTick, side);
            return 0;
        }
    }

    function growRuns(PoolState storage pool) internal {
        if (pool.deflator < pool.entryDeflator) {
            Quad integral = pool.entryDeflator - pool.deflator;
            SparseFloat.Info storage priceRun0 = pool.priceRun[0];
            SparseFloat.Info storage priceRun1 = pool.priceRun[1];
            Quad mid = mid(pool.splits, pool.tickBin);
            Quad h = h(pool.splits);
            priceRun0.add(integral * h / mid);
            priceRun1.add(h * mid * integral);

            // Add Merge
            pool.reserveRun[0].add(integral / (mid * h).sqrt());
            pool.reserveRun[1].add((mid / h).sqrt() * integral);
            pool.entryDeflator = pool.deflator;
        }
    }

    function runLevel(PoolState storage pool, int256 pivotTick, bool side, bool sqRoot) internal returns (FloatBits.Info memory) {
        growRuns(pool);
        Quad t1 = (sqRoot ? (logTick() / POSITIVE_TWO) : logTick());
        Quad log2Pivot = ((t1 * fromInt256(pivotTick)) / LAMBDA);
        Quad t2 = ((side == Z) ? log2Pivot.neg() : log2Pivot);
        int256 modulo = ceil(((t2 - pool.date) + HALF));

        uint256 t3 = side ? 1 : 0;
        SparseFloat.Info storage run = (sqRoot ? pool.reserveRun[t3] : pool.priceRun[t3]);
        return run.read((modulo.toInt128() - WORD_SIZE));
    }

    function uptick(PoolState storage pool) public {
        growRuns(pool);
        Spot._uptick(pool);

        if (pool.tickBin % (1 << subSplits(pool.splits).toUint256()).toInt256() == 0) {
            int256 pivotTick = binLowTick(pool.splits, pool.tickBin);
            bytes32 key;
            key = keccak256(abi.encode(pivotTick, Z));
            Capper.Info storage capperZ = pool.capper[key];
            if (capperZ.isInitialized) capperZ.unpeg(pool, pivotTick, Z);

            key = keccak256(abi.encode(pivotTick, I));
            Capper.Info storage capperI = pool.capper[key];
            if (capperI.isInitialized) capperI.pegUp(pool, pivotTick, I);
        }
    }

    function downtick(PoolState storage pool) public {
        growRuns(pool);

        if (pool.tickBin % (1 << subSplits(pool.splits).toUint256()).toInt256() == 0) {
            int256 pivotTick = binLowTick(pool.splits, pool.tickBin);
            bytes32 key;
            key = keccak256(abi.encode(pivotTick, Z));
            Capper.Info storage capperZ = pool.capper[key];
            if (capperZ.isInitialized) capperZ.pegUp(pool, pivotTick, Z);

            key = keccak256(abi.encode(pivotTick, I));
            Capper.Info storage capperI = pool.capper[key];
            if (capperI.isInitialized) capperI.unpeg(pool, pivotTick, I);
        }

        Spot._downtick(pool);
    }

    function binFracAccrue(PoolState storage pool, Quad accrual, Quad stepRise) public {
        if (accrual > POSITIVE_ZERO) pool.reserveRun[1].add(accrual * pool.epsilon * sqrtStrike(pool.splits, pool.tickBin) * pool.deflator);
        if (stepRise > accrual) pool.reserveRun[0].add((stepRise - accrual) * pool.epsilon / sqrtStrike(pool.splits, pool.tickBin) * pool.deflator);
    }
}

File 23 of 52 : PoolHelper.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, fromUint256, fromInt256, intoInt256, POSITIVE_ZERO, POSITIVE_ONE, POSITIVE_TWO, HALF} from "src/types/ABDKMathQuad/Quad.sol";
import {
    LOG1PPC,
    JUMPS,
    MIN_SPLITS,
    EPOCH,
    DEFLATOR_STEP_0,
    DEFLATOR_STEP_1,
    DEFLATOR_STEP_2,
    DEFLATOR_STEP_3,
    DEFLATOR_STEP_4,
    DEFLATOR_STEP_5,
    DEFLATOR_STEP_6,
    DEFLATOR_STEP_7,
    DEFLATOR_STEP_8,
    DEFLATOR_STEP_9,
    DEFLATOR_STEP_10,
    DEFLATOR_STEP_11,
    DEFLATOR_STEP_12
} from "src/Constants.sol";
import {BoxcarTubFrame} from "src/libraries/internal/BoxcarTubFrame.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {PiecewiseGrowthNew} from "src/libraries/internal/DeadlineJumps.sol";
import {BucketRolling} from "src/libraries/internal/BucketRolling.sol";
import {TUBS, TICK_SUB_SLITS} from "src/Constants.sol";
import {floor, max} from "src/types/ABDKMathQuad/Math.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

using JumpyFallback for JumpyFallback.Info;
using GrowthSplitFrame for GrowthSplitFrame.Info;
using BucketRolling for BucketRolling.Info;
using BoxcarTubFrame for BoxcarTubFrame.Info;
using PiecewiseGrowthNew for PiecewiseGrowthNew.Info;

Quad constant O = POSITIVE_ZERO;

function subSplits(int256 splits) pure returns (int256) {
    return splits - int256(MIN_SPLITS);
}

function exFee(Quad fees) pure returns (Quad) {
    return POSITIVE_ONE - fees;
}

function logBin(int256 splits) pure returns (Quad) {
    return LOG1PPC / fromUint256(1 << uint256(subSplits(splits)));
}

function h(int256 splits) pure returns (Quad) {
    return (logBin(splits) / POSITIVE_TWO).exp();
}

function BINS(int256 splits) pure returns (int256) {
    return int256(1 << uint256(splits));
}

function binStrike(int256 splits, int256 bin) pure returns (Quad) {
    return (logBin(splits) * (fromInt256(bin) - (fromInt256(BINS(splits)) / POSITIVE_TWO) + HALF)).exp();
}

function sqrtStrike(int256 splits, int256 bin) pure returns (Quad) {
    return (logBin(splits) / POSITIVE_TWO * (fromInt256(bin) - (fromInt256(BINS(splits)) / POSITIVE_TWO) + HALF)).exp();
}

function mid(int256 splits, int256 tickBin) pure returns (Quad) {
    return binStrike(splits, tickBin);
}

function fracBin(int256 splits, Quad startPrice) pure returns (Quad) {
    return startPrice.log() / logBin(splits) + fromInt256(BINS(splits)) / POSITIVE_TWO;
}

function fracPrice(int256 splits, int256 tickBin, Quad binFrac) pure returns (Quad) {
    return (logBin(splits) * (fromInt256(tickBin) + binFrac - fromInt256(BINS(splits)) / fromInt256(2))).exp();
}

function tickBin(Quad fracBin) pure returns (int256) {
    return intoInt256(fracBin);
}

function tickBin(int256 splits, Quad startPrice) pure returns (int256) {
    return tickBin(fracBin(splits, startPrice));
}

function fluidAt(BoxcarTubFrame.Info storage self, int256 bin, int256 splits) view returns (Quad) {
    return BoxcarTubFrame.apply_(self, bin >> uint256(subSplits(splits)));
}

function liquid(BoxcarTubFrame.Info storage self, int256 bin, int256 splits, Quad gamma, Quad deflator) view returns (Quad) {
    return max(fluidAt(self, bin, splits) - gamma * deflator, O);
}

function gamma(JumpyFallback.Info storage lent, BoxcarTubFrame.Info storage offRamp, int256 poolEra, int256 splits, int256 tickBin) returns (Quad) {
    return lent.nowAt(poolEra, splits, tickBin) - offRamp.active(splits, tickBin);
}

function spotFee(GrowthSplitFrame.Info[2] storage fees, int256 splits, bool inToken, Quad accrual, Quad) {
    PiecewiseGrowthNew.Info storage fee = fees[inToken ? 1 : 0].live(splits);
    fee.accrued = fee.accrued + accrual;
}

function edgePrice(int256 edge) pure returns (Quad) {
    return (LOG1PPC * fromInt256(edge)).exp();
}

function edgeSqrtPrice(int256 edge) pure returns (Quad) {
    return (LOG1PPC / POSITIVE_TWO * fromInt256(edge)).exp();
}

function binLowSqrt(int256 splits, int256 bin) pure returns (Quad) {
    return (logBin(bin) / POSITIVE_TWO * (fromInt256(bin) - fromInt256(BINS(splits)) / POSITIVE_TWO)).exp();
}

function tubLowSqrt(int256 tub) pure returns (Quad) {
    return edgeSqrtPrice(tub - TUBS / 2);
}

function tubLowTick(int256 tub) pure returns (int256) {
    return ((tub - TUBS / 2) << TICK_SUB_SLITS);
}

function lowEdgeTub(int256 edge) pure returns (int256) {
    return edge + TUBS / 2;
}

// inverse of lowEdgeTub
function tubLowEdge(int256 tub) pure returns (int256) {
    return tub - TUBS / 2;
}

function tickSqrtPrice(int256 tick) pure returns (Quad) {
    return ((logTick() / POSITIVE_TWO * fromInt256(tick))).exp();
}

function binLowTick(int256 splits, int256 bin) pure returns (int256) {
    return (bin - BINS(splits) / 2) << (TICK_SUB_SLITS - uint256(subSplits(splits)));
}

function eraDay(int256 era) pure returns (int256) {
    return era >> 11;
}

function dayEra(int256 day) pure returns (int256) {
    return day << 11;
}

function dayEras() pure returns (int256) {
    return dayEra(1);
}

function dateEra(Quad date) pure returns (int256) {
    return floor(date * fromInt256(dayEras()));
}

function eraDate(int256 era) pure returns (Quad) {
    return fromInt256(era) / fromInt256(dayEras());
}

function timestampDate(uint256 timestamp) pure returns (Quad) {
    return fromInt256(int256(timestamp) - EPOCH) / fromUint256(1 days);
}

function floorMod(int256 x, int256 y) pure returns (int256) {
    int256 mod = (x % y);

    if ((mod ^ y) < 0 && mod != 0) mod = (mod + y);

    return mod;
}

function floorDiv(int256 x, int256 y) pure returns (int256) {
    int256 r = (x / y);

    if ((x ^ y) < 0 && (r * y != x)) r = (r - 1);

    return r;
}

function logTick() pure returns (Quad) {
    return LOG1PPC / fromUint256(1 << TICK_SUB_SLITS);
}

function getDeflatorStep(uint256 index) pure returns (Quad) {
    require(index <= 12, "Index out of bounds");

    if (index < 6) {
        if (index < 3) {
            if (index == 0) return DEFLATOR_STEP_0;
            else if (index == 1) return DEFLATOR_STEP_1;
            else return DEFLATOR_STEP_2;
        } else {
            if (index == 3) return DEFLATOR_STEP_3;
            else if (index == 4) return DEFLATOR_STEP_4;
            else return DEFLATOR_STEP_5;
        }
    } else {
        if (index < 9) {
            if (index == 6) return DEFLATOR_STEP_6;
            else if (index == 7) return DEFLATOR_STEP_7;
            else return DEFLATOR_STEP_8;
        } else {
            if (index == 9) return DEFLATOR_STEP_9;
            else if (index == 10) return DEFLATOR_STEP_10;
            else if (index == 11) return DEFLATOR_STEP_11;
            else return DEFLATOR_STEP_12;
        }
    }
}

library PoolHelper {
    function epsilon_(PoolState storage pool) public view returns (Quad) {
        return pool.epsilon;
    }

    function logBin(PoolState storage pool) public view returns (Quad) {
        return _logBin(pool);
    }

    function BINS(PoolState storage pool) public view returns (int256) {
        return _BINS(pool);
    }

    function sqrtMid(PoolState storage pool) public view returns (Quad) {
        return _sqrtMid(pool);
    }

    function mid(PoolState storage pool) public view returns (Quad) {
        return _mid(pool);
    }

    function liquid(PoolState storage pool) public returns (Quad) {
        return _liquid(pool);
    }
}

function _logBin(PoolState storage pool) view returns (Quad) {
    return logBin(pool.splits);
}

function _BINS(PoolState storage pool) view returns (int256) {
    return BINS(pool.splits);
}

function _sqrtMid(PoolState storage pool) view returns (Quad) {
    return sqrtStrike(pool.splits, pool.tickBin);
}

function _mid(PoolState storage pool) view returns (Quad) {
    return mid(pool.splits, pool.tickBin);
}

function _liquid(PoolState storage pool) returns (Quad) {
    Quad gamma = gamma(pool.lent, pool.offRamp, pool.era, pool.splits, pool.tickBin);
    return liquid(pool.minted, pool.tickBin, pool.splits, gamma, pool.deflator);
}

File 24 of 52 : DeadlineJumps.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO, wrap} from "src/types/ABDKMathQuad/Quad.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {JUMPS, JUMP_NONE} from "src/Constants.sol";
import {eraDate, getDeflatorStep} from "src/libraries/helpers/PoolHelper.sol";
import {
    jumpBitLength,
    maybeJump,
    jumpIndex as jumpIndexHelper,
    isJumpDefined,
    validDeadline,
    deadEra as deadEraHelper
} from "src/libraries/helpers/DeadlineHelper.sol";

/**
 * @dev This library allows for tracking float values called "drops" associated to future time points, they are defined as a finite and fixed number of indexes (up to `JUMPS`) based on the `dropsEra` tracked in the data structure.
 * @dev The tracked values represent liquidity drops expiring in the future.
 * @dev The functions overloading on `deadEra` that can be both specified or not (int256 vs OptInt256) are used to keep the caller logic simpler since the overloading is resolved internally in the library.
 */
library DeadlineSet {
    using SafeCast for int256;

    struct Info {
        Quad[JUMPS] drops;
        Quad latest;
        int32 dropsEra;
        // The next `jump` (0 <= `jump` < `JUMPS`) to be processed
        int8 nextJump;
        // If the data structure is initialized
        bool touched;
    }

    error InvalidDeadline();

    /**
     * @dev Conversion function from an "era" to a "jump"
     */
    function jumpIndex(Info storage self, int256 deadEra) internal view returns (int8) {
        return jumpIndexHelper(self.dropsEra, deadEra);
    }

    /**
     * @dev Conversion function from a "jump" to an "era"
     */
    function deadEra(Info storage self, int8 jumpIndex) internal view returns (int256) {
        return deadEraHelper(self.dropsEra, jumpIndex);
    }

    function dropAt(Info storage self, int256 deadEra) internal view returns (Quad) {
        if (jumpIndex(self, deadEra) == JUMP_NONE) return POSITIVE_ZERO;
        else return self.drops[int256(jumpIndex(self, deadEra)).toUint256()];
    }

    /**
     * @dev This function returns the index in the drops array that corresponds to the input deadEra.
     * @dev This function is called within many other functions to get the nextJump Index to work on.
     * @param self The storage struct
     * @param deadEra The deadline era
     * @return The index corresponding to the input deadline era, resetting the `self.nextJump` to this value
     */
    function slot(Info storage self, int256 poolEra, int256 deadEra, function (DeadlineSet.Info storage,Quad) internal dropoff)
        internal
        returns (int8)
    {
        // Input Validation
        if (!(validDeadline(poolEra, deadEra))) revert InvalidDeadline();

        if (!(self.touched)) {
            // If not touched, initialize the data structure with the current pool era and compute the nextJump index for the input deadline era, then return the index.
            self.dropsEra = int32(poolEra);
            self.nextJump = jumpIndex(self, deadEra);
            self.touched = true;
            return self.nextJump;
        }

        // If touched, compute the nextJump index for the input deadline era
        int8 jump = jumpIndex(self, deadEra);

        // It is possible that the jump is not defined, in this case, it is required to do a temporal realignment of the data structure which involves resetting the `self.dropsEra = poolEra` (like in the initialization logic branch above, this is done in the reanchor() function
        if (!isJumpDefined(jump)) {
            reanchor(self, poolEra, dropoff);
            jump = maybeJump(self.dropsEra, deadEra);
        }

        if ((jump < self.nextJump)) self.nextJump = jump;

        return int8(jump);
    }

    /**
     * @dev Assigns a value to a deadline slot
     * @param self The storage struct
     * @param poolEra The current pool era
     * @param value The value to assign
     * @param deadEra The deadline era
     * @param dropoff The function to call when a deadline is reached
     */
    function assign(Info storage self, int256 poolEra, Quad value, int256 deadEra, function (DeadlineSet.Info storage,Quad) internal dropoff)
        internal
    {
        int8 jump = slot(self, poolEra, deadEra, dropoff);
        self.drops[int256(jump).toUint256()] = value;
    }

    /**
     * @dev Assigns a value to a deadline slot
     * @param self The storage struct
     * @param poolEra The current pool era
     * @param value The value to assign
     * @param deadEra The deadline era
     * @param dropoff The function to call when a deadline is reached
     */
    function assign(Info storage self, int256 poolEra, Quad value, OptInt256 deadEra, function (DeadlineSet.Info storage,Quad) internal dropoff)
        internal
    {
        if (deadEra.isDefined()) assign(self, poolEra, value, deadEra.get(), dropoff);
    }

    /**
     * @dev This function is used to adjust the data structure when the timeline has moved past certain deadlines.
     * @dev It shifts valid deadlines to new positions, resets others, and updates the timeline (dropsEra) and nextJump.
     * @param self The storage struct
     * @param poolEra The current pool era
     * @param dropoff The function to call when a deadline is reached
     */
    function reanchor(Info storage self, int256 poolEra, function (DeadlineSet.Info storage,Quad) internal dropoff) internal {
        bool unset = true;

        // Loop over all the indexes between the nextJump and JUMPS
        for (int8 oldIndex = self.nextJump; (oldIndex < JUMPS); oldIndex = (oldIndex + 1)) {
            Quad drop = self.drops[int256(oldIndex).toUint256()];

            // Only non zero drops are processed in this loop
            if ((drop != POSITIVE_ZERO)) {
                // Computes the deadline era for the current index / jump
                int256 deadline = deadEra(self, oldIndex);

                if ((deadline <= poolEra)) {
                    // If this non zero drop is expired, call the dropoff function
                    dropoff(self, drop);
                } else {
                    // If this non zero drop is not expired, compute the new index / jump based on the current poolEra
                    int8 newIndex = maybeJump(poolEra, deadline);

                    if (unset) {
                        // If this is the first non zero drop encountered in this loop, set the nextJump to the new index
                        // All the zero jumps before this non zero jump are skipped
                        self.nextJump = newIndex;
                        unset = false;
                    }

                    // If this index did not change, then there is no need to check the other following indexes / jumps
                    if ((newIndex == oldIndex)) break;

                    // Reassign the drop to the new index
                    // This does not interfere with the assignment of `drop` in the loop since it should always be the case that `newIndex < oldIndex` in this branch of logic
                    self.drops[int256(newIndex).toUint256()] = drop;
                }
                self.drops[int256(oldIndex).toUint256()] = POSITIVE_ZERO;
            }
        }

        // Updates the `dropsEra` to the current `poolEra` as all the jumps have been reset accordingly
        self.dropsEra = int32(poolEra);
        if (unset) self.nextJump = JUMPS;
    }
}

library DropsGroup {
    using DeadlineSet for DeadlineSet.Info;
    using SafeCast for int256;

    error DeadlineNotDefined();

    /**
     * @dev These functions handle the process of updating a drop at specific deadline.
     * @dev This specific overloaded version of the function, directly updates the drop at a specified deadline by adding to the existing drop value.
     * @dev This process utilizes the `slot()` function from the DeadlineSet library to reanchor it in time if necessary and compute the correct index in the drops array and updates it.
     * @dev It calls the dropoff function when a deadline is reached.
     * @param self The storage struct
     * @param poolEra The current pool era
     * @param drop The amount to drop
     * @param deadEra The deadline era
     * @param dropoff The function to call when a deadline is reached
     */
    function expire(
        DeadlineSet.Info storage self,
        int256 poolEra,
        Quad drop,
        int256 deadEra,
        function (DeadlineSet.Info storage,Quad) internal dropoff
    ) internal {
        int256 idx = self.slot(poolEra, deadEra, dropoff);

        self.drops[idx.toUint256()] = self.drops[idx.toUint256()] + drop;
    }

    function expire(
        DeadlineSet.Info storage self,
        int256 poolEra,
        Quad drop,
        OptInt256 deadEra,
        function (DeadlineSet.Info storage,Quad) internal dropoff
    ) internal {
        if (deadEra.isDefined()) expire(self, poolEra, drop, deadEra.get(), dropoff);
    }

    function expire(DeadlineSet.Info storage self, int256 poolEra, Quad drop, int256 deadEra) internal {
        expire(self, poolEra, drop, deadEra, dropOff);
    }

    function expire(DeadlineSet.Info storage self, int256 poolEra, Quad drop, OptInt256 deadEra) internal {
        expire(self, poolEra, drop, deadEra, dropOff);
    }

    /**
     * @dev This function is used to adjust the value of a drop at an old deadline and move or replicate this value to a new deadline.
     * @dev It calls the dropoff function when a deadline is reached.
     * @param self The storage struct
     * @param poolEra The current pool era
     * @param amount The amount to extend
     * @param oldDeadEra The old deadline era
     * @param newDeadEra The new deadline era
     * @param dropoff The function to call when a deadline is reached
     */
    function extend(
        DeadlineSet.Info storage self,
        int256 poolEra,
        Quad amount,
        OptInt256 oldDeadEra,
        OptInt256 newDeadEra,
        function (DeadlineSet.Info storage,Quad) internal dropoff
    ) internal {
        if (!(oldDeadEra.isDefined())) revert DeadlineNotDefined();

        expire(self, poolEra, amount.neg(), oldDeadEra, dropoff);
        expire(self, poolEra, amount, newDeadEra, dropoff);
    }

    function dropOff(DeadlineSet.Info storage self, Quad drop) internal {}

    function extend(DeadlineSet.Info storage self, int256 poolEra, Quad amount, OptInt256 oldDeadEra, OptInt256 newDeadEra) internal {
        extend(self, poolEra, amount, oldDeadEra, newDeadEra, dropOff);
    }

    function reanchor(DeadlineSet.Info storage self, int256 poolEra) internal {
        self.reanchor(poolEra, dropOff);
    }
}

library PiecewiseCurve {
    using DropsGroup for DeadlineSet.Info;
    using DeadlineSet for DeadlineSet.Info;
    using SafeCast for int256;

    function dropOff(DeadlineSet.Info storage self, Quad drop) internal {
        self.latest = self.latest - drop;
    }

    /**
     * @dev This function is used to get the current value of the `latest` tracker.
     * @dev It adjusts the data structure by removing expired deadlines and their values and updating the `latest` accordingly.
     * @param self The storage struct
     * @param poolEra The current pool era
     * @return The current value of the latest deadline
     */
    function now(DeadlineSet.Info storage self, int256 poolEra) internal returns (Quad) {
        if (self.touched) {
            // Loops over the expired jumps, if any, and removes the corresponding amount from the `latest` tracker
            // A jump is expired if the `poolEra`, a measure of the current time, is greater than the deadline era
            while (self.nextJump < JUMPS && (poolEra >= (self.deadEra(self.nextJump)))) {
                self.latest = self.latest - self.drops[(int256(self.nextJump)).toUint256()];
                self.drops[int256(self.nextJump).toUint256()] = POSITIVE_ZERO;
                self.nextJump = self.nextJump + 1;
            }
        }

        return self.latest;
    }

    /**
     * @dev This function is used to initialize or update the state.
     * @dev The update case just increases the tracked amount while the initialize case also defines the temporal dimension by setting the `dropsEra` field.
     * @param self The storage struct
     * @param poolEra The current pool era
     */
    function begin(DeadlineSet.Info storage self, int256 poolEra, Quad amount) internal {
        if (!(self.touched)) {
            // Data structure needs to be re-initialized from a temporal perspective so it is anchored to the current time, represented by `poolEra`, and the Jumps scheduled is initialized as empty
            self.latest = amount;
            self.dropsEra = int32(poolEra);
            self.nextJump = JUMPS;
            self.touched = true;
        } else {
            // Data structure is already initialized so it just needs to be updated with the new amount
            self.latest = (self.latest + amount);
        }
    }

    function expire(DeadlineSet.Info storage self, int256 poolEra, Quad drop, int256 deadEra) internal {
        self.expire(poolEra, drop, deadEra, dropOff);
    }

    function expire(DeadlineSet.Info storage self, int256 poolEra, Quad drop, OptInt256 deadEra) internal {
        self.expire(poolEra, drop, deadEra, dropOff);
    }

    function extend(DeadlineSet.Info storage self, int256 poolEra, Quad amount, OptInt256 oldDeadEra, OptInt256 newDeadEra) internal {
        self.extend(poolEra, amount, oldDeadEra, newDeadEra, dropOff);
    }

    function assign(DeadlineSet.Info storage self, int256 poolEra, Quad value, int256 deadEra) internal {
        self.assign(poolEra, value, deadEra, dropOff);
    }

    function assign(DeadlineSet.Info storage self, int256 poolEra, Quad value, OptInt256 deadEra) internal {
        self.assign(poolEra, value, deadEra, dropOff);
    }

    function reanchor(DeadlineSet.Info storage self, int256 poolEra) internal {
        self.reanchor(poolEra, dropOff);
    }
}

library DeadlineJumps {
    using DeadlineSet for DeadlineSet.Info;
    using PiecewiseCurve for DeadlineSet.Info;

    /**
     * @dev Adds a new value `amount` that expires at `deadEra`
     */
    function create(DeadlineSet.Info storage self, int256 poolEra, Quad amount, OptInt256 deadEra) internal {
        self.begin(poolEra, amount);
        self.expire(poolEra, amount, deadEra);
    }
}

library TailedJumps {
    using PiecewiseCurve for DeadlineSet.Info;

    struct Info {
        DeadlineSet.Info deadlineJumps;
        Quad tail;
    }

    /**
     * @dev If the `deadEra` is defined, tracks the new expiring value `drop` at `deadEra`, otherwise it tracks it at `tail` that has no temporal reference.
     */
    function expire(Info storage self, int256 poolEra, Quad drop, OptInt256 deadEra) internal {
        if (deadEra.isDefined()) self.deadlineJumps.expire(poolEra, drop, deadEra);
        else self.tail = self.tail + drop;
    }

    function create(Info storage self, int256 poolEra, Quad amount, OptInt256 deadEra) internal {
        self.deadlineJumps.begin(poolEra, amount);
        expire(self, poolEra, amount, deadEra);
    }
}

/**
 * @dev Here time is tracked in 2 reference systems: the discretized time represented by the concept of "eras" and the continuous time represented by the concept of "deflator".
 */
library PiecewiseGrowthNew {
    using SafeCast for int256;
    using SafeCast for uint256;
    using PiecewiseCurve for DeadlineSet.Info;
    using DeadlineSet for DeadlineSet.Info;

    struct Info {
        // Reordered to save space
        Quad accrued;
        Quad atDeflator;
        Quad tail;
        int32 atEra;
        DeadlineSet.Info deadlineJumps;
    }

    function accruing(Info storage self) public view returns (bool) {
        return (self.atDeflator != POSITIVE_ZERO);
    }

    /**
     * @dev Tracks the last time the accrual accounting was updated.
     */
    function markAccrual(Info storage self, int256 poolEra, Quad poolDeflator) public {
        self.atEra = int32(poolEra);
        self.atDeflator = poolDeflator;
    }

    function growAccruing(Info storage self, int256 poolEra, Quad poolDeflator) public {
        if (self.deadlineJumps.touched && (((poolDeflator) < self.atDeflator))) {
            // If some time passed since last time the accrual accounting was updated, then there is some new accrual to account
            // NOTE: The deflator decreases over time therefore the `poolDeflator < self.atDeflator` means some time has passed since the last update

            // Loop to clear all the expired jumps the usual way (see above)
            while (self.deadlineJumps.nextJump < JUMPS && self.atEra >= self.deadlineJumps.deadEra(self.deadlineJumps.nextJump)) {
                self.deadlineJumps.latest = self.deadlineJumps.latest - self.deadlineJumps.drops[int256(self.deadlineJumps.nextJump).toUint256()];
                self.deadlineJumps.drops[int256(self.deadlineJumps.nextJump).toUint256()] = POSITIVE_ZERO;
                self.deadlineJumps.nextJump = self.deadlineJumps.nextJump + 1;
            }

            if (poolEra == (self.atEra) || ((self.deadlineJumps.nextJump == JUMPS))) {
                // If some time has passed since the last update but not enough to trigger a new era or if there are no jumps in the future to process just update the accrual accounting (this is done also in the other branch of the logic)

                // The `latest` accrued value is proportional to the DeltaT between the `atDeflator` and the current `poolDeflator`
                self.accrued = (self.accrued + (self.deadlineJumps.latest * (self.atDeflator - poolDeflator)));
            } else {
                int8 t1 = maybeJump(self.deadlineJumps.dropsEra, poolEra);

                int256 lastJump = (t1 < (JUMPS - 1)) ? int8(t1) : (JUMPS - 1);

                if ((poolEra < self.deadlineJumps.deadEra(lastJump.toInt8()))) lastJump = (lastJump - 1);

                int256 pastJump = -1;

                int256 pastEra = 0;

                Quad nextDeflator = self.atDeflator;

                while (self.deadlineJumps.nextJump <= (lastJump)) {
                    // Process the jumps between the last processed jump and the one in `lastJump` (inclusive)
                    Quad drop = self.deadlineJumps.drops[int256(self.deadlineJumps.nextJump).toUint256()];

                    if ((drop != POSITIVE_ZERO)) {
                        if ((pastJump == -1)) {
                            pastJump = int8(self.deadlineJumps.nextJump);
                            pastEra = self.deadlineJumps.deadEra(pastJump.toInt8());
                            nextDeflator = (-eraDate(pastEra)).exp2();
                        } else {
                            self.atDeflator = nextDeflator;
                            while ((pastJump < self.deadlineJumps.nextJump)) {
                                int256 eraStep = 0;

                                if ((pastJump == 0)) eraStep = 0;
                                else if (((pastEra & (1 << (pastJump.toUint256() - 1)).toInt256()) != 0)) eraStep = (pastJump - 1);
                                else eraStep = pastJump;

                                pastEra = (pastEra + (1 << eraStep.toUint256()).toInt256());
                                nextDeflator = (nextDeflator * getDeflatorStep(eraStep.toUint256()));
                                pastJump = (pastJump + 1);
                            }
                        }
                        // Before clearing out the expired jumps, update the accrual accounting with that included
                        self.accrued = (self.accrued + (self.deadlineJumps.latest * (self.atDeflator - nextDeflator)));
                        self.deadlineJumps.latest = (self.deadlineJumps.latest - drop);
                        self.deadlineJumps.drops[int256(self.deadlineJumps.nextJump).toUint256()] = POSITIVE_ZERO;
                    }

                    self.deadlineJumps.nextJump = self.deadlineJumps.nextJump + 1;
                }
                self.accrued = (self.accrued + (self.deadlineJumps.latest * (nextDeflator - poolDeflator)));
            }
        }

        markAccrual(self, poolEra, poolDeflator);
    }

    function grow(Info storage self, int256 poolEra, Quad poolDeflator) public returns (Quad) {
        if (accruing(self)) growAccruing(self, poolEra, poolDeflator);
        return self.accrued;
    }

    function reanchor(Info storage self, int256 poolEra, Quad poolDeflator) public {
        grow(self, poolEra, poolDeflator);
        self.deadlineJumps.reanchor(poolEra);
    }

    /**
     * @dev Adds a new value `amount` that expires at `deadEra` but if `isAccruing` is true, it will also update the accruing accounting.
     */
    function create(Info storage self, int256 poolEra, Quad poolDeflator, Quad amount, OptInt256 deadEra, bool isAccruing) public {
        if (isAccruing) growAccruing(self, poolEra, poolDeflator);
        self.deadlineJumps.begin(poolEra, amount);
        expire(self, poolEra, amount, deadEra);
    }

    function expire(Info storage self, int256 poolEra, Quad drop, OptInt256 deadEra) public {
        if (deadEra.isDefined()) self.deadlineJumps.expire(poolEra, drop, deadEra);
        else self.tail = self.tail + drop;
    }
}

File 25 of 52 : ValueType.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;
import "./Constants.sol" as Constants;

type Quad is bytes16;

using {Casting.unwrap} for Quad global;

using {Math.exp2, Math.exp, Math.log, Math.sqrt, Math.max} for Quad global;

using {
    Helpers.add,
    Helpers.sub,
    Helpers.mul,
    Helpers.div,
    Helpers.eq,
    Helpers.neq,
    Helpers.neg,
    Helpers.lt,
    Helpers.lte,
    Helpers.gt,
    Helpers.gte,
    Helpers.abs,
    Helpers.isNaN
} for Quad global;

using {
    Helpers.add as +,
    Helpers.sub as -,
    Helpers.mul as *,
    Helpers.div as /,
    Helpers.eq as ==,
    Helpers.neq as !=,
    Helpers.neg as -,
    Helpers.lt as <,
    Helpers.lte as <=,
    Helpers.gt as >,
    Helpers.gte as >=
} for Quad global;

File 26 of 52 : Constants.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {ABDKMathQuad} from "../../../lib/abdk-libraries-solidity/ABDKMathQuad.sol";
import {Quad} from "./ValueType.sol";

Quad constant POSITIVE_ZERO = Quad.wrap(bytes16(0));
Quad constant NEGATIVE_ZERO = Quad.wrap(bytes16(0x80000000000000000000000000000000));
Quad constant HALF = Quad.wrap(0x3ffe0000000000000000000000000000);
Quad constant POSITIVE_ONE = Quad.wrap(bytes16(0x3fff0000000000000000000000000000));
Quad constant NEGATIVE_ONE = Quad.wrap(bytes16(0xbfff0000000000000000000000000000));
Quad constant NEGATIVE_TWO = Quad.wrap(bytes16(0xc0000000000000000000000000000000));
Quad constant POSITIVE_TWO = Quad.wrap(bytes16(0x40000000000000000000000000000000));
Quad constant POSITIVE_THREE = Quad.wrap(bytes16(0x40008000000000000000000000000000));
Quad constant POSITIVE_FOUR = Quad.wrap(bytes16(0x40010000000000000000000000000000));
Quad constant POSITIVE_EIGHT = Quad.wrap(bytes16(0x40020000000000000000000000000000));
Quad constant POSITIVE_NINE = Quad.wrap(bytes16(0x40022000000000000000000000000000));
Quad constant POSITIVE_TEN = Quad.wrap(bytes16(0x40024000000000000000000000000000));
Quad constant NEGATIVE_INFINITY = Quad.wrap(bytes16(0xFFFF0000000000000000000000000000));
Quad constant POSITIVE_INFINITY = Quad.wrap(bytes16(0x7FFF0000000000000000000000000000));

Quad constant POSITIVE_EIGHT_OVER_NINE = Quad.wrap(bytes16(0x3ffec71c71c71c71c71c71c71c71c71c));
Quad constant POSITIVE_ONE_OVER_FOUR = Quad.wrap(bytes16(0x3ffd0000000000000000000000000000));

Quad constant EXP1MQ_THRESHOLD = Quad.wrap(0x40060000000000000000000000000000);

Quad constant EXP1MQ_P0 = Quad.wrap(0x401b18b74db8e974042c1eac2c0a7a5e);
Quad constant EXP1MQ_P1 = Quad.wrap(0xC018b49e5c6b648d31976b98b3e697c3);
Quad constant EXP1MQ_P2 = Quad.wrap(0x401610f7ed9cdae66efa8978bd75c0a5);
Quad constant EXP1MQ_P3 = Quad.wrap(0xC012602b68aef25d83c39ea400d894a2);
Quad constant EXP1MQ_P4 = Quad.wrap(0x400e65bb3fe055cce75e048a64c689c0);
Quad constant EXP1MQ_P5 = Quad.wrap(0xC009ad3170bea3194cbe8d312eb34724);
Quad constant EXP1MQ_P6 = Quad.wrap(0x4004601acdf8f4a35cb7b3af79c1ce70);
Quad constant EXP1MQ_P7 = Quad.wrap(0xbffdf49b524a2c731e1a0a08790ae2ce);

Quad constant EXP1MQ_Q0 = Quad.wrap(0x401da512f4955e2e06422e02420fb8ce);
Quad constant EXP1MQ_Q1 = Quad.wrap(0xc01c7644dcf2f4cbf5b9df5a647e501b);
Quad constant EXP1MQ_Q2 = Quad.wrap(0x401a3433da9ed469900a26e6c4460f28);
Quad constant EXP1MQ_Q3 = Quad.wrap(0xc017342de8ba7627efc2e1ebcb9f9029);
Quad constant EXP1MQ_Q4 = Quad.wrap(0x40139ade0baac376535af72a52fad173);
Quad constant EXP1MQ_Q5 = Quad.wrap(0xc00f779b1d90dd705e51381c74ca5bae);
Quad constant EXP1MQ_Q6 = Quad.wrap(0x400ace36e0e390d4c5b2a0885cb5703e);
Quad constant EXP1MQ_Q7 = Quad.wrap(0xc0056017f7f4f644ac4924ea19c21ecb);

Quad constant EXP1MQ_C1 = Quad.wrap(0x3ffe62e4000000000000000000000000);
Quad constant EXP1MQ_C2 = Quad.wrap(0x3feb7f7d1cf79abc9e3b39803f2f6af4);

Quad constant EXP1MQ_MINARG = Quad.wrap(0xc0053c133ab16db990b9ff9d97e6c709);
Quad constant EXP1MQ_MAXLOG = Quad.wrap(0x400c62e42fefa39ef35793c7673007e6);
Quad constant EXP1MQ_TWO114 = Quad.wrap(0x40710000000000000000000000000000);
Quad constant EXP1MQ_HUGE_VALQ = Quad.wrap(0x400effffffffffffffffffffffffffff);

Quad constant LOG1PQ_P12 = Quad.wrap(0x3feb9d04a0d6ed8295434922008560fc);
Quad constant LOG1PQ_P11 = Quad.wrap(0x3ffdffd7e21347cc2e9cb5e91a8c2fa0);
Quad constant LOG1PQ_P10 = Quad.wrap(0x400373615178fe96674c43ea62a592e7);
Quad constant LOG1PQ_P9 = Quad.wrap(0x40079b73a8639c28fa539715d5fd0560);
Quad constant LOG1PQ_P8 = Quad.wrap(0x400ade1e79b3ae125ec5c60d38b7fa2a);
Quad constant LOG1PQ_P7 = Quad.wrap(0x400d4ca24f0550cf6369f0cada64eeec);
Quad constant LOG1PQ_P6 = Quad.wrap(0x400f28a791822d40115104b644c1f464);
Quad constant LOG1PQ_P5 = Quad.wrap(0x40105f196a49f17195ec43488121aff8);
Quad constant LOG1PQ_P4 = Quad.wrap(0x401116caba9f2757a2484b7171ab5034);
Quad constant LOG1PQ_P3 = Quad.wrap(0x401125a72eb05ba7e49b2bf8646a8a1e);
Quad constant LOG1PQ_P2 = Quad.wrap(0x4010897ca319418d17ac5c737d1b8ad4);
Quad constant LOG1PQ_P1 = Quad.wrap(0x400f2f8f8bfbf9a19ff15925da76d408);
Quad constant LOG1PQ_P0 = Quad.wrap(0x400c9a7dcad5d0efe740b8544d79077c);

Quad constant LOG1PQ_Q11 = Quad.wrap(0x40048322fbda4d3f4a2113daac8d7fa5);
Quad constant LOG1PQ_Q10 = Quad.wrap(0x4008c73f14777e569efb2fe2c778f56f);
Quad constant LOG1PQ_Q9 = Quad.wrap(0x400c1dd933ea5565f23a98d434d3a705);
Quad constant LOG1PQ_Q8 = Quad.wrap(0x400eb5f4d77aed024b44059a3b76f461);
Quad constant LOG1PQ_Q7 = Quad.wrap(0x4010b71bb67f5eff2962234d48fff0bc);
Quad constant LOG1PQ_Q6 = Quad.wrap(0x40122b6c5ddac3b8e673c713bcf24ee3);
Quad constant LOG1PQ_Q5 = Quad.wrap(0x40131ab83fa3b03b34d8d36e8de37c71);
Quad constant LOG1PQ_Q4 = Quad.wrap(0x401371d8273f762a061338bb0e95b314);
Quad constant LOG1PQ_Q3 = Quad.wrap(0x401348fbe89d38e2e379b5d8e7071d74);
Quad constant LOG1PQ_Q2 = Quad.wrap(0x40127bc5211688c1412eafafea233277);
Quad constant LOG1PQ_Q1 = Quad.wrap(0x40110088814003ea16378fd2514ba129);
Quad constant LOG1PQ_Q0 = Quad.wrap(0x400e33de58205cb3ed708a3f3a1ac5ca);

Quad constant LOG1PQ_R5 = Quad.wrap(0xbffec40a1c874f5a68479d54e4ced708);
Quad constant LOG1PQ_R4 = Quad.wrap(0x40054247b533971e565b5611a30df628);
Quad constant LOG1PQ_R3 = Quad.wrap(0xc009fa1350a9210eb690eddd457e03b0);
Quad constant LOG1PQ_R2 = Quad.wrap(0x400d4020cbb3c4edea1230d4dc2a41c8);
Quad constant LOG1PQ_R1 = Quad.wrap(0xc00f5eac94780e23388e5d3ae806c32a);
Quad constant LOG1PQ_R0 = Quad.wrap(0x401014fab5e2e8c16802a6fb3250b4fd);

Quad constant LOG1PQ_S5 = Quad.wrap(0xc005da8b34108b632575cd7cadd52c63);
Quad constant LOG1PQ_S4 = Quad.wrap(0x400af3d0db24df089022bf51e9d20aec);
Quad constant LOG1PQ_S3 = Quad.wrap(0xc00ec11ad77cc51ceb27fc1032bb267d);
Quad constant LOG1PQ_S2 = Quad.wrap(0x401186c6f13df72eaeec5bd6a5211cbd);
Quad constant LOG1PQ_S1 = Quad.wrap(0xc013455371e04bc5ee9e91e4b3020178);
Quad constant LOG1PQ_S0 = Quad.wrap(0x40139f7810d45d221c03fa78cb791730);

Quad constant LOG1PQ_C1 = Quad.wrap(0x3ffe62e4000000000000000000000000);
Quad constant LOG1PQ_C2 = Quad.wrap(0x3feb7f7d1cf79abc9e3b39803f2f6af4);
Quad constant LOG1PQ_SQRTH = Quad.wrap(0x3ffe6a09e667f3bcc908b2fb1366ea95);
Quad constant LOG1PQ_THRESHOLD = Quad.wrap(0x3f8e0000000000000000000000000000);

// Corresponds to Int128(-1L, 0L)
bytes16 constant FREXP_H128 = bytes16(0xFFFFFFFFFFFFFFFF0000000000000000);
// Corresponds to Int128(0L, -1L)
bytes16 constant FREXP_L128 = bytes16(0x0000000000000000FFFFFFFFFFFFFFFF);

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "./ValueType.sol";
import {ABDKMathQuad} from "../../../lib/abdk-libraries-solidity/ABDKMathQuad.sol";

function intoInt256(Quad x) pure returns (int256 result) {
    result = ABDKMathQuad.toInt(x.unwrap());
}

function intoUint256(Quad x) pure returns (uint256 result) {
    result = ABDKMathQuad.toUInt(x.unwrap());
}

function fromInt256(int256 x) pure returns (Quad result) {
    result = Quad.wrap(ABDKMathQuad.fromInt(x));
}

function fromUint256(uint256 x) pure returns (Quad result) {
    result = Quad.wrap(ABDKMathQuad.fromUInt(x));
}

function unwrap(Quad x) pure returns (bytes16 result) {
    result = Quad.unwrap(x);
}

function wrap(bytes16 x) pure returns (Quad result) {
    result = Quad.wrap(x);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {ABDKMathQuad} from "../../../lib/abdk-libraries-solidity/ABDKMathQuad.sol";
import {wrap} from "./Helpers.sol";
import {Quad} from "./ValueType.sol";
import {exp, floor} from "./Math.sol";
import {fromUint256, fromInt256, intoUint256, intoInt256} from "./Casting.sol";
import {POSITIVE_ONE, NEGATIVE_ONE, POSITIVE_ZERO, HALF, POSITIVE_TWO, POSITIVE_INFINITY, NEGATIVE_INFINITY} from "./Constants.sol";
import {
    EXP1MQ_THRESHOLD,
    EXP1MQ_MAXLOG,
    EXP1MQ_C1,
    EXP1MQ_C2,
    EXP1MQ_P7,
    EXP1MQ_P6,
    EXP1MQ_P5,
    EXP1MQ_P4,
    EXP1MQ_P3,
    EXP1MQ_P2,
    EXP1MQ_P1,
    EXP1MQ_P0,
    EXP1MQ_Q7,
    EXP1MQ_Q6,
    EXP1MQ_Q5,
    EXP1MQ_Q4,
    EXP1MQ_Q3,
    EXP1MQ_Q2,
    EXP1MQ_Q1,
    EXP1MQ_Q0,
    EXP1MQ_MINARG,
    FREXP_H128,
    FREXP_L128,
    EXP1MQ_TWO114,
    LOG1PQ_R5,
    LOG1PQ_R4,
    LOG1PQ_R3,
    LOG1PQ_R2,
    LOG1PQ_R1,
    LOG1PQ_R0,
    LOG1PQ_S5,
    LOG1PQ_S4,
    LOG1PQ_S3,
    LOG1PQ_S2,
    LOG1PQ_S1,
    LOG1PQ_S0,
    LOG1PQ_P12,
    LOG1PQ_P11,
    LOG1PQ_P10,
    LOG1PQ_P9,
    LOG1PQ_P8,
    LOG1PQ_P7,
    LOG1PQ_P6,
    LOG1PQ_P5,
    LOG1PQ_P4,
    LOG1PQ_P3,
    LOG1PQ_P2,
    LOG1PQ_P1,
    LOG1PQ_P0,
    LOG1PQ_Q11,
    LOG1PQ_Q10,
    LOG1PQ_Q9,
    LOG1PQ_Q8,
    LOG1PQ_Q7,
    LOG1PQ_Q6,
    LOG1PQ_Q5,
    LOG1PQ_Q4,
    LOG1PQ_Q3,
    LOG1PQ_Q2,
    LOG1PQ_Q1,
    LOG1PQ_Q0,
    LOG1PQ_C2,
    LOG1PQ_C1,
    LOG1PQ_SQRTH,
    LOG1PQ_THRESHOLD
} from "./Constants.sol";
import {FloatBits, QuadPacker} from "src/libraries/internal/SparseFloat.sol";

struct State_exp1mq {
    int8 sign;
    Quad xx;
    Quad px;
    Quad qx;
    int128 e;
    Quad X;
    Quad z;
    Quad y;
    int256 _k;
    int128 k;
    FloatBits.Info temp;
    Quad t1;
}

error KOutOfRange(int256 k);

function expm1(Quad x) pure returns (Quad) {
    State_exp1mq memory state;
    // On purpose, we want ignore NaN handling so that we crash here instead of risking to corrupt the state

    // This function will crash if x is NaN
    state.sign = ABDKMathQuad.sign(x.unwrap());
    if ((state.sign > 0) && (x >= EXP1MQ_THRESHOLD)) return exp(x);
    if (ABDKMathQuad.isInfinity(x.unwrap())) {
        if (state.sign < 0) return NEGATIVE_ONE;
        else return x;
    }

    if (state.sign == 0) return x;

    if (x > EXP1MQ_MAXLOG) return POSITIVE_INFINITY;

    if (x < EXP1MQ_MINARG) return NEGATIVE_ONE;

    state.xx = EXP1MQ_C1 + EXP1MQ_C2;
    state.px = fromInt256(floor(HALF + (x / state.xx)));
    state._k = intoInt256(state.px);
    if (!((state._k >= type(int128).min) && (state._k <= type(int128).max))) revert KOutOfRange(state._k);
    state.k = int128(state._k);
    state.X = (x - state.px * EXP1MQ_C1) - state.px * EXP1MQ_C2;

    // Solving stack too deep error
    state.t1 = ((EXP1MQ_P7 * state.X + EXP1MQ_P6) * state.X + EXP1MQ_P5);
    state.px =
        (((((state.t1 * state.X + EXP1MQ_P4) * state.X + EXP1MQ_P3) * state.X + EXP1MQ_P2) * state.X + EXP1MQ_P1) * state.X + EXP1MQ_P0) * state.X;
    state.qx = (
        ((((((state.X + EXP1MQ_Q7) * state.X + EXP1MQ_Q6) * state.X + EXP1MQ_Q5) * state.X + EXP1MQ_Q4) * state.X + EXP1MQ_Q3) * state.X + EXP1MQ_Q2)
            * state.X + EXP1MQ_Q1
    ) * state.X + EXP1MQ_Q0;
    state.xx = state.X * state.X;
    state.qx = state.X + (HALF * state.xx + state.xx * state.px / state.qx);

    state.temp.exponent = state.k;
    state.temp.significand = 1;
    state.px = QuadPacker.repack(state.temp);
    return state.px * state.qx + (state.px - POSITIVE_ONE);
}

function computeHX(bytes16 x) pure returns (bytes8 hx) {
    bytes16 th1 = FREXP_H128 & x;
    // NOTE: Solidity is big endian so the bytes16 to bytes8 casting takes the leftmost bytes
    hx = bytes8(th1);
}

function computeLX(bytes16 x) pure returns (bytes8 lx) {
    bytes16 tl1 = FREXP_L128 & x;
    bytes16 tl2;
    assembly {
        tl2 := shl(64, tl1)
    }
    // NOTE: Solidity is big endian so the bytes16 to bytes8 casting takes the leftmost bytes
    lx = bytes8(tl2);
}

function frexp(Quad xm1signed) pure returns (Quad xx, int128 e) {
    int8 sign = ABDKMathQuad.sign(xm1signed.unwrap());
    Quad xm1 = (sign < 0) ? NEGATIVE_ONE * xm1signed : xm1signed;
    bytes16 x = xm1.unwrap();
    bytes8 hx = computeHX(x);
    bytes8 lx = computeLX(x);
    bytes8 ix = hx & 0x7fffffffffffffff;
    int128 eptr = 0;

    if (ix >= 0x7fff000000000000 || ((ix | lx) == 0)) return (xm1signed, eptr);
    if (ix < 0x0001000000000000) {
        x = (xm1 * EXP1MQ_TWO114).unwrap();
        hx = computeHX(x);
        ix = hx & 0x7fffffffffffffff;
        eptr = -114;
    }

    // This also works
    //        uint128 temp = uint128(uint64(ix >> 48));

    bytes8 temp1;
    assembly {
        temp1 := shr(48, ix)
    }
    uint128 temp = uint128(uint64(temp1));
    eptr = eptr + int128(int128(temp) - 16382);
    hx = (hx & 0x8000ffffffffffff) | 0x3ffe000000000000;
    int128 t1 = int128(uint128(uint64(hx)));
    x = bytes16((x & FREXP_L128) | bytes16(uint128(t1 << 64)));
    Quad xm1_final = wrap(x);
    if (sign < 0) return (NEGATIVE_ONE * xm1_final, eptr);
    else return (xm1_final, eptr);
}

struct LOG1PQ_State {
    Quad xm1;
    int8 sign;
    Quad x;
    Quad xx;
    int128 e;
    Quad z;
    Quad y;
    Quad r;
    Quad s;
    Quad res;
    Quad t1;
    Quad t2;
    Quad t3;
    Quad t4;
    Quad t5;
    Quad t6;
    Quad t7;
    Quad t8;
    Quad t9;
    Quad t10;
    Quad t11;
    Quad t12;
}

function log1p(Quad xm1) pure returns (Quad res) {
    LOG1PQ_State memory state;
    if (xm1 <= NEGATIVE_ONE) return NEGATIVE_INFINITY;
    if (ABDKMathQuad.isInfinity(xm1.unwrap())) return xm1;
    state.sign = ABDKMathQuad.sign(xm1.unwrap());
    if (state.sign == 0) return xm1;

    if (xm1.abs() < LOG1PQ_THRESHOLD) return xm1;

    state.x = xm1 + POSITIVE_ONE;
    if (state.x < POSITIVE_ZERO) return NEGATIVE_INFINITY;
    (state.xx, state.e) = frexp(state.x);
    state.z = POSITIVE_ZERO;
    state.y = POSITIVE_ZERO;

    if ((state.e > 2) || (state.e < -2)) {
        if (state.xx < LOG1PQ_SQRTH) {
            state.e -= 1;
            state.z = state.xx - HALF;
            state.y = HALF * state.z + HALF;
        } else {
            state.z = state.xx - POSITIVE_ONE;
            state.y = HALF * state.xx + HALF;
        }
        state.xx = state.z / state.y;
        state.z = state.xx * state.xx;
        state.r = ((((LOG1PQ_R5 * state.z + LOG1PQ_R4) * state.z + LOG1PQ_R3) * state.z + LOG1PQ_R2) * state.z + LOG1PQ_R1) * state.z + LOG1PQ_R0;
        state.s = (((((state.z + LOG1PQ_S5) * state.z + LOG1PQ_S4) * state.z + LOG1PQ_S3) * state.z + LOG1PQ_S2) * state.z + LOG1PQ_S1) * state.z
            + LOG1PQ_S0;
        state.z = state.xx * (state.z * state.r / state.s) + state.xx + fromInt256(state.e) * (EXP1MQ_C2 + EXP1MQ_C1);
        return state.z;
    }

    if (state.xx < LOG1PQ_SQRTH) {
        state.e -= 1;
        if (state.e != 0) state.xx = POSITIVE_TWO * state.xx - POSITIVE_ONE;
        else state.xx = xm1;
    } else {
        if (state.e != 0) state.xx = state.xx - POSITIVE_ONE;
        else state.xx = xm1;
    }

    state.z = state.xx * state.xx;
    state.t1 = ((LOG1PQ_P12 * state.xx + LOG1PQ_P11) * state.xx + LOG1PQ_P10);
    state.t2 = (state.t1 * state.xx + LOG1PQ_P9);
    state.t3 = (state.t2 * state.xx + LOG1PQ_P8);
    state.t4 = (state.t3 * state.xx + LOG1PQ_P7);
    state.t5 = (state.t4 * state.xx + LOG1PQ_P6);
    state.t6 = (state.t5 * state.xx + LOG1PQ_P5);
    state.t7 = (state.t6 * state.xx + LOG1PQ_P4);
    state.t8 = (state.t7 * state.xx + LOG1PQ_P3);
    state.t9 = (state.t8 * state.xx + LOG1PQ_P2);
    state.t10 = (state.t9 * state.xx + LOG1PQ_P1);
    state.t11 = state.t10 * state.xx;

    state.r = state.t11 + LOG1PQ_P0;
    state.t1 = (state.xx + LOG1PQ_Q11);
    state.t2 = (state.t1 * state.xx + LOG1PQ_Q10);
    state.t3 = (state.t2 * state.xx + LOG1PQ_Q9);
    state.t4 = (state.t3 * state.xx + LOG1PQ_Q8);
    state.t5 = (state.t4 * state.xx + LOG1PQ_Q7);
    state.t6 = (state.t5 * state.xx + LOG1PQ_Q6);
    state.t7 = (state.t6 * state.xx + LOG1PQ_Q5);
    state.t8 = (state.t7 * state.xx + LOG1PQ_Q4);
    state.t9 = (state.t8 * state.xx + LOG1PQ_Q3);
    state.t10 = (state.t9 * state.xx + LOG1PQ_Q2);
    state.t11 = (state.t10 * state.xx + LOG1PQ_Q1);
    state.s = state.t11 * state.xx + LOG1PQ_Q0;
    state.y = state.xx * (state.z * state.r / state.s) + fromInt256(state.e) * LOG1PQ_C2;
    state.z = state.y - HALF * state.z + state.xx + fromInt256(state.e) * LOG1PQ_C1;
    return state.z;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, fromInt256, POSITIVE_ZERO, POSITIVE_ONE, fromUint256} from "src/types/ABDKMathQuad/Quad.sol";
import {OptInt256, wrap} from "src/types/Optional/OptInt256.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {Z, I, LAMBDA, JUMPS, LN2, NEVER_AGO} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve, DropsGroup} from "src/libraries/internal/DeadlineJumps.sol";
import {sqrtStrike, fluidAt, eraDate} from "src/libraries/helpers/PoolHelper.sol";
import {max} from "src/types/ABDKMathQuad/Math.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";
import {DropFaberTotals} from "src/libraries/internal/DropFaberTotals.sol";
import {JumpyAnchorFaber} from "src/libraries/internal/JumpyAnchorFaber.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";
import {AnyPayoff} from "src/libraries/internal/Payoff.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {max as maxInt32} from "src/libraries/internal/Utils.sol";
import {NettingGrowth} from "src/libraries/internal/NettingGrowth.sol";
import {SwapperInternal} from "./SwapperInternal.sol";
import {NewLoan} from "./NewLoan.sol";

library Swapper {
    using SafeCast for int256;
    using GapStagedFrame for GapStagedFrame.Info;
    using GrowthSplitFrame for GrowthSplitFrame.Info;
    using JumpyFallback for JumpyFallback.Info;
    using DropFaberTotals for DropFaberTotals.Info;
    using JumpyAnchorFaber for JumpyAnchorFaber.Info;
    using DeadlineSet for DeadlineSet.Info;
    using DeadlineJumps for DeadlineSet.Info;
    using NettingGrowth for NettingGrowth.Info;

    error OnlyOwnerAllowed();
    error SwapperLocked(Quad currentDate, Quad unlockDate);
    error CannotWithDrawFromFixedLoan();
    error TokenMixIsNaN();

    /**
     * @dev This function allows to change some key aspects of the swapper: the time when the swapper expires (and the nature of the loan related to it, in case deadEra is set from not None to None and viceversa) and the TWAP
     *     @dev Changing the other aspects like time when the swapper expires and the TWAP might result in more tokens to be provided
     *     @dev This function can also be used to swap the swapper but using `reflow()` for this purpose is more gas efficient
     */
    function reset(SwapperInternal.Info storage self, PoolState storage pool, OptInt256 deadEra, Quad tokenMix, bool fixedToken, OptInt256 twapUntil)
        external
        returns (UserPay.Info memory)
    {
        if (!self.deadEra.isDefined()) revert CannotWithDrawFromFixedLoan();
        if (tokenMix.isNaN()) revert TokenMixIsNaN();
        if (msg.sender != self.owner) revert OnlyOwnerAllowed();

        SwapperInternal.checkAlive(self, pool);
        Quad oldBacking0 = SwapperInternal.backing(self, pool, Z);

        Quad oldBacking1 = SwapperInternal.backing(self, pool, I);

        // Updates the flows and fees with respect to the current state of the Swapper, before any changes
        SwapperInternal.borrow(self, pool, false);
        SwapperInternal.flow(self, pool, false);
        self.deadEra = deadEra;
        self.tokenMix = tokenMix;
        self.twapUntil = twapUntil;
        self.token = fixedToken;
        // Validates the new state of the Swapper
        SwapperInternal.validate(self, pool);
        // Updates the flows and fees with respect to the new state of the Swapper, after any changes
        SwapperInternal.borrow(self, pool, true);
        SwapperInternal.flow(self, pool, true);

        // Compute the amount of tokens needed to back the new position of the swapper
        return UserPay.Info((SwapperInternal.backing(self, pool, Z) - oldBacking0), (SwapperInternal.backing(self, pool, I) - oldBacking1));
    }

    /**
     * @dev Used to change the TWAP and to perform swaps inside the swapper
     *     @dev The swap is done by changing the `tokenMix`
     *     @dev The operations allowed by this function are a subset of the ones allowed by `reset()` but it is less gas expensive because of this reason
     */
    function reflow(SwapperInternal.Info storage self, PoolState storage pool, Quad tokenMix, bool fixedToken, OptInt256 twapUntil)
        external
        returns (UserPay.Info memory)
    {
        if (tokenMix.isNaN()) revert TokenMixIsNaN();
        if (msg.sender != self.owner) revert OnlyOwnerAllowed();
        Quad oldBacking0 = SwapperInternal.backing(self, pool, Z);
        Quad oldBacking1 = SwapperInternal.backing(self, pool, I);

        SwapperInternal.flow(self, pool, false);
        self.tokenMix = tokenMix;
        self.twapUntil = twapUntil;
        self.token = fixedToken;
        SwapperInternal.validate(self, pool);
        SwapperInternal.flow(self, pool, true);
        return UserPay.Info((SwapperInternal.backing(self, pool, Z) - oldBacking0), (SwapperInternal.backing(self, pool, I) - oldBacking1));
    }

    function created(SwapperInternal.Info storage self, PoolState storage pool) public returns (SwapperInternal.Info storage) {
        SwapperInternal.validate(self, pool);
        SwapperInternal.borrow(self, pool, true);
        SwapperInternal.flow(self, pool, true);
        return self;
    }

    struct UnwindLocalVars {
        Quad release0;
        Quad release1;
        int32 midIndex;
    }

    function unwind(SwapperInternal.Info storage self, PoolState storage pool) external returns (UserPay.Info memory) {
        UnwindLocalVars memory vars;
        if (msg.sender != self.owner) revert OnlyOwnerAllowed();
        SwapperInternal.checkAlive(self, pool);
        if (pool.date < self.unlockDate) revert SwapperLocked(pool.date, self.unlockDate);
        if (!self.deadEra.isDefined()) revert CannotWithDrawFromFixedLoan();

        vars.release0 = SwapperInternal.backing(self, pool, Z);
        vars.release1 = SwapperInternal.backing(self, pool, I);

        vars.midIndex = pool.tickBin - self.startBin;

        for (uint256 index = 0; index < self.lent.length && int256(index) < vars.midIndex; index++) {
            vars.release1 = vars.release1 - pool.epsilon * self.lent[index] * sqrtStrike(pool.splits, self.startBin + int256(index)) * pool.deflator;
        }

        for (uint256 index = uint256(int256(maxInt32(int32(0), vars.midIndex + 1))); index < self.lent.length; index++) {
            vars.release0 = vars.release0 - pool.epsilon * self.lent[index] / sqrtStrike(pool.splits, self.startBin + int256(index)) * pool.deflator;
        }

        if (0 <= vars.midIndex && uint256(int256(vars.midIndex)) < self.lent.length) {
            Quad sqrtMid = sqrtStrike(pool.splits, pool.tickBin);
            vars.release1 = vars.release1 - pool.binFrac * pool.epsilon * self.lent[uint256(int256(vars.midIndex))] * sqrtMid * pool.deflator;
            vars.release0 =
                vars.release0 - (POSITIVE_ONE - pool.binFrac) * pool.epsilon * self.lent[uint256(int256(vars.midIndex))] / sqrtMid * pool.deflator;
        }

        SwapperInternal.borrow(self, pool, false);
        SwapperInternal.flow(self, pool, false);
        self.deadEra = NEVER_AGO;

        return UserPay.Info(-vars.release0, -vars.release1);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, fromInt256, POSITIVE_ZERO, POSITIVE_ONE, POSITIVE_TWO, HALF} from "src/types/ABDKMathQuad/Quad.sol";
import {ceil} from "src/types/ABDKMathQuad/Math.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {TUBS, JUMPS, WORD_SIZE, Z, I} from "src/Constants.sol";
import {DailyJumps} from "src/libraries/internal/DailyJumps.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {DeadlineFlag} from "src/libraries/internal/DeadlineFlag.sol";
import {JumpyAnchorFaber, Anchor, AnchorSet} from "src/libraries/internal/JumpyAnchorFaber.sol";
import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {BoxcarTubFrame} from "src/libraries/internal/BoxcarTubFrame.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {eraDate, subSplits, sqrtStrike, logBin, BINS, tubLowSqrt, tubLowTick} from "src/libraries/helpers/PoolHelper.sol";
import {deadEra} from "src/libraries/helpers/DeadlineHelper.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";

import {EachPayoff} from "src/libraries/internal/EachPayoff.sol";
import {FloatBits, SparseFloat, QuadPacker} from "src/libraries/internal/SparseFloat.sol";
import {Capper} from "src/libraries/internal/Capper.sol";
import {LAMBDA} from "src/Constants.sol";

import {LPShed} from "./LPShed.sol";

library LP {
    using DeadlineFlag for DeadlineFlag.Info;
    using BoxcarTubFrame for BoxcarTubFrame.Info;
    using JumpyAnchorFaber for JumpyAnchorFaber.Info;
    using GrowthSplitFrame for GrowthSplitFrame.Info;
    using GapStagedFrame for GapStagedFrame.Info;
    using JumpyFallback for JumpyFallback.Info;
    using AnchorSet for AnchorSet.Info;
    using Capper for Capper.Info;
    using SafeCast for uint256;
    using SafeCast for int256;
    using FloatBits for Quad;

    enum Stage {
        Join,
        Earn,
        Exit
    }

    struct Info {
        //Stage enum parameters
        int128 lower0;
        int128 upper0;
        int128 lower1;
        int128 upper1;
        Quad liquidity;
        Quad growPast0;
        Quad growPast1;
        Quad drainDate;
        int32 earnEra;
        int32 startTub;
        int32 stopTub;
        // It should take 1 slot since address is 20 bytes and Stage is an enum with 3 values and therefore represented by a unit8
        address owner;
        Stage stage;
    }

    error InvalidPourArguments();
    error MustWaitTillTailEraToStartDraining(int256 tailEra, int256 poolEra);
    error MustWaitTillEarnEraToStartTapping(int256 earnEra, int256 poolEra);
    error LiquidityPositionAlreadyDraining();
    error StageNotJoined();
    error OnlyOwnerAllowed();
    error liquidityIsNaN();

    function init(int256 startTub, int256 stopTub, Quad liquidity, Stage stage, int256 earnEra) internal view returns (Info memory) {
        Info memory lp;
        lp.owner = msg.sender;
        lp.startTub = int32(startTub);
        lp.stopTub = int32(stopTub);
        lp.liquidity = liquidity;
        lp.stage = stage;
        lp.earnEra = int32(earnEra);
        return lp;
    }

    function accrued(Info storage self, PoolState storage pool) internal returns (Quad, Quad) {
        GrowthSplitFrame.Info storage fees0 = pool.fees[0];
        GrowthSplitFrame.Info storage fees1 = pool.fees[1];
        Quad res1 = fees0.accruedRange(pool, self.startTub, self.stopTub);
        Quad res2 = fees1.accruedRange(pool, self.startTub, self.stopTub);
        return (res1, res2);
    }

    function earn(Info storage self, PoolState storage pool, bool flush) internal returns (Quad, Quad) {
        if (self.stage == Stage.Earn) {
            Quad oldAccrued0 = self.growPast0;
            Quad oldAccrued1 = self.growPast1;

            (Quad newAccrued0, Quad newAccrued1) = accrued(self, pool);

            if (flush) {
                self.stage = Stage.Earn; // (mchen) extraneous
                self.growPast0 = newAccrued0;
                self.growPast1 = newAccrued1;
            }

            return ((newAccrued0 - oldAccrued0).max(POSITIVE_ZERO) * self.liquidity, (newAccrued1 - oldAccrued1).max(POSITIVE_ZERO) * self.liquidity);
        } else {
            return (POSITIVE_ZERO, POSITIVE_ZERO);
        }
    }

    struct PourLocalVars {
        int256 startBin;
        int256 stopBin;
        Quad reserve0;
        Quad reserve1;
        Quad yield0;
        Quad yield1;
        int256 tailEra;
        Quad tailYield0;
        Quad yieldFlow0;
        Quad tailYield1;
        Quad yieldFlow1;
        Quad yieldRatio;
    }

    function bins(int256 splits, int256 startTub, int256 stopTub) public pure returns (int256, int256) {
        return ((startTub << subSplits(splits).toUint256()), (stopTub << subSplits(splits).toUint256()));
    }

    // startBin and stopBin can be calculated from startTub and stopTub.
    // but it may be cheaper to pass them
    function reserves(PoolState storage pool, int256 startTub, int256 stopTub, int256 startBin, int256 stopBin) internal view returns (Quad, Quad) {
        Quad reserve0 = POSITIVE_ZERO;
        Quad reserve1 = POSITIVE_ZERO;

        if ((startBin > pool.tickBin)) {
            reserve0 = ((POSITIVE_ONE / tubLowSqrt(startTub)) - (POSITIVE_ONE / tubLowSqrt(stopTub)));
            reserve1 = POSITIVE_ZERO;
        } else {
            if ((stopBin <= pool.tickBin)) {
                reserve0 = POSITIVE_ZERO;
                reserve1 = (tubLowSqrt(stopTub) - tubLowSqrt(startTub));
            } else {
                reserve0 = (
                    ((POSITIVE_ONE / lowSqrtBin(pool, pool.tickBin)) - (POSITIVE_ONE / tubLowSqrt(stopTub)))
                        - ((pool.epsilon / sqrtStrike(pool.splits, pool.tickBin)) * pool.binFrac)
                );
                reserve1 = (
                    (lowSqrtBin(pool, pool.tickBin) - tubLowSqrt(startTub)) + ((pool.epsilon * sqrtStrike(pool.splits, pool.tickBin)) * pool.binFrac)
                );
            }
        }
        return (reserve0, reserve1);
    }

    /**
     * @dev Adds liquidity to the pool
     *     @dev The precision of the liquidity range is in the `TUBS` scale, which is more coarse grained than the `BINS` since they are separated by 1% increments in the log price space, scale and the `TICKS` scale
     *     @param startTub The lower bound of the liquidity range
     *     @param stopTub The upper bound of the liquidity range
     *     @param liquidity The liquidity to be added
     */
    function pour(PoolState storage pool, int256 startTub, int256 stopTub, Quad liquidity) external returns (UserPay.Info memory) {
        if(liquidity.isNaN()) revert liquidityIsNaN();
        PourLocalVars memory vars;

        if (((((startTub < 0) || (startTub >= stopTub)) || (stopTub > TUBS)) || (liquidity <= POSITIVE_ZERO))) revert InvalidPourArguments();

        pool.minted.addRange(startTub, stopTub, liquidity);
        (vars.startBin, vars.stopBin) = bins(pool.splits, startTub, stopTub);
        (vars.reserve0, vars.reserve1) = reserves(pool, startTub, stopTub, vars.startBin, vars.stopBin);

        // The `tailEra` effect

        // Compute the era corresponding to the last jump in the future, based on the current pool era
        vars.tailEra = deadEra(pool.era, (JUMPS - 1));

        // In this case `true` is associated to the `tailEra`
        pool.resets.assign(pool.era, true, vars.tailEra);

        for (int256 i = 0; (i < pool.flowHat.length.toInt256()); i = (i + 1)) {
            JumpyAnchorFaber.Info storage temp = pool.flowHat[i.toUint256()];
            temp.setEnd(pool, vars.tailEra, pool.tickBin);
        }

        // Fees calculation

        // Setup fees accounting for the new liquidity
        pool.joinStaged.stage(pool, vars.startBin, vars.stopBin, liquidity);
        GrowthSplitFrame.Info storage fees0 = pool.fees[0];
        GrowthSplitFrame.Info storage fees1 = pool.fees[1];
        JumpyAnchorFaber.Info storage flowHat0 = pool.flowHat[0];
        JumpyAnchorFaber.Info storage flowHat1 = pool.flowHat[1];

        // Computing the estimated yield in token0 and token1 for the liquidity added
        // In token0 for the sub-range above the pool price and
        // In token1 for the sub-range below the pool price
        vars.yield0 = fees0.sumTail(pool, SignedMath.max(vars.startBin, pool.tickBin), vars.stopBin);
        vars.yield1 = fees1.sumTail(pool, vars.startBin, SignedMath.min(vars.stopBin, pool.tickBin));

        if (((vars.startBin <= pool.tickBin) && (pool.tickBin < vars.stopBin))) {
            // If the liquidity range includes the pool price

            vars.tailYield0 = fees0.tailAt(pool, pool.tickBin);
            vars.yieldFlow0 = ((vars.tailYield0 / pool.epsilon) * liquidity);
            vars.tailYield1 = fees1.tailAt(pool, pool.tickBin);
            vars.yieldFlow1 = ((vars.tailYield1 / pool.epsilon) * liquidity);

            flowHat0.lateExpire(pool.era, vars.yieldFlow0, pool.splits, pool.tickBin, vars.tailEra);
            flowHat1.lateExpire(pool.era, vars.yieldFlow1, pool.splits, pool.tickBin, vars.tailEra);
            vars.yieldRatio = (vars.yieldFlow0 * sqrtStrike(pool.splits, pool.tickBin));

            pool.owed.expireOne(pool.era, pool.splits, vars.yieldRatio.neg(), pool.tickBin, vars.tailEra);

            // NOTE: The `binFrac` represents the intra-bin price and is such that
            // - when `binFrac=0` the bin liquidity is all token0
            // - when `binFrac=1` the bin liquidity is all token1

            // In the yield related calculations above, the effect of `binFrac` is not taken into account, this is done here

            // Adjusting the yield related to token0 for the `binFrac`
            vars.yield0 = (vars.yield0 - (vars.tailYield0 * pool.binFrac));
            Anchor.Info storage temp0 = pool.flowHat[0].drops._apply(pool.era, vars.tailEra);
            temp0.halfsum = (temp0.halfsum + (vars.yieldFlow0 * pool.binFrac));

            // Adjusting the yield related to token1 for the `binFrac`
            vars.yield1 = (vars.yield1 + (vars.tailYield1 * pool.binFrac));
            Anchor.Info storage temp1 = pool.flowHat[1].drops._apply(pool.era, vars.tailEra);
            temp1.halfsum = (temp1.halfsum - (vars.yieldFlow1 * pool.binFrac));
        }

        // Creating the LP tracking accounting object
        Info memory lp = init(startTub, stopTub, liquidity, Stage.Join, vars.tailEra);
        pool.lps.push(lp);
        pool.lpCount++;

        Quad tailDeflator = (-eraDate(vars.tailEra)).exp2();

        return
            UserPay.Info((liquidity * (vars.reserve0 + (vars.yield0 * tailDeflator))), (liquidity * (vars.reserve1 + (vars.yield1 * tailDeflator))));
    }

    /**
     *  Copied from pour() with some irrelevant state-changing code removed.
     *  This function does change the state (in sumTail) but the changes are
     *  immaterial in that they simply update the lazy data structures.
     */
    function getPourQuantities(PoolState storage pool, int256 startTub, int256 stopTub, Quad liquidity) external returns (UserPay.Info memory) {
        PourLocalVars memory vars;

        if (((((startTub < 0) || (startTub >= stopTub)) || (stopTub > TUBS)) || (liquidity <= POSITIVE_ZERO))) revert InvalidPourArguments();

        // TODO: move the following code to a common function to be shared
        // between pour() and getPourQuantities. This refactor will come
        // after current PR passes the (new) unit tests. This way, we compare
        // the results of getPourQuantities against those from the original
        // pour().
        (vars.startBin, vars.stopBin) = bins(pool.splits, startTub, stopTub);
        (vars.reserve0, vars.reserve1) = reserves(pool, startTub, stopTub, vars.startBin, vars.stopBin);

        vars.tailEra = deadEra(pool.era, (JUMPS - 1));

        GrowthSplitFrame.Info storage fees0 = pool.fees[0];
        GrowthSplitFrame.Info storage fees1 = pool.fees[1];

        vars.yield0 = fees0.sumTail(pool, SignedMath.max(vars.startBin, pool.tickBin), vars.stopBin);
        vars.yield1 = fees1.sumTail(pool, vars.startBin, SignedMath.min(vars.stopBin, pool.tickBin));

        Quad tailDeflator = (-eraDate(vars.tailEra)).exp2();

        return
            UserPay.Info((liquidity * (vars.reserve0 + (vars.yield0 * tailDeflator))), (liquidity * (vars.reserve1 + (vars.yield1 * tailDeflator))));
    }

    function lowSqrtBin(PoolState storage pool, int256 bin) internal view returns (Quad) {
        return (((logBin(pool.splits) / POSITIVE_TWO) * fromInt256((bin - BINS(pool.splits) / (2))))).exp();
    }

    struct DrainLocalVars {
        Stage stage;
        int256 tailEra;
        int128 exponent;
        int256 lowerTick;
        int128 lower0;
        int128 lower1;
        int256 upperTick;
        int128 upper0;
        int128 upper1;
        Quad earned0;
        Quad earned1;
    }

    /**
     * @dev Drains liquidity from the pool associated to a given LP position
     *     @dev Draining liquidity is a process that takes some time to be completed
     *     @param lpNum The LP position number
     */
    function drain(PoolState storage pool, uint256 lpNum) external returns (UserPay.Info memory) {
        if (msg.sender != pool.lps[lpNum].owner) revert OnlyOwnerAllowed();
        DrainLocalVars memory vars;

        // The `stage` represents the LP position state, it can be
        // - Join: just added liquidity
        // - Earn: earning from deposited liquidity
        // - Exit: draining liquidity, no interests since the liquidity is locked and can't be lent out so not accruing interests
        vars.stage = pool.lps[lpNum].stage;

        if (vars.stage == Stage.Join) {
            // Liquidity just added
            // It is not possible to immediately remove liquidity once added, need to wait until a specific era, which is the LP `earnEra` is reached
            vars.tailEra = pool.lps[lpNum].earnEra;
            // If the LP `earnEra` has not been reached yet, it is not possible to proceed with draining the position
            if (pool.era < vars.tailEra) revert MustWaitTillTailEraToStartDraining(vars.tailEra, pool.era);
        } else {
            // If the current position is already draining, it cannot be drained again
            if (vars.stage == Stage.Exit) revert LiquidityPositionAlreadyDraining();
        }

        pool.minted.addRange(pool.lps[lpNum].startTub, pool.lps[lpNum].stopTub, pool.lps[lpNum].liquidity.neg());
        // Removing all of the LP liquidity in one shot from the tracker related to the available liquidity, by minting negative liquidity
        pool.offRamp.addRange(pool.lps[lpNum].startTub, pool.lps[lpNum].stopTub, pool.lps[lpNum].liquidity / pool.deflator);

        vars.exponent = int128(ceil((pool.date.neg() + HALF))) - int128(WORD_SIZE);

        // Tick corresponding to the startTub
        vars.lowerTick = tubLowTick(pool.lps[lpNum].startTub);
        vars.lower0 = EachPayoff.capperBegin(pool, vars.lowerTick, Z, vars.exponent);

        // NOTE: Handling the `startTub=0` edge case
        vars.lower1 =
            pool.lps[lpNum].startTub > 0 ? EachPayoff.capperBegin(pool, vars.lowerTick, I, vars.exponent) : ~pool.deflator.truncate(vars.exponent);

        // Tick corresponding to the stopTub
        vars.upperTick = tubLowTick(pool.lps[lpNum].stopTub);

        // NOTE: Handling the `stopTub=TUBS` edge case
        vars.upper0 =
            pool.lps[lpNum].stopTub < TUBS ? EachPayoff.capperBegin(pool, vars.upperTick, Z, vars.exponent) : ~pool.deflator.truncate(vars.exponent);
        vars.upper1 = EachPayoff.capperBegin(pool, vars.upperTick, I, vars.exponent);

        // Computed the earned amounts in token0 and token1
        (vars.earned0, vars.earned1) = earn(pool.lps[lpNum], pool, true);

        // Tracking the LP position entered the Drain state
        pool.lps[lpNum].stage = Stage.Exit;
        pool.lps[lpNum].lower0 = vars.lower0;
        pool.lps[lpNum].upper0 = vars.upper0;
        pool.lps[lpNum].lower1 = vars.lower1;
        pool.lps[lpNum].upper1 = vars.upper1;

        // Tracking the date when the drain started
        pool.lps[lpNum].drainDate = pool.date;

        // Returning the earned money to the LPer (paying negative amount means transferring money from the pool to the LPer)
        return UserPay.Info(vars.earned0.neg(), vars.earned1.neg());
    }

    function collect(PoolState storage pool, uint256 lpNum) external returns (UserPay.Info memory) {
        if (msg.sender != pool.lps[lpNum].owner) revert OnlyOwnerAllowed();

        LP.Info storage lp = pool.lps[lpNum];

        Quad gained0 = POSITIVE_ZERO;
        Quad gained1 = POSITIVE_ZERO;

        if ((lp.stage == Stage.Join)) {
            gained0 = POSITIVE_ZERO;
            gained1 = POSITIVE_ZERO;
        } else {
            if ((lp.stage == Stage.Earn)) (gained0, gained1) = earn(lp, pool, true);
            else (gained0, gained1) = LPShed.shed(lp, pool, true);
        }
        return UserPay.Info(gained0.neg(), gained1.neg());
    }

    function tap(PoolState storage pool, uint256 lpNum) external {
        LP.Info storage lp = pool.lps[lpNum];

        Stage stage = lp.stage;

        if ((stage == Stage.Join)) {
            if ((pool.era < lp.earnEra)) revert MustWaitTillEarnEraToStartTapping(lp.earnEra, pool.era);
        } else {
            revert StageNotJoined();
        }
        lp.stage = Stage.Earn;
        (Quad accrued0, Quad accrued1) = accrued(lp, pool);
        lp.growPast0 = accrued0;
        lp.growPast1 = accrued1;
    }
}

File 32 of 52 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {MIN_SPLITS} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DropsGroup, DeadlineSet} from "src/libraries/internal/DeadlineJumps.sol";
import {EraFaberTotals} from "src/libraries/internal/EraFaberTotals.sol";

library DropFaberTotals {
    using DropsGroup for DeadlineSet.Info;
    using SafeCast for int256;
    using SafeCast for uint256;

    struct Info {
        EraFaberTotals.Info eraFaberTotals;
    }

    error InvalidAddArguments();

    function expire(Info storage self, int256 poolEra, Quad[] memory amounts, int256 scale, int256 start, OptInt256 deadEra) internal {
        if (deadEra.isDefined()) {
            int256 aDeadEra = deadEra.get();
            add(self, poolEra, scale, start, amounts, aDeadEra);
        }
    }

    function add(Info storage self, int256 poolEra, int256 scale, int256 start, Quad[] memory areas, int256 aDeadEra) internal {
        if (!((scale.toUint256() >= MIN_SPLITS && ((start >= 0)) && ((start.toUint256() + areas.length) <= (1 << scale.toUint256()))))) {
            revert InvalidAddArguments();
        }

        Quad[] memory carry = new Quad[](scale.toUint256());

        int256 first = ((1 << scale.toUint256()) + start.toUint256()).toInt256();

        int256 node = 0;

        int256 depth = 0;

        Quad change = POSITIVE_ZERO;

        for (int256 index = 0; (index.toUint256() < areas.length); index = (index + 1)) {
            node = (first + index);
            change = areas[index.toUint256()];
            depth = (scale - 1);
            while ((depth.toUint256() >= MIN_SPLITS)) {
                bool leftChild = ((node & 1) == 0);

                node = (node >> 1);
                if (leftChild) {
                    carry[depth.toUint256()] = change;
                    break;
                } else {
                    smallAdd(self, poolEra, node, (carry[depth.toUint256()] - change), aDeadEra);
                    change = (change + carry[depth.toUint256()]);
                }
                depth = (depth - 1);
            }

            if ((depth.toUint256() < MIN_SPLITS)) {
                largeAdd(self, poolEra, node, change, MIN_SPLITS.toInt256(), aDeadEra);
                while (true) {
                    bool leftChild = ((node & 1) == 0);

                    node = (node >> 1);
                    if (leftChild) {
                        carry[depth.toUint256()] = change;
                        break;
                    } else {
                        change = (change + carry[depth.toUint256()]);
                        if ((node <= 1)) break;

                        largeAdd(self, poolEra, node, change, depth, aDeadEra);
                    }
                    depth = (depth - 1);
                }
            }
        }

        if ((depth.toUint256() >= MIN_SPLITS)) {
            smallAdd(self, poolEra, node, change, aDeadEra);
            while ((depth.toUint256() > MIN_SPLITS)) {
                depth = (depth - 1);
                bool leftChild = ((node & 1) == 0);

                node = (node >> 1);
                if (leftChild) {
                    smallAdd(self, poolEra, node, change, aDeadEra);
                } else {
                    smallAdd(self, poolEra, node, (carry[depth.toUint256()] - change), aDeadEra);
                    change = (change + carry[depth.toUint256()]);
                }
            }
        }

        while ((node > 1)) {
            largeAdd(self, poolEra, node, change, depth, aDeadEra);
            depth = (depth - 1);
            if (((node & 1) != 0)) change = (change + carry[depth.toUint256()]);

            node = (node >> 1);
        }

        largeAdd(self, poolEra, 1, change, 0, aDeadEra);
    }

    function smallAdd(Info storage self, int256 poolEra, int256 node, Quad amount, int256 aDeadEra) internal {
        DeadlineSet.Info storage temp = self.eraFaberTotals.small[node.toUint256()];
        temp.expire(poolEra, amount, aDeadEra);
    }

    function largeAdd(Info storage self, int256 poolEra, int256 node, Quad amount, int256, int256 aDeadEra) internal {
        DeadlineSet.Info storage temp = self.eraFaberTotals.large[node.toUint256()];
        temp.expire(poolEra, amount, aDeadEra);
    }
}

File 34 of 52 : JumpyAnchorFaber.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO, POSITIVE_TWO} from "src/types/ABDKMathQuad/Quad.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {MIN_SPLITS, MAX_SPLITS} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DropsGroup, DeadlineSet, PiecewiseCurve, DeadlineJumps} from "src/libraries/internal/DeadlineJumps.sol";
import {EraFaberTotals} from "src/libraries/internal/EraFaberTotals.sol";
import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol";
import {JUMPS, JUMP_NONE} from "src/Constants.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

import {
    jumpBitLength,
    maybeJump,
    jumpIndex as jumpIndexHelper,
    isJumpDefined,
    validDeadline,
    deadEra as deadEraHelper
} from "src/libraries/helpers/DeadlineHelper.sol";

using DeadlineSet for DeadlineSet.Info;
using AnchorSet for AnchorSet.Info;
using Anchor for Anchor.Info;
using JumpyAnchorFaber for JumpyAnchorFaber.Info;
using SafeCast for uint256;
using SafeCast for int256;
using PiecewiseCurve for DeadlineSet.Info;
using DeadlineJumps for DeadlineSet.Info;
using EraFaberTotals for EraFaberTotals.Info;

library Anchor {
    struct Info {
        // Reorder the variables to save gas
        Quad halfsum;
        int32 end;
        bool touched;
    }

    function zero(Info storage self) internal {
        self.touched = false;
        self.end = 0;
        self.halfsum = POSITIVE_ZERO;
    }

    function validEnd(Info storage self, JumpyAnchorFaber.Info storage jumpyAnchorFaberDataStructure) internal returns (int256) {
        if (!self.touched) {
            self.end = int32(jumpyAnchorFaberDataStructure.end);
            self.touched = true;
        }
        return self.end;
    }

    function addAt(Info storage self, JumpyAnchorFaber.Info storage jumpyAnchorFaberDataStructure, int256 bin, Quad amount) internal {
        bool shouldAdd;
        if (jumpyAnchorFaberDataStructure.eraFaberTotals.below) shouldAdd = (bin < validEnd(self, jumpyAnchorFaberDataStructure));
        else shouldAdd = (bin >= validEnd(self, jumpyAnchorFaberDataStructure));

        if (shouldAdd) self.halfsum = self.halfsum + amount;
    }
}

library AnchorSet {
    struct Info {
        Anchor.Info[JUMPS] drops;
        int32 dropsEra;
        int8 nextJump;
        bool touched;
    }

    error InvalidDeadline();

    function dropOff(AnchorSet.Info storage self, Anchor.Info storage drop) internal {}

    function _apply(Info storage self, int256 poolEra, int256 deadEra) internal returns (Anchor.Info storage) {
        int256 idx = self.slot(poolEra, deadEra, dropOff);
        return self.drops[idx.toUint256()];
    }

    function jumpIndex(Info storage self, int256 deadEra) internal view returns (int8) {
        return jumpIndexHelper(self.dropsEra, deadEra);
    }

    function deadEra(Info storage self, int8 jumpIndex) internal view returns (int256) {
        return deadEraHelper(self.dropsEra, jumpIndex);
    }

    function slot(Info storage self, int256 poolEra, int256 deadEra, function (AnchorSet.Info storage, Anchor.Info storage) internal dropoff)
        internal
        returns (int8)
    {
        if (!(validDeadline(poolEra, deadEra))) revert InvalidDeadline();

        if (!(self.touched)) {
            self.dropsEra = int32(poolEra);
            self.nextJump = jumpIndex(self, deadEra);
            self.touched = true;
            return self.nextJump;
        }

        int8 jump = jumpIndex(self, deadEra);

        if (!isJumpDefined(jump)) {
            reanchor(self, poolEra, dropoff);
            jump = maybeJump(self.dropsEra, deadEra);
        }

        if ((jump < self.nextJump)) self.nextJump = jump;

        return int8(jump);
    }

    function assign(
        Info storage self,
        int256 poolEra,
        Anchor.Info storage value,
        int256 deadEra,
        function (AnchorSet.Info storage, Anchor.Info storage) internal dropoff
    ) internal {
        int8 jump = slot(self, poolEra, deadEra, dropoff);
        self.drops[int256(jump).toUint256()] = value;
    }

    function assign(
        Info storage self,
        int256 poolEra,
        Anchor.Info storage value,
        OptInt256 deadEra,
        function (AnchorSet.Info storage, Anchor.Info storage) internal dropoff
    ) internal {
        if (deadEra.isDefined()) assign(self, poolEra, value, deadEra.get(), dropoff);
    }

    function reanchor(Info storage self, int256 poolEra, function (AnchorSet.Info storage, Anchor.Info storage) internal dropoff) internal {
        bool unset = true;

        for (int8 oldIndex = self.nextJump; (oldIndex < JUMPS); oldIndex = (oldIndex + 1)) {
            Anchor.Info storage drop = self.drops[int256(oldIndex).toUint256()];

            if ((!(drop.touched && drop.end == 0 && drop.halfsum == POSITIVE_ZERO))) {
                int256 deadline = deadEra(self, oldIndex);

                if ((deadline <= poolEra)) {
                    dropoff(self, drop);
                } else {
                    int8 newIndex = maybeJump(poolEra, deadline);

                    if (unset) {
                        self.nextJump = newIndex;
                        unset = false;
                    }

                    if ((newIndex == oldIndex)) break;

                    self.drops[int256(newIndex).toUint256()] = drop;
                }
                self.drops[int256(oldIndex).toUint256()].zero();
            }
        }

        self.dropsEra = int32(poolEra);
        if (unset) self.nextJump = JUMPS;
    }
}

library JumpyAnchorFaber {
    struct Info {
        int256 end;
        Quad halfsum;
        EraFaberTotals.Info eraFaberTotals;
        AnchorSet.Info drops;
    }

    error InvalidAddArguments();
    error InvalidSubLeftSumArguments();
    error InvalidCreateArguments();
    error InvalidMoveDropArguments();
    error InvalidSumTotalArguments();
    error InvalidSumRangeArguments();

    function init(Info storage self, int256 end, bool below) public {
        self.end = end;
        self.eraFaberTotals.init(below);
    }

    function setEnd(Info storage self, PoolState storage pool, int256 deadEra, int256 newEnd) public {
        Anchor.Info storage anchor = self.drops._apply(pool.era, deadEra);

        if (anchor.touched) {
            moveDrop(self, pool, anchor, deadEra, newEnd);
        } else {
            anchor.end = int32(newEnd);
            anchor.touched = true;
        }
    }

    function propel(Info storage self, PoolState storage pool, int256 toEra) external {
        int8 temp = self.drops.jumpIndex(toEra);

        if (temp != JUMP_NONE) {
            int8 jumpIndex = temp;

            int256 idx = int256(jumpIndex);

            Anchor.Info storage anchor = self.drops.drops[idx.toUint256()];

            if (anchor.touched) {
                moveDrop(self, pool, anchor, toEra, self.end);
                self.halfsum = self.halfsum - anchor.halfsum;
                self.drops.drops[idx.toUint256()].zero();
            }

            int256 t2 = (idx + 1);

            int8 t3 = t2.toInt8();

            self.drops.nextJump = t3;
        }
    }

    function largeAddExpireWriter(Info storage self, int256 poolEra, int256 node, Quad amount, int256, int256 deadEra) public {
        DeadlineSet.Info storage temp = self.eraFaberTotals.large[node.toUint256()];

        temp.expire(poolEra, amount, deadEra);
    }

    function largeAddCreateWriter(Info storage self, int256 poolEra, int256 node, Quad amount, int256, OptInt256 deadEra) public {
        DeadlineSet.Info storage temp = self.eraFaberTotals.large[node.toUint256()];

        temp.create(poolEra, amount, deadEra);
    }

    function lateCreate(Info storage self, int256 poolEra, Quad amount, int256 scale, int256 offset, OptInt256 deadEra) public {
        addCreateWriter(self, poolEra, scale, offset, amount, deadEra);
    }

    function smallAddCreateWriter(Info storage self, int256 poolEra, int256 node, Quad amount, OptInt256 deadEra) public {
        DeadlineSet.Info storage temp = self.eraFaberTotals.small[node.toUint256()];

        temp.create(poolEra, amount, deadEra);
    }

    function smallAddExpireWriter(Info storage self, int256 poolEra, int256 node, Quad amount, int256 deadEra) public {
        DeadlineSet.Info storage temp = self.eraFaberTotals.small[node.toUint256()];

        temp.expire(poolEra, amount, deadEra);
    }

    struct AddCreateWriteLocalState {
        Quad[] carry;
        int256 first;
        int256 node;
        int256 depth;
        Quad change;
        uint256 index;
        bool leftChild;
    }

    function addCreateWriter(Info storage self, int256 poolEra, int256 scale, int256 start, Quad[] memory areas, OptInt256 deadEra) public {
        AddCreateWriteLocalState memory localState;

        if (!((scale >= MIN_SPLITS.toInt256() && ((start >= 0)) && ((start + areas.length.toInt256()) <= (1 << scale.toUint256()).toInt256())))) {
            revert InvalidAddArguments();
        }

        localState.carry = new Quad[](scale.toUint256());
        localState.first = ((1 << scale.toUint256()).toInt256() + start);

        for (localState.index = 0; (localState.index < areas.length); localState.index++) {
            localState.node = (localState.first + int256(localState.index));
            localState.change = areas[localState.index];
            localState.depth = (scale - 1);

            while (localState.depth >= MIN_SPLITS.toInt256()) {
                localState.leftChild = ((localState.node & 1) == 0);

                localState.node = (localState.node >> 1);

                if (localState.leftChild) {
                    localState.carry[localState.depth.toUint256()] = localState.change;
                    break;
                } else {
                    smallAddCreateWriter(
                        self, poolEra, localState.node, (localState.carry[localState.depth.toUint256()] - localState.change), deadEra
                    );
                    localState.change = (localState.change + localState.carry[localState.depth.toUint256()]);
                }

                localState.depth = (localState.depth - 1);
            }

            if (localState.depth < MIN_SPLITS.toInt256()) {
                largeAddCreateWriter(self, poolEra, localState.node, localState.change, MIN_SPLITS.toInt256(), deadEra);

                while (true) {
                    localState.leftChild = ((localState.node & 1) == 0);
                    localState.node = (localState.node >> 1);

                    if (localState.leftChild) {
                        localState.carry[localState.depth.toUint256()] = localState.change;
                        break;
                    } else {
                        localState.change = (localState.change + localState.carry[localState.depth.toUint256()]);

                        if (localState.node <= 1) break;

                        largeAddCreateWriter(self, poolEra, localState.node, localState.change, localState.depth, deadEra);
                    }

                    localState.depth = (localState.depth - 1);
                }
            }
        }

        if (localState.depth >= MIN_SPLITS.toInt256()) {
            smallAddCreateWriter(self, poolEra, localState.node, localState.change, deadEra);

            while (localState.depth > MIN_SPLITS.toInt256()) {
                localState.depth = (localState.depth - 1);
                localState.leftChild = ((localState.node & 1) == 0);
                localState.node = (localState.node >> 1);

                if (localState.leftChild) {
                    smallAddCreateWriter(self, poolEra, localState.node, localState.change, deadEra);
                } else {
                    smallAddCreateWriter(
                        self, poolEra, localState.node, (localState.carry[localState.depth.toUint256()] - localState.change), deadEra
                    );
                    localState.change = (localState.change + localState.carry[localState.depth.toUint256()]);
                }
            }
        }

        while (localState.node > 1) {
            largeAddCreateWriter(self, poolEra, localState.node, localState.change, localState.depth, deadEra);
            localState.depth = (localState.depth - 1);

            if ((localState.node & 1) != 0) localState.change = (localState.change + localState.carry[localState.depth.toUint256()]);

            localState.node = (localState.node >> 1);
        }

        largeAddCreateWriter(self, poolEra, 1, localState.change, 0, deadEra);
    }

    function addCreateWriter(Info storage self, int256 poolEra, int256 scale, int256 offset, Quad area, OptInt256 deadEra) public {
        if (!(scale >= MIN_SPLITS.toInt256() && (((offset & (-1 << scale.toUint256())) == 0)))) revert InvalidAddArguments();

        int256 node = ((1 << scale.toUint256()).toInt256() + offset);

        while ((node >= (2 << MIN_SPLITS).toInt256())) {
            bool branchUp = ((node & 1) != 0);

            node = (node >> 1);
            if (branchUp) smallAddCreateWriter(self, poolEra, node, area.neg(), deadEra);
            else smallAddCreateWriter(self, poolEra, node, area, deadEra);
        }

        int256 depth = MIN_SPLITS.toInt256();

        while ((node > 0)) {
            largeAddCreateWriter(self, poolEra, node, area, depth, deadEra);
            node = (node >> 1);
            depth = (depth - 1);
        }
    }

    function smallAtNowReader(Info storage self, int256 poolEra, int256 node) public returns (Quad) {
        DeadlineSet.Info storage temp = self.eraFaberTotals.small[node.toUint256()];

        return temp.now(poolEra);
    }

    function lateExpire(Info storage self, int256 poolEra, Quad amount, int256 scale, int256 offset, int256 deadEra) public {
        addExpireWriter(self, poolEra, scale, offset, amount, deadEra);
    }

    function subLeftSumNowReader(Info storage self, int256 poolEra, int256 scale, int256 stopIdx) public returns (Quad) {
        if (((stopIdx % (1 << (scale - MIN_SPLITS.toInt256()).toUint256()).toInt256()) == 0)) {
            return POSITIVE_ZERO;
        } else {
            if (!(scale >= MIN_SPLITS.toInt256() && (((stopIdx & (-1 << scale.toUint256())) == 0)))) revert InvalidSubLeftSumArguments();

            Quad subArea = largeAtNowReader(self, poolEra, ((1 << MIN_SPLITS).toInt256() + (stopIdx >> (scale - MIN_SPLITS.toInt256()).toUint256())));

            Quad total = POSITIVE_ZERO;

            for (int256 depth = MIN_SPLITS.toInt256(); (depth < scale); depth = (depth + 1)) {
                Quad nextCoef = smallAtNowReader(self, poolEra, ((1 << depth.toUint256()).toInt256() + (stopIdx >> (scale - depth).toUint256())));

                bool branchUp = ((stopIdx & (1 << ((scale - depth) - 1).toUint256()).toInt256()) != 0);

                if (branchUp) total = (total + (subArea + nextCoef));

                if (branchUp) subArea = (subArea - nextCoef);
                else subArea = (subArea + nextCoef);

                subArea = (subArea / POSITIVE_TWO);
            }

            return (total / POSITIVE_TWO);
        }
    }

    function largeAtNowReader(Info storage self, int256 poolEra, int256 node) public returns (Quad) {
        DeadlineSet.Info storage temp = self.eraFaberTotals.large[node.toUint256()];

        return temp.now(poolEra);
    }

    function sumRangeNowReader(Info storage self, int256 poolEra, int256 scale, int256 startIdx, int256 stopIdx) public returns (Quad) {
        if ((scale <= MIN_SPLITS.toInt256())) {
            return sumTotalNowReader(self, poolEra, scale, startIdx, stopIdx);
        } else {
            if (!((((0 <= startIdx) && (startIdx <= stopIdx)) && (stopIdx <= (1 << scale.toUint256()).toInt256())))) {
                revert InvalidSumRangeArguments();
            }

            Quad coarse = sumTotalNowReader(
                self,
                poolEra,
                MIN_SPLITS.toInt256(),
                (startIdx >> (scale - MIN_SPLITS.toInt256()).toUint256()),
                (stopIdx >> (scale - MIN_SPLITS.toInt256()).toUint256())
            );

            return ((coarse - subLeftSumNowReader(self, poolEra, scale, startIdx)) + subLeftSumNowReader(self, poolEra, scale, stopIdx));
        }
    }

    function addExpireWriter(Info storage self, int256 poolEra, int256 scale, int256 offset, Quad area, int256 deadEra) public {
        if (!(scale >= MIN_SPLITS.toInt256() && (((offset & (-1 << scale.toUint256())) == 0)))) revert InvalidAddArguments();

        int256 node = ((1 << scale.toUint256()).toInt256() + offset);

        while ((node >= (2 << MIN_SPLITS).toInt256())) {
            bool branchUp = ((node & 1) != 0);

            node = (node >> 1);
            if (branchUp) smallAddExpireWriter(self, poolEra, node, area.neg(), deadEra);
            else smallAddExpireWriter(self, poolEra, node, area, deadEra);
        }

        int256 depth = MIN_SPLITS.toInt256();

        while ((node > 0)) {
            largeAddExpireWriter(self, poolEra, node, area, depth, deadEra);
            node = (node >> 1);
            depth = (depth - 1);
        }
    }

    function move(Info storage self, PoolState storage pool, int256 newEnd) public {
        Quad diff = sumRangeNowReader(self, pool.era, pool.splits, SignedMath.min(self.end, newEnd), SignedMath.max(self.end, newEnd));

        if (self.eraFaberTotals.below != ((newEnd < self.end))) self.halfsum = self.halfsum + diff;
        else self.halfsum = self.halfsum + diff.neg();

        self.end = newEnd;
    }

    function create(Info storage self, PoolState storage pool, Quad[] memory amounts, int256 scale, int256 start, OptInt256 deadEra) public {
        if (!((scale <= pool.splits))) revert InvalidCreateArguments();

        addCreateWriter(self, pool.era, scale, start, amounts, deadEra);

        int256 _start;
        if (self.eraFaberTotals.below) _start = 0;
        else _start = SignedMath.max((self.end - start), 0);

        int256 _end;
        if (self.eraFaberTotals.below) _end = SignedMath.min((self.end - start), amounts.length.toInt256());
        else _end = amounts.length.toInt256();

        for (int256 index = _start; (index < _end); index = (index + 1)) {
            self.halfsum = (self.halfsum + amounts[index.toUint256()]);
        }

        if (deadEra.isDefined()) {
            int256 _deadEra = deadEra.get();

            Anchor.Info storage anchor = self.drops._apply(pool.era, _deadEra);

            int256 end = anchor.validEnd(self);

            int256 _start;
            if (self.eraFaberTotals.below) _start = 0;
            else _start = SignedMath.max((end - start), 0);

            int256 _end;
            if (self.eraFaberTotals.below) _end = SignedMath.min((end - start), amounts.length.toInt256());
            else _end = amounts.length.toInt256();

            for (int256 index = _start; (index < _end); index = (index + 1)) {
                anchor.halfsum = (anchor.halfsum + amounts[index.toUint256()]);
            }
        }
    }

    function moveDrop(Info storage self, PoolState storage pool, Anchor.Info storage anchor, int256 deadEra, int256 newEnd) public {
        if (!(anchor.touched)) revert InvalidMoveDropArguments();

        Quad diff =
            self.eraFaberTotals.sumRangeDropReader(pool.splits, SignedMath.min(anchor.end, newEnd), SignedMath.max(anchor.end, newEnd), deadEra);

        if (self.eraFaberTotals.below != ((newEnd < anchor.end))) anchor.halfsum = anchor.halfsum + diff;
        else anchor.halfsum = anchor.halfsum + diff.neg();

        anchor.end = int32(newEnd);
    }

    function sumTotalNowReader(Info storage self, int256 poolEra, int256 scale, int256 startIdx, int256 stopIdx) public returns (Quad) {
        if (!(((scale <= MIN_SPLITS.toInt256() && ((0 <= startIdx)) && (startIdx <= stopIdx)) && (stopIdx <= (1 << scale.toUint256()).toInt256())))) {
            revert InvalidSumTotalArguments();
        }

        Quad total = POSITIVE_ZERO;

        int256 start = ((1 << scale.toUint256()).toInt256() + startIdx);

        int256 stop = ((1 << scale.toUint256()).toInt256() + stopIdx);

        while ((start != stop)) {
            if (((start & 1) != 0)) {
                total = (total + largeAtNowReader(self, poolEra, start));
                start = (start + 1);
            }

            if (((stop & 1) != 0)) {
                stop = (stop - 1);
                total = (total + largeAtNowReader(self, poolEra, stop));
            }

            start = (start >> 1);
            stop = (stop >> 1);
        }

        return total;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {Quad} from "src/types/ABDKMathQuad/Quad.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {OptInt256, wrap} from "src/types/Optional/OptInt256.sol";
import {DeadlineFlag} from "src/libraries/internal/DeadlineFlag.sol";
import {sqrtStrike} from "src/libraries/helpers/PoolHelper.sol";
import {Z, I} from "src/Constants.sol";
import {EraBoxcarMidSum} from "src/libraries/internal/EraBoxcarMidSum.sol";

library AnyPayoff {
    using DeadlineFlag for DeadlineFlag.Info;
    using EraBoxcarMidSum for EraBoxcarMidSum.Info;

    function payoffCreate(PoolState storage pool, bool token, Quad liquidity, int256 bin, OptInt256 deadEra) public {
        int256 scale = pool.splits;
        int256 offset = bin;
        pool.resets.assign(pool.era, true, deadEra);
        Quad sqrtStrike = sqrtStrike(pool.splits, bin);

        if ((token == Z)) {
            pool.flowDot[0][1].create(pool, liquidity * sqrtStrike, scale, offset, deadEra);
            pool.flowDot[0][0].create(pool, liquidity / sqrtStrike, scale, offset, deadEra);
        } else {
            pool.flowDot[1][0].create(pool, liquidity / sqrtStrike, scale, offset, deadEra);
            pool.flowDot[1][1].create(pool, liquidity * sqrtStrike, scale, offset, deadEra);
        }
    }

    function payoffExtend(PoolState storage pool, bool token, Quad liquidity, int256 bin, OptInt256 oldDeadEra, OptInt256 newDeadEra) public {
        int256 scale = pool.splits;
        int256 offset = bin;
        pool.resets.assign(pool.era, true, newDeadEra);
        Quad sqrtStrike = sqrtStrike(pool.splits, bin);

        if ((token == Z)) {
            pool.flowDot[0][1].extend(pool, liquidity * sqrtStrike, scale, offset, oldDeadEra, newDeadEra);
            pool.flowDot[0][0].extend(pool, liquidity / sqrtStrike, scale, offset, oldDeadEra, newDeadEra);
        } else {
            pool.flowDot[1][0].extend(pool, liquidity / sqrtStrike, scale, offset, oldDeadEra, newDeadEra);
            pool.flowDot[1][1].extend(pool, liquidity * sqrtStrike, scale, offset, oldDeadEra, newDeadEra);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

function int128ShiftRightUnsigned(int128 value, uint256 shift) pure returns (int128) {
    if (shift < 128) return (value >> shift) & int128(int256((1 << (128 - shift)) - 1));
    return 0;
}

function max(int32 a, int32 b) pure returns (int32) {
    return a > b ? a : b;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve, DropsGroup} from "src/libraries/internal/DeadlineJumps.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {LN2, JUMPS} from "src/Constants.sol";
import {eraDate} from "src/libraries/helpers/PoolHelper.sol";

library NettingGrowth {
    using PiecewiseCurve for DeadlineSet.Info;
    using DeadlineSet for DeadlineSet.Info;
    using DropsGroup for DeadlineSet.Info;
    using GapStagedFrame for GapStagedFrame.Info;

    struct Info {
        // Reordered to save space
        Quad accrued;
        Quad atDate;
        DeadlineSet.Info superC;
    }

    function now(Info storage self, PoolState storage pool) internal returns (Quad) {
        if (self.superC.touched) {
            while ((self.superC.nextJump < JUMPS) && (pool.era >= (self.superC.deadEra(int8(self.superC.nextJump))))) {
                Quad nextDate = eraDate(self.superC.deadEra(int8(self.superC.nextJump)));

                self.accrued = (self.accrued + (((pool.twapSpread * LN2) * self.superC.latest) * (nextDate - self.atDate)));
                self.atDate = nextDate;
                uint8 idx = uint8(self.superC.nextJump);
                self.superC.latest = (self.superC.latest - self.superC.drops[idx]);
                self.superC.drops[idx] = POSITIVE_ZERO;
                self.superC.nextJump += 1;
            }
            self.accrued = (self.accrued + (((pool.twapSpread * LN2) * self.superC.latest) * (pool.date - self.atDate)));
            self.atDate = pool.date;
        }

        return self.accrued;
    }

    function dropOff(DeadlineSet.Info storage self, Quad drop) internal {
        self.latest = (self.latest - drop);
    }

    function reanchor(Info storage self, PoolState storage pool) internal {
        now(self, pool);
        self.superC.reanchor(pool.era, dropOff);
    }

    function create(Info storage self, PoolState storage pool, Quad amount, int256 deadEra) internal {
        now(self, pool);
        if (!(self.superC.touched)) self.atDate = pool.date;

        self.superC.begin(pool.era, amount);
        self.superC.expire(pool.era, amount, deadEra, dropOff);
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {ABDKMathQuad} from "../../../lib/abdk-libraries-solidity/ABDKMathQuad.sol";
import {Quad} from "./ValueType.sol";
import {wrap} from "./Casting.sol";

function add(Quad x, Quad y) pure returns (Quad) {
    return wrap(ABDKMathQuad.add(x.unwrap(), y.unwrap()));
}

function sub(Quad x, Quad y) pure returns (Quad) {
    return wrap(ABDKMathQuad.sub(x.unwrap(), y.unwrap()));
}

function mul(Quad x, Quad y) pure returns (Quad) {
    return wrap(ABDKMathQuad.mul(x.unwrap(), y.unwrap()));
}

function div(Quad x, Quad y) pure returns (Quad) {
    return wrap(ABDKMathQuad.div(x.unwrap(), y.unwrap()));
}

function eq(Quad x, Quad y) pure returns (bool) {
    return ABDKMathQuad.eq(x.unwrap(), y.unwrap());
}

function neq(Quad x, Quad y) pure returns (bool) {
    return !eq(x, y);
}

function lt(Quad x, Quad y) pure returns (bool) {
    return ABDKMathQuad.cmp(x.unwrap(), y.unwrap()) < 0;
}

function lte(Quad x, Quad y) pure returns (bool) {
    return ABDKMathQuad.cmp(x.unwrap(), y.unwrap()) <= 0;
}

function gt(Quad x, Quad y) pure returns (bool) {
    return ABDKMathQuad.cmp(x.unwrap(), y.unwrap()) > 0;
}

function gte(Quad x, Quad y) pure returns (bool) {
    return ABDKMathQuad.cmp(x.unwrap(), y.unwrap()) >= 0;
}

function neg(Quad x) pure returns (Quad) {
    return wrap(ABDKMathQuad.neg(x.unwrap()));
}

function abs(Quad x) pure returns (Quad) {
    return wrap(ABDKMathQuad.abs(x.unwrap()));
}

function isNaN(Quad x) pure returns (bool) {
    return ABDKMathQuad.isNaN(x.unwrap());
}

function isInfinity(Quad x) pure returns (bool) {
    return ABDKMathQuad.isInfinity(x.unwrap());
}

function mostSignificantBit(uint256 x) pure returns (uint256) {
    unchecked {
        require(x > 0);

        uint256 result = 0;

        if (x >= 0x100000000000000000000000000000000) {
            x >>= 128;
            result += 128;
        }
        if (x >= 0x10000000000000000) {
            x >>= 64;
            result += 64;
        }
        if (x >= 0x100000000) {
            x >>= 32;
            result += 32;
        }
        if (x >= 0x10000) {
            x >>= 16;
            result += 16;
        }
        if (x >= 0x100) {
            x >>= 8;
            result += 8;
        }
        if (x >= 0x10) {
            x >>= 4;
            result += 4;
        }
        if (x >= 0x4) {
            x >>= 2;
            result += 2;
        }
        if (x >= 0x2) result += 1; // No need to shift x anymore

        return result;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "src/types/ABDKMathQuad/Quad.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve} from "src/libraries/internal/DeadlineJumps.sol";
import {OptInt256, wrap} from "src/types/Optional/OptInt256.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {subSplits} from "src/libraries/helpers/PoolHelper.sol";

import {MIN_SPLITS} from "src/Constants.sol";

library BoxcarTubFrame {
    using SafeCast for int256;

    error OffsetOutOfRange();
    error StartIdOrStopIdOutOfRange();

    struct Info {
        Quad[2 << MIN_SPLITS] coef;
    }

    function coefAt(Info storage self, int256 node) internal view returns (Quad) {
        return self.coef[node.toUint256()];
    }

    function coefAdd(Info storage self, int256 node, Quad amount) internal {
        self.coef[node.toUint256()] = self.coef[node.toUint256()] + amount;
    }

    function apply_(Info storage self, int256 offset) internal view returns (Quad) {
        if (((offset < 0) || (offset >= int256(1 << int256(MIN_SPLITS).toUint256())))) revert OffsetOutOfRange();
        Quad total = coefAt(self, 1);
        int256 node = (int256(1 << int256(MIN_SPLITS).toUint256()) + offset);
        while ((node > 1)) {
            total = (total + coefAt(self, node));
            node = (node >> 1);
        }
        return total;
    }

    function active(Info storage self, int256 splits, int256 tickBin) internal view returns (Quad) {
        return apply_(self, tickBin >> uint256(subSplits(splits)));
    }

    function addRange(Info storage self, int256 startIdx, int256 stopIdx, Quad change) internal {
        int256 length = int256(1 << int256(MIN_SPLITS).toUint256());
        if ((((startIdx < 0) || (startIdx > stopIdx)) || (stopIdx > length))) revert StartIdOrStopIdOutOfRange();
        int256 start = (length + startIdx);

        int256 stop = (length + stopIdx);
        while ((start != stop)) {
            if (((start & 1) != 0)) {
                coefAdd(self, start, change);
                start = (start + 1);
            }
            if (((stop & 1) != 0)) {
                stop = (stop - 1);
                coefAdd(self, stop, change);
            }
            start = (start >> 1);
            stop = (stop >> 1);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "src/types/ABDKMathQuad/Quad.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DeadlineJumps, DeadlineSet, PiecewiseCurve} from "src/libraries/internal/DeadlineJumps.sol";
import {OptInt256, wrap, OPT_INT256_NONE} from "src/types/Optional/OptInt256.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

import {MAX_SPLITS} from "src/Constants.sol";

library EraBoxcarMidSum {
    using DeadlineJumps for DeadlineSet.Info;
    using PiecewiseCurve for DeadlineSet.Info;
    using SafeCast for int256;

    struct Info {
        DeadlineSet.Info[4 << MAX_SPLITS] frame;
        bool ofBelow;
    }

    error OffsetOutOfRange();
    error InvalidAddRangeArguments();

    function init(Info storage self, bool ofBelow) public {
        self.ofBelow = ofBelow;
    }

    function apply_(Info storage self, int256 poolEra, int256 scale, int256 offset) public returns (Quad) {
        if (((offset < 0) || (offset >= int256(1 << scale.toUint256())))) revert OffsetOutOfRange();
        Quad total = coefAt(self, poolEra, 1);
        int256 node = (int256(1 << scale.toUint256()) + offset);
        while ((node > 1)) {
            total = (total + coefAt(self, poolEra, node));
            node = (node >> 1);
        }
        return total;
    }

    function coefAt(Info storage self, int256 poolEra, int256 node) public returns (Quad) {
        return self.frame[node.toUint256()].now(poolEra);
    }

    function coefAddExtend(Info storage self, int256 poolEra, int256 node, Quad amount, OptInt256 oldDeadEra, OptInt256 newDeadEra) public {
        self.frame[node.toUint256()].extend(poolEra, amount, oldDeadEra, newDeadEra);
    }

    function coefAddCreate(Info storage self, int256 poolEra, int256 node, Quad amount, OptInt256 deadEra, OptInt256) public {
        self.frame[node.toUint256()].create(poolEra, amount, deadEra);
    }

    function halfsum(Info storage self, PoolState storage pool, bool ask) public returns (Quad) {
        return apply_(self, pool.era, pool.splits, ask ? int256(pool.tickBin + 1) : int256(pool.tickBin));
    }

    function bidSum(Info storage self, PoolState storage pool) public returns (Quad) {
        return halfsum(self, pool, false);
    }

    function askSum(Info storage self, PoolState storage pool) public returns (Quad) {
        return halfsum(self, pool, true);
    }

    function addRange(
        Info storage self,
        PoolState storage pool,
        int256 scale,
        int256 startIdx,
        int256 stopIdx,
        Quad change,
        OptInt256 deadEra,
        OptInt256 newDeadEra,
        function (Info storage,int256, int256, Quad, OptInt256, OptInt256) internal coeffAdd
    ) internal {
        int256 length = int256(1 << scale.toUint256());

        if (!((((0 <= startIdx) && (startIdx <= stopIdx)) && (stopIdx <= length)))) revert InvalidAddRangeArguments();
        int256 start = (length + startIdx);
        int256 stop = (length + stopIdx);
        while ((start != stop)) {
            if (((start & 1) != 0)) {
                coeffAdd(self, pool.era, start, change, deadEra, newDeadEra);
                start = (start + 1);
            }

            if (((stop & 1) != 0)) {
                stop = (stop - 1);
                coeffAdd(self, pool.era, stop, change, deadEra, newDeadEra);
            }

            start = (start >> 1);
            stop = (stop >> 1);
        }
    }

    function create(Info storage self, PoolState storage pool, Quad amount, int256 scale, int256 offset, OptInt256 deadEra) public {
        int256 startIdx = self.ofBelow ? (offset + 1) : int256(0);
        int256 stopIdx = self.ofBelow ? int256(1 << scale.toUint256()) : (offset + 1);
        addRange(self, pool, scale, startIdx, stopIdx, amount, deadEra, wrap(OPT_INT256_NONE), coefAddCreate);
        coefAddCreate(
            self,
            pool.era,
            (((int256(1 << scale.toUint256()) + offset) << 1) + 1),
            self.ofBelow ? amount : amount.neg(),
            deadEra,
            wrap(OPT_INT256_NONE)
        );
    }

    function extend(Info storage self, PoolState storage pool, Quad amount, int256 scale, int256 offset, OptInt256 deadEra, OptInt256 newDeadEra)
        public
    {
        int256 startIdx = self.ofBelow ? (offset + 1) : int256(0);
        int256 stopIdx = self.ofBelow ? int256(1 << scale.toUint256()) : (offset + 1);
        addRange(self, pool, scale, startIdx, stopIdx, amount, deadEra, newDeadEra, coefAddExtend);
        coefAddExtend(
            self, pool.era, (((int256(1 << scale.toUint256()) + offset) << 1) + 1), self.ofBelow ? amount : amount.neg(), deadEra, newDeadEra
        );
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {JUMPS, JUMP_NONE} from "src/Constants.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {
    maybeJump, jumpIndex as jumpIndexHelper, isJumpDefined, validDeadline, deadEra as deadEraHelper
} from "src/libraries/helpers/DeadlineHelper.sol";

library DeadlineFlag {
    using DeadlineFlag for DeadlineFlag.Info;
    using SafeCast for uint256;
    using SafeCast for int256;

    struct Info {
        int32 dropsEra;
        int8 nextJump;
        bool touched;
        bool[JUMPS] drops;
    }

    error InvalidDeadline();

    function dropOff(DeadlineFlag.Info storage self, bool drop) internal {}

    function dropAt(Info storage self, int256 deadEra) internal view returns (bool) {
        if (jumpIndex(self, deadEra) == JUMP_NONE) return false;
        else return self.drops[int256(jumpIndex(self, deadEra)).toUint256()];
    }

    function _apply(Info storage self, int256 poolEra, int256 deadEra) internal returns (bool) {
        int256 idx = self.slot(deadEra, poolEra, dropOff);
        return self.drops[idx.toUint256()];
    }

    function jumpIndex(Info storage self, int256 deadline) internal view returns (int8) {
        return jumpIndexHelper(self.dropsEra, deadline);
    }

    function deadEra(Info storage self, int8 jumpIndex) internal view returns (int256) {
        return deadEraHelper(self.dropsEra, jumpIndex);
    }

    function slot(Info storage self, int256 poolEra, int256 deadEra, function (DeadlineFlag.Info storage, bool) internal dropoff)
        internal
        returns (int8)
    {
        if (!(validDeadline(poolEra, deadEra))) revert InvalidDeadline();

        if (!(self.touched)) {
            self.dropsEra = int32(poolEra);
            self.nextJump = jumpIndex(self, deadEra);
            self.touched = true;
            return self.nextJump;
        }

        int8 jump = jumpIndex(self, deadEra);

        if (!isJumpDefined(jump)) {
            reanchor(self, poolEra, dropoff);
            jump = maybeJump(self.dropsEra, deadEra);
        }

        if ((jump < self.nextJump)) self.nextJump = jump;

        return int8(jump);
    }

    function assign(Info storage self, int256 poolEra, bool value, int256 deadEra) internal {
        int8 jump = slot(self, poolEra, deadEra, dropOff);
        self.drops[int256(jump).toUint256()] = value;
    }

    function assign(Info storage self, int256 poolEra, bool value, OptInt256 deadEra) internal {
        if (deadEra.isDefined()) assign(self, poolEra, value, deadEra.get());
    }

    function reanchor(Info storage self, int256 poolEra, function (DeadlineFlag.Info storage, bool) internal dropoff) internal {
        bool unset = true;

        for (int8 oldIndex = self.nextJump; (oldIndex < JUMPS); oldIndex = (oldIndex + 1)) {
            bool drop = self.drops[int256(oldIndex).toUint256()];

            if (drop) {
                int256 deadline = deadEra(self, oldIndex);

                if ((deadline <= poolEra)) {
                    dropoff(self, drop);
                } else {
                    int8 newIndex = maybeJump(poolEra, deadline);

                    if (unset) {
                        self.nextJump = newIndex;
                        unset = false;
                    }

                    if ((newIndex == oldIndex)) break;

                    self.drops[int256(newIndex).toUint256()] = drop;
                }
                self.drops[int256(oldIndex).toUint256()] = false;
            }
        }

        self.dropsEra = int32(poolEra);
        if (unset) self.nextJump = JUMPS;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {TUBS, JUMPS, WORD_SIZE, TICK_SUB_SLITS, Z, I} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {EachPayoff} from "src/libraries/internal/EachPayoff.sol";
import {logBin, tickSqrtPrice, binLowTick} from "src/libraries/helpers/PoolHelper.sol";

import {FloatBits, SparseFloat, QuadPacker} from "src/libraries/internal/SparseFloat.sol";

library Capper {
    using SparseFloat for SparseFloat.Info;
    using Capper for Info;
    using FloatBits for FloatBits.Info;

    struct Info {
        Quad entryDeflator;
        int32 lastDrain;
        bool isInitialized;
        FloatBits.Info entryLevel;
        SparseFloat.Info integral;
    }

    function init(Info storage self, PoolState storage pool, int256 pivotTick, bool token) internal {
        self.isInitialized = true;
        if (pivotTick <= binLowTick(pool.splits, pool.tickBin) == token) {
            self.entryDeflator = pool.deflator;
        } else {
            self.entryLevel = EachPayoff.runLevel(pool, pivotTick, token, true);
            self.entryDeflator = POSITIVE_ZERO;
        }
        self.lastDrain = pool.era;
    }

    function unpeg(Info storage self, PoolState storage pool, int256 pivotTick, bool token) internal {
        self.integral.add(due(self, pool, pivotTick, token));
        self.entryLevel = EachPayoff.runLevel(pool, pivotTick, token, true);
        self.entryDeflator = POSITIVE_ZERO;
    }

    function pegUp(Info storage self, PoolState storage pool, int256 pivotTick, bool token) internal {
        self.integral.add(due(self, pool, pivotTick, token));
        self.entryDeflator = pool.deflator;

        self.entryLevel.exponent = 0;
        self.entryLevel.significand = 0;
    }

    function due(Info storage self, PoolState storage pool, int256 pivotTick, bool token) internal returns (Quad) {
        if (self.entryDeflator == POSITIVE_ZERO) {
            EachPayoff.growRuns(pool);
            Quad t2 = pool.reserveRun[(token == Z ? 0 : 1)].growth(self.entryLevel);
            return (t2 * tickSqrtPrice(((token == Z) ? pivotTick : -pivotTick)));
        } else {
            return (self.entryDeflator - pool.deflator);
        }
    }

    function grow(Info storage self, PoolState storage pool, int256 pivotTick, bool token) internal {
        if (self.entryDeflator == POSITIVE_ZERO) unpeg(self, pool, pivotTick, token);
        else pegUp(self, pool, pivotTick, token);
    }

    function read(Info storage self, PoolState storage pool, int256 pivotTick, bool token, int128 exponent)
        internal
        returns (FloatBits.Info memory)
    {
        return self.integral.read(exponent).add(due(self, pool, pivotTick, token));
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad} from "src/types/ABDKMathQuad/ValueType.sol";
import {POSITIVE_ZERO, NEGATIVE_INFINITY, POSITIVE_INFINITY} from "src/types/ABDKMathQuad/Constants.sol";
import {mostSignificantBit} from "src/types/ABDKMathQuad/Helpers.sol";
import {WORD_SIZE} from "src/Constants.sol";
import {floorDiv, floorMod} from "../helpers/PoolHelper.sol";
import {int128ShiftRightUnsigned} from "src/libraries/internal/Utils.sol";

library FloatBits {
    error TruncateOverflow();

    struct Info {
        int128 exponent;
        int128 significand;
    }

    function add(Info memory self, Quad other) internal pure returns (Info memory result) {
        result.exponent = self.exponent;
        unchecked {
            result.significand = self.significand + truncate(other, self.exponent);
        }
    }

    function truncate(Quad quad, int128 newExponent) internal pure returns (int128 resultSignificand) {
        FloatBits.Info memory bits = QuadPacker.unpack(quad);
        int128 shift = bits.exponent - newExponent;
        if (shift <= 0) {
            if (-shift < WORD_SIZE) resultSignificand = int128ShiftRightUnsigned(bits.significand, uint128(-shift));
            else resultSignificand = 0;
        } else {
            //the shift cannot be greater than 256 or this(uint256(256-shift)) fails silently with underflow
            if (!(int128ShiftRightUnsigned(bits.significand, ((WORD_SIZE - shift) > 0 ? uint128(WORD_SIZE - shift) : 0)) == 0)) {
                revert TruncateOverflow();
            }
            resultSignificand = bits.significand << uint128(shift);
        }
    }

    function _apply(int128 exponent, int128 significand) internal pure returns (Info memory result) {
        return Info(exponent, significand);
    }
}

library QuadPacker {
    error UnpackInvalidFloat();

    function unpack(Quad quad) internal pure returns (FloatBits.Info memory) {
        if (quad < POSITIVE_ZERO || quad == POSITIVE_INFINITY || quad == NEGATIVE_INFINITY) revert UnpackInvalidFloat();
        int128 float = int128(uint128(quad.unwrap()));

        int128 exponent = int128ShiftRightUnsigned(float, 112);

        if (exponent == 0) {
            int128 val = type(int128).max;
            int128 temp = int128ShiftRightUnsigned(val, 15);
            return FloatBits.Info(-16382 - 112, int128((float & temp)));
        } else {
            int128 t2 = float << 15 | type(int128).min;
            int128 significand = int128ShiftRightUnsigned(t2, 15);

            return FloatBits.Info(exponent - 16383 - 112, significand);
        }
    }

    function repack(FloatBits.Info memory bits) internal pure returns (Quad quad) {
        if (bits.significand == 0) return POSITIVE_ZERO;

        int128 intHighestBit = int128(int256(mostSignificantBit(uint256(uint128(bits.significand)))));
        int128 realTopBit = bits.exponent + intHighestBit;
        if (realTopBit <= -16383) {
            return POSITIVE_ZERO;
        } else if (realTopBit > 16383) {
            return POSITIVE_INFINITY;
        } else {
            uint128 bitmask = uint128(~(1 << uint128(intHighestBit)));
            uint128 significand = uint128(bits.significand) & bitmask;
            uint128 t1 = significand << uint128(128 - intHighestBit);
            int128 t2 = int128(t1 >> 16);
            return Quad.wrap(bytes16(uint128((realTopBit + 16383))) << 112 | bytes16(uint128(t2)));
        }
    }
}

library SparseFloat {
    struct Info {
        mapping(int128 => int128) blocks;
    }

    function add(Info storage self, Quad summand) internal {
        FloatBits.Info memory bits = QuadPacker.unpack(summand);
        int128 lowBlock = int128(floorDiv(bits.exponent, WORD_SIZE));
        int128 shift = int128(floorMod(bits.exponent, WORD_SIZE));

        int128 shift1 = WORD_SIZE - shift;
        int128 base1 = bits.significand;
        int128 t1 = int128ShiftRightUnsigned(base1, uint128(shift1));
        int128 highPart = shift == 0 ? int128(0) : t1;
        //if shift is negative then below casting would be dangerous
        int128 lowPart = bits.significand << uint128((shift));

        bool carry;
        {
            int128 oldLow = self.blocks[lowBlock];
            int128 newLow;
            unchecked {
                newLow = oldLow + lowPart;
            }
            self.blocks[lowBlock] = newLow;

            carry = oldLow < 0 == lowPart < 0 ? oldLow < 0 : !(newLow < 0);
        }

        int128 blockNum = lowBlock + 1;
        int128 oldHigh = self.blocks[blockNum];
        int128 newHigh;
        unchecked {
            newHigh = oldHigh + highPart + (carry ? int128(1) : int128(0));
        }
        self.blocks[blockNum] = newHigh;
        carry = oldHigh < 0 == highPart < 0 ? oldHigh < 0 : !(newHigh < 0);

        while (carry) {
            blockNum += 1;
            int128 newBlock;
            unchecked {
                newBlock = self.blocks[blockNum] + 1;
            }
            carry = newBlock == 0;
            self.blocks[blockNum] = newBlock;
        }
    }

    function read(Info storage self, int128 exponent) internal view returns (FloatBits.Info memory) {
        int128 lowBlock = int128(floorDiv(exponent, WORD_SIZE));
        int128 shift = int128(floorMod(exponent, WORD_SIZE));

        int128 t1 = self.blocks[lowBlock];
        uint128 t2 = uint128(shift);
        int128 lowPart = int128ShiftRightUnsigned(t1, t2);

        if (shift != 0) {
            int128 highPart = self.blocks[lowBlock + 1] << uint128(WORD_SIZE - shift);
            return FloatBits.Info(exponent, highPart | lowPart);
        } else {
            return FloatBits.Info(exponent, lowPart);
        }
    }

    function growth(Info storage self, FloatBits.Info memory sinceLevel) internal view returns (Quad) {
        FloatBits.Info memory endValue = read(self, sinceLevel.exponent);
        int128 significand;
        unchecked {
            significand = endValue.significand - sinceLevel.significand;
        }
        return QuadPacker.repack(FloatBits.Info(endValue.exponent, significand));
    }
}

// SPDX-License-Identifier: BSD-4-Clause
/*
 * ABDK Math Quad Smart Contract Library.  Copyright © 2019 by ABDK Consulting.
 * Author: Mikhail Vladimirov <[email protected]>
 */
pragma solidity ^0.8.0;

/**
 * Smart contract library of mathematical functions operating with IEEE 754
 * quadruple-precision binary floating-point numbers (quadruple precision
 * numbers).  As long as quadruple precision numbers are 16-bytes long, they are
 * represented by bytes16 type.
 */
library ABDKMathQuad {
  /*
   * 0.
   */
  bytes16 private constant POSITIVE_ZERO = 0x00000000000000000000000000000000;

  /*
   * -0.
   */
  bytes16 private constant NEGATIVE_ZERO = 0x80000000000000000000000000000000;

  /*
   * +Infinity.
   */
  bytes16 private constant POSITIVE_INFINITY = 0x7FFF0000000000000000000000000000;

  /*
   * -Infinity.
   */
  bytes16 private constant NEGATIVE_INFINITY = 0xFFFF0000000000000000000000000000;

  /*
   * Canonical NaN value.
   */
  bytes16 private constant NaN = 0x7FFF8000000000000000000000000000;

  /**
   * Convert signed 256-bit integer number into quadruple precision number.
   *
   * @param x signed 256-bit integer number
   * @return quadruple precision number
   */
  function fromInt (int256 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        // We rely on overflow behavior here
        uint256 result = uint256 (x > 0 ? x : -x);

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16383 + msb << 112;
        if (x < 0) result |= 0x80000000000000000000000000000000;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into signed 256-bit integer number
   * rounding towards zero.  Revert on overflow.
   *
   * @param x quadruple precision number
   * @return signed 256-bit integer number
   */
  function toInt (bytes16 x) internal pure returns (int256) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      require (exponent <= 16638); // Overflow
      if (exponent < 16383) return 0; // Underflow

      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16495) result >>= 16495 - exponent;
      else if (exponent > 16495) result <<= exponent - 16495;

      if (uint128 (x) >= 0x80000000000000000000000000000000) { // Negative
        require (result <= 0x8000000000000000000000000000000000000000000000000000000000000000);
        return -int256 (result); // We rely on overflow behavior here
      } else {
        require (result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int256 (result);
      }
    }
  }

  /**
   * Convert unsigned 256-bit integer number into quadruple precision number.
   *
   * @param x unsigned 256-bit integer number
   * @return quadruple precision number
   */
  function fromUInt (uint256 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        uint256 result = x;

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16383 + msb << 112;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into unsigned 256-bit integer number
   * rounding towards zero.  Revert on underflow.  Note, that negative floating
   * point numbers in range (-1.0 .. 0.0) may be converted to unsigned integer
   * without error, because they are rounded to zero.
   *
   * @param x quadruple precision number
   * @return unsigned 256-bit integer number
   */
  function toUInt (bytes16 x) internal pure returns (uint256) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      if (exponent < 16383) return 0; // Underflow

      require (uint128 (x) < 0x80000000000000000000000000000000); // Negative

      require (exponent <= 16638); // Overflow
      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16495) result >>= 16495 - exponent;
      else if (exponent > 16495) result <<= exponent - 16495;

      return result;
    }
  }

  /**
   * Convert signed 128.128 bit fixed point number into quadruple precision
   * number.
   *
   * @param x signed 128.128 bit fixed point number
   * @return quadruple precision number
   */
  function from128x128 (int256 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        // We rely on overflow behavior here
        uint256 result = uint256 (x > 0 ? x : -x);

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16255 + msb << 112;
        if (x < 0) result |= 0x80000000000000000000000000000000;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into signed 128.128 bit fixed point
   * number.  Revert on overflow.
   *
   * @param x quadruple precision number
   * @return signed 128.128 bit fixed point number
   */
  function to128x128 (bytes16 x) internal pure returns (int256) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      require (exponent <= 16510); // Overflow
      if (exponent < 16255) return 0; // Underflow

      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16367) result >>= 16367 - exponent;
      else if (exponent > 16367) result <<= exponent - 16367;

      if (uint128 (x) >= 0x80000000000000000000000000000000) { // Negative
        require (result <= 0x8000000000000000000000000000000000000000000000000000000000000000);
        return -int256 (result); // We rely on overflow behavior here
      } else {
        require (result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int256 (result);
      }
    }
  }

  /**
   * Convert signed 64.64 bit fixed point number into quadruple precision
   * number.
   *
   * @param x signed 64.64 bit fixed point number
   * @return quadruple precision number
   */
  function from64x64 (int128 x) internal pure returns (bytes16) {
    unchecked {
      if (x == 0) return bytes16 (0);
      else {
        // We rely on overflow behavior here
        uint256 result = uint128 (x > 0 ? x : -x);

        uint256 msb = mostSignificantBit (result);
        if (msb < 112) result <<= 112 - msb;
        else if (msb > 112) result >>= msb - 112;

        result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16319 + msb << 112;
        if (x < 0) result |= 0x80000000000000000000000000000000;

        return bytes16 (uint128 (result));
      }
    }
  }

  /**
   * Convert quadruple precision number into signed 64.64 bit fixed point
   * number.  Revert on overflow.
   *
   * @param x quadruple precision number
   * @return signed 64.64 bit fixed point number
   */
  function to64x64 (bytes16 x) internal pure returns (int128) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      require (exponent <= 16446); // Overflow
      if (exponent < 16319) return 0; // Underflow

      uint256 result = uint256 (uint128 (x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
        0x10000000000000000000000000000;

      if (exponent < 16431) result >>= 16431 - exponent;
      else if (exponent > 16431) result <<= exponent - 16431;

      if (uint128 (x) >= 0x80000000000000000000000000000000) { // Negative
        require (result <= 0x80000000000000000000000000000000);
        return -int128 (int256 (result)); // We rely on overflow behavior here
      } else {
        require (result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return int128 (int256 (result));
      }
    }
  }

  /**
   * Convert octuple precision number into quadruple precision number.
   *
   * @param x octuple precision number
   * @return quadruple precision number
   */
  function fromOctuple (bytes32 x) internal pure returns (bytes16) {
    unchecked {
      bool negative = x & 0x8000000000000000000000000000000000000000000000000000000000000000 > 0;

      uint256 exponent = uint256 (x) >> 236 & 0x7FFFF;
      uint256 significand = uint256 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (exponent == 0x7FFFF) {
        if (significand > 0) return NaN;
        else return negative ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
      }

      if (exponent > 278526)
        return negative ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
      else if (exponent < 245649)
        return negative ? NEGATIVE_ZERO : POSITIVE_ZERO;
      else if (exponent < 245761) {
        significand = (significand | 0x100000000000000000000000000000000000000000000000000000000000) >> 245885 - exponent;
        exponent = 0;
      } else {
        significand >>= 124;
        exponent -= 245760;
      }

      uint128 result = uint128 (significand | exponent << 112);
      if (negative) result |= 0x80000000000000000000000000000000;

      return bytes16 (result);
    }
  }

  /**
   * Convert quadruple precision number into octuple precision number.
   *
   * @param x quadruple precision number
   * @return octuple precision number
   */
  function toOctuple (bytes16 x) internal pure returns (bytes32) {
    unchecked {
      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;

      uint256 result = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (exponent == 0x7FFF) exponent = 0x7FFFF; // Infinity or NaN
      else if (exponent == 0) {
        if (result > 0) {
          uint256 msb = mostSignificantBit (result);
          result = result << 236 - msb & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          exponent = 245649 + msb;
        }
      } else {
        result <<= 124;
        exponent += 245760;
      }

      result |= exponent << 236;
      if (uint128 (x) >= 0x80000000000000000000000000000000)
        result |= 0x8000000000000000000000000000000000000000000000000000000000000000;

      return bytes32 (result);
    }
  }

  /**
   * Convert double precision number into quadruple precision number.
   *
   * @param x double precision number
   * @return quadruple precision number
   */
  function fromDouble (bytes8 x) internal pure returns (bytes16) {
    unchecked {
      uint256 exponent = uint64 (x) >> 52 & 0x7FF;

      uint256 result = uint64 (x) & 0xFFFFFFFFFFFFF;

      if (exponent == 0x7FF) exponent = 0x7FFF; // Infinity or NaN
      else if (exponent == 0) {
        if (result > 0) {
          uint256 msb = mostSignificantBit (result);
          result = result << 112 - msb & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          exponent = 15309 + msb;
        }
      } else {
        result <<= 60;
        exponent += 15360;
      }

      result |= exponent << 112;
      if (x & 0x8000000000000000 > 0)
        result |= 0x80000000000000000000000000000000;

      return bytes16 (uint128 (result));
    }
  }

  /**
   * Convert quadruple precision number into double precision number.
   *
   * @param x quadruple precision number
   * @return double precision number
   */
  function toDouble (bytes16 x) internal pure returns (bytes8) {
    unchecked {
      bool negative = uint128 (x) >= 0x80000000000000000000000000000000;

      uint256 exponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 significand = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (exponent == 0x7FFF) {
        if (significand > 0) return 0x7FF8000000000000; // NaN
        else return negative ?
            bytes8 (0xFFF0000000000000) : // -Infinity
            bytes8 (0x7FF0000000000000); // Infinity
      }

      if (exponent > 17406)
        return negative ?
            bytes8 (0xFFF0000000000000) : // -Infinity
            bytes8 (0x7FF0000000000000); // Infinity
      else if (exponent < 15309)
        return negative ?
            bytes8 (0x8000000000000000) : // -0
            bytes8 (0x0000000000000000); // 0
      else if (exponent < 15361) {
        significand = (significand | 0x10000000000000000000000000000) >> 15421 - exponent;
        exponent = 0;
      } else {
        significand >>= 60;
        exponent -= 15360;
      }

      uint64 result = uint64 (significand | exponent << 52);
      if (negative) result |= 0x8000000000000000;

      return bytes8 (result);
    }
  }

  /**
   * Test whether given quadruple precision number is NaN.
   *
   * @param x quadruple precision number
   * @return true if x is NaN, false otherwise
   */
  function isNaN (bytes16 x) internal pure returns (bool) {
    unchecked {
      return uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >
        0x7FFF0000000000000000000000000000;
    }
  }

  /**
   * Test whether given quadruple precision number is positive or negative
   * infinity.
   *
   * @param x quadruple precision number
   * @return true if x is positive or negative infinity, false otherwise
   */
  function isInfinity (bytes16 x) internal pure returns (bool) {
    unchecked {
      return uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ==
        0x7FFF0000000000000000000000000000;
    }
  }

  /**
   * Calculate sign of x, i.e. -1 if x is negative, 0 if x if zero, and 1 if x
   * is positive.  Note that sign (-0) is zero.  Revert if x is NaN. 
   *
   * @param x quadruple precision number
   * @return sign of x
   */
  function sign (bytes16 x) internal pure returns (int8) {
    unchecked {
      uint128 absoluteX = uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      require (absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN

      if (absoluteX == 0) return 0;
      else if (uint128 (x) >= 0x80000000000000000000000000000000) return -1;
      else return 1;
    }
  }

  /**
   * Calculate sign (x - y).  Revert if either argument is NaN, or both
   * arguments are infinities of the same sign. 
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return sign (x - y)
   */
  function cmp (bytes16 x, bytes16 y) internal pure returns (int8) {
    unchecked {
      uint128 absoluteX = uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      require (absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN

      uint128 absoluteY = uint128 (y) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      require (absoluteY <= 0x7FFF0000000000000000000000000000); // Not NaN

      // Not infinities of the same sign
      require (x != y || absoluteX < 0x7FFF0000000000000000000000000000);

      if (x == y) return 0;
      else {
        bool negativeX = uint128 (x) >= 0x80000000000000000000000000000000;
        bool negativeY = uint128 (y) >= 0x80000000000000000000000000000000;

        if (negativeX) {
          if (negativeY) return absoluteX > absoluteY ? -1 : int8 (1);
          else return -1; 
        } else {
          if (negativeY) return 1;
          else return absoluteX > absoluteY ? int8 (1) : -1;
        }
      }
    }
  }

  /**
   * Test whether x equals y.  NaN, infinity, and -infinity are not equal to
   * anything. 
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return true if x equals to y, false otherwise
   */
  function eq (bytes16 x, bytes16 y) internal pure returns (bool) {
    unchecked {
      if (x == y) {
        return uint128 (x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF <
          0x7FFF0000000000000000000000000000;
      } else return false;
    }
  }

  /**
   * Calculate x + y.  Special values behave in the following way:
   *
   * NaN + x = NaN for any x.
   * Infinity + x = Infinity for any finite x.
   * -Infinity + x = -Infinity for any finite x.
   * Infinity + Infinity = Infinity.
   * -Infinity + -Infinity = -Infinity.
   * Infinity + -Infinity = -Infinity + Infinity = NaN.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function add (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 yExponent = uint128 (y) >> 112 & 0x7FFF;

      if (xExponent == 0x7FFF) {
        if (yExponent == 0x7FFF) { 
          if (x == y) return x;
          else return NaN;
        } else return x; 
      } else if (yExponent == 0x7FFF) return y;
      else {
        bool xSign = uint128 (x) >= 0x80000000000000000000000000000000;
        uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xExponent == 0) xExponent = 1;
        else xSignifier |= 0x10000000000000000000000000000;

        bool ySign = uint128 (y) >= 0x80000000000000000000000000000000;
        uint256 ySignifier = uint128 (y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (yExponent == 0) yExponent = 1;
        else ySignifier |= 0x10000000000000000000000000000;

        if (xSignifier == 0) return y == NEGATIVE_ZERO ? POSITIVE_ZERO : y;
        else if (ySignifier == 0) return x == NEGATIVE_ZERO ? POSITIVE_ZERO : x;
        else {
          int256 delta = int256 (xExponent) - int256 (yExponent);
  
          if (xSign == ySign) {
            if (delta > 112) return x;
            else if (delta > 0) ySignifier >>= uint256 (delta);
            else if (delta < -112) return y;
            else if (delta < 0) {
              xSignifier >>= uint256 (-delta);
              xExponent = yExponent;
            }
  
            xSignifier += ySignifier;
  
            if (xSignifier >= 0x20000000000000000000000000000) {
              xSignifier >>= 1;
              xExponent += 1;
            }
  
            if (xExponent == 0x7FFF)
              return xSign ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
            else {
              if (xSignifier < 0x10000000000000000000000000000) xExponent = 0;
              else xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
  
              return bytes16 (uint128 (
                  (xSign ? 0x80000000000000000000000000000000 : 0) |
                  (xExponent << 112) |
                  xSignifier)); 
            }
          } else {
            if (delta > 0) {
              xSignifier <<= 1;
              xExponent -= 1;
            } else if (delta < 0) {
              ySignifier <<= 1;
              xExponent = yExponent - 1;
            }

            if (delta > 112) ySignifier = 1;
            else if (delta > 1) ySignifier = (ySignifier - 1 >> uint256 (delta - 1)) + 1;
            else if (delta < -112) xSignifier = 1;
            else if (delta < -1) xSignifier = (xSignifier - 1 >> uint256 (-delta - 1)) + 1;

            if (xSignifier >= ySignifier) xSignifier -= ySignifier;
            else {
              xSignifier = ySignifier - xSignifier;
              xSign = ySign;
            }

            if (xSignifier == 0)
              return POSITIVE_ZERO;

            uint256 msb = mostSignificantBit (xSignifier);

            if (msb == 113) {
              xSignifier = xSignifier >> 1 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
              xExponent += 1;
            } else if (msb < 112) {
              uint256 shift = 112 - msb;
              if (xExponent > shift) {
                xSignifier = xSignifier << shift & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
                xExponent -= shift;
              } else {
                xSignifier <<= xExponent - 1;
                xExponent = 0;
              }
            } else xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

            if (xExponent == 0x7FFF)
              return xSign ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
            else return bytes16 (uint128 (
                (xSign ? 0x80000000000000000000000000000000 : 0) |
                (xExponent << 112) |
                xSignifier));
          }
        }
      }
    }
  }

  /**
   * Calculate x - y.  Special values behave in the following way:
   *
   * NaN - x = NaN for any x.
   * Infinity - x = Infinity for any finite x.
   * -Infinity - x = -Infinity for any finite x.
   * Infinity - -Infinity = Infinity.
   * -Infinity - Infinity = -Infinity.
   * Infinity - Infinity = -Infinity - -Infinity = NaN.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function sub (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      return add (x, y ^ 0x80000000000000000000000000000000);
    }
  }

  /**
   * Calculate x * y.  Special values behave in the following way:
   *
   * NaN * x = NaN for any x.
   * Infinity * x = Infinity for any finite positive x.
   * Infinity * x = -Infinity for any finite negative x.
   * -Infinity * x = -Infinity for any finite positive x.
   * -Infinity * x = Infinity for any finite negative x.
   * Infinity * 0 = NaN.
   * -Infinity * 0 = NaN.
   * Infinity * Infinity = Infinity.
   * Infinity * -Infinity = -Infinity.
   * -Infinity * Infinity = -Infinity.
   * -Infinity * -Infinity = Infinity.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function mul (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 yExponent = uint128 (y) >> 112 & 0x7FFF;

      if (xExponent == 0x7FFF) {
        if (yExponent == 0x7FFF) {
          if (x == y) return x ^ y & 0x80000000000000000000000000000000;
          else if (x ^ y == 0x80000000000000000000000000000000) return x | y;
          else return NaN;
        } else {
          if (y & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN;
          else return x ^ y & 0x80000000000000000000000000000000;
        }
      } else if (yExponent == 0x7FFF) {
          if (x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN;
          else return y ^ x & 0x80000000000000000000000000000000;
      } else {
        uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xExponent == 0) xExponent = 1;
        else xSignifier |= 0x10000000000000000000000000000;

        uint256 ySignifier = uint128 (y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (yExponent == 0) yExponent = 1;
        else ySignifier |= 0x10000000000000000000000000000;

        xSignifier *= ySignifier;
        if (xSignifier == 0)
          return (x ^ y) & 0x80000000000000000000000000000000 > 0 ?
              NEGATIVE_ZERO : POSITIVE_ZERO;

        xExponent += yExponent;

        uint256 msb =
          xSignifier >= 0x200000000000000000000000000000000000000000000000000000000 ? 225 :
          xSignifier >= 0x100000000000000000000000000000000000000000000000000000000 ? 224 :
          mostSignificantBit (xSignifier);

        if (xExponent + msb < 16496) { // Underflow
          xExponent = 0;
          xSignifier = 0;
        } else if (xExponent + msb < 16608) { // Subnormal
          if (xExponent < 16496)
            xSignifier >>= 16496 - xExponent;
          else if (xExponent > 16496)
            xSignifier <<= xExponent - 16496;
          xExponent = 0;
        } else if (xExponent + msb > 49373) {
          xExponent = 0x7FFF;
          xSignifier = 0;
        } else {
          if (msb > 112)
            xSignifier >>= msb - 112;
          else if (msb < 112)
            xSignifier <<= 112 - msb;

          xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

          xExponent = xExponent + msb - 16607;
        }

        return bytes16 (uint128 (uint128 ((x ^ y) & 0x80000000000000000000000000000000) |
            xExponent << 112 | xSignifier));
      }
    }
  }

  /**
   * Calculate x / y.  Special values behave in the following way:
   *
   * NaN / x = NaN for any x.
   * x / NaN = NaN for any x.
   * Infinity / x = Infinity for any finite non-negative x.
   * Infinity / x = -Infinity for any finite negative x including -0.
   * -Infinity / x = -Infinity for any finite non-negative x.
   * -Infinity / x = Infinity for any finite negative x including -0.
   * x / Infinity = 0 for any finite non-negative x.
   * x / -Infinity = -0 for any finite non-negative x.
   * x / Infinity = -0 for any finite non-negative x including -0.
   * x / -Infinity = 0 for any finite non-negative x including -0.
   * 
   * Infinity / Infinity = NaN.
   * Infinity / -Infinity = -NaN.
   * -Infinity / Infinity = -NaN.
   * -Infinity / -Infinity = NaN.
   *
   * Division by zero behaves in the following way:
   *
   * x / 0 = Infinity for any finite positive x.
   * x / -0 = -Infinity for any finite positive x.
   * x / 0 = -Infinity for any finite negative x.
   * x / -0 = Infinity for any finite negative x.
   * 0 / 0 = NaN.
   * 0 / -0 = NaN.
   * -0 / 0 = NaN.
   * -0 / -0 = NaN.
   *
   * @param x quadruple precision number
   * @param y quadruple precision number
   * @return quadruple precision number
   */
  function div (bytes16 x, bytes16 y) internal pure returns (bytes16) {
    unchecked {
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 yExponent = uint128 (y) >> 112 & 0x7FFF;

      if (xExponent == 0x7FFF) {
        if (yExponent == 0x7FFF) return NaN;
        else return x ^ y & 0x80000000000000000000000000000000;
      } else if (yExponent == 0x7FFF) {
        if (y & 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFF != 0) return NaN;
        else return POSITIVE_ZERO | (x ^ y) & 0x80000000000000000000000000000000;
      } else if (y & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) {
        if (x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN;
        else return POSITIVE_INFINITY | (x ^ y) & 0x80000000000000000000000000000000;
      } else {
        uint256 ySignifier = uint128 (y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (yExponent == 0) yExponent = 1;
        else ySignifier |= 0x10000000000000000000000000000;

        uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xExponent == 0) {
          if (xSignifier != 0) {
            uint shift = 226 - mostSignificantBit (xSignifier);

            xSignifier <<= shift;

            xExponent = 1;
            yExponent += shift - 114;
          }
        }
        else {
          xSignifier = (xSignifier | 0x10000000000000000000000000000) << 114;
        }

        xSignifier = xSignifier / ySignifier;
        if (xSignifier == 0)
          return (x ^ y) & 0x80000000000000000000000000000000 > 0 ?
              NEGATIVE_ZERO : POSITIVE_ZERO;

        assert (xSignifier >= 0x1000000000000000000000000000);

        uint256 msb =
          xSignifier >= 0x80000000000000000000000000000 ? mostSignificantBit (xSignifier) :
          xSignifier >= 0x40000000000000000000000000000 ? 114 :
          xSignifier >= 0x20000000000000000000000000000 ? 113 : 112;

        if (xExponent + msb > yExponent + 16497) { // Overflow
          xExponent = 0x7FFF;
          xSignifier = 0;
        } else if (xExponent + msb + 16380  < yExponent) { // Underflow
          xExponent = 0;
          xSignifier = 0;
        } else if (xExponent + msb + 16268  < yExponent) { // Subnormal
          if (xExponent + 16380 > yExponent)
            xSignifier <<= xExponent + 16380 - yExponent;
          else if (xExponent + 16380 < yExponent)
            xSignifier >>= yExponent - xExponent - 16380;

          xExponent = 0;
        } else { // Normal
          if (msb > 112)
            xSignifier >>= msb - 112;

          xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

          xExponent = xExponent + msb + 16269 - yExponent;
        }

        return bytes16 (uint128 (uint128 ((x ^ y) & 0x80000000000000000000000000000000) |
            xExponent << 112 | xSignifier));
      }
    }
  }

  /**
   * Calculate -x.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function neg (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return x ^ 0x80000000000000000000000000000000;
    }
  }

  /**
   * Calculate |x|.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function abs (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
    }
  }

  /**
   * Calculate square root of x.  Return NaN on negative x excluding -0.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function sqrt (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      if (uint128 (x) >  0x80000000000000000000000000000000) return NaN;
      else {
        uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
        if (xExponent == 0x7FFF) return x;
        else {
          uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          if (xExponent == 0) xExponent = 1;
          else xSignifier |= 0x10000000000000000000000000000;

          if (xSignifier == 0) return POSITIVE_ZERO;

          bool oddExponent = xExponent & 0x1 == 0;
          xExponent = xExponent + 16383 >> 1;

          if (oddExponent) {
            if (xSignifier >= 0x10000000000000000000000000000)
              xSignifier <<= 113;
            else {
              uint256 msb = mostSignificantBit (xSignifier);
              uint256 shift = (226 - msb) & 0xFE;
              xSignifier <<= shift;
              xExponent -= shift - 112 >> 1;
            }
          } else {
            if (xSignifier >= 0x10000000000000000000000000000)
              xSignifier <<= 112;
            else {
              uint256 msb = mostSignificantBit (xSignifier);
              uint256 shift = (225 - msb) & 0xFE;
              xSignifier <<= shift;
              xExponent -= shift - 112 >> 1;
            }
          }

          uint256 r = 0x10000000000000000000000000000;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1;
          r = (r + xSignifier / r) >> 1; // Seven iterations should be enough
          uint256 r1 = xSignifier / r;
          if (r1 < r) r = r1;

          return bytes16 (uint128 (xExponent << 112 | r & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF));
        }
      }
    }
  }

  /**
   * Calculate binary logarithm of x.  Return NaN on negative x excluding -0.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function log_2 (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      if (uint128 (x) > 0x80000000000000000000000000000000) return NaN;
      else if (x == 0x3FFF0000000000000000000000000000) return POSITIVE_ZERO; 
      else {
        uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
        if (xExponent == 0x7FFF) return x;
        else {
          uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          if (xExponent == 0) xExponent = 1;
          else xSignifier |= 0x10000000000000000000000000000;

          if (xSignifier == 0) return NEGATIVE_INFINITY;

          bool resultNegative;
          uint256 resultExponent = 16495;
          uint256 resultSignifier;

          if (xExponent >= 0x3FFF) {
            resultNegative = false;
            resultSignifier = xExponent - 0x3FFF;
            xSignifier <<= 15;
          } else {
            resultNegative = true;
            if (xSignifier >= 0x10000000000000000000000000000) {
              resultSignifier = 0x3FFE - xExponent;
              xSignifier <<= 15;
            } else {
              uint256 msb = mostSignificantBit (xSignifier);
              resultSignifier = 16493 - msb;
              xSignifier <<= 127 - msb;
            }
          }

          if (xSignifier == 0x80000000000000000000000000000000) {
            if (resultNegative) resultSignifier += 1;
            uint256 shift = 112 - mostSignificantBit (resultSignifier);
            resultSignifier <<= shift;
            resultExponent -= shift;
          } else {
            uint256 bb = resultNegative ? 1 : 0;
            while (resultSignifier < 0x10000000000000000000000000000) {
              resultSignifier <<= 1;
              resultExponent -= 1;
  
              xSignifier *= xSignifier;
              uint256 b = xSignifier >> 255;
              resultSignifier += b ^ bb;
              xSignifier >>= 127 + b;
            }
          }

          return bytes16 (uint128 ((resultNegative ? 0x80000000000000000000000000000000 : 0) |
              resultExponent << 112 | resultSignifier & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF));
        }
      }
    }
  }

  /**
   * Calculate natural logarithm of x.  Return NaN on negative x excluding -0.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function ln (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return mul (log_2 (x), 0x3FFE62E42FEFA39EF35793C7673007E5);
    }
  }

  /**
   * Calculate 2^x.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function pow_2 (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      bool xNegative = uint128 (x) > 0x80000000000000000000000000000000;
      uint256 xExponent = uint128 (x) >> 112 & 0x7FFF;
      uint256 xSignifier = uint128 (x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

      if (xExponent == 0x7FFF && xSignifier != 0) return NaN;
      else if (xExponent > 16397)
        return xNegative ? POSITIVE_ZERO : POSITIVE_INFINITY;
      else if (xExponent < 16255)
        return 0x3FFF0000000000000000000000000000;
      else {
        if (xExponent == 0) xExponent = 1;
        else xSignifier |= 0x10000000000000000000000000000;

        if (xExponent > 16367)
          xSignifier <<= xExponent - 16367;
        else if (xExponent < 16367)
          xSignifier >>= 16367 - xExponent;

        if (xNegative && xSignifier > 0x406E00000000000000000000000000000000)
          return POSITIVE_ZERO;

        if (!xNegative && xSignifier > 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
          return POSITIVE_INFINITY;

        uint256 resultExponent = xSignifier >> 128;
        xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        if (xNegative && xSignifier != 0) {
          xSignifier = ~xSignifier;
          resultExponent += 1;
        }

        uint256 resultSignifier = 0x80000000000000000000000000000000;
        if (xSignifier & 0x80000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;
        if (xSignifier & 0x40000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;
        if (xSignifier & 0x20000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;
        if (xSignifier & 0x10000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10B5586CF9890F6298B92B71842A98363 >> 128;
        if (xSignifier & 0x8000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;
        if (xSignifier & 0x4000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;
        if (xSignifier & 0x2000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;
        if (xSignifier & 0x1000000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;
        if (xSignifier & 0x800000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;
        if (xSignifier & 0x400000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;
        if (xSignifier & 0x200000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;
        if (xSignifier & 0x100000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;
        if (xSignifier & 0x80000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;
        if (xSignifier & 0x40000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;
        if (xSignifier & 0x20000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000162E525EE054754457D5995292026 >> 128;
        if (xSignifier & 0x10000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000B17255775C040618BF4A4ADE83FC >> 128;
        if (xSignifier & 0x8000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;
        if (xSignifier & 0x4000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;
        if (xSignifier & 0x2000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000162E43F4F831060E02D839A9D16D >> 128;
        if (xSignifier & 0x1000000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000B1721BCFC99D9F890EA06911763 >> 128;
        if (xSignifier & 0x800000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;
        if (xSignifier & 0x400000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;
        if (xSignifier & 0x200000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000162E430E5A18F6119E3C02282A5 >> 128;
        if (xSignifier & 0x100000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;
        if (xSignifier & 0x80000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;
        if (xSignifier & 0x40000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000002C5C8601CC6B9E94213C72737A >> 128;
        if (xSignifier & 0x20000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;
        if (xSignifier & 0x10000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;
        if (xSignifier & 0x8000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;
        if (xSignifier & 0x4000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;
        if (xSignifier & 0x2000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;
        if (xSignifier & 0x1000000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000B17217F80F4EF5AADDA45554 >> 128;
        if (xSignifier & 0x800000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;
        if (xSignifier & 0x400000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;
        if (xSignifier & 0x200000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000162E42FEFB2FED257559BDAA >> 128;
        if (xSignifier & 0x100000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;
        if (xSignifier & 0x80000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;
        if (xSignifier & 0x40000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;
        if (xSignifier & 0x20000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000162E42FEFA494F1478FDE05 >> 128;
        if (xSignifier & 0x10000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000B17217F7D20CF927C8E94C >> 128;
        if (xSignifier & 0x8000000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;
        if (xSignifier & 0x4000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000002C5C85FDF477B662B26945 >> 128;
        if (xSignifier & 0x2000000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000162E42FEFA3AE53369388C >> 128;
        if (xSignifier & 0x1000000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000B17217F7D1D351A389D40 >> 128;
        if (xSignifier & 0x800000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;
        if (xSignifier & 0x400000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
        if (xSignifier & 0x200000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000162E42FEFA39FE95583C2 >> 128;
        if (xSignifier & 0x100000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
        if (xSignifier & 0x80000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
        if (xSignifier & 0x40000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000002C5C85FDF473E242EA38 >> 128;
        if (xSignifier & 0x20000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000162E42FEFA39F02B772C >> 128;
        if (xSignifier & 0x10000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
        if (xSignifier & 0x8000000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
        if (xSignifier & 0x4000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000002C5C85FDF473DEA871F >> 128;
        if (xSignifier & 0x2000000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000162E42FEFA39EF44D91 >> 128;
        if (xSignifier & 0x1000000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000B17217F7D1CF79E949 >> 128;
        if (xSignifier & 0x800000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
        if (xSignifier & 0x400000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
        if (xSignifier & 0x200000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000162E42FEFA39EF366F >> 128;
        if (xSignifier & 0x100000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000B17217F7D1CF79AFA >> 128;
        if (xSignifier & 0x80000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
        if (xSignifier & 0x40000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
        if (xSignifier & 0x20000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000162E42FEFA39EF358 >> 128;
        if (xSignifier & 0x10000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000B17217F7D1CF79AB >> 128;
        if (xSignifier & 0x8000000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000058B90BFBE8E7BCD5 >> 128;
        if (xSignifier & 0x4000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000002C5C85FDF473DE6A >> 128;
        if (xSignifier & 0x2000000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000162E42FEFA39EF34 >> 128;
        if (xSignifier & 0x1000000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000B17217F7D1CF799 >> 128;
        if (xSignifier & 0x800000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000058B90BFBE8E7BCC >> 128;
        if (xSignifier & 0x400000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000002C5C85FDF473DE5 >> 128;
        if (xSignifier & 0x200000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000162E42FEFA39EF2 >> 128;
        if (xSignifier & 0x100000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000B17217F7D1CF78 >> 128;
        if (xSignifier & 0x80000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000058B90BFBE8E7BB >> 128;
        if (xSignifier & 0x40000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000002C5C85FDF473DD >> 128;
        if (xSignifier & 0x20000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000162E42FEFA39EE >> 128;
        if (xSignifier & 0x10000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000B17217F7D1CF6 >> 128;
        if (xSignifier & 0x8000000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000058B90BFBE8E7A >> 128;
        if (xSignifier & 0x4000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000002C5C85FDF473C >> 128;
        if (xSignifier & 0x2000000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000162E42FEFA39D >> 128;
        if (xSignifier & 0x1000000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000B17217F7D1CE >> 128;
        if (xSignifier & 0x800000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000058B90BFBE8E6 >> 128;
        if (xSignifier & 0x400000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000002C5C85FDF472 >> 128;
        if (xSignifier & 0x200000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000162E42FEFA38 >> 128;
        if (xSignifier & 0x100000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000B17217F7D1B >> 128;
        if (xSignifier & 0x80000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000058B90BFBE8D >> 128;
        if (xSignifier & 0x40000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000002C5C85FDF46 >> 128;
        if (xSignifier & 0x20000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000162E42FEFA2 >> 128;
        if (xSignifier & 0x10000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000B17217F7D0 >> 128;
        if (xSignifier & 0x8000000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000058B90BFBE7 >> 128;
        if (xSignifier & 0x4000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000002C5C85FDF3 >> 128;
        if (xSignifier & 0x2000000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000162E42FEF9 >> 128;
        if (xSignifier & 0x1000000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000B17217F7C >> 128;
        if (xSignifier & 0x800000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000058B90BFBD >> 128;
        if (xSignifier & 0x400000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000002C5C85FDE >> 128;
        if (xSignifier & 0x200000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000162E42FEE >> 128;
        if (xSignifier & 0x100000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000B17217F6 >> 128;
        if (xSignifier & 0x80000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000058B90BFA >> 128;
        if (xSignifier & 0x40000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000002C5C85FC >> 128;
        if (xSignifier & 0x20000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000162E42FD >> 128;
        if (xSignifier & 0x10000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000B17217E >> 128;
        if (xSignifier & 0x8000000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000058B90BE >> 128;
        if (xSignifier & 0x4000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000002C5C85E >> 128;
        if (xSignifier & 0x2000000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000162E42E >> 128;
        if (xSignifier & 0x1000000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000B17216 >> 128;
        if (xSignifier & 0x800000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000058B90A >> 128;
        if (xSignifier & 0x400000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000002C5C84 >> 128;
        if (xSignifier & 0x200000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000162E41 >> 128;
        if (xSignifier & 0x100000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000B1720 >> 128;
        if (xSignifier & 0x80000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000058B8F >> 128;
        if (xSignifier & 0x40000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000002C5C7 >> 128;
        if (xSignifier & 0x20000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000162E3 >> 128;
        if (xSignifier & 0x10000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000B171 >> 128;
        if (xSignifier & 0x8000 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000058B8 >> 128;
        if (xSignifier & 0x4000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000002C5B >> 128;
        if (xSignifier & 0x2000 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000162D >> 128;
        if (xSignifier & 0x1000 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000B16 >> 128;
        if (xSignifier & 0x800 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000058A >> 128;
        if (xSignifier & 0x400 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000002C4 >> 128;
        if (xSignifier & 0x200 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000161 >> 128;
        if (xSignifier & 0x100 > 0) resultSignifier = resultSignifier * 0x1000000000000000000000000000000B0 >> 128;
        if (xSignifier & 0x80 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000057 >> 128;
        if (xSignifier & 0x40 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000002B >> 128;
        if (xSignifier & 0x20 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000015 >> 128;
        if (xSignifier & 0x10 > 0) resultSignifier = resultSignifier * 0x10000000000000000000000000000000A >> 128;
        if (xSignifier & 0x8 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000004 >> 128;
        if (xSignifier & 0x4 > 0) resultSignifier = resultSignifier * 0x100000000000000000000000000000001 >> 128;

        if (!xNegative) {
          resultSignifier = resultSignifier >> 15 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          resultExponent += 0x3FFF;
        } else if (resultExponent <= 0x3FFE) {
          resultSignifier = resultSignifier >> 15 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          resultExponent = 0x3FFF - resultExponent;
        } else {
          resultSignifier = resultSignifier >> resultExponent - 16367;
          resultExponent = 0;
        }

        return bytes16 (uint128 (resultExponent << 112 | resultSignifier));
      }
    }
  }

  /**
   * Calculate e^x.
   *
   * @param x quadruple precision number
   * @return quadruple precision number
   */
  function exp (bytes16 x) internal pure returns (bytes16) {
    unchecked {
      return pow_2 (mul (x, 0x3FFF71547652B82FE1777D0FFDA0D23A));
    }
  }

  /**
   * Get index of the most significant non-zero bit in binary representation of
   * x.  Reverts if x is zero.
   *
   * @return index of the most significant non-zero bit in binary representation
   *         of x
   */
  function mostSignificantBit (uint256 x) private pure returns (uint256) {
    unchecked {
      require (x > 0);

      uint256 result = 0;

      if (x >= 0x100000000000000000000000000000000) { x >>= 128; result += 128; }
      if (x >= 0x10000000000000000) { x >>= 64; result += 64; }
      if (x >= 0x100000000) { x >>= 32; result += 32; }
      if (x >= 0x10000) { x >>= 16; result += 16; }
      if (x >= 0x100) { x >>= 8; result += 8; }
      if (x >= 0x10) { x >>= 4; result += 4; }
      if (x >= 0x4) { x >>= 2; result += 2; }
      if (x >= 0x2) result += 1; // No need to shift x anymore

      return result;
    }
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, POSITIVE_ZERO} from "src/types/ABDKMathQuad/Quad.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {eraDay} from "src/libraries/helpers/PoolHelper.sol";

library DailyJumps {
    using SafeCast for int256;
    using SafeCast for uint256;

    struct Info {
        Quad[2] jumps;
        Quad latest;
        int32 atDay;
        bool touched;
    }

    error InvalidDay();

    function now(Info storage self, PoolState storage pool) internal returns (Quad) {
        fresh(self, pool);
        return self.latest;
    }

    function begin(Info storage self, PoolState storage pool, Quad amount) internal {
        if (!(self.touched)) {
            self.atDay = int32(eraDay(pool.era));
            self.latest = amount;
            self.touched = true;
        } else {
            self.latest = (self.latest + amount);
        }
    }

    function liftAt(Info storage self, int256 day) internal view returns (Quad) {
        if (((day <= self.atDay) && self.touched)) revert InvalidDay();

        if (self.touched && (self.atDay >= ((day - 2)))) {
            int256 idx = (day & 1);

            return self.jumps[idx.toUint256()];
        } else {
            return POSITIVE_ZERO;
        }
    }

    function fresh(Info storage self, PoolState storage pool) internal {
        if (self.touched) {
            int256 temp = (eraDay(pool.era) - self.atDay);

            if ((temp == 0)) {
                return;
            } else {
                if ((temp == 1)) {
                    int256 idx = (eraDay(pool.era) & 1);

                    self.latest = (self.latest + self.jumps[idx.toUint256()]);
                    self.jumps[idx.toUint256()] = POSITIVE_ZERO;
                } else {
                    self.latest = (self.latest + (self.jumps[0] + self.jumps[1]));
                    self.jumps[0] = POSITIVE_ZERO;
                    self.jumps[1] = POSITIVE_ZERO;
                }
            }
            self.atDay = int32(eraDay(pool.era));
        }
    }

    function defer(Info storage self, PoolState storage pool, Quad amount, int256 day) internal {
        if ((day <= eraDay(pool.era))) revert InvalidDay();

        if (!(self.touched)) {
            self.atDay = int32(eraDay(pool.era));
            int256 idx = (day & 1);

            self.jumps[idx.toUint256()] = amount;
            self.touched = true;
        } else {
            fresh(self, pool);
            int256 idx = (day & 1);

            self.jumps[idx.toUint256()] = (self.jumps[idx.toUint256()] + amount);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` 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.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {JUMPS, JUMP_NONE} from "src/Constants.sol";

error InvalidJumpIndex();

function jumpBitLength(int256 num) pure returns (int8) {
    int256 lowerBound = 0;

    int256 bitsLeft = num;

    int256 checkBits = (bitsLeft >> 8);

    if ((checkBits != 0)) {
        lowerBound = (lowerBound + 8);
        bitsLeft = checkBits;
    }

    checkBits = (bitsLeft >> 4);
    if ((checkBits != 0)) {
        lowerBound = (lowerBound + 4);
        bitsLeft = checkBits;
    }

    checkBits = (bitsLeft >> 2);
    if ((checkBits != 0)) {
        lowerBound = (lowerBound + 2);
        bitsLeft = (checkBits & 3);
    }

    lowerBound += (bitsLeft < 2) ? bitsLeft : int256(2);
    return lowerBound < JUMPS ? int8(lowerBound) : JUMPS;
}

function maybeJump(int256 fromEra, int256 deadline) pure returns (int8) {
    return jumpBitLength(((deadline - fromEra) - 1));
}

function jumpIndex(int256 fromEra, int256 deadline) pure returns (int8) {
    int8 maybeIndex = maybeJump(fromEra, deadline);

    if ((maybeIndex != JUMPS) && ((maybeIndex <= 1) || ((deadline & ~((-1 << uint256(int256(maybeIndex - 1))))) == 0))) return maybeIndex;
    else return JUMP_NONE;
}

function isJumpDefined(int8 jumpIndex) pure returns (bool) {
    return jumpIndex != JUMP_NONE;
}

function validDeadline(int256 poolEra, int256 deadEra) pure returns (bool) {
    return jumpIndex(poolEra, deadEra) != JUMP_NONE;
}

function deadEra(int256 baseEra, int8 jumpIndex) pure returns (int256) {
    if (jumpIndex < 0 || jumpIndex >= JUMPS) revert InvalidJumpIndex();
    if (jumpIndex == 0) return (baseEra + 1);
    else return (((baseEra >> (uint256(int256(jumpIndex - 1)))) + 2) << uint256(int256(jumpIndex - 1)));
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Quad, fromInt256, POSITIVE_ZERO, POSITIVE_ONE, POSITIVE_TWO, HALF} from "src/types/ABDKMathQuad/Quad.sol";
import {ceil} from "src/types/ABDKMathQuad/Math.sol";
import {UserPay} from "src/libraries/internal/UserPay.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";
import {TUBS, JUMPS, WORD_SIZE, Z, I} from "src/Constants.sol";
import {DailyJumps} from "src/libraries/internal/DailyJumps.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {DeadlineFlag} from "src/libraries/internal/DeadlineFlag.sol";
import {JumpyAnchorFaber, Anchor, AnchorSet} from "src/libraries/internal/JumpyAnchorFaber.sol";
import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {BoxcarTubFrame} from "src/libraries/internal/BoxcarTubFrame.sol";
import {GrowthSplitFrame} from "src/libraries/internal/GrowthSplitFrame.sol";
import {eraDate, subSplits, sqrtStrike, logBin, BINS, tubLowSqrt, tubLowTick} from "src/libraries/helpers/PoolHelper.sol";
import {deadEra} from "src/libraries/helpers/DeadlineHelper.sol";
import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {GapStagedFrame} from "src/libraries/internal/GapStagedFrame.sol";
import {JumpyFallback} from "src/libraries/internal/JumpyFallback.sol";

import {EachPayoff} from "src/libraries/internal/EachPayoff.sol";
import {FloatBits, SparseFloat, QuadPacker} from "src/libraries/internal/SparseFloat.sol";
import {Capper} from "src/libraries/internal/Capper.sol";
import {LAMBDA} from "src/Constants.sol";
import {LP} from "./LP.sol";

library LPShed {
    using DeadlineFlag for DeadlineFlag.Info;
    using BoxcarTubFrame for BoxcarTubFrame.Info;
    using JumpyAnchorFaber for JumpyAnchorFaber.Info;
    using GrowthSplitFrame for GrowthSplitFrame.Info;
    using GapStagedFrame for GapStagedFrame.Info;
    using JumpyFallback for JumpyFallback.Info;
    using AnchorSet for AnchorSet.Info;
    using Capper for Capper.Info;
    using SafeCast for uint256;
    using SafeCast for int256;
    using FloatBits for Quad;

    struct ShedLocalVars {
        Quad drainDate;
        int128 oldLower0;
        int128 oldUpper0;
        int128 oldLower1;
        int128 oldUpper1;
        int128 exponent;
        int256 lowerTick;
        bytes32 tLZ1;
        FloatBits.Info tLZ3;
        int128 newLower0;
        bytes32 tLI1;
        FloatBits.Info tLI3;
        int128 newLower1;
        int256 upperTick;
        bytes32 TUZ1;
        FloatBits.Info TUZ3;
        int128 newUpper0;
        bytes32 TUI1;
        FloatBits.Info TUI3;
        int128 newUpper1;
        Quad lowerSqrtPrice;
        Quad upperSqrtPrice;
        Quad ratio0;
        Quad ratio1;
        Quad reflator;
        Quad res1;
        Quad res2;
    }

    function shed(LP.Info storage self, PoolState storage pool, bool flush) public returns (Quad, Quad) {
        if (self.stage == LP.Stage.Exit) {
            ShedLocalVars memory vars;

            vars.drainDate = self.drainDate;
            vars.oldLower0 = self.lower0;
            vars.oldUpper0 = self.upper0;
            vars.oldLower1 = self.lower1;
            vars.oldUpper1 = self.upper1;

            int128 exponent = int128(ceil((vars.drainDate.neg() + HALF))) - int128(WORD_SIZE);

            vars.lowerTick = tubLowTick(self.startTub);
            vars.tLZ1 = keccak256(abi.encode(vars.lowerTick, Z));

            Capper.Info storage tLZ2 = pool.capper[vars.tLZ1];
            vars.tLZ3 = tLZ2.read(pool, vars.lowerTick, Z, exponent);
            vars.newLower0 = vars.tLZ3.significand;

            if (self.startTub > 0) {
                vars.tLI1 = keccak256(abi.encode(vars.lowerTick, I));
                Capper.Info storage tLI2 = pool.capper[vars.tLI1];
                vars.tLI3 = tLI2.read(pool, vars.lowerTick, I, exponent);
                vars.newLower1 = vars.tLI3.significand;
            } else {
                vars.newLower1 = ~pool.deflator.truncate(exponent);
            }

            vars.upperTick = tubLowTick(self.stopTub);

            if (self.stopTub < TUBS) {
                vars.TUZ1 = keccak256(abi.encode(vars.upperTick, Z));
                Capper.Info storage TUZ2 = pool.capper[vars.TUZ1];
                vars.TUZ3 = TUZ2.read(pool, vars.upperTick, Z, exponent);
                vars.newUpper0 = vars.TUZ3.significand;
            } else {
                vars.newUpper0 = ~pool.deflator.truncate(exponent);
            }

            vars.TUI1 = keccak256(abi.encode(vars.upperTick, I));
            Capper.Info storage TUI2 = pool.capper[vars.TUI1];
            vars.TUI3 = TUI2.read(pool, vars.upperTick, I, exponent);
            vars.newUpper1 = vars.TUI3.significand;

            if (flush) {
                self.stage = LP.Stage.Exit;
                self.drainDate = vars.drainDate;
                self.lower0 = vars.newLower0;
                self.upper0 = vars.newUpper0;
                self.lower1 = vars.newLower1;
                self.upper1 = vars.newUpper1;
            }

            vars.lowerSqrtPrice = tubLowSqrt(self.startTub);
            vars.upperSqrtPrice = tubLowSqrt(self.stopTub);

            unchecked {
                vars.ratio0 = (
                    (QuadPacker.repack(FloatBits._apply(exponent, vars.newLower0 - vars.oldLower0)) / vars.lowerSqrtPrice)
                        - (QuadPacker.repack(FloatBits._apply(exponent, vars.newUpper0 - vars.oldUpper0)) / vars.upperSqrtPrice)
                );
                vars.ratio1 = (
                    (QuadPacker.repack(FloatBits._apply(exponent, vars.newUpper1 - vars.oldUpper1)) * vars.upperSqrtPrice)
                        - (QuadPacker.repack(FloatBits._apply(exponent, vars.newLower1 - vars.oldLower1)) * vars.lowerSqrtPrice)
                );
            }

            vars.reflator = (((LAMBDA * vars.drainDate)).exp() * self.liquidity);
            vars.res1 = (vars.ratio0 * vars.reflator);
            vars.res2 = (vars.ratio1 * vars.reflator);
            return (vars.res1.max(POSITIVE_ZERO), vars.res2.max(POSITIVE_ZERO));
        } else {
            return (POSITIVE_ZERO, POSITIVE_ZERO);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {OptInt256} from "src/types/Optional/OptInt256.sol";
import {Quad, POSITIVE_ZERO, POSITIVE_TWO} from "src/types/ABDKMathQuad/Quad.sol";
import {MIN_SPLITS, MAX_SPLITS} from "src/Constants.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {DropsGroup, DeadlineSet} from "src/libraries/internal/DeadlineJumps.sol";
import {PoolState} from "src/interfaces/IInfinityPoolState.sol";

library EraFaberTotals {
    using DeadlineSet for DeadlineSet.Info;
    using DropsGroup for DeadlineSet.Info;
    using SafeCast for int256;
    using SafeCast for uint256;

    struct Info {
        DeadlineSet.Info[2 << MIN_SPLITS] large;
        DeadlineSet.Info[1 << MAX_SPLITS] small;
        bool below;
    }

    error InvalidSumTotalArguments();
    error InvalidSumRangeArguments();
    error InvalidSubLeftSumArguments();

    function init(Info storage self, bool below) internal {
        self.below = below;
    }

    function dropSum(Info storage self, PoolState storage pool, int256 end, int256 deadEra) internal view returns (Quad) {
        int256 t1;
        int256 t2;
        if (self.below) {
            t1 = 0;
            t2 = end;
        } else {
            t1 = end;
            t2 = (1 << int256(pool.splits).toUint256()).toInt256();
        }

        return sumRangeDropReader(self, pool.splits, t1, t2, deadEra);
    }

    function smallAtDropReader(Info storage self, int256 node, int256 deadEra) internal view returns (Quad) {
        DeadlineSet.Info storage temp = self.small[node.toUint256()];
        return temp.dropAt(deadEra);
    }

    function sumTotalDropReader(Info storage self, int256 scale, int256 startIdx, int256 stopIdx, int256 deadEra) internal view returns (Quad) {
        if (!(((scale <= MIN_SPLITS.toInt256() && ((0 <= startIdx)) && (startIdx <= stopIdx)) && (stopIdx <= (1 << scale.toUint256()).toInt256())))) {
            revert InvalidSumTotalArguments();
        }

        Quad total = POSITIVE_ZERO;

        int256 start = ((1 << scale.toUint256()).toInt256() + startIdx);

        int256 stop = ((1 << scale.toUint256()).toInt256() + stopIdx);

        while ((start != stop)) {
            if (((start & 1) != 0)) {
                total = (total + largeAtDropReader(self, start, deadEra));
                start = (start + 1);
            }

            if (((stop & 1) != 0)) {
                stop = (stop - 1);
                total = (total + largeAtDropReader(self, stop, deadEra));
            }

            start = (start >> 1);
            stop = (stop >> 1);
        }

        return total;
    }

    function sumRangeDropReader(Info storage self, int256 scale, int256 startIdx, int256 stopIdx, int256 deadEra) internal view returns (Quad) {
        if ((scale <= MIN_SPLITS.toInt256())) {
            return sumTotalDropReader(self, scale, startIdx, stopIdx, deadEra);
        } else {
            if (!((((0 <= startIdx) && (startIdx <= stopIdx)) && (stopIdx <= (1 << scale.toUint256()).toInt256())))) {
                revert InvalidSumRangeArguments();
            }

            Quad coarse = sumTotalDropReader(
                self,
                MIN_SPLITS.toInt256(),
                (startIdx >> (scale - MIN_SPLITS.toInt256()).toUint256()),
                (stopIdx >> (scale - MIN_SPLITS.toInt256()).toUint256()),
                deadEra
            );

            return ((coarse - subLeftSumDropReader(self, scale, startIdx, deadEra)) + subLeftSumDropReader(self, scale, stopIdx, deadEra));
        }
    }

    function subLeftSumDropReader(Info storage self, int256 scale, int256 stopIdx, int256 deadEra) internal view returns (Quad) {
        if (((stopIdx % (1 << (scale - MIN_SPLITS.toInt256()).toUint256()).toInt256()) == 0)) {
            return POSITIVE_ZERO;
        } else {
            if (!(scale >= MIN_SPLITS.toInt256() && (((stopIdx & (-1 << scale.toUint256())) == 0)))) revert InvalidSubLeftSumArguments();

            Quad subArea = largeAtDropReader(self, ((1 << MIN_SPLITS).toInt256() + (stopIdx >> (scale - MIN_SPLITS.toInt256()).toUint256())), deadEra);

            Quad total = POSITIVE_ZERO;

            for (int256 depth = MIN_SPLITS.toInt256(); (depth < scale); depth = (depth + 1)) {
                Quad nextCoef = smallAtDropReader(self, ((1 << depth.toUint256()).toInt256() + (stopIdx >> (scale - depth).toUint256())), deadEra);

                bool rightChild = ((stopIdx & (1 << ((scale - depth) - 1).toUint256()).toInt256()) != 0);

                if (rightChild) total = (total + (subArea + nextCoef));

                subArea = (rightChild ? (subArea - nextCoef) : (subArea + nextCoef));
                subArea = (subArea / POSITIVE_TWO);
            }

            return (total / POSITIVE_TWO);
        }
    }

    function largeAtDropReader(Info storage self, int256 node, int256 deadEra) internal view returns (Quad) {
        DeadlineSet.Info storage temp = self.large[node.toUint256()];

        return temp.dropAt(deadEra);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

Settings
{
  "remappings": [
    "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
    "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
    "abdk-libraries-solidity/=lib/abdk-libraries-solidity/",
    "aerodrome/=lib/aerodrome/contracts/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "gsn/=lib/aerodrome/contracts/lib/",
    "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
    "solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/",
    "uniswap/=lib/uniswap/",
    "v3-core/=lib/aerodrome/contracts/lib/v3-core/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 300
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "shanghai",
  "viaIR": false,
  "libraries": {
    "src/PoolReader.sol": {
      "PoolReader": "0xe1e9689435f5b969E92aF4CdCD0186cAFF4D2EBE"
    },
    "src/libraries/external/Advance.sol": {
      "Advance": "0x578e7f66A2aEe4802DcCA1f5cD3136ECe65835b7"
    },
    "src/libraries/external/CollectNetted.sol": {
      "CollectNetted": "0xD696eC7Aca2DF41Ce27bB1a02Af5a77949118283"
    },
    "src/libraries/external/LP.sol": {
      "LP": "0x7Df68ee8a98D9B17c21d1dF5520f6f803670c975"
    },
    "src/libraries/external/LPShed.sol": {
      "LPShed": "0xCEeA17E6b99c2132536f38Dd79D0De0F1E7FdaD0"
    },
    "src/libraries/external/NewLoan.sol": {
      "NewLoan": "0xFFb501652135DE45F002EcdF32278DF950A6b03F"
    },
    "src/libraries/external/PoolConstructor.sol": {
      "PoolConstructor": "0x993b163bd3425435AC288C0F13309Caf3B14B064"
    },
    "src/libraries/external/Spot.sol": {
      "Spot": "0xd78a7d3b61D1F84B51FB703a45299CF855da5f8E"
    },
    "src/libraries/external/SubAdvance.sol": {
      "SubAdvance": "0xE4eeEb46402fEC7b3AEb158eb47a60e3aa66A31F"
    },
    "src/libraries/external/SubAdvanceHelpers.sol": {
      "SubAdvanceHelpers": "0x9114B540ed43695186F62082538ecF26a4395137"
    },
    "src/libraries/external/Swapper.sol": {
      "Swapper": "0x3115F1FB05748F0df61400e31F06D10dF2aC05e5"
    },
    "src/libraries/external/SwapperInternal.sol": {
      "SwapperInternal": "0x2C20c9802601a83C2E745A317b6443451D707610"
    },
    "src/libraries/helpers/PoolHelper.sol": {
      "PoolHelper": "0xfd5CB92071689dd9D07d27513B277be89418BC33"
    },
    "src/libraries/internal/DeadlineJumps.sol": {
      "PiecewiseGrowthNew": "0x1346Ffe0763a8d32c36a19642bD3C9C479B3BB0d"
    },
    "src/libraries/internal/EachPayoff.sol": {
      "EachPayoff": "0xe545B588337E15AB74E4aAAc693BCf8ac6f74AEE"
    },
    "src/libraries/internal/EraBoxcarMidSum.sol": {
      "EraBoxcarMidSum": "0xd4AE07C1C0700C33b7f8eeD19DeF3B7A7814e7D4"
    },
    "src/libraries/internal/GapStagedFrame.sol": {
      "GapStagedFrame": "0xeDAA7578Ed66a3C23f79689CD52283205E375d90"
    },
    "src/libraries/internal/GrowthSplitFrame.sol": {
      "GrowthSplitFrame": "0x6E1BB4E4669c8e70c925B2E63427dEcC67c46272"
    },
    "src/libraries/internal/JumpyAnchorFaber.sol": {
      "JumpyAnchorFaber": "0x8b24FFDd20C9820e30025502076E1705d206d116"
    },
    "src/libraries/internal/JumpyFallback.sol": {
      "JumpyFallback": "0x94B9720155fB2615702e9BA25e702b321032cd74"
    },
    "src/libraries/internal/Payoff.sol": {
      "AnyPayoff": "0x2146a8DDF50473E96d921c566c287c460bBC833E"
    },
    "src/libraries/internal/UserPay.sol": {
      "UserPay": "0x250Cc58cE7121709437FD45463b79FA101D2DAd5"
    },
    "src/periphery/libraries/external/PeripheryActions.sol": {
      "PeripheryActions": "0xb6927e2c82fA843750DF7CacB01d196F6988a7B5"
    },
    "src/periphery/libraries/external/VaultActions.sol": {
      "VaultActions": "0x86B75aa878f108d6A670AB12FF9C154Fe4784CE9"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"PoolDoesNotExist","type":"error"},{"inputs":[{"internalType":"contract IInfinityPoolFactory","name":"factory","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"int256","name":"splits","type":"int256"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"components":[{"internalType":"Quad","name":"shove","type":"bytes16"},{"internalType":"bool","name":"ofToken","type":"bool"},{"internalType":"OptQuad","name":"limitPrice","type":"bytes16"},{"internalType":"OptQuad","name":"remainingAmount","type":"bytes16"}],"internalType":"struct Spot.SpotSwapParams","name":"spotSwapParams","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IInfinityPoolFactory","name":"factory","type":"address"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"int256","name":"splits","type":"int256"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"components":[{"internalType":"Quad","name":"shove","type":"bytes16"},{"internalType":"bool","name":"ofToken","type":"bool"},{"internalType":"OptQuad","name":"limitPrice","type":"bytes16"},{"internalType":"OptQuad","name":"remainingAmount","type":"bytes16"}],"internalType":"struct Spot.SpotSwapParams","name":"spotSwapParams","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0","type":"int256"},{"internalType":"int256","name":"amount1","type":"int256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"infinityPoolPaymentCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IInfinityPool","name":"pool","type":"address"},{"components":[{"internalType":"Quad","name":"shove","type":"bytes16"},{"internalType":"bool","name":"ofToken","type":"bool"},{"internalType":"OptQuad","name":"limitPrice","type":"bytes16"},{"internalType":"OptQuad","name":"remainingAmount","type":"bytes16"}],"internalType":"struct Spot.SpotSwapParams","name":"spotSwapParams","type":"tuple"}],"name":"quoteSwap","outputs":[{"internalType":"int256","name":"amount0","type":"int256"},{"internalType":"int256","name":"amount1","type":"int256"}],"stateMutability":"nonpayable","type":"function"}]

608060405234801561000f575f80fd5b506110408061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80635950e5631461004e5780639e09efb514610078578063e8a27b731461008b578063eaa6d5bf146100a1575b5f80fd5b61006161005c366004610ba3565b6100c9565b60405161006f929190610c04565b60405180910390f35b610061610086366004610ba3565b61031c565b61009f610099366004610c47565b50505050565b005b6100b46100af366004610ce6565b610536565b6040805192835260208301919091520161006f565b604080516080810182525f8082526020820181905291810182905260608101829052604051632f52289560e11b81526001600160a01b0388811660048301528781166024830152604482018790525f91908a1690635ea4512a90606401602060405180830381865afa158015610141573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101659190610d95565b90506001600160a01b03811661018e576040516302721e1f60e61b815260040160405180910390fd5b5f816001600160a01b03166360246c886040518163ffffffff1660e01b8152600401606060405180830381865afa1580156101cb573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101ef9190610db0565b505090505f6102678a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610233573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102579190610df0565b61026290600a610f04565b610613565b905060405180608001604052806102866102808a610613565b84610623565b6001600160801b03191681526001600160a01b03848116908d16141560208201526f7fff000000000000000000000000000160801b6040820181905260609091015293505f806102d68587610536565b915091508b6001600160a01b0316846001600160a01b031603610301576102fc81610f12565b61030a565b61030a82610f12565b96505050505050965096945050505050565b604080516080810182525f8082526020820181905291810182905260608101829052604051632f52289560e11b81526001600160a01b0388811660048301528781166024830152604482018790525f91908a1690635ea4512a90606401602060405180830381865afa158015610394573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b89190610d95565b90506001600160a01b0381166103e1576040516302721e1f60e61b815260040160405180910390fd5b5f816001600160a01b03166360246c886040518163ffffffff1660e01b8152600401606060405180830381865afa15801561041e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104429190610db0565b505090505f610486896001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610233573d5f803e3d5ffd5b905060405180608001604052806104b16102806104a28b610613565b6001600160801b03191661064f565b6001600160801b03191681526001600160a01b03848116908c16141560208201526f7fff000000000000000000000000000160801b6040820181905260609091015293505f806105018587610536565b915091508a6001600160a01b0316846001600160a01b031603610524578061030a565b509b939a509298505050505050505050565b5f80836001600160a01b031663e9664e3a84306040518363ffffffff1660e01b8152600401610566929190610f2c565b60408051808303815f875af192505050801561059f575060408051601f3d908101601f1916820190925261059c91810190610f9a565b60015b610609573d8080156105cc576040519150601f19603f3d011682016040523d82523d5f602084013e6105d1565b606091505b506105ea8160048084516105e59190610fbc565b610666565b8060200190518101906105fd9190610f9a565b909350915061060c9050565b50505b9250929050565b5f61061d826107c1565b92915050565b5f6106486106456001600160801b031985166001600160801b03198516610829565b90565b9392505050565b5f6001600160801b03198216600160ff1b1861061d565b60608161067481601f610fcf565b10156106b85760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064015b60405180910390fd5b826106c38382610fcf565b10156107025760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064016106af565b61070c8284610fcf565b845110156107505760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b60448201526064016106af565b60608215801561076e5760405191505f8252602082016040526107b8565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156107a757805183526020928301920161078f565b5050858452601f01601f1916604052505b50949350505050565b5f815f036107d057505f919050565b815f6107db82610aec565b905060708110156107f4578060700382901b9150610807565b6070811115610807576070810382901c91505b613fff0160701b6001600160701b03919091161760801b92915050565b919050565b5f617fff60f084811c8216919084901c81169082900361086d5780617fff0361085c575061ffff60ef1b915061061d9050565b505050600160ff1b8116821861061d565b80617fff036108b0576dffffffffffffffffffffffffffff60801b84161561089f575061ffff60ef1b915061061d9050565b505050808218600160ff1b1661061d565b600160801b600160ff1b0384165f036108fe57600160801b600160ff1b0385165f036108e6575061ffff60ef1b915061061d9050565b505050808218600160ff1b16617fff60f01b1761061d565b6001600160701b03608085901c165f82900361091d5760019150610924565b600160701b175b6001600160701b03608087901c165f849003610965578015610960575f61094a82610aec565b6001955060e20393840160711901939190911b90505b61096f565b600160701b1760721b5b81818161097e5761097e610fe2565b049050805f036109ac57600160ff1b8787181661099b575f6109a1565b600160ff1b5b94505050505061061d565b6001606c1b8110156109c0576109c0610ff6565b5f600160731b8210156109fe57600160721b8210156109f357600160711b8210156109ec5760706109f6565b60716109f6565b60725b60ff16610a07565b610a0782610aec565b905083614071018186011115610a2457617fff94505f9150610ab4565b83818601613ffc011015610a3d575f94505f9150610ab4565b83818601613f8c011015610a89578385613ffc011115610a68578385613ffc010382901b9150610a81565b8385613ffc011015610a8157613ffc8585030382901c91505b5f9450610ab4565b6070811115610a9c576070810382901c91505b6001600160701b038216915083818601613f8d010394505b81607086901b888a186001607f1b60801b1660801c6fffffffffffffffffffffffffffffffff16171760801b9550505050505061061d565b5f808211610af8575f80fd5b5f600160801b8310610b0c57608092831c92015b680100000000000000008310610b2457604092831c92015b6401000000008310610b3857602092831c92015b620100008310610b4a57601092831c92015b6101008310610b5b57600892831c92015b60108310610b6b57600492831c92015b60048310610b7b57600292831c92015b6002831061061d5760010192915050565b6001600160a01b0381168114610ba0575f80fd5b50565b5f805f805f8060c08789031215610bb8575f80fd5b8635610bc381610b8c565b95506020870135610bd381610b8c565b94506040870135610be381610b8c565b959894975094956060810135955060808101359460a0909101359350915050565b82815260a08101610648602083018480516001600160801b0319908116835260208083015115159084015260408083015182169084015260609182015116910152565b5f805f8060608587031215610c5a575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610c7f575f80fd5b818701915087601f830112610c92575f80fd5b813581811115610ca0575f80fd5b886020828501011115610cb1575f80fd5b95989497505060200194505050565b80356001600160801b031981168114610824575f80fd5b80358015158114610824575f80fd5b5f8082840360a0811215610cf8575f80fd5b8335610d0381610b8c565b92506080601f1982011215610d16575f80fd5b506040516080810181811067ffffffffffffffff82111715610d4657634e487b7160e01b5f52604160045260245ffd5b604052610d5560208501610cc0565b8152610d6360408501610cd7565b6020820152610d7460608501610cc0565b6040820152610d8560808501610cc0565b6060820152809150509250929050565b5f60208284031215610da5575f80fd5b815161064881610b8c565b5f805f60608486031215610dc2575f80fd5b8351610dcd81610b8c565b6020850151909350610dde81610b8c565b80925050604084015190509250925092565b5f60208284031215610e00575f80fd5b815160ff81168114610648575f80fd5b634e487b7160e01b5f52601160045260245ffd5b600181815b80851115610e5e57815f1904821115610e4457610e44610e10565b80851615610e5157918102915b93841c9390800290610e29565b509250929050565b5f82610e745750600161061d565b81610e8057505f61061d565b8160018114610e965760028114610ea057610ebc565b600191505061061d565b60ff841115610eb157610eb1610e10565b50506001821b61061d565b5060208310610133831016604e8410600b8410161715610edf575081810a61061d565b610ee98383610e24565b805f1904821115610efc57610efc610e10565b029392505050565b5f61064860ff841683610e66565b5f600160ff1b8201610f2657610f26610e10565b505f0390565b610f65818480516001600160801b0319908116835260208083015115159084015260408083015182169084015260609182015116910152565b6001600160a01b0391909116608082015260c060a082018190526003908201526230783160e81b60e082015261010001919050565b5f8060408385031215610fab575f80fd5b505080516020909101519092909150565b8181038181111561061d5761061d610e10565b8082018082111561061d5761061d610e10565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52600160045260245ffdfea2646970667358221220f64e208b452c2c9b1326a3115e545d7833d6e63ff173b7b2c70247cdc8d7e87d64736f6c63430008140033

Deployed Bytecode

0x608060405234801561000f575f80fd5b506004361061004a575f3560e01c80635950e5631461004e5780639e09efb514610078578063e8a27b731461008b578063eaa6d5bf146100a1575b5f80fd5b61006161005c366004610ba3565b6100c9565b60405161006f929190610c04565b60405180910390f35b610061610086366004610ba3565b61031c565b61009f610099366004610c47565b50505050565b005b6100b46100af366004610ce6565b610536565b6040805192835260208301919091520161006f565b604080516080810182525f8082526020820181905291810182905260608101829052604051632f52289560e11b81526001600160a01b0388811660048301528781166024830152604482018790525f91908a1690635ea4512a90606401602060405180830381865afa158015610141573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101659190610d95565b90506001600160a01b03811661018e576040516302721e1f60e61b815260040160405180910390fd5b5f816001600160a01b03166360246c886040518163ffffffff1660e01b8152600401606060405180830381865afa1580156101cb573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101ef9190610db0565b505090505f6102678a6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610233573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102579190610df0565b61026290600a610f04565b610613565b905060405180608001604052806102866102808a610613565b84610623565b6001600160801b03191681526001600160a01b03848116908d16141560208201526f7fff000000000000000000000000000160801b6040820181905260609091015293505f806102d68587610536565b915091508b6001600160a01b0316846001600160a01b031603610301576102fc81610f12565b61030a565b61030a82610f12565b96505050505050965096945050505050565b604080516080810182525f8082526020820181905291810182905260608101829052604051632f52289560e11b81526001600160a01b0388811660048301528781166024830152604482018790525f91908a1690635ea4512a90606401602060405180830381865afa158015610394573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b89190610d95565b90506001600160a01b0381166103e1576040516302721e1f60e61b815260040160405180910390fd5b5f816001600160a01b03166360246c886040518163ffffffff1660e01b8152600401606060405180830381865afa15801561041e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104429190610db0565b505090505f610486896001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610233573d5f803e3d5ffd5b905060405180608001604052806104b16102806104a28b610613565b6001600160801b03191661064f565b6001600160801b03191681526001600160a01b03848116908c16141560208201526f7fff000000000000000000000000000160801b6040820181905260609091015293505f806105018587610536565b915091508a6001600160a01b0316846001600160a01b031603610524578061030a565b509b939a509298505050505050505050565b5f80836001600160a01b031663e9664e3a84306040518363ffffffff1660e01b8152600401610566929190610f2c565b60408051808303815f875af192505050801561059f575060408051601f3d908101601f1916820190925261059c91810190610f9a565b60015b610609573d8080156105cc576040519150601f19603f3d011682016040523d82523d5f602084013e6105d1565b606091505b506105ea8160048084516105e59190610fbc565b610666565b8060200190518101906105fd9190610f9a565b909350915061060c9050565b50505b9250929050565b5f61061d826107c1565b92915050565b5f6106486106456001600160801b031985166001600160801b03198516610829565b90565b9392505050565b5f6001600160801b03198216600160ff1b1861061d565b60608161067481601f610fcf565b10156106b85760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064015b60405180910390fd5b826106c38382610fcf565b10156107025760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064016106af565b61070c8284610fcf565b845110156107505760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b60448201526064016106af565b60608215801561076e5760405191505f8252602082016040526107b8565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156107a757805183526020928301920161078f565b5050858452601f01601f1916604052505b50949350505050565b5f815f036107d057505f919050565b815f6107db82610aec565b905060708110156107f4578060700382901b9150610807565b6070811115610807576070810382901c91505b613fff0160701b6001600160701b03919091161760801b92915050565b919050565b5f617fff60f084811c8216919084901c81169082900361086d5780617fff0361085c575061ffff60ef1b915061061d9050565b505050600160ff1b8116821861061d565b80617fff036108b0576dffffffffffffffffffffffffffff60801b84161561089f575061ffff60ef1b915061061d9050565b505050808218600160ff1b1661061d565b600160801b600160ff1b0384165f036108fe57600160801b600160ff1b0385165f036108e6575061ffff60ef1b915061061d9050565b505050808218600160ff1b16617fff60f01b1761061d565b6001600160701b03608085901c165f82900361091d5760019150610924565b600160701b175b6001600160701b03608087901c165f849003610965578015610960575f61094a82610aec565b6001955060e20393840160711901939190911b90505b61096f565b600160701b1760721b5b81818161097e5761097e610fe2565b049050805f036109ac57600160ff1b8787181661099b575f6109a1565b600160ff1b5b94505050505061061d565b6001606c1b8110156109c0576109c0610ff6565b5f600160731b8210156109fe57600160721b8210156109f357600160711b8210156109ec5760706109f6565b60716109f6565b60725b60ff16610a07565b610a0782610aec565b905083614071018186011115610a2457617fff94505f9150610ab4565b83818601613ffc011015610a3d575f94505f9150610ab4565b83818601613f8c011015610a89578385613ffc011115610a68578385613ffc010382901b9150610a81565b8385613ffc011015610a8157613ffc8585030382901c91505b5f9450610ab4565b6070811115610a9c576070810382901c91505b6001600160701b038216915083818601613f8d010394505b81607086901b888a186001607f1b60801b1660801c6fffffffffffffffffffffffffffffffff16171760801b9550505050505061061d565b5f808211610af8575f80fd5b5f600160801b8310610b0c57608092831c92015b680100000000000000008310610b2457604092831c92015b6401000000008310610b3857602092831c92015b620100008310610b4a57601092831c92015b6101008310610b5b57600892831c92015b60108310610b6b57600492831c92015b60048310610b7b57600292831c92015b6002831061061d5760010192915050565b6001600160a01b0381168114610ba0575f80fd5b50565b5f805f805f8060c08789031215610bb8575f80fd5b8635610bc381610b8c565b95506020870135610bd381610b8c565b94506040870135610be381610b8c565b959894975094956060810135955060808101359460a0909101359350915050565b82815260a08101610648602083018480516001600160801b0319908116835260208083015115159084015260408083015182169084015260609182015116910152565b5f805f8060608587031215610c5a575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610c7f575f80fd5b818701915087601f830112610c92575f80fd5b813581811115610ca0575f80fd5b886020828501011115610cb1575f80fd5b95989497505060200194505050565b80356001600160801b031981168114610824575f80fd5b80358015158114610824575f80fd5b5f8082840360a0811215610cf8575f80fd5b8335610d0381610b8c565b92506080601f1982011215610d16575f80fd5b506040516080810181811067ffffffffffffffff82111715610d4657634e487b7160e01b5f52604160045260245ffd5b604052610d5560208501610cc0565b8152610d6360408501610cd7565b6020820152610d7460608501610cc0565b6040820152610d8560808501610cc0565b6060820152809150509250929050565b5f60208284031215610da5575f80fd5b815161064881610b8c565b5f805f60608486031215610dc2575f80fd5b8351610dcd81610b8c565b6020850151909350610dde81610b8c565b80925050604084015190509250925092565b5f60208284031215610e00575f80fd5b815160ff81168114610648575f80fd5b634e487b7160e01b5f52601160045260245ffd5b600181815b80851115610e5e57815f1904821115610e4457610e44610e10565b80851615610e5157918102915b93841c9390800290610e29565b509250929050565b5f82610e745750600161061d565b81610e8057505f61061d565b8160018114610e965760028114610ea057610ebc565b600191505061061d565b60ff841115610eb157610eb1610e10565b50506001821b61061d565b5060208310610133831016604e8410600b8410161715610edf575081810a61061d565b610ee98383610e24565b805f1904821115610efc57610efc610e10565b029392505050565b5f61064860ff841683610e66565b5f600160ff1b8201610f2657610f26610e10565b505f0390565b610f65818480516001600160801b0319908116835260208083015115159084015260408083015182169084015260609182015116910152565b6001600160a01b0391909116608082015260c060a082018190526003908201526230783160e81b60e082015261010001919050565b5f8060408385031215610fab575f80fd5b505080516020909101519092909150565b8181038181111561061d5761061d610e10565b8082018082111561061d5761061d610e10565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52600160045260245ffdfea2646970667358221220f64e208b452c2c9b1326a3115e545d7833d6e63ff173b7b2c70247cdc8d7e87d64736f6c63430008140033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

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.