ETH Price: $2,022.82 (+1.93%)

Contract

0xEE84aaC4ea6F6Aeb1B1F6C045A22Ec0399CdFa5D
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Setup244770332026-02-17 14:04:4739 days ago1771337087IN
0xEE84aaC4...399CdFa5D
0 ETH0.000018730.10030969

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Vogue

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {BasketLib} from "./imports/BasketLib.sol";
import {stdMath} from "forge-std/StdMath.sol";
import {Types} from "./imports/Types.sol";
import {VogueCore} from "./VogueCore.sol";
import {Basket} from "./Basket.sol";
import {Aux} from "./Aux.sol";

// import {AuxUni as Aux} from "./L2/AuxUni.sol";
// import {AuxBase as Aux} from "./L2/AuxBase.sol";
// import {AuxArb as Aux} from "./L2/AuxArb.sol";
// import {AuxPoly as Aux} from "./L2/AuxPoly.sol";

contract Vogue is
    Ownable, ReentrancyGuard {
    uint constant RAY = 1e27;
    uint constant WAD = 1e18;
    VogueCore V4; WETH9 WETH;
    bool public token1isETH;
    // range = between ticks
    int24 public UPPER_TICK;
    int24 public LOWER_TICK;
    uint internal lastVogueETH; // for yield delta
    uint public LAST_REPACK;
    // ^ timestamp allows us
    // to measure APY% for
    uint public USD_FEES;
    uint public ETH_FEES;
    Basket QUID; Aux AUX;
    uint public YIELD;

    // V4.POOLED_ETH() = principal + ALL compounded fees (even unclaimed)
    // totalShares = sum of all LP.pooled_eth = principal + claimed fees
    // that got added to positions; gap represents fees that compounded
    // into the pool but haven't been attributed to any depositor yet...
    // swap fees accumulating in the V4 pool aren't added to `POOLED_ETH`
    // until collection via _modifyLiquidity during repack or withdrawal
    uint public totalShares;

    mapping(address => Types.Deposit) public autoManaged;
    mapping(uint => Types.SelfManaged) public selfManaged;
    // ^ key is tokenId of ID++ for that position
    uint internal ID;
    // ^ always grows

    mapping(address => uint[]) public positions;
    // ^ allows several selfManaged positions...
    constructor()
        Ownable(msg.sender) {
    }   fallback() external payable {}

     modifier onlyUs {
        require(msg.sender == address(AUX)
             || msg.sender == address(V4)
             || msg.sender == address(this), "403"); _;
    }

    function setup(address _quid,
        address _aux, address _core) external {
        require(address(AUX) == address(0), "!");
        AUX = Aux(payable(_aux)); V4 = VogueCore(_core);
        QUID = Basket(_quid); renounceOwnership();
        require(QUID.V4() == address(this), "?");
        WETH = WETH9(payable(address(AUX.WETH())));
        WETH.approve(address(AUX), type(uint).max);
        (uint160 sqrtPriceX96,,) = V4.poolStats(0, 0);
        token1isETH = V4.token1isETH();
        (LOWER_TICK,, UPPER_TICK,) = _updateTicks(
                                sqrtPriceX96, 200);
    }

    function _outOfRangeTicks(uint160 currentSqrtPrice,
        int24 width, int24 range, int24 distance) internal
        returns (int24 newLowerTick, int24 newUpperTick) {
        int24 targetTick = TickMath.getTickAtSqrtPrice(
                           currentSqrtPrice) - distance;
        if (distance < 0) { // above the current price
            newLowerTick = _alignTick(targetTick, width);
            newUpperTick = _alignTick(targetTick + range, width);
        } else {
            newUpperTick = _alignTick(targetTick, width);
            newLowerTick = _alignTick(targetTick - range, width);
        }
    }

    /// @notice Create a single-sided liquidity position outside the current price range
    /// @dev Automatically adjusts for token ordering to ensure valid positions
    /// @param amount Amount of tokens to deposit (0 if sending ETH as msg.value)
    /// @param token Token address (address(0) for ETH, or stablecoin address for USD)
    /// @param distance Distance from current price in ticks
    /// positive = subtract (below), negative = add (above)
    /// @param range Width of the position in ticks
    /// @return next The ID of the newly created position
    function outOfRange(uint amount, address token,
        int24 distance, int24 range) public nonReentrant
        payable returns (uint next) { int24 width = int24(10);
        require(range >= 100 && range <= 1000 && range % 50 == 0,
                "Range must be 100-1000 in increments of 50");

        require(distance % 100 == 0 && distance != 0 &&
                distance >= -5000 && distance <= 5000,
                "must be -5000 to 5000 in increments of 100");

        (uint160 currentSqrtPrice,
         int24 currentLowerTick,
         int24 currentUpperTick,) = _repack();
        if (!token1isETH) distance = -distance;

        (int24 newLowerTick,
         int24 newUpperTick) = _outOfRangeTicks(
        currentSqrtPrice, width, range, distance);

        uint128 liquidity;
        if (token == address(0)) { amount = _depositETH(
                                     msg.sender, amount);
            if (token1isETH) {
                require(newLowerTick > currentUpperTick);
                liquidity = LiquidityAmounts.getLiquidityForAmount1(
                             TickMath.getSqrtPriceAtTick(newLowerTick),
                             TickMath.getSqrtPriceAtTick(newUpperTick), amount);
            } else {
                require(newUpperTick < currentLowerTick);
                liquidity = LiquidityAmounts.getLiquidityForAmount0(
                             TickMath.getSqrtPriceAtTick(newLowerTick),
                             TickMath.getSqrtPriceAtTick(newUpperTick), amount);
            }
        } else { amount = AUX.deposit(
            msg.sender, token, amount);
            // Normalize to 6 decimals for USD side
            uint8 decimals = IERC20(token).decimals();
            if (decimals != 6) amount = decimals > 6 ?
                               amount / 10 ** (decimals - 6):
                               amount * 10 ** (6 - decimals);
            if (token1isETH) {
                require(newUpperTick < currentLowerTick);
                // Above current = buy ETH with USD (provide $)
                // Below current = sell ETH for USD (provide ETH)
                liquidity = LiquidityAmounts.getLiquidityForAmount0(
                          TickMath.getSqrtPriceAtTick(newLowerTick),
                          TickMath.getSqrtPriceAtTick(newUpperTick), amount);
            } else {
                require(newLowerTick > currentUpperTick);
                liquidity = LiquidityAmounts.getLiquidityForAmount1(
                          TickMath.getSqrtPriceAtTick(newLowerTick),
                          TickMath.getSqrtPriceAtTick(newUpperTick), amount);
            }
        } Types.SelfManaged memory newPosition = Types.SelfManaged({
              created: block.number, owner: msg.sender,
              lower: newLowerTick, upper: newUpperTick,
              liq: int(uint(liquidity)) }); next = ++ID;

        require(liquidity > 0, "dust");
        selfManaged[next] = newPosition;
        positions[msg.sender].push(next);
        V4.outOfRange(msg.sender, int(uint(liquidity)),
                newLowerTick, newUpperTick, address(0));
    }

    function pendingRewards(address user) public
        view returns (uint ethReward, uint usdReward) {
        Types.Deposit memory LP = autoManaged[user];
        if (LP.pooled_eth == 0) return (0, 0);

        uint ethOwed = FullMath.mulDiv(LP.pooled_eth, ETH_FEES, WAD);
        uint usdOwed = FullMath.mulDiv(LP.pooled_eth, USD_FEES, WAD);

        // Saturating subtraction to prevent underflow
        ethReward = ethOwed > LP.fees_eth ? ethOwed - LP.fees_eth : 0;
        usdReward = usdOwed > LP.fees_usd ? usdOwed - LP.fees_usd : 0;
    }

    // withdrawal by LP of ETH specifically, depositor may
    // not know exactly how much they have accumulated in
    // fees, so it's alright to pass in a huge number...
    function withdraw(uint amount)
        external nonReentrant {
        (uint160 sqrtPriceX96,
         int24 tickLower, int24 tickUpper,) = _repack();
        Types.Deposit storage LP = autoManaged[msg.sender];
        uint pooled_eth = V4.POOLED_ETH();
        uint fees_eth; uint fees_usd;
        if (LP.pooled_eth > 0)
            (fees_eth,
             fees_usd) = pendingRewards(msg.sender);
        if (fees_eth > 0) { // rewards
            LP.pooled_eth += fees_eth;
            totalShares += fees_eth;
        } // Handle USD rewards
        fees_usd += LP.usd_owed;
        if (fees_usd > 0) {
            LP.usd_owed = 0;
            QUID.mint(msg.sender,
            fees_usd, address(QUID), 0);
        }
        // Cap withdrawal at user's total balance
        // (i.e. principal + compounded rewards)
        amount = Math.min(amount, LP.pooled_eth);
        if (amount > 0) { uint sent;
            uint pulled = Math.min(
                amount, pooled_eth);
            if (pulled > 0) {
                // Only pull from V4 pool if position actually exists at these ticks.
                // After a depleted repack, ticks point to an empty position —
                // calling modLP would revert (CannotUpdateEmptyPosition).
                // Skip and let shortfall path (vault excess + arbETH) handle
                (,, uint128 posLiquidity) = V4.poolStats(tickLower, tickUpper);
                if (posLiquidity > 0) {
                    sent = V4.modLP(sqrtPriceX96, pulled, 0,
                            tickLower, tickUpper, msg.sender);
                }
            }
            // Arb flows naturally balance over time...
            // The V4/V3 spread keeps the pool in equilibrium
            // Any temporary imbalance self-corrects before LP withdrawals,
            // arbETH is mainly emergency code that should never execute...
            if (amount > sent) { uint shortfall = amount - sent;
                // FIX: Re-read POOLED_ETH after modLP reduced it
                uint currentPooledETH = V4.POOLED_ETH();
                uint available = AUX.vogueETH();
                uint excess = available > currentPooledETH
                            ? available - currentPooledETH : 0;

                excess = Math.min(
                shortfall, excess);
                if (excess > 0) {
                    excess = _sendETH(excess, msg.sender);
                    sent += excess; shortfall -= excess;
                }
                if (shortfall > 0) { uint arbed = AUX.arbETH(shortfall);
                    if (arbed > 0) { uint arbSent = _sendETH(
                        Math.min(arbed, shortfall), msg.sender);
                        sent += arbSent;
                    }
                } // Only burn shares equal to actual delivered amount
                // unrecovered shortfall is socialized across all LPs
                amount = Math.min(sent, amount);
                LP.pooled_eth -= amount;
                totalShares -= amount;
                // _sendETH / arbETH reduced vogueETH but lastVogueETH
                // is stale-high → next _syncYield sees current < last → skips.
                // Re-sync bookmark so gap yield isn't lost.
                lastVogueETH = AUX.vogueETH();
            } else { // Pool delivered enough
                // (or more) - burn full amount
                LP.pooled_eth -= amount;
                totalShares -= amount;
            }
        } if (LP.pooled_eth == 0) delete autoManaged[msg.sender];
        else {
            LP.fees_eth = FullMath.mulDiv(LP.pooled_eth, ETH_FEES, WAD);
            LP.fees_usd = FullMath.mulDiv(LP.pooled_eth, USD_FEES, WAD);
        }
    }

    // this is for single-sided liquidity (ETH deposit)
    // if you want to deposit dollars, mint with Basket
    function deposit(uint amount)
        external payable nonReentrant {
        uint price = AUX.getTWAP(1800);
        require(price > 0, "TWAP");
        uint deltaETH; uint deltaUSD;
        if (amount == 0 && msg.value == 0) return;

        // _repack MUST run before _depositETH so that _syncYield
        // reads the AAVE balance *before* the new deposit lands.
        // Otherwise the deposit is misattributed as AAVE yield,
        // inflating ETH_FEES and double-counting into pooled_eth.
        Types.Deposit storage LP = autoManaged[msg.sender];
        (uint160 sqrtPriceX96, int24 tickLower,
         int24 tickUpper,) = _repack();

        amount = _depositETH(msg.sender, amount);
        // Advance the yield bookmark past the deposit so the
        // *next* _syncYield doesn't mistake it for yield either.
        lastVogueETH += amount;

        uint eth_fees = ETH_FEES;
        uint usd_fees = USD_FEES;
        if (LP.pooled_eth > 0) {
            (uint ethReward, uint usdReward) = pendingRewards(msg.sender);
            LP.pooled_eth += ethReward; LP.usd_owed += usdReward;
            totalShares += ethReward;
        }
        (deltaUSD, // how much can be paired
         deltaETH) = this.addLiquidityHelper(
                               amount, price);
        if (deltaETH > 0) {
            LP.pooled_eth += deltaETH; totalShares += deltaETH;
            LP.fees_eth = FullMath.mulDiv(LP.pooled_eth, eth_fees, WAD);
            LP.fees_usd = FullMath.mulDiv(LP.pooled_eth, usd_fees, WAD);

            V4.modLP(sqrtPriceX96, deltaETH, deltaUSD,
                    tickLower, tickUpper, msg.sender);
        } // withdraw and refund excess from vault...
        if (deltaETH < amount) {
            _sendETH(amount - deltaETH, msg.sender);
            // _sendETH may pull from vogueETH, stale WETH, or native ETH.
            // Re-sync bookmark to actual vogueETH so _syncYield
            // doesn't misattribute the refunded portion as yield.
            lastVogueETH = AUX.vogueETH();
        }
    }

    function addLiquidityHelper(
        uint deltaETH, uint price) public
        onlyUs returns (uint, uint) {
        uint[13] memory deposits = AUX.get_deposits();
        uint liquidTotal = deposits[12] - deposits[11]
                            + AUX.getUSYCRedeemable();

        uint committed = V4.POOLED_USD() * 1e12;
        if (committed >= liquidTotal) return (0, 0);
        uint surplus = liquidTotal - committed;
        uint aaveAvail = AUX.vogueETH();

        uint pooledETH = V4.POOLED_ETH();
        uint availableETH = aaveAvail > pooledETH
                          ? aaveAvail - pooledETH : 0;

        uint targetUSD = FullMath.mulDiv(
                    deltaETH, price, WAD);

        if (targetUSD > surplus) {
            targetUSD = surplus;
            deltaETH = FullMath.mulDiv(
                   surplus, WAD, price);
        }
        if (deltaETH > availableETH) {
            deltaETH = availableETH;
            targetUSD = FullMath.mulDiv(
                   deltaETH, price, WAD);
        }
        uint usdOut = targetUSD / 1e12;
        if (usdOut == 0) return (0, 0);
        return (usdOut, deltaETH);
    }

     // pull liquidity from. . .
    function pull(uint id, // existing self-managed position
        int percent, address token) external nonReentrant {
        Types.SelfManaged storage position = selfManaged[id];
        require(position.owner == msg.sender, "403");

        require(block.number >=
        position.created + 47, "too soon");
        require(percent > 0 && percent < 101, "%");

        int liquidity = position.liq * percent / 100;
        require(liquidity > 0, "dust");
        int24 lower = position.lower;
        int24 upper = position.upper;

        uint[] storage myIds = positions[msg.sender];
        uint lastIndex = myIds.length > 0 ?
                         myIds.length - 1 : 0;

        if (percent == 100) { delete selfManaged[id];
            for (uint i = 0; i <= lastIndex; i++) {
                if (myIds[i] == id) {
                    if (i < lastIndex) {
                        myIds[i] = myIds[lastIndex];
                    }   myIds.pop(); break;
                }
            }
        } else {    position.liq -= liquidity;
            require(position.liq > 0, "pull");
        }
        V4.outOfRange(msg.sender, -liquidity,
                        lower, upper, token);
    }

    function _calculateYield(uint fees0, uint fees1, uint delta0,
        uint delta1, uint price) internal returns (uint yield) {
        uint last_repack = LAST_REPACK;
        uint deltaUSD; uint delta;
        uint usd_fees; uint fees;
        if (token1isETH) {
            (delta, deltaUSD) = (delta1, delta0);
            (fees, usd_fees) = (fees1, fees0);
        } else {
            (delta, deltaUSD) = (delta0, delta1);
            (fees, usd_fees) = (fees0, fees1);
        }
        if (totalShares > 0) {
            ETH_FEES += FullMath.mulDiv(fees, WAD, totalShares);
            USD_FEES += FullMath.mulDiv(usd_fees, WAD, totalShares);
        }
        if (last_repack > 0) {
            uint elapsed = block.timestamp - last_repack;
            uint denom = (deltaUSD * 1e12 + FullMath.mulDiv(
                            price, delta, WAD)) * elapsed;
            if (denom > 0) {
                yield = FullMath.mulDiv((usd_fees * 1e12 +
                    FullMath.mulDiv(price, fees, WAD)) * 365 days,
                      WAD, denom) / WAD;
            }
        } LAST_REPACK = block.timestamp;
    }

    function _depositETH(address sender,
        uint amount) internal returns (uint sent) {
        if (msg.value > 0) {
            WETH.deposit{value: msg.value}();
            sent = msg.value; amount -= Math.min(
                              amount, msg.value);
        }
        if (amount > 0) { uint available = Math.min(
                WETH.allowance(sender, address(this)),
                WETH.balanceOf(sender));

            uint took = Math.min(amount, available);
            if (took > 0) { WETH.transferFrom(sender,
                                address(this), took);
                                        sent += took;
            }
        } if (sent > 0) {
            // Sweep ALL WETH to AAVE — captures sent + any residual.
            // Only `sent` is returned for LP accounting; any extra
            // goes to vogueETH and _syncYield distributes it as yield.
            uint toDeposit = WETH.balanceOf(address(this));
            AUX.vogueETHOp(toDeposit, 0);
        }
    }

    function takeETH(uint howMuch, address recipient)
       external onlyUs returns (uint sent) {
       sent = _sendETH(howMuch, recipient);
    }

    /// @notice Sync AAVE yield into ETH_FEES
    /// @dev Reads Vogue's entitled ETH from Aux
    function _syncYield() internal {
        uint current = AUX.vogueETHOp(0, 2);
        if (lastVogueETH > 0 && current > lastVogueETH) {
            uint aaveYield = current - lastVogueETH;
            if (totalShares > 0)
                ETH_FEES += FullMath.mulDiv(
                aaveYield, WAD, totalShares);
        } lastVogueETH = current;
    }

    function _sendETH(uint howMuch,
       address toWhom) internal returns (uint sent) {
        uint alreadyInETH = address(this).balance;
        if (alreadyInETH >= howMuch) sent = howMuch;
        else { uint needed = howMuch - alreadyInETH;
            uint inWETH = WETH.balanceOf(address(this));
            if (needed > inWETH) {
                uint got = AUX.vogueETHOp(
                       needed - inWETH, 1);
                             inWETH += got;
            }  WETH.withdraw(inWETH);
            sent = inWETH + alreadyInETH;
        }
        (bool success, ) = payable(toWhom).call{
                                   value: sent }("");
        if (!success) sent = 0;
    }

    function _repack() internal returns (uint160 sqrtPriceX96,
        int24 tickLower, int24 tickUpper, uint128 myLiquidity) {
        _syncYield(); // capture vault yield before anything else
        int24 currentTick; tickUpper = UPPER_TICK; tickLower = LOWER_TICK;
        (sqrtPriceX96, currentTick, myLiquidity) = V4.poolStats(
                                            tickLower, tickUpper);

        uint price; uint fees0; uint fees1; uint delta0; uint delta1;
        if (currentTick > tickUpper || currentTick < tickLower) {
            // Don't repack if deviating significantly from TWAP
            delta1 = AUX.getTWAP(1800);
            delta0 = BasketLib.getPrice(
              sqrtPriceX96, token1isETH);

            if (BasketLib.isManipulated(delta0, delta1, 3))
                return (sqrtPriceX96, tickLower,
                        tickUpper, myLiquidity);

            (int24 newTickLower,,
             int24 newTickUpper,) = _updateTicks(
                               sqrtPriceX96, 200);

            if (myLiquidity > 0) {
                (price, fees0, fees1,
                 delta0, delta1) = V4.repack(myLiquidity, sqrtPriceX96,
                        tickLower, tickUpper, newTickLower, newTickUpper);
                YIELD = _calculateYield(fees0, fees1, delta0, delta1, price);
            } LOWER_TICK = newTickLower; UPPER_TICK = newTickUpper;
            tickLower = newTickLower; tickUpper = newTickUpper;
        }
    }

    function repack() public onlyUs returns (uint160 sqrtPriceX96,
        int24 tickLower, int24 tickUpper, uint128 myLiquidity) {
        (sqrtPriceX96, tickLower, tickUpper, myLiquidity) = _repack();
    }

    function paddedSqrtPrice(uint160 sqrtPriceX96,
        bool up, uint delta) public view returns (uint160) {
        uint factor = up ? FixedPointMathLib.sqrt((10000 + delta) * 1e18 / 10000)
                         : FixedPointMathLib.sqrt((10000 - delta) * 1e18 / 10000);
        return uint160(FixedPointMathLib.mulDivDown(sqrtPriceX96, factor, 1e9));
    }

    function _alignTick(int24 tick, int24 width)
        internal pure returns (int24) {
        if (tick < 0 && tick % width != 0) {
            return ((tick - width + 1) / width) * width;
        }   return (tick / width) * width;
    }

    function _updateTicks(uint160 sqrtPriceX96, uint delta)
        internal view returns (int24 tickLower, uint160 lower,
                             int24 tickUpper, uint160 upper) {

        lower = paddedSqrtPrice(sqrtPriceX96, false, delta);
        upper = paddedSqrtPrice(sqrtPriceX96, true, delta);

        require(lower >= TickMath.MIN_SQRT_PRICE + 1, "minPrice");
        require(upper <= TickMath.MAX_SQRT_PRICE - 1, "maxPrice");

        tickLower = _alignTick(TickMath.getTickAtSqrtPrice(lower), int24(10));
        tickUpper = _alignTick(TickMath.getTickAtSqrtPrice(upper), int24(10));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";

/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
    using SafeCast for uint256;

    /// @notice Computes the amount of liquidity received for a given amount of token0 and price range
    /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
    /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
    /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount0 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
        internal
        pure
        returns (uint128 liquidity)
    {
        unchecked {
            if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
            uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
            return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
        }
    }

    /// @notice Computes the amount of liquidity received for a given amount of token1 and price range
    /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
    /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
    /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
    /// @param amount1 The amount1 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
        internal
        pure
        returns (uint128 liquidity)
    {
        unchecked {
            if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
            return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
        }
    }

    /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtPriceX96 A sqrt price representing the current pool prices
    /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
    /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount of token0 being sent in
    /// @param amount1 The amount of token1 being sent in
    /// @return liquidity The maximum amount of liquidity received
    function getLiquidityForAmounts(
        uint160 sqrtPriceX96,
        uint160 sqrtPriceAX96,
        uint160 sqrtPriceBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

        if (sqrtPriceX96 <= sqrtPriceAX96) {
            liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
        } else if (sqrtPriceX96 < sqrtPriceBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
        }
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MAX_UINT256 = 2**256 - 1;

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // Divide x * y by the denominator.
            z := div(mul(x, y), denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Mod x by y. Note this will return
            // 0 instead of reverting if y is zero.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // Divide x by y. Note this will return
            // 0 instead of reverting if y is zero.
            r := div(x, y)
        }
    }

    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Add 1 to x * y if x % y > 0. Note this will
            // return 0 instead of reverting if y is zero.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}

File 4 of 78 : ReentrancyGuard.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {BitMath} from "./BitMath.sol";
import {CustomRevert} from "./CustomRevert.sol";

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    using CustomRevert for bytes4;

    /// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
    error InvalidTick(int24 tick);
    /// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
    error InvalidSqrtPrice(uint160 sqrtPriceX96);

    /// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
    /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
    /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
    int24 internal constant MAX_TICK = 887272;

    /// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
    int24 internal constant MIN_TICK_SPACING = 1;
    /// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
    int24 internal constant MAX_TICK_SPACING = type(int16).max;

    /// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_PRICE = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
    /// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
    uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
        1461446703485210103287273052203988822378723970342 - 4295128739 - 1;

    /// @notice Given a tickSpacing, compute the maximum usable tick
    function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
        unchecked {
            return (MAX_TICK / tickSpacing) * tickSpacing;
        }
    }

    /// @notice Given a tickSpacing, compute the minimum usable tick
    function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
        unchecked {
            return (MIN_TICK / tickSpacing) * tickSpacing;
        }
    }

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
    /// at the given tick
    function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        unchecked {
            uint256 absTick;
            assembly ("memory-safe") {
                tick := signextend(2, tick)
                // mask = 0 if tick >= 0 else -1 (all 1s)
                let mask := sar(255, tick)
                // if tick >= 0, |tick| = tick = 0 ^ tick
                // if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
                // either way, |tick| = mask ^ (tick + mask)
                absTick := xor(mask, add(mask, tick))
            }

            if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);

            // The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
            // is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer

            // Equivalent to:
            //     price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
            //     or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
            uint256 price;
            assembly ("memory-safe") {
                price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
            }
            if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;

            assembly ("memory-safe") {
                // if (tick > 0) price = type(uint256).max / price;
                if sgt(tick, 0) { price := div(not(0), price) }

                // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
                // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
                // we round up in the division so getTickAtSqrtPrice of the output price is always consistent
                // `sub(shl(32, 1), 1)` is `type(uint32).max`
                // `price + type(uint32).max` will not overflow because `price` fits in 192 bits
                sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
            }
        }
    }

    /// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
    function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        unchecked {
            // Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
            // second inequality must be >= because the price can never reach the price at the max tick
            // if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
            // if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
            if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
                InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
            }

            uint256 price = uint256(sqrtPriceX96) << 32;

            uint256 r = price;
            uint256 msb = BitMath.mostSignificantBit(r);

            if (msb >= 128) r = price >> (msb - 127);
            else r = price << (127 - msb);

            int256 log_2 = (int256(msb) - 128) << 64;

            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(63, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(62, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(61, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(60, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(59, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(58, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(57, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(56, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(55, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(54, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(53, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(52, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(51, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(50, f))
            }

            int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number

            // Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
            int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);

            // Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when
            // sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE
            // is changed, this may need to be changed too
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

            tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then use the Chinese Remainder Theorem to reconstruct
            // the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2**256 + prod0
            uint256 prod0 = a * b; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly ("memory-safe") {
                let mm := mulmod(a, b, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                assembly ("memory-safe") {
                    result := div(prod0, denominator)
                }
                return result;
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly ("memory-safe") {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly ("memory-safe") {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (0 - denominator) & denominator;
            // Divide denominator by power of two
            assembly ("memory-safe") {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly ("memory-safe") {
                prod0 := div(prod0, twos)
            }
            // Shift in bits from prod1 into prod0. For this we need
            // to flip `twos` such that it is 2**256 / twos.
            // If twos is zero, then it becomes one
            assembly ("memory-safe") {
                twos := add(div(sub(0, twos), twos), 1)
            }
            prod0 |= prod1 * twos;

            // Invert denominator mod 2**256
            // Now that denominator is an odd number, it has an inverse
            // modulo 2**256 such that denominator * inv = 1 mod 2**256.
            // Compute the inverse by starting with a seed that is correct
            // correct for four bits. That is, denominator * inv = 1 mod 2**4
            uint256 inv = (3 * denominator) ^ 2;
            // Now use Newton-Raphson iteration to improve the precision.
            // Thanks to Hensel's lifting lemma, this also works in modular
            // arithmetic, doubling the correct bits in each step.
            inv *= 2 - denominator * inv; // inverse mod 2**8
            inv *= 2 - denominator * inv; // inverse mod 2**16
            inv *= 2 - denominator * inv; // inverse mod 2**32
            inv *= 2 - denominator * inv; // inverse mod 2**64
            inv *= 2 - denominator * inv; // inverse mod 2**128
            inv *= 2 - denominator * inv; // inverse mod 2**256

            // Because the division is now exact we can divide by multiplying
            // with the modular inverse of denominator. This will give us the
            // correct result modulo 2**256. Since the preconditions guarantee
            // that the outcome is less than 2**256, this is the final result.
            // We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inv;
            return result;
        }
    }

    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            result = mulDiv(a, b, denominator);
            if (mulmod(a, b, denominator) != 0) {
                require(++result > 0);
            }
        }
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

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

import {SafeTransferLib} from "../utils/SafeTransferLib.sol";

/// @notice Minimalist and modern Wrapped Ether implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol)
/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol)
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
    using SafeTransferLib for address;

    event Deposit(address indexed from, uint256 amount);

    event Withdrawal(address indexed to, uint256 amount);

    function deposit() public payable virtual {
        _mint(msg.sender, msg.value);

        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) public virtual {
        _burn(msg.sender, amount);

        emit Withdrawal(msg.sender, amount);

        msg.sender.safeTransferETH(amount);
    }

    receive() external payable virtual {
        deposit();
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
    /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
    /// is the new allowance.
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /// @notice Returns the amount of tokens in existence.
    function totalSupply() external view returns (uint256);

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

    /// @notice Moves `amount` tokens from the caller's account to `to`.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Returns the remaining number of tokens that `spender` is allowed
    /// to spend on behalf of `owner`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
    /// `amount` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice Returns the name of the token.
    function name() external view returns (string memory);

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

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

File 11 of 78 : BasketLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";
import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {Types} from "./Types.sol";

interface IUniswapV3Pool {
    function slot0() external
    view returns (uint160, int24,
    uint16, uint16, uint16,
    uint8, bool);
}

interface ISwapRouter {
    struct ExactInputParams { bytes path;
        address recipient; uint deadline;
        uint amountIn; uint amountOutMinimum;
    }
    function exactInput(ExactInputParams calldata params)
                       external payable returns (uint);
}

interface IAux {
    function vaults(address) external returns (address);
    function untouchables(address) external returns (uint);
    function take(address who,
        uint amount, address token,
        uint seed) external returns (uint);
}


// Oracle interfaces for L2 staked price lookups
/* TODO uncomment
interface IChainlinkOracle {
    function latestRoundData() external view
    returns (uint80, int, uint, uint, uint80);
}

interface IDSRRate {
    function getRate() external view returns (uint);
}

interface ISCRVOracle {
    function pricePerShare() external view returns (uint);
} */

interface IRover {
    function take(uint amount) external returns (uint);
    function withdrawUSDC(uint amount) external returns (uint);
    function deposit(uint amount) external;
    function depositUSDC(uint amount, uint price) external;
}

interface AAVEv3 {
    function getReserveAToken(address asset)
            external view returns (address);

    function getReserveNormalizedIncome(address asset)
                        external view returns (uint);


    function supply(address asset, uint amount,
    address onBehalfOf, uint16 referralCode) external;

    function withdraw(address asset, uint amount,
            address to) external returns (uint);
}

interface IHub {
    function getAssetId(address underlying)
            external view returns (uint256);
}

interface AAVEv4 {
    struct UserAccountData {
        uint totalCollateralValue;
        uint totalDebtValue;
        uint avgCollateralFactor;
    }
    struct Reserve { uint8 decimals; }

    function getReserveId(address hub,
    uint assetId) external returns (uint);

    function withdraw(uint reserveId,
    uint amount, address onBehalfOf)
    external returns (uint, uint);

    function supply(uint reserveId,
    uint amount, address onBehalfOf)
    external returns (uint256, uint256);

    function borrow(uint reserveId,
    uint amount, address onBehalfOf)
    external returns (uint256);

    function repay(uint reserveId,
    uint amount, address onBehalfOf)
    external returns (uint256);

    function getUserSuppliedAssets(uint reserveId,
        address user) external view returns (uint);

    function getUserSuppliedShares(uint reserveId,
        address user) external view returns (uint);

    function getUserAccountData(address user)
        external view returns (UserAccountData memory);

    function getReserve(uint reserveId)
        external view returns (Reserve memory);

    function ORACLE() external view returns (address);
}

interface IAaveOracle {
    function getReservePrice(uint reserveId)
        external view returns (uint);
}

interface IVogueCore {
    function POOLED_ETH() external view returns (uint);
    function POOLED_USD() external view returns (uint);
    function MAX_POOLED_USD() external view returns (uint);
    function token1isETH() external view returns (bool);
    function swap(uint160 sqrtPriceX96, address sender,
        bool forOne, address token, uint amount) external returns (uint);
}

/// @notice Minimal interface for Liquity V2 StabilityPool
/// @dev 0x5721cbbd64fc7Ae3Ef44A0A3F9a790A9264Cf9BF (WETH)
interface IStabilityPool {
    function provideToSP(uint _topUp, bool _doClaim) external;
    function withdrawFromSP(uint _amount, bool _doClaim) external;
    function getCompoundedBoldDeposit(address _depositor) external view returns (uint);
    function getDepositorYieldGainWithPending(address _depositor) external view returns (uint);
}

interface ITeller {
    function share() external view returns (address);
    function asset() external view returns (address);
    function todayTimestamp() external view returns (uint);
    function convertToAssets(uint shares) external view returns (uint);
    function convertToShares(uint assets) external view returns (uint);
    function deposit(uint assets, address receiver) external returns (uint shares);
    function redeem(uint shares, address receiver, address owner) external returns (uint assets);
    function redemptionLimitRemaining(address account, uint day) external view returns (uint);
    function limit(address) external view returns (uint56 depositLimit, uint56 redeemLimit);
}

library BasketLib {
    uint public constant RAY = 1e27;
    uint public constant WAD = 1e18;
    uint public constant WEEK = 604800;
    uint public constant MONTH = 2420000;

    // Oracle type flags

    struct Metrics {
        uint total;
        uint last;
        uint yield;
        uint trackingStart;
        uint yieldAccum;
    }

    function computeMetrics(Metrics memory stats,
        uint elapsed, uint raw, uint yieldWeighted,
        uint tvl) internal view returns (Metrics memory) {

        if (stats.trackingStart > 0 && stats.last > 0)
            stats.yieldAccum += stats.yield * elapsed;

        else if (stats.trackingStart == 0)
            stats.trackingStart = block.timestamp;

        stats.yield = (raw > 0 && yieldWeighted >= raw) ?
            FullMath.mulDiv(WAD, yieldWeighted, raw) - WAD
                                           : stats.yield;
        stats.total = tvl; stats.last = block.timestamp;

        return stats;
    }

    function get_deposits(address aux, address pool,
        address hub, address[] memory stables) external
        returns (uint[13] memory amounts) { uint i;
        // amounts[0]     = yield-weighted sum across ALL sources
        // amounts[1..11] = per-token deposit values (18 dec)
        // amounts[12]    = raw TVL total (all sources)
        address vault; uint balance; address stable;
        for (i = 0; i < 4; i++) { stable = stables[i];
            uint yieldWeighted;
            if (hub == address(0)) {
                vault = AAVEv3(pool).getReserveAToken(stable);
                balance = IERC20(vault).balanceOf(address(this));
                uint yieldFactor = AAVEv3(pool).getReserveNormalizedIncome(stable);
                yieldWeighted = FullMath.mulDiv(balance, yieldFactor, RAY);
            } else { uint reserveId = AAVEv4(pool).getReserveId(hub,
                                        IHub(hub).getAssetId(stable));

                balance = AAVEv4(pool).getUserSuppliedAssets(
                                    reserveId, address(this));
                uint shares = AAVEv4(pool).getUserSuppliedShares(
                                        reserveId, address(this));
                yieldWeighted = (shares > 0) ? FullMath.mulDiv(
                    balance, balance, shares) : balance;
            }
            if (balance > 0) { balance *= i < 3 ? 1e12 : 1;
                         yieldWeighted *= i < 3 ? 1e12 : 1;
                uint reserved = IAux(aux).untouchables(stable);
                if (reserved > 0) { uint cap = Math.min(balance, reserved);
                    balance -= cap; yieldWeighted -= Math.min(yieldWeighted, cap);
                } amounts[i + 1] = balance; amounts[12] += balance;
                amounts[0] += yieldWeighted;
            }
        } for (i = 4; i < 9; i++) {
            stable = stables[i]; vault = IAux(aux).vaults(stable);
            uint shares = IERC4626(vault).balanceOf(address(this));
            uint reserved = IAux(aux).untouchables(stable);
            if (reserved > 0) shares -= Math.min(shares,
                IERC4626(vault).convertToShares(reserved));
            if (shares > 0) {
                balance = IERC4626(vault).convertToAssets(shares);
                amounts[i + 1] = balance;  // raw position value
                amounts[12] += balance;    // raw TVL total
                // yield-weighted: balance × sharePrice
                uint supply = IERC4626(vault).totalSupply();
                if (supply > 0) {
                    amounts[0] += FullMath.mulDiv(balance,
                    IERC4626(vault).totalAssets(), supply);
                } else amounts[0] += balance;
            }
        } stable = stables[10];
        vault = IAux(aux).vaults(stable);
        (uint usycValue, ) = getUSYCValue(
                     vault, address(this));

        if (usycValue > 0) {
            uint usycReserved = IAux(aux).untouchables(stable);
            usycValue -= Math.min(usycValue, usycReserved);
            amounts[11] = usycValue; amounts[12] += usycValue;
            // Yield-weighted: usycValue × teller growth rate.
            // growth = convertToAssets(shares) / shares (teller-wide, pre-reserve).
            // So yieldWeighted = usycValue × totalValue / parValue.
            address usyc = ITeller(vault).share();
            uint shares = IERC20(usyc).balanceOf(address(this));
            if (shares > 0) {
                uint totalValue = ITeller(vault).convertToAssets(shares) * 1e12;
                uint parValue = shares * 1e12;
                amounts[0] += FullMath.mulDiv(usycValue, totalValue, parValue);
            } else amounts[0] += usycValue;
        }
    }

    function getAverageYield(Metrics memory stats)
        external view returns (uint) {
        if (stats.trackingStart == 0) return 0;
        uint totalTime = block.timestamp - stats.trackingStart;
        uint timeSinceUpdate = block.timestamp - stats.last;
        uint currentAccum = stats.yieldAccum
            + stats.yield * timeSinceUpdate;
        return currentAccum / (totalTime + 1);
    }

    // sqrt(deficit) × avgYield / 4
    function seedFee(uint usd,
        uint untouchable, uint target,
        uint avgYield) internal pure returns (uint) {
        if (target == 0 || untouchable >= target) return 0;
        uint deficit = FullMath.mulDiv(
        target - untouchable, WAD, target);
        uint sqrtDef = Math.sqrt(deficit * WAD);
        if (sqrtDef == 0 || avgYield == 0) return 0;
        return Math.min(FullMath.mulDiv(FullMath.mulDiv(
            usd, sqrtDef, WAD), avgYield, WAD * 4),
            target - untouchable);
    }

    /// @notice Calculate SP position
    //  & yield contribution in get_deposits
    /// @param sp StabilityPool address...
    /// @param depositor Address to check
    /// @param reserved Untouchable amount
    /// @param state Current SP tracking state
    /// @return totalValue Position value (compounded + yield - reserved)
    /// @return yieldContrib Contribution to amounts[12] for weighted average
    function calcSPValue(address sp, address depositor,
        uint reserved, SPState memory state) external view returns
        (uint totalValue, uint yieldContrib) { if (sp == address(0)) return (0, 0);
        uint compounded = IStabilityPool(sp).getCompoundedBoldDeposit(depositor);
        uint yieldGain = IStabilityPool(sp).getDepositorYieldGainWithPending(depositor);
        totalValue = compounded + yieldGain; totalValue -= Math.min(totalValue, reserved);

        if (totalValue == 0) return (0, 0);
        // Calculate time-weighted APY for yield contribution
        uint currentPrincipalTime = state.spPrincipalTime +
        state.spValue * (block.timestamp - state.spLastUpdate);
        if (currentPrincipalTime > 0 && state.spTotalYield > 0) {
            uint rate = FullMath.mulDiv(state.spTotalYield,
                    WAD * 365 days, currentPrincipalTime);

            yieldContrib = FullMath.mulDiv(
               totalValue, WAD + rate, WAD);
        } else
            yieldContrib = totalValue;
            // no yield history, 1:1

    }
    /*
    function get_deposits(address[10] memory vaults,
        address[10] memory stables, bool v4) external
        view returns (uint[13] memory amounts) {
    } */

    struct SPState {
        uint spValue;         // original principal tracking (not compounded yield)
        uint spTotalYield;    // cumulative harvested yield (USD value, WAD)
        uint spPrincipalTime; // cumulative (principal * seconds)
        uint spLastUpdate;    // last update timestamp
    }

    /// @notice Result from SP withdraw operation
    struct SPWithdrawResult {
        uint sent;            // BOLD amount being sent out
        uint toRedeposit;     // BOLD amount to redeposit (already done)
        uint wethGain;        // ETH collateral gained (caller handles swap)
        uint boldReceived;    // Total BOLD received from SP
        // Updated state values - caller must write these
        uint newSpValue;
        uint newSpTotalYield;
        uint newSpPrincipalTime;
        uint newSpLastUpdate;
    }

    /// @notice Execute SP withdrawal and return results with updated state
    /// @dev Performs withdrawFromSP and provideToSP, caller handles WETH swap
    /// @param sp StabilityPool address
    /// @param bold BOLD token address
    /// @param weth WETH token address
    /// @param amount Requested withdrawal amount
    /// @param ethPrice Current ETH price (WAD) for yield calculation
    /// @param state Current SP tracking state
    /// @return r Result with amounts and updated state values
    function withdrawFromSP(address sp, address bold,
        address weth, uint amount, uint ethPrice,
        SPState memory state) external returns (SPWithdrawResult memory r) {
        uint compounded = IStabilityPool(sp).getCompoundedBoldDeposit(address(this));
        uint yieldGain = IStabilityPool(sp).getDepositorYieldGainWithPending(address(this));
        uint totalBold = compounded + yieldGain;
        if (totalBold == 0) return r;

        // Only from principal, not yield...
        amount = Math.min(amount, compounded);
        uint wethBefore = WETH9(payable(weth)).balanceOf(address(this));
        uint boldBefore = IERC20(bold).balanceOf(address(this));
        IStabilityPool(sp).withdrawFromSP(compounded, true);

        r.boldReceived = IERC20(bold).balanceOf(address(this)) - boldBefore;
        r.wethGain = WETH9(payable(weth)).balanceOf(address(this)) - wethBefore;

        // Send only from principal
        r.sent = Math.min(amount, r.boldReceived);
        // Redeposit everything else (includes all yield)
        r.toRedeposit = r.boldReceived - r.sent;

        r.newSpPrincipalTime = state.spPrincipalTime +
           state.spValue * (block.timestamp - state.spLastUpdate);
        r.newSpLastUpdate = block.timestamp;

        // Only ETH gains count as harvested (BOLD yield compounds)
        uint wethValueUSD = FullMath.mulDiv(r.wethGain, ethPrice, WAD);
        r.newSpTotalYield = state.spTotalYield + wethValueUSD;

        // Sent is all principal
        r.newSpValue = state.spValue > r.sent ? state.spValue - r.sent : 0;
        if (r.toRedeposit > 0) IStabilityPool(sp).provideToSP(r.toRedeposit, false);

    }

    /// @notice Deposit BOLD to StabilityPool and return updated state
    /// @param sp StabilityPool address
    /// @param amount Amount to deposit
    /// @param state Current SP tracking state
    /// @return newSpValue Updated spValue
    /// @return newSpPrincipalTime Updated spPrincipalTime
    /// @return newSpLastUpdate Updated spLastUpdate
    function depositToSP(address sp,
        uint amount, SPState memory state) external
        returns (uint newSpValue, uint newSpPrincipalTime, uint newSpLastUpdate) {
        newSpPrincipalTime = state.spPrincipalTime + state.spValue * (
                                 block.timestamp - state.spLastUpdate);

        newSpValue = state.spValue + amount;
        newSpLastUpdate = block.timestamp;
        IStabilityPool(sp).provideToSP(amount, false);
    }

    /// @param amount Amount being deposited
    /// @return cut Fee amount to deduct from deposit
    /// @notice Deposit fee driven by weighted median vote (K).
    ///         Symmetric with withdrawal: stressed (high haircut)
    ///         → higher fee → reserves build faster.
    ///         K=0 → 900bps (9%), K=32 → 100bps (1%)

    /// @notice ETH price from sqrtPriceX96
    /// @param sqrtPriceX96 Square root price
    /// @param token0isUSD Whether token0 is USD
    /// @return price ETH price in USD 1e18
    function getPrice(uint160 sqrtPriceX96, bool token0isUSD)
        public pure returns (uint price) {
        uint casted = uint(sqrtPriceX96);
        uint ratioX128 = FullMath.mulDiv(
               casted, casted, 1 << 64);

        if (token0isUSD) {
          price = FullMath.mulDiv(1 << 128,
              WAD * 1e12, ratioX128);
        } else {
          price = FullMath.mulDiv(ratioX128,
              WAD * 1e12, 1 << 128);
        }
    }

    function getUSYCRedeemable(address teller) public view returns
        (uint) { address usycToken = ITeller(teller).share();
        uint usycBalance = ITeller(teller).convertToAssets(
                           IERC20(usycToken).balanceOf(msg.sender));
        uint dailyLimit = ITeller(teller).redemptionLimitRemaining(
                      msg.sender, ITeller(teller).todayTimestamp());
        return Math.min(usycBalance, dailyLimit) * 1e12; // Scale to 1e18
    }

    function supplyAAVE(address aave,
        address asset, uint amount,
        address to, address hub) // 6909
        public returns (uint deposited) {
        if (hub == address(0)) { deposited = amount;
                            AAVEv3(aave).supply(asset,
                                        amount, to, 0);
        } else { uint reserveId = AAVEv4(aave).getReserveId(hub,
                                    IHub(hub).getAssetId(asset));
            (, deposited) = AAVEv4(aave).supply(reserveId,
                                amount, address(this));
        }
    }

    function withdrawAAVE(address aave, address asset,
        uint amount, address to, address hub)
        public returns (uint drawn) {
        if (hub == address(0)) {
            amount = Math.min(amount, aaveAvailableV3(
                                        aave, asset));
            if (amount == 0) return 0;
            drawn = AAVEv3(aave).withdraw(asset, amount, to);
        } else {
            uint reserveId = AAVEv4(aave).getReserveId(hub,
                              IHub(hub).getAssetId(asset));
            uint max = AAVEv4(aave).getUserSuppliedAssets(
                                 reserveId, address(this));

            amount = amount > 0 ? Math.min(amount, max) : max;
            if (amount == 0) return 0;
            (, drawn) = AAVEv4(aave).withdraw(reserveId,
                                amount, address(this));

            if (to != address(this))
                IERC20(asset).transfer(to, drawn);
        }
    }

    function getUSYCValue(// time value of money
        address teller, address holder) public
        view returns (uint value, uint yield) {
        if (teller == address(0)) return (0, 0);
        address usyc = ITeller(teller).share();
        uint shares = IERC20(usyc).balanceOf(holder);
        uint assets = ITeller(teller).convertToAssets(shares); // USDC 6 dec
        value = assets * 1e12; // Scale to 18 dec
        // Yield = value above par (1 USYC started at $1)
        uint parValue = shares * 1e12; // par = 1:1 with USDC
        yield = value > parValue ? value - parValue : 0;
    }

    function withdrawUSYC(address teller,
        address recipient, uint amount)
        external returns (uint sent,
        uint sharesUsed) { address usyc = ITeller(teller).share();
        uint shares = IERC20(usyc).balanceOf(address(this));
        shares = Math.min(ITeller(teller).convertToShares(amount), shares);

        uint today = ITeller(teller).todayTimestamp();
        uint remaining = ITeller(teller).redemptionLimitRemaining(
                                             address(this), today);
        sharesUsed = Math.min(shares,
        ITeller(teller).convertToShares(remaining));
        if (sharesUsed > 0) sent = ITeller(teller).redeem(sharesUsed,
                                            recipient, address(this));
    }

    function depositUSYC(address teller, address aave,
        address usdc, uint amount, address hub) public returns
        (uint pulled, uint deposited) { if (amount == 0) return (0, 0);
        (uint56 depositLimit,) = ITeller(teller).limit(address(this));
        if (depositLimit == 0) return (0, amount);
        // reuse `deposited` as maxToUSYC, `pulled` as capacity
        try ITeller(teller).redemptionLimitRemaining(
            address(this), ITeller(teller).todayTimestamp())
            returns (uint redeemable) {
            address usycToken = ITeller(teller).share();
            deposited = ITeller(teller).convertToAssets(
                     IERC20(usycToken).balanceOf(address(this)));
            deposited = redeemable > deposited ?
                        redeemable - deposited : 0;
        } catch { return (0, amount); }
        pulled = Math.min(uint(depositLimit), deposited);
        // pulled = capacity, deposited = 0 from here
        deposited = Math.min(amount, pulled); // fromIncoming
        uint fromAAVE;
        if (pulled > deposited) {
            uint aaveBal = hub == address(0)
                ? aaveAvailableV3(aave, usdc)
                : AAVEv4(aave).getUserSuppliedAssets(
                    AAVEv4(aave).getReserveId(hub,
                    IHub(hub).getAssetId(usdc)),
                                  address(this));
            fromAAVE = Math.min(pulled - deposited, aaveBal);
            if (fromAAVE > 0)
                withdrawAAVE(aave, usdc, fromAAVE,
                              address(this), hub);
        }
        // toTeller = deposited + fromAAVE (reuse `pulled`)
        pulled = deposited + fromAAVE;
        if (pulled > 0) {
            deposited = IERC20(usdc).balanceOf(address(this));
            try ITeller(teller).deposit(pulled, address(this))
                returns (uint) { pulled = deposited -
                    IERC20(usdc).balanceOf(address(this));
            } catch {
                if (fromAAVE > 0) {
                    supplyAAVE(aave, usdc, fromAAVE,
                                 address(this), hub);
                  fromAAVE = 0;
                } pulled = 0;
            }
        } else pulled = 0;
        // pulled = actual USDC consumed by teller
        // deposited = remainder for caller to send to AAVE
        deposited = amount - (pulled > fromAAVE
                            ? pulled - fromAAVE : 0);
    }

    function ticksToPrice(int56 tickCum0,
        int56 tickCum1, uint32 period, bool token0isUSD) external
        pure returns (uint price) { int56 delta = tickCum1 - tickCum0;
        int24 averageTick = int24(delta / int56(uint56(period)));
        uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(averageTick);
        price = getPrice(sqrtPriceX96, token0isUSD);
    }

    /// @notice Find index of last mature batch
    function matureBatches(uint[] memory batches,
        uint currentTimestamp, uint deployedTime)
        external pure returns (int i) {
        uint currentMonth = (currentTimestamp - deployedTime) / MONTH;
        int start = int(batches.length - 1);
        for (i = start; i >= 0; i--)
            if (batches[uint(i)] <= currentMonth) return i;

        return -1;
    }

    /// @param thresholdPercent Max deviation
    function isManipulated(uint spot, uint twap,
        uint thresholdPercent) public pure returns (bool) {
        uint dev = spot > twap ? spot - twap : twap - spot;
        return dev * 100 > twap * thresholdPercent;
    }

    /// fetch V3 spot, check against twap
    function isV3Manipulated(address pool,
        bool token1isWETH, uint twapPrice)
        public view returns (bool) {
        (uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0();
        return isManipulated(getPrice(sqrtPriceX96, token1isWETH), twapPrice, 2);
    }

    function calculateVaultWithdrawal(address vault, uint amount)
        external view returns (uint sharesNeeded, uint assetsReceived) {
        uint vaultBalance = IERC4626(vault).balanceOf(address(this));
        sharesNeeded = IERC4626(vault).convertToShares(amount);
        sharesNeeded = Math.min(vaultBalance, sharesNeeded);
        assetsReceived = IERC4626(vault).convertToAssets(sharesNeeded);
        return (sharesNeeded, assetsReceived);
    }

    /// @notice Scale token amounts between precisions...
    function scaleTokenAmount(uint amount, address token,
        bool scaleUp) external view returns (uint scaled) {
        uint decimals = IERC20(token).decimals();
        uint scale = decimals < 18 ? 18 - decimals : 0;
        scaled = scale > 0 ? (scaleUp ? amount * (10 ** scale):
              amount / (10 ** scale)) : amount; return scaled;
    }

    function arbETH(Types.AuxContext memory ctx, uint shortfall,
        uint price) external returns (uint got, bool failed) {
        uint usdNeeded = convert(shortfall, price, false);
        uint took = IAux(address(this)).take(address(this),
                                   usdNeeded, ctx.usdc, 0);
        if (took == 0)
            return (0, false);
         got = swapUSDCtoWETH(ctx,
               took / 1e12, price);

        if (got == 0) {
            // V3 failed — USDC was pulled from AAVE but never
            // converted. Re-deposit so it earns yield and stays
            // visible to get_deposits().
            uint stranded = IERC20(ctx.usdc).balanceOf(address(this));
            if (stranded > 0)
                supplyAAVE(ctx.vault, ctx.usdc,
                           stranded, address(this), ctx.hub);
            return (0, false);
        }

        supplyAAVE(ctx.vault, ctx.weth,
          got, address(this), ctx.hub);
    }

    function swapWETHtoUSDC(Types.AuxContext memory ctx,
        uint amountIn, uint price) public returns (uint amountOut) {
        uint poolUSDC = IERC20(ctx.usdc).balanceOf(ctx.v3Pool);
        uint max = convert(poolUSDC, price, true);
        if (amountIn > max) amountIn = max;
        if (amountIn > 0) {
            uint minOut = convert(amountIn, price, false) * 99500 / 100000;
            (bool ok, bytes memory ret) = ctx.v3Router.call(abi.encodeWithSelector(
                    ISwapRouter.exactInput.selector, ISwapRouter.ExactInputParams(
                        abi.encodePacked(ctx.weth, ctx.v3Fee, ctx.usdc),
                        address(this), block.timestamp, amountIn, minOut)));
            if (ok && ret.length >= 32) amountOut = abi.decode(ret, (uint));
        }
    }

    function sourceExternalUSD(Types.AuxContext memory ctx,
        uint wethIn, uint price) public returns (uint usdcOut) {
        uint wethBefore = IERC20(ctx.weth).balanceOf(address(this));
        if (ctx.rover != address(0)) {
            uint targetUSDC = convert(wethIn, price, false);
            uint fromRover = IRover(ctx.rover).withdrawUSDC(targetUSDC);
            if (fromRover > 0) {
                uint wethForRover = convert(
                     fromRover, price, true);

                IRover(ctx.rover).deposit(wethForRover);
                usdcOut = fromRover;
                wethIn = wethIn > wethForRover ?
                         wethIn - wethForRover : 0;
            }
        } if (wethIn > 0) usdcOut += swapWETHtoUSDC(ctx, wethIn, price);
        // Only sweep WETH that accumulated during THIS call
        // (i.e., was pulled for swapping but V3 failed)
        uint wethAfter = IERC20(ctx.weth).balanceOf(address(this));
        if (wethAfter > wethBefore) {
            uint stranded = wethAfter - wethBefore;
            supplyAAVE(ctx.vault, ctx.weth,
             stranded, address(this), ctx.hub);
        }
    }

    function sourceExternalWETH(Types.AuxContext memory ctx,
        uint usdcIn, uint price)
        public returns (uint wethOut) {
        uint usdcBefore = IERC20(ctx.usdc).balanceOf(address(this));

        if (ctx.rover != address(0)) {
            uint targetWETH = convert(usdcIn, price, true);
            uint fromRover = IRover(ctx.rover).take(targetWETH);
            if (fromRover > 0) {
                uint usdcForRover = convert(fromRover, price, false);
                IRover(ctx.rover).depositUSDC(usdcForRover, price);
                wethOut = fromRover;
                usdcIn = usdcIn > usdcForRover ?
                         usdcIn - usdcForRover : 0;
            }
        } if (usdcIn > 0) wethOut += swapUSDCtoWETH(ctx, usdcIn, price);

        // Sweep USDC that arrived during this call but wasn't consumed
        uint usdcAfter = IERC20(ctx.usdc).balanceOf(address(this));
        if (usdcAfter > usdcBefore) {
            uint stranded = usdcAfter - usdcBefore;
            supplyAAVE(ctx.vault, ctx.usdc,
                       stranded, address(this), ctx.hub);
        }
    }


    /// @notice Unified token sourcing via Rover → V3
    /// @param target Amount of output token needed
    /// @param input Available input token to swap
    /// @param price Current price for external swaps
    /// @param forUSD true = WETH→USDC, false = USDC→WETH
    function source(Types.AuxContext memory ctx,
        uint target, uint input, uint price,
        bool forUSD) external returns
        (uint got, uint used) {
        if (forUSD) {
            // Want USDC from WETH...
            if (target > 0 && input > 0) {
                uint selling = Math.min(convert(target,
                                  price, true), input);
                if (selling > 0) {
                    got = sourceExternalUSD(
                        ctx, selling, price);

                    used = selling;
                }
            }
        } else { // Want WETH from USDC...
            if (target > 0 && input > 0) {
                used = Math.min(convert(target,
                          price, false), input);

                if (used > 0)
                    got = sourceExternalWETH(
                            ctx, used, price);
            }
        }
    }

    function routeSwap(Types.AuxContext memory ctx,
        Types.RouteParams memory p) external returns
        (uint out, uint poolSupplied) { uint remainder;
        if (!isManipulated(getPrice(p.sqrtPriceX96,
                IVogueCore(ctx.core).token1isETH()), p.v4Price, 2)) {
            uint pooled = Math.min(p.amount, convert(p.pooled,
                              p.v4Price, p.token != address(0)));
            if (pooled > 0) {
                if (p.token != address(0)) {
                    supplyAAVE(ctx.vault, ctx.weth,
                            pooled, address(this), ctx.hub);
                    poolSupplied = pooled;
                } out = IVogueCore(ctx.core).swap(p.sqrtPriceX96,
                    p.recipient, p.zeroForOne, p.token, pooled);
            } remainder = p.amount - pooled;
        } else remainder = p.amount;
        if (remainder > 0) {
            require(!isV3Manipulated(ctx.v3Pool,
                IVogueCore(ctx.core).token1isETH(), p.v3Price));

            if (p.token == address(0)) {
                remainder = IAux(address(this)).take(address(this),
                                           remainder, ctx.usdc, 0);
                if (remainder > 0) {
                    remainder = sourceExternalWETH(ctx,
                              remainder, p.v3Price);

                    WETH9(payable(ctx.weth)).withdraw(remainder);
                    { (bool ok,) = p.recipient.call{
                                   value: remainder}("");
                    require(ok); } out += remainder;
                }
            } else { remainder = sourceExternalUSD(ctx,
                            remainder, p.v3Price);

                IERC20(ctx.usdc).transfer(
                     p.recipient, remainder);
                          out += remainder;
            }
        }
    }

    /// @notice Swap USDC→WETH via V3, capped at pool liquidity
    function swapUSDCtoWETH(Types.AuxContext memory ctx,
        uint amountIn, uint price) public returns (uint amountOut) {
        uint poolWETH = IERC20(ctx.weth).balanceOf(ctx.v3Pool);
        uint max = convert(poolWETH, price, false);
        if (amountIn > max) amountIn = max;
        if (amountIn > 0) {
            uint minOut = convert(amountIn, price, true) * 99500 / 100000; // TODO slippage?
            (bool ok, bytes memory ret) = ctx.v3Router.call(abi.encodeWithSelector(
                ISwapRouter.exactInput.selector, ISwapRouter.ExactInputParams(
                    abi.encodePacked(ctx.usdc, ctx.v3Fee, ctx.weth),
                    address(this), block.timestamp, amountIn, minOut)));
            if (ok && ret.length >= 32) amountOut = abi.decode(ret, (uint));
        }
    }

    /// @notice Get available AAVE liquidity
    /// (min of aToken balance and reserve)
    function aaveAvailableV3(address aave,
        address asset) public view returns (uint) {
        address aToken = AAVEv3(aave).getReserveAToken(asset);
        uint balance = IERC20(aToken).balanceOf(address(this));
        uint reserve = IERC20(asset).balanceOf(aToken);
        return Math.min(balance, reserve);
    }

    /// @notice Convert amount between
    // ETH (18 dec) & USD (6 dec) using price
    function convert(uint amount, uint price,
        bool toETH) public pure returns (uint) {
        return toETH ? FullMath.mulDiv(amount * 1e12, WAD, price)  // USD to ETH
                     : FullMath.mulDiv(amount, price, WAD) / 1e12; // ETH to USD
    }

}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.9.0;

library stdMath {
    int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function abs(int256 a) internal pure returns (uint256) {
        // Required or it will fail when `a = type(int256).min`
        if (a == INT256_MIN) {
            return 57896044618658097711785492504343953926634992332820282019728792003956564819968;
        }

        return uint256(a > 0 ? a : -a);
    }

    function delta(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a - b : b - a;
    }

    function delta(int256 a, int256 b) internal pure returns (uint256) {
        // a and b are of the same sign
        // this works thanks to two's complement, the left-most bit is the sign bit
        if ((a ^ b) > -1) {
            return delta(abs(a), abs(b));
        }

        // a and b are of opposite signs
        return abs(a) + abs(b);
    }

    function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 absDelta = delta(a, b);

        return absDelta * 1e18 / b;
    }

    function percentDelta(int256 a, int256 b) internal pure returns (uint256) {
        uint256 absDelta = delta(a, b);
        uint256 absB = abs(b);

        return absDelta * 1e18 / absB;
    }
}

File 13 of 78 : Types.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

library Types {
    /// @notice Vogue
    /// self-managed LP
    struct SelfManaged {
        uint created;
        address owner;
        int24 lower;
        int24 upper;
        int liq;
    }

    /// @notice Amp AAVE
    /// leveraged position
    struct viaAAVE {
        uint breakeven;
        uint supplied;
        uint borrowed;
        uint buffer;
        int price;
    }

    /// @notice Vogue LP deposit...
    /// MasterChef-style fee tracking
    struct Deposit { uint pooled_eth;
        uint usd_owed;
        uint fees_eth;
        uint fees_usd;
    }

    /// @notice routing
    struct AuxContext {
        address v3Pool;
        address v3Router;
        address weth;
        address usdc;
        address vault;
        address vogue;
        address core;
        address rover;
        uint24 v3Fee;
        address hub;
    }

    struct PositionEntry {
        uint    capital;
        uint    tokens;
        bytes32 commitmentHash;
        uint    timestamp;
        uint    revealedConfidence; // max 10000 bps, 0 = not yet revealed
    }

    struct Position {
        address user;
        uint8   side;
        uint    totalCapital;
        uint    totalTokens;
        bytes32 commitmentHash;
        bool    revealed;
        uint    revealedConfidence;  // max 10000 bps
        bool    autoRollover;
        uint    weight;              // final weight (confidence × time decay)
        bool    paidOut;
        uint    entryTimestamp;      // when capital entered this round
        uint    lastRound;           // round this position is active in
        address delegate; // our keeper so user doesnt need to manually reveal their commit
    }

    /// @dev Forensic evidence submitted by CRE workflow.
    /// Advisory only — DVM vote is sole source of truth.
    struct ForensicEvidence {
        uint8 claimedSide;
        uint8 recommendedSide;
        int maxDeviationBps;
        uint8 confidence; // 0-100
        bytes32 evidenceHash;
        // keccak256 of data
        uint timestamp;
    }

    enum Phase { Trading, Asserting, Disputed, Resolved }

    struct Market {
        uint8   numSides;       // stables.length + 1
        uint    startTime;      // initial creation
        uint    roundStartTime; // beginning of current round
        int128  b;              // LSMR liquidity parameter
        Phase   phase;
        bool    resolved;

        uint    resolutionTimestamp;
        uint8    claimedSide;  // what the asserter claims
        uint8    winningSide;  // confirmed outcome
        uint8    consecutiveRejections; // escalates bond after griefing
        bytes32  assertionId;
        address  asserter;
        uint     revealDeadline;
        uint     requestTimestamp; // when requestResolution was called

        int128[12] q;
        // LSMR quantities per side
        uint[12] capitalPerSide;
        uint    totalCapital;
        uint    positionsTotal;
        uint    positionsRevealed;

        uint    totalWinnerCapital;
        uint    totalLoserCapital;
        uint    totalWinnerWeight;
        uint    totalLoserWeight;
        bool    weightsComplete;
        bool    payoutsComplete;
        bool    assertionPending;   // blocks new buys during OOV3 liveness
        uint    positionsPaidOut;   // tracks payout progress for safe restart
        uint    positionsWeighed;   // tracks weight computation for safe weightsComplete
        uint    roundNumber;
    }

    struct RouteParams {
        uint160 sqrtPriceX96;
        bool    zeroForOne;
        address token;
        uint    amount;
        uint    pooled;
        uint    v4Price;
        uint    v3Price;
        address recipient;
    }

    struct DepegStats {
        uint   capOnSide;
        uint   capNone;
        uint   capTotal;
        bool   depegged;
        uint8  side;
        uint   avgConf;      // Bayesian prior: last round's avg confidence on this side
    }

    /// @dev Every field must be present for ABI compatibility with the
    ///      on-chain JamSettlement contract, even if our solver doesn't
    ///      use all of them.
    ///      For ERC20-only arb, pass:
    ///        sellNFTIds:         new uint256[](0)
    ///        buyNFTIds:          new uint256[](0)
    ///        sellTokenTransfers: ""  (0x)
    ///        buyTokenTransfers:  ""  (0x)
    struct JamOrder {
        address   taker;              // order creator (EOA that signed)
        address   receiver;           // where buy tokens are sent
        uint256   expiry;             // block.timestamp deadline
        uint256   nonce;              // unique per taker, prevents replay
        address   executor;           // solver address, or address(0) for open
        uint16    minFillPercent;     // 10000 = 100%, minimum acceptable fill (Bebop ABI)
        bytes32   hooksHash;          // keccak256 of hooks, or EMPTY_HOOKS_HASH
        address[] sellTokens;         // tokens taker is selling
        address[] buyTokens;          // tokens taker wants to receive
        uint256[] sellAmounts;        // amounts of each sell token
        uint256[] buyAmounts;         // minimum amounts of each buy token
        uint256[] sellNFTIds;         // NFT token IDs to sell  (empty for ERC20)
        uint256[] buyNFTIds;          // NFT token IDs to buy   (empty for ERC20)
        bytes     sellTokenTransfers; // per-token transfer cmds (empty for ERC20)
        bytes     buyTokenTransfers;  // per-token transfer cmds (empty for ERC20)
        bool      usingPermit2;       // true if taker approved via Permit2
        uint256   partnerInfo;        // encoded partner + fee data (0 = none)
    }

    /// @notice A single external call for JamSettlement to execute.
    /// @dev Matches Bebop's JamInteraction.Data exactly.
    /// Used for settlement interactions (executed by JamSettlement)
    /// and for repay swaps (executed directly by the Solver contract).
    struct Interaction {
        bool result; // if true, runInteractions checks call returns true
        address to; // target contract to call
        uint256 value; // ETH value to forward
        bytes data; // calldata for the external call
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Aux} from "./Aux.sol";
import {mock} from "./mock.sol";
import {Vogue} from "./Vogue.sol";
import {Types} from "./imports/Types.sol";
import {BasketLib} from "./imports/BasketLib.sol";

// import {VogueUni as Vogue} from "./L2/VogueUni.sol";
// import {AuxPoly as Aux} from "./L2/AuxPoly.sol";
// import {AuxBase as Aux} from "./L2/AuxBase.sol";
// import {AuxArb as Aux} from "./L2/AuxArb.sol";
// import {AuxUni as Aux} from "./L2/AuxUni.sol";

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {SafeCallback} from "v4-periphery/src/base/SafeCallback.sol";

import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "v4-core/src/types/BalanceDelta.sol";
import {TransientStateLibrary} from "v4-core/src/libraries/TransientStateLibrary.sol";

import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol";
import {CurrencySettler} from "v4-core/test/utils/CurrencySettler.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";

import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";

contract VogueCore is SafeCallback {
    using TransientStateLibrary for IPoolManager;
    using BalanceDeltaLibrary for BalanceDelta;
    using StateLibrary for IPoolManager;
    using CurrencyLibrary for Currency;
    using CurrencySettler for Currency;
    using PoolIdLibrary for PoolKey;

    int24 public initialTick;
    int24 public lastTick;
    struct Observation {
        uint32 blockTimestamp;
        int56 tickCumulative;
        bool initialized;
    }   PoolKey VANILLA;
    Observation[65535] public observations;
    uint16 public observationCardinality;
    uint16 public observationIndex;

    uint public MAX_POOLED_USD;
    uint public POOLED_ETH;
    uint public POOLED_USD;
    mock internal mockETH;
    mock internal mockUSD;

    uint constant WAD = 1e18;
    bool public token1isETH;
    Aux AUX; Vogue VOGUE;
    enum Action { Swap,
        Repack, ModLP,
        OutsideRange } // 4 actions...
    modifier onlyUs { // 2 contracts...
        require(msg.sender == address(AUX)
             || msg.sender == address(VOGUE), "403"); _;
    } bytes internal constant ZERO_BYTES = bytes("");

    constructor(IPoolManager _manager) SafeCallback(_manager) {}
    function setup(address _vogue, // vanilla pool (hookless)
        address _aux, address _poolETH) external {
        require(address(VOGUE) == address(0), "!");
        mockETH = new mock(address(this), 18);
        mockUSD = new mock(address(this), 6);
        address token0; address token1;

        // requires address currency0 < currency1
        if (address(mockETH) > address(mockUSD)) {
            token1isETH = true;
            token0 = address(mockUSD);
            token1 = address(mockETH);
        } else {
            token0 = address(mockETH);
            token1 = address(mockUSD);
        }
        VOGUE = Vogue(payable(_vogue));
        AUX = Aux(payable(_aux));
        VANILLA = PoolKey({
            currency0: Currency.wrap(address(token0)),
            currency1: Currency.wrap(address(token1)),
            fee: 420, tickSpacing: 10,
            hooks: IHooks(address(0))});

        (,int24 tickETH,,,,,) = IUniswapV3Pool(_poolETH).slot0();
        mockUSD.approve(address(poolManager), type(uint).max);
        mockETH.approve(address(poolManager), type(uint).max);

        // Adjust tick if
        // V4 and V3 have
        // different order
        if (token1isETH)
            tickETH *= AUX.token1isWETH()
                    ? int24(1) : int24(-1);
        else
            tickETH *= AUX.token1isWETH()
                    ? int24(-1) : int24(1);

        poolManager.initialize(VANILLA,
        TickMath.getSqrtPriceAtTick(tickETH));
        // Initialize the oracle observations...
        initialTick = tickETH; lastTick = tickETH;
        observations[0] = Observation({
            blockTimestamp: uint32(block.timestamp),
            tickCumulative: 0, initialized: true });
                         observationCardinality = 1;
    }

    function modLP(uint160 sqrtPriceX96, uint deltaETH,
        uint deltaUSD, int24 tickLower, int24 tickUpper,
        address sender) public onlyUs returns (uint ethSent) {
        BalanceDelta delta = abi.decode(poolManager.unlock(abi.encode(
                Action.ModLP, sqrtPriceX96, deltaETH, deltaUSD,
                tickLower, tickUpper, sender)), (BalanceDelta));
        int128 ethDelta = token1isETH ? delta.amount1() : delta.amount0();
        ethSent = ethDelta > 0 ? uint(int(ethDelta)) : 0;
    }

    function outOfRange(address sender, int liquidity,
        int24 tickLower, int24 tickUpper, address token)
        public onlyUs {
        abi.decode(poolManager.unlock(abi.encode(
            Action.OutsideRange, sender, liquidity,
            tickLower, tickUpper, token)), (BalanceDelta));
    }

    function swap(uint160 sqrtPriceX96, address sender,
        bool forOne, address token, uint amount)
        onlyUs public returns (uint out) {
        BalanceDelta delta = abi.decode(poolManager.unlock(
          abi.encode(Action.Swap, sqrtPriceX96, sender,
            forOne, token, amount)), (BalanceDelta));

        // zeroForOne=true: input token0, output token1
        // zeroForOne=false: input token1, output token0
        out = uint(int(forOne ? delta.amount1():
                                delta.amount0()));

        uint totalShares = VOGUE.totalShares();
        if (POOLED_ETH < totalShares) {
            uint shortfall = totalShares - POOLED_ETH;
            // Arb if shortfall >= 1% of total LP claims
            if (shortfall * 100 >= totalShares)
                AUX.arbETH(shortfall);
        }
    }

    function _unlockCallback(bytes calldata data)
        internal override returns (bytes memory) {
        uint8 firstByte;
        assembly {
            let word := calldataload(data.offset)
            firstByte := and(word, 0xFF)
        }
        Action discriminator = Action(firstByte);
        if (discriminator == Action.Swap)
            return _handleSwap(data[32:]);
        else if (discriminator == Action.Repack)
            return _handleRepack(data[32:]);
        else if (discriminator == Action.OutsideRange)
            return _handleOutsideRange(data[32:]);
        else if (discriminator == Action.ModLP)
            return _handleMod(data[32:]);

        return "";
    }

    function _handleSwap(bytes calldata data)
        internal returns (bytes memory) {
        (uint160 sqrtPriceX96, address sender, bool forOne,
            address token, uint amount) = abi.decode(data,
             (uint160, address, bool, address, uint));

        BalanceDelta delta = poolManager.swap(VANILLA,
            IPoolManager.SwapParams({ zeroForOne: forOne,
                amountSpecified: -int(amount), sqrtPriceLimitX96:
                    VOGUE.paddedSqrtPrice(sqrtPriceX96,
                        !forOne, 300) }), ZERO_BYTES);

        (, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
        _writeObservation(currentTick); _handleDelta(delta, true,
                                            false, sender, token);
        return abi.encode(delta);
    }

    function _handleRepack(bytes calldata data)
        internal returns (bytes memory) {
        POOLED_USD = 0; POOLED_ETH = 0;
        (uint128 myLiquidity, uint160 sqrtPriceX96,
        int24 oldTickLower, int24 oldTickUpper,
        int24 newTickLower, int24 newTickUpper) = abi.decode(data,
                  (uint128, uint160, int24, int24, int24, int24));

        (BalanceDelta delta,
         BalanceDelta fees) = _modifyLiquidity(-int(uint(myLiquidity)),
                                            oldTickLower, oldTickUpper);

        (uint delta0, uint delta1) = _handleDelta(delta, false, true,
                                            address(0), address(0));
        BalanceDelta addDelta;
        uint price = BasketLib.getPrice(
              sqrtPriceX96, token1isETH);

        if (token1isETH) {
            (delta0, delta1) = VOGUE.addLiquidityHelper(delta1, price);
            if (delta0 > 0 && delta1 > 0) {
                addDelta = _modLP(delta0, delta1, newTickLower,
                                    newTickUpper, sqrtPriceX96);

                _handleDelta(addDelta, true, false,
                            address(0), address(0));
            }
        } else {
            (delta1, delta0) = VOGUE.addLiquidityHelper(delta0, price);
            if (delta1 > 0 && delta0 > 0) {
                addDelta = _modLP(delta1, delta0, newTickLower,
                                    newTickUpper, sqrtPriceX96);

                _handleDelta(addDelta, true, false,
                            address(0), address(0));
            }
        } (, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
                                          _writeObservation(currentTick);

        return abi.encode(price, uint(int(fees.amount0())), uint(int(fees.amount1())),
                        uint(int(addDelta.amount0())), uint(int(addDelta.amount1())));
    }

    function _handleOutsideRange(bytes calldata data)
        internal returns (bytes memory) { (address sender, int liquidity,
        int24 tickLower, int24 tickUpper, address token) = abi.decode(data,
                                      (address, int, int24, int24, address));

        (BalanceDelta delta, ) = _modifyLiquidity(liquidity, tickLower, tickUpper);
        _handleDelta(delta, false, false, sender, token); return abi.encode(delta);
    }

    function _handleMod(bytes calldata data)
        internal returns (bytes memory) {
        (uint160 sqrtPriceX96, uint deltaETH, uint deltaUSD,
        int24 tickLower, int24 tickUpper, address sender) = abi.decode(
                    data, (uint160, uint, uint, int24, int24, address));

        BalanceDelta delta = _modLP(deltaUSD, deltaETH,
                    tickLower, tickUpper, sqrtPriceX96);

        bool keep = deltaUSD == 0;
        _handleDelta(delta, true,
        keep, sender, address(0));
        return abi.encode(delta);
    }

    function _handleDelta(BalanceDelta delta, bool inRange, bool keep,
        address who, address token) internal returns (uint, uint) {
        Currency usdCurrency; Currency ethCurrency;
        int128 usdDelta; int128 ethDelta;
        uint usdAmount; uint ethAmount;
        if (token1isETH) {
            usdDelta = delta.amount0();
            ethDelta = delta.amount1();
            usdCurrency = VANILLA.currency0;
            ethCurrency = VANILLA.currency1;
        } else {
            ethDelta = delta.amount0();
            usdDelta = delta.amount1();
            usdCurrency = VANILLA.currency1;
            ethCurrency = VANILLA.currency0;
        }
        if (usdDelta > 0) {
            usdAmount = uint(int(usdDelta));
            usdCurrency.take(poolManager,
            address(this), usdAmount, false);
            mockUSD.burn(usdAmount);
            if (inRange) POOLED_USD -= Math.min(
                          usdAmount, POOLED_USD);

            if (!keep && token != address(0))
            AUX.take(who, usdAmount, token, 0);
        }
        else if (usdDelta < 0) {
            usdAmount = uint(int(-usdDelta));
            mockUSD.mint(usdAmount);
            usdCurrency.settle(poolManager,
            address(this), usdAmount, false);
            if (inRange) { POOLED_USD += usdAmount;
                if (POOLED_USD > MAX_POOLED_USD)
                    MAX_POOLED_USD = POOLED_USD;
            }
        } if (ethDelta > 0) {
            ethAmount = uint(int(ethDelta));
            ethCurrency.take(poolManager,
            address(this), ethAmount, false);
            mockETH.burn(ethAmount);
            if (inRange) POOLED_ETH -= Math.min(
                          ethAmount, POOLED_ETH);

            if (who != address(0)) VOGUE.takeETH(
                                  ethAmount, who);
        } else if (ethDelta < 0) {
            ethAmount = uint(int(-ethDelta));
            mockETH.mint(ethAmount);
            ethCurrency.settle(poolManager,
            address(this), ethAmount, false);
            if (inRange) POOLED_ETH += ethAmount;
        } if (token1isETH)
            return (usdAmount, ethAmount);
        else return (ethAmount, usdAmount);
    }

    function _modifyLiquidity(int delta, int24 lowerTick, int24 upperTick)
        internal returns (BalanceDelta totalDelta, BalanceDelta feesAccrued) {
        (totalDelta, feesAccrued) = poolManager.modifyLiquidity(
            VANILLA, IPoolManager.ModifyLiquidityParams({
            tickLower: lowerTick, tickUpper: upperTick,
            liquidityDelta: delta, salt: bytes32(0) }), ZERO_BYTES);
    }

    function _modLP(uint deltaUSD, uint deltaETH,
        int24 tickLower, int24 tickUpper, uint160 sqrtPriceX96)
        internal returns (BalanceDelta totalDelta) {
        int flip = deltaUSD > 0 ? int(1) : int(-1);
        uint128 liquidity = token1isETH ? LiquidityAmounts.getLiquidityForAmount1(
                   TickMath.getSqrtPriceAtTick(tickLower), sqrtPriceX96, deltaETH):
                            LiquidityAmounts.getLiquidityForAmount0(sqrtPriceX96,
                                TickMath.getSqrtPriceAtTick(tickUpper), deltaETH);
        if (flip < 0) {
            (,, uint128 posLiquidity) = poolStats(tickLower, tickUpper);
            if (posLiquidity == 0) return BalanceDeltaLibrary.ZERO_DELTA;
            if (liquidity > posLiquidity) liquidity = posLiquidity;
        }
        (totalDelta, ) = _modifyLiquidity(flip *
        int(uint(liquidity)), tickLower, tickUpper);
    }

    function poolStats(int24 tickLower, int24 tickUpper) public view returns
        (uint160 sqrtPriceX96, int24 currentTick, uint128 liquidity) { PoolId pool;
        (pool, sqrtPriceX96, currentTick) = poolTicks();
        (liquidity,,) = poolManager.getPositionInfo(pool,
            address(this), tickLower, tickUpper, bytes32(0));
    }

    function poolTicks() public view
        returns(PoolId, uint160, int24) {
        PoolId pool = VANILLA.toId();
        (uint160 sqrtPriceX96,
         int24 currentTick,,) = poolManager.getSlot0(pool);
         return (pool, sqrtPriceX96, currentTick);
    }

    /// @notice Write a new observation to the oracle
    /// @dev Called on each swap/repack to track tick history
    /// @param tick The current tick AFTER the action
    function _writeObservation(int24 tick) internal {
        uint32 blockTimestamp = uint32(block.timestamp);
        Observation memory last = observations[observationIndex];
        // Only write if time has passed since last observation
        if (last.blockTimestamp == blockTimestamp) {
            lastTick = tick; return;
        }
        // cumulative: accumulate lastTick over the elapsed time
        uint32 delta = blockTimestamp - last.blockTimestamp;
        int56 tickCumulative = last.tickCumulative
            + int56(lastTick) * int56(uint56(delta));

        // Write to next slot (ring buffer)
        uint16 indexNext = (observationIndex + 1) % 65535;
        // Grow cardinality if needed (up to 65535 max)
        if (indexNext >= observationCardinality &&
            observationCardinality < 65535) {
            observationCardinality = indexNext + 1;
        }
        observations[indexNext] = Observation({
            blockTimestamp: blockTimestamp,
            tickCumulative: tickCumulative,
            initialized: true });

        observationIndex = indexNext;
        lastTick = tick;
    }

    /// @notice Observe tick cumulatives at given seconds ago
    /// @param secondsAgos Array of seconds ago to observe
    /// @return tickCumulatives Array of tick cumulatives at each time
    function observe(uint32[] calldata secondsAgos)
        external view returns (int56[] memory tickCumulatives) {
        tickCumulatives = new int56[](secondsAgos.length);
        uint32 time = uint32(block.timestamp);
        Observation memory latest = observations[observationIndex];
        Observation memory oldest = _getOldestObservation();
        for (uint i = 0; i < secondsAgos.length; i++) {
            uint32 target = time - secondsAgos[i];
            // Current: extrapolate forward from latest
            if (secondsAgos[i] == 0) {
                uint32 delta = time - latest.blockTimestamp;
                tickCumulatives[i] = latest.tickCumulative
                    + int56(lastTick) * int56(uint56(delta));
            }
            else if (target <= oldest.blockTimestamp) {
                // before/at oldest observation - extrapolate BACKWARDS
                // This handles "not enough history", case by the way
                uint32 beforeDelta = oldest.blockTimestamp - target;
                // Use initialTick for backward extrapolation
                // (assume tick was constant before init)...
                tickCumulatives[i] = oldest.tickCumulative -
                int56(initialTick) * int56(uint56(beforeDelta));
            } // Target is at or after latest - extrapolate forward
            else if (target >= latest.blockTimestamp) {
                uint32 delta = target - latest.blockTimestamp;
                tickCumulatives[i] = latest.tickCumulative
                    + int56(lastTick) * int56(uint56(delta));
            } else { // Target is between oldest and latest - interpolate
                tickCumulatives[i] = _interpolate(target, oldest, latest);
            }
        }
    }

    function _getOldestObservation()
        internal view returns (Observation memory) {
        // In a ring buffer, oldest is at
        // (observationIndex + 1) % cardinality
        // But only if that slot is initialized
        if (observationCardinality == 1)
            return observations[0];

        uint16 oldestIndex = (observationIndex + 1) % observationCardinality;
        Observation memory oldest = observations[oldestIndex];
        // If not initialized (ring buffer not full), oldest 0
        if (!oldest.initialized)
            return observations[0];
            return oldest;
    }

    /// @notice Interpolate between
    /// observations to find the
    /// tickCumulative at target time
    function _interpolate(uint32 target, Observation memory oldest,
        Observation memory latest) internal view returns (int56) {
        // If only 2 observations (oldest and latest), interpolate directly
        if (observationCardinality <= 2) {
          uint32 totalDelta = latest.blockTimestamp - oldest.blockTimestamp;
          uint32 targetDelta = target - oldest.blockTimestamp;
          if (totalDelta == 0) return oldest.tickCumulative;

          int56 cumulativeDelta = latest.tickCumulative - oldest.tickCumulative;
          return oldest.tickCumulative + (cumulativeDelta *
              int56(uint56(targetDelta))) / int56(uint56(totalDelta));
        } // Binary search for the bracketing pair of observations...
        // so that TWAP reflects actual price history, not just
        // a straight line between oldest and latest
        uint16 card = observationCardinality;
        uint16 oldestIdx = (observationIndex + 1) % card;
        if (!observations[oldestIdx].initialized) oldestIdx = 0;
        // Search space: offsets [0, card-1] from oldestIdx
        // Find largest offset where timestamp <= target
        uint16 lo = 0; uint16 hi = card - 1;
        while (lo < hi) {
            uint16 mid = lo + (hi - lo + 1) / 2;
            uint16 idx = (oldestIdx + mid) % card;
            if (observations[idx].blockTimestamp <= target)
                lo = mid;
            else
                hi = mid - 1;
        }
        // lo is now the offset of the observation at or just before target
        Observation memory before = observations[(oldestIdx + lo) % card];
        Observation memory later  = observations[(oldestIdx + lo + 1) % card];
        uint32 totalDelta = later.blockTimestamp - before.blockTimestamp;

        if (totalDelta == 0) return before.tickCumulative;
        uint32 targetDelta = target - before.blockTimestamp;
        int56 cumulativeDelta = later.tickCumulative - before.tickCumulative;

        return before.tickCumulative +
            (cumulativeDelta * int56(uint56(targetDelta))) /
                                int56(uint56(totalDelta));
    }

    function repack(uint128 myLiquidity,
        uint160 sqrtPriceX96, int24 oldTickLower,
        int24 oldTickUpper, int24 newTickLower, int24 newTickUpper) public onlyUs
        returns (uint price, uint fees0, uint fees1, uint delta0, uint delta1) {
        (price, fees0, fees1, delta0, delta1) = abi.decode(poolManager.unlock(
            abi.encode(Action.Repack, myLiquidity, sqrtPriceX96, oldTickLower,
                oldTickUpper, newTickLower, newTickUpper)),
                            (uint, uint, uint, uint, uint));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

// import {AuxBase as Aux} from "./L2/AuxBase.sol";
// import {AuxPoly as Aux} from "./L2/AuxPoly.sol";
// import {AuxArb as Aux} from "./L2/AuxArb.sol";
// import {AuxUni as Aux} from "./L2/AuxUni.sol";

import {Aux} from  "./Aux.sol";
import {Hook} from "./Hook.sol";
import {UMA} from  "./UMA.sol";

import {Types} from "./imports/Types.sol";
import {SortedSetLib} from "./imports/SortedSet.sol";
import {BasketLib} from "./imports/BasketLib.sol";

import {ERC6909} from "solmate/src/tokens/ERC6909.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";

import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

contract Basket is ERC6909, ReentrancyGuard {
    using SortedSetLib for SortedSetLib.Set;
    uint constant WAD = 1e18; // dollarito
    address internal _deployer; // ricardo
    uint internal _deployed; // quidmint
    uint constant CAP = 500_000 * 1e18;
    uint public seeded; // seed round
    uint public target; // ^ backing

    bool public marketCreated;
    address payable public V4;
    uint public totalSupply;
    UMA public UMA_ORACLE;
    IERC20 public USDC;
    Hook public HOOK;
    Aux public AUX;

    // ERC20 compatibility events
    event ERC20Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);

    function name() external pure returns (string memory) { return "QU!D"; }
    function symbol() external pure returns (string memory) { return "QD"; }
    function decimals() external pure returns (uint8) { return 18; }
    mapping(address => mapping(address => uint)) public allowances;

    mapping(address => uint) public balances;
    mapping(uint => uint) public totalSupplies;
    mapping(address => uint) public untouchables;

    // ─── wMedian: Haircut (1–9%, 25bps steps) ───
    // Prices depegged tokens below par in redemption.
    // TVL captures spread → stayers profit on re-peg.
    // index 0 = 9% haircut, index 32 = 1% haircut
    uint internal K = 28; // default 2% (200 bps)
    uint public SUM; uint[33] public WEIGHTS;
    uint internal TOTAL_WEIGHT; // incremental
    mapping(address => uint) public feeVotes;
    // stored as vote + 1 as we do in Aux...
    function _adjustWeight(address who,
        uint amount, bool adding) internal {
        uint v = feeVotes[who];
        if (v == 0) return; uint idx = v - 1;
        if (adding) { WEIGHTS[idx] += amount;
            TOTAL_WEIGHT += amount;
            if (idx <= K) SUM += amount;
        } else { uint w = Math.min(
                     WEIGHTS[idx], amount);
            WEIGHTS[idx] -= w;
            TOTAL_WEIGHT -= w;
            if (idx <= K) SUM -= Math.min(SUM, w);
        }
    } function _calculateMedian()
        internal { uint mid = TOTAL_WEIGHT / 2;
        if (mid == 0) { SUM = 0; return; }
        while (K >= 1 && (SUM - WEIGHTS[K]) >= mid) {
                         SUM -= WEIGHTS[K]; K -= 1; }
        while (K < 32 && SUM < mid) { K += 1;
                         SUM += WEIGHTS[K]; }
    }

    function _update(address from,
        address to, uint amount) internal {
        if (from == address(0))
            totalSupply += amount;
        else { _adjustWeight(from,
                    amount, false);
        balances[from] -= amount; }
        if (to == address(0))
            totalSupply -= amount;
        else { balances[to] += amount;
               _adjustWeight(to,
                   amount, true);
        } _calculateMedian();
        emit ERC20Transfer(from, to, amount);
    }

    function _spendAllowance(address owner,
        address spender, uint value) internal {
        uint a = allowances[owner][spender];
        if (a != type(uint).max) //
            allowances[owner][spender] = a - value;
    }

    function approve(address spender,
        uint value) public returns (bool) {
        allowances[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function auth(address who) public view returns (bool) {
        return who == address(AUX) || who == V4 // AAVEv3
            || who == address(HOOK); // vanilla horizons
    }

    function vote(uint voteIndex) external {
        require(voteIndex <= 32, "out of range");
        uint stake = balances[msg.sender];
        require(stake > 0, "no balance");
        _adjustWeight(msg.sender, stake, false);
        feeVotes[msg.sender] = voteIndex + 1;
        _adjustWeight(msg.sender, stake, true);
        _calculateMedian(); // x.com/QuidMint
    } /// @notice in bps [100..900]
    function getHaircut() // 0.8.26
        external view returns // '26
        (uint) { return 900 - K * 25; }
    constructor(address _vogue, address _aux,
        address _uma, address _usdc) {
        _deployed = block.timestamp;
        AUX = Aux(payable(_aux));
        UMA_ORACLE = UMA(_uma);
        _deployer = msg.sender;
        V4 = payable(_vogue);
        USDC = IERC20(_usdc);
    }

    function setHook(address _hook) external {
        require(msg.sender == _deployer
            && address(HOOK) == address(0));
                         HOOK = Hook(_hook);
    }

    mapping(address => SortedSetLib.Set) private perMonth;
    function currentMonth() public view returns (uint month) {
        month = (block.timestamp - _deployed) / BasketLib.MONTH;
    }

    function totalMatureBalanceOf(address owner) // matured
        public view returns (uint total) { // redeemable quid
        uint[] memory batches = perMonth[owner].getSortedSet();
        int idx = BasketLib.matureBatches(batches,
                        block.timestamp, _deployed);

        if (idx < 0) return 0;
        for (int i = idx; i >= 0; i--)
            total += balanceOf[owner][batches[uint(i)]];
    }

    function turn(address from, uint value) external
        returns (uint sent, uint seedBurned) {
        require(auth(msg.sender));
        uint seedBefore = untouchables[from];
        sent = _transferHelper(from, address(0), value);
        seedBurned = seedBefore - untouchables[from];
    }

    function _mint(address receiver,
        uint when, uint amount)
        internal override {
        totalSupplies[when] += amount;
        perMonth[receiver].insert(when);
        _update(address(0), receiver, amount);
        balanceOf[receiver][when] += amount;
        emit Transfer(msg.sender, address(0),
                    receiver, when, amount);
    }

    function mint(address pledge,
        uint amount, address token,
        uint when) external nonReentrant
        returns (uint) { uint yield; // ибо
        uint nextMonth = currentMonth() + 1;
        // this is used by Vogue.withdraw()
        if (auth(msg.sender)) { _mint(pledge,
                    nextMonth, amount);
                         return amount;
        } uint deposited = AUX.deposit(
                 pledge, token, amount);

        if (!marketCreated && token == address(USDC)) {
            uint[13] memory deposits = AUX.get_deposits();
            // amounts[2] = USDC in AAVE, amounts[11] = USYC
            if (deposits[2] + deposits[11] >= 1_515e18) {
                address[] memory stables = AUX.getStables();
                uint bond = 1_515e6; bond = AUX.take(// $1515
                address(this), bond, address(USDC), 0);
                USDC.transfer(address(UMA_ORACLE), bond / 1e12);
                UMA_ORACLE.registerMarket(stables);
                HOOK.createMarket(stables);
                marketCreated = true;
            }
        } uint decimals = IERC20(token).decimals();
          uint normalized = decimals < 18 ? deposited
                * (10 ** (18 - decimals)) : deposited;

          uint month = Math.max(when, nextMonth);
          bool isSeed = month - nextMonth > 12 &&
                        month < 24 && seeded < CAP;
          if (isSeed) {
              yield = AUX.getAverageYield() * 2;
              month -= 1; seeded += normalized;
          } else { yield = AUX.getAverageYield();
          } normalized += FullMath.mulDiv(normalized * yield,
                           month - currentMonth(), WAD * 12);
          if (isSeed) { untouchables[pledge] += normalized;
                        target += normalized; }
          _mint(pledge, month, normalized); return normalized;

    }

    function transfer(address to,
        uint value) public returns (bool) {
        require(value == _transferHelper(msg.sender,
                          to, value)); return true;
    }

    function transferFrom(address from, address to,
                uint value) public returns (bool) {
             _spendAllowance(from, msg.sender, value);
        require(value == _transferHelper(from, to, value));
        return true;
    } // times flies...make a statement; take a stand
    function _transferHelper(address from, address to,
        uint amount) internal returns (uint sent) {
        uint[] memory batches = perMonth[from].getSortedSet();
        bool turning = to == address(0); int i = turning &&
            from != address(HOOK) ? BasketLib.matureBatches(
                batches, block.timestamp, _deployed):
                             int(batches.length - 1);

        require(balances[from] >= amount);
        while (amount > 0 && i >= 0) {
            uint k = batches[uint(i)];
            uint amt = balanceOf[from][k];
            if (amt > 0) {
                amt = Math.min(amount, amt);
                balanceOf[from][k] -= amt;
                if (!turning) {
                    perMonth[to].insert(k);
                    balanceOf[to][k] += amt;
                } else
                    totalSupplies[k] -= amt;
                if (balanceOf[from][k] == 0)
                    perMonth[from].remove(k);
                amount -= amt; sent += amt;
            } i -= 1; // liable to be -1
        } // -1 means "no mature batches"
        if (sent > 0) { _update(from, to, sent);
            // ^ should burn from totalSupply
            // if necessary (to = address(0))
            if (untouchables[from] > 0) {
                uint seed = Math.min(sent,
                  untouchables[from]);

                untouchables[from] -= seed;
                if (to == address(0))
                    target -= Math.min(
                          target, seed);
                else
                    untouchables[to] += seed;
            }
        }
    }
}

File 16 of 78 : Aux.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Amp} from "./Amp.sol";
import {Vogue} from "./Vogue.sol";
import {Rover} from "./Rover.sol";
import {Basket} from "./Basket.sol";
import {Hook} from "./Hook.sol";

import {Types} from "./imports/Types.sol";
import {VogueCore} from "./VogueCore.sol";
import {FeeLib} from "./imports/FeeLib.sol";
import {BasketLib} from "./imports/BasketLib.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

interface IFlashBorrower {
    function onFlashLoan(address initiator,
             address token, uint amount,
        uint shareBps, bytes calldata data)
        external returns (bytes32);
}

interface AAVEv3 {
    function getReserveAToken(address asset)
            external view returns (address);


    function supply(address asset, uint amount,
    address onBehalfOf, uint16 referralCode) external;

    function withdraw(address asset, uint amount,
            address to) external returns (uint);
}

interface IHub {
    function getAssetId(address underlying)
            external view returns (uint256);
}

interface AAVEv4 {
    function getReserveId(address hub,
    uint assetId) external returns (uint);

    function withdraw(uint reserveId,
    uint amount, address onBehalfOf)
    external returns (uint, uint);

    function supply(uint reserveId,
    uint amount, address onBehalfOf)
    external returns (uint256, uint256);

    function getUserSuppliedAssets(uint reserveId,
        address user) external view returns (uint);

    function getUserSuppliedShares(uint reserveId,
        address user) external view returns (uint);
}

contract Aux is // Auxiliary
    Ownable, ReentrancyGuard {
    address[] public stables;
    bool public token1isWETH;

    IERC20 internal USDC; Basket internal QUID;
    Vogue internal V4; VogueCore internal CORE;
    WETH9 public WETH; Rover internal V3;
    IUniswapV3Pool internal v3PoolWETH;
    BasketLib.Metrics internal metrics;

    uint internal spValue; // current BOLD principal in SP
    uint internal spTotalYield; // cumulative harvested USD
    uint internal spPrincipalTime; // (principal * seconds)
    uint internal spLastUpdate; // last update timestamp...
    uint internal _lastTotalETH; // AAVE WETH at last sync
    uint public vogueETH; // WETH attributed to Vogue pool

    mapping(address => uint) public untouchables;
    mapping(address => address) public vaults;
    mapping(address => address) public tokens;
    mapping(address => uint) internal toIndex;

    uint public untouchable;
    // ^ in vault shares, 1e18
    address internal v3Router;
    uint constant WAD = 1e18;
    uint constant RAY = 1e27;
    uint internal lastBlock;

    address internal JAM;
    AAVEv3 internal POOL;
    uint24 internal v3Fee;
    AAVEv4 internal SPOKE;
    IHub internal HUB;
    Amp internal AMP;

    error LengthMismatch();
    error Unauthorized();
    modifier onlyUs {
        if (msg.sender != address(V4)
         && msg.sender != address(CORE)
         && msg.sender != address(QUID)
         && msg.sender != address(this))
            revert Unauthorized(); _;
    }

    bytes32 constant CALLBACK_SUCCESS = keccak256(
                "ERC3156FlashBorrower.onFlashLoan");

    /// @notice init (plug) Aux with addresses
    /// @dev optional: V3 rover & AAVE amp...
    constructor(address _vogue, address _core,
        address _amp, address _aave,
        address _v3poolWETH,
        address _v3router, address _v3,
        address[] memory _stables,
        address[] memory _vaults)
        Ownable(msg.sender) {
        POOL = AAVEv3(_aave);
        v3Router = _v3router;

        lastBlock = block.number - 1;
        v3PoolWETH = IUniswapV3Pool(_v3poolWETH);
        address token0 = v3PoolWETH.token0();
        address token1 = v3PoolWETH.token1();

        if (IERC20(token1).decimals() >
            IERC20(token0).decimals()) {
            WETH = WETH9(payable(token1));
            USDC = IERC20(token0);
            token1isWETH = true;
        } else { token1isWETH = false;
            WETH = WETH9(payable(token0));
            USDC = IERC20(token1);
        } v3Fee = v3PoolWETH.fee();
        V4 = Vogue(payable(_vogue));
        CORE = VogueCore(_core);
        if (_amp != address(0))
            AMP = Amp(payable(_amp));
        if (_v3 != address(0))
            V3 = Rover(payable(_v3));

        if (_stables.length != _vaults.length) revert LengthMismatch();
        spLastUpdate = block.timestamp; stables = _stables;
        uint len = _vaults.length - 1; metrics.last = 1;
        metrics.trackingStart = block.timestamp;

        for (uint i; i <= len; i++) {
            address stable = _stables[i];
            address vault = _vaults[i];
            toIndex[stable] = i + 1;
            tokens[vault] = stable; vaults[stable] = vault;
            stable.call(abi.encodeWithSelector(0x095ea7b3,
                                vault, type(uint).max));
        }
    } fallback() external payable {}
    function get_metrics(bool force)
        public returns (uint, uint) {
        BasketLib.Metrics memory stats = metrics;
        uint elapsed = block.timestamp - stats.last;
        if (force || elapsed > 10 minutes) {
            uint[13] memory amounts = get_deposits();
            // amounts[12] = raw TVL, amounts[0] = yield-weighted
            metrics = BasketLib.computeMetrics(stats,
                elapsed, amounts[12], amounts[0], amounts[12]);
        } return (metrics.total, metrics.yield);
    }

    function getAverageYield()
        public view returns (uint) {
        return BasketLib.getAverageYield(metrics);
    }

    function getStables() external view
        returns (address[] memory) { return stables;
    } function setQuid(address _quid, address _jam)
        external onlyOwner { QUID = Basket(_quid);
        JAM = _jam; //
        USDC.approve(v3Router, type(uint).max);
        WETH.approve(v3Router, type(uint).max);
        WETH.approve(address(V4), type(uint).max);
        WETH.approve(address(POOL), type(uint).max);
        USDC.approve(vaults[stables[stables.length - 1]], type(uint).max);
        WETH.approve(address(AMP), type(uint).max);
        USDC.approve(address(AMP), type(uint).max);
        WETH.approve(address(V3), type(uint).max);
        USDC.approve(address(V3), type(uint).max);
    } //

    function setV4(address _hub, address _spoke) external
        onlyOwner { require(!AMP.hasOpenDebt());
        uint i; uint[4] memory amounts; uint reserveId; address stable;
        uint weth = _withdrawAAVE(address(WETH), _availableETH(),
                    address(this), address(HUB) != address(0));
        for (i = 0; i < 4; i++) { stable = stables[i];
            amounts[i] = _withdrawAAVE(stable, 0,
            address(this), address(HUB) != address(0));
        } SPOKE = AAVEv4(_spoke); HUB = IHub(_hub);
        WETH.approve(_hub, type(uint).max);
        for (i = 0; i < 4; i++) {
            IERC20(stables[i]).approve(
                  _hub, type(uint).max);

            _supplyAAVE(stables[i], amounts[i],
                        address(this), true);

        } _supplyAAVE(address(WETH),
         weth, address(this), true);
        _lastTotalETH = _availableETH();
        AMP.setV4(_hub, _spoke);
        renounceOwnership();
    }

    function getTWAP(uint32 period)
        public view returns (uint price) {
        uint32[] memory secondsAgos = new uint32[](2);
        int56[] memory tickCumulatives; bool token0isUSD;
        if (period == 0) { secondsAgos[0] = 1800; secondsAgos[1] = 0;
            (tickCumulatives, ) = v3PoolWETH.observe(secondsAgos);
            period = 1800; token0isUSD = token1isWETH;
        } else { secondsAgos[0] = period; secondsAgos[1] = 0;
            tickCumulatives = CORE.observe(secondsAgos);
            token0isUSD = V4.token1isETH();
        } price = BasketLib.ticksToPrice(tickCumulatives[0],
                    tickCumulatives[1], period, token0isUSD);
    } function v3Fair(uint twapPrice) internal view returns (bool) {
        return !BasketLib.isV3Manipulated(address(v3PoolWETH),
                                      token1isWETH, twapPrice);
    } /// @param token either token we are paying or want to get
    /// @param forETH ^ for $ --> ETH, opposite for ETH --> $
    /// @param amount Amount to swap (either ETH, QD, or $)
    /// @param minOut Minimum output (slippage protection)
    function swap(address token, bool forETH, uint amount,
        uint minOut) public payable nonReentrant returns
        (uint max) { bool stable; bool zeroForOne;
        Types.AuxContext memory ctx = _buildContext();
        (uint160 sqrtPriceX96,,,) = V4.repack();
        stable = toIndex[token] > 0;
        if (!forETH) {
            if (token != address(QUID)) require(stable);
            amount = _depositETH(msg.sender, amount);
            zeroForOne = !V4.token1isETH();
            max = CORE.POOLED_USD();
        } else { max = CORE.POOLED_ETH();
            zeroForOne = V4.token1isETH();
            address vault = tokens[token];
            uint index = toIndex[vault];
            if (index > 4) { if (token == vaults[stables[10]])
                                 token = address(USDC);
                amount = _withdraw(address(this),
                                  index, amount);
            } else require(stable);
            if (token == address(QUID)) {
                (uint burned, uint seedBurned) = QUID.turn(
                                        msg.sender, amount);
                amount = burned;
                if (seedBurned > 0) {
                    for (uint i = 0; i < stables.length; i++) {
                        uint share = FullMath.mulDiv(
                            untouchables[stables[i]],
                            seedBurned, burned);
                        _tip(share, stables[i], -1);
                    }
                }
            }
            else amount = deposit(msg.sender,
                              token, amount);
                              token = address(0);
        } _syncETH();
        uint poolSupplied;
        (max, poolSupplied) = BasketLib.routeSwap(ctx,
            Types.RouteParams({ sqrtPriceX96: sqrtPriceX96,
                zeroForOne: zeroForOne, token: token,
                amount: amount, pooled: max,
                v4Price: getTWAP(1800),
                v3Price: getTWAP(0),
                recipient: msg.sender
            }));
            if (poolSupplied > 0) {
                vogueETH += poolSupplied;
                _lastTotalETH = _availableETH();
            }   require(max >= minOut);
    }

    function _buildContext() internal view
        returns (Types.AuxContext memory) {
        address aave = address(SPOKE) != address(0) ? address(SPOKE) : address(POOL);
        return Types.AuxContext({ v3Pool: address(v3PoolWETH), hub: address(HUB),
            v3Router: v3Router, weth: address(WETH), usdc: address(USDC),
            vault: aave, vogue: address(V4), core: address(CORE),
            rover: address(V3), v3Fee: v3Fee });
    }

    function arbETH(uint shortfall)
        public onlyUs returns (uint got) {
        _syncETH();
        (got,) = BasketLib.arbETH(_buildContext(),
                            shortfall, getTWAP(0));
        if (got > 0) {
            vogueETH += got;
            _lastTotalETH = _availableETH();
        }
    }
    
    /// @notice Proportional AAVE
    /// yield attribution to Vogue
    function _syncETH() internal {
        if (_lastTotalETH > 0) {
            uint avail = _availableETH();
            if (avail > _lastTotalETH)
                vogueETH += (avail - _lastTotalETH)
                          * vogueETH / _lastTotalETH;
            _lastTotalETH = avail;
        }
    }

    /// @notice Unified Vogue ETH operation
    /// @param op 0=deposit, 1=take, 2=sync
    function vogueETHOp(uint amount, uint8 op)
        external returns (uint ret) {
        require(msg.sender == address(V4));
        if (op == 0) { // deposit
            WETH.transferFrom(msg.sender, address(this), amount);
            _supplyAAVE(address(WETH), amount, address(this),
                address(HUB) != address(0)); vogueETH += amount;
        } else if (op == 1) { // take
            amount = Math.min(amount, vogueETH);
            ret = _withdrawAAVE(address(WETH), amount,
            address(this), address(HUB) != address(0));
            vogueETH -= Math.min(ret, vogueETH);
            WETH.transfer(msg.sender, ret);
        } else { // sync
            _syncETH(); ret = vogueETH;
        }
    }

    /// @notice leveraged long (borrow WETH against USDC)
    /// @dev 70% LTV on AAVE, excess USDC as collateral
    /// @param amount WETH amount to deposit in AAVE
    function leverETH(uint amount) payable
        external { uint twapPrice = getTWAP(0);
        amount = _depositETH(msg.sender, amount);
        uint usdcNeeded = BasketLib.convert(
                    amount, twapPrice, false);

        uint took = _take(address(this),
          usdcNeeded, address(USDC), 0);
        if (took <= usdcNeeded) { require(v3Fair(twapPrice));
            (uint more, uint used) = BasketLib.source(_buildContext(),
                            usdcNeeded - took, amount, twapPrice, true);
            took += more;
            amount -= used;
        } require(took >= usdcNeeded * 99 / 100);
        AMP.leverETH(msg.sender, amount, took / 1e12);
    }

    function leverUSD(uint amount, address token)
        external returns (uint usdcAmount) {
        usdcAmount = amount; uint160 sqrtPriceX96;
        if (token == address(USDC)) {
            USDC.transferFrom(msg.sender,
                address(this), usdcAmount);
        } else {
            (sqrtPriceX96,,,) = V4.repack();
            uint depositedAmount = deposit(
             msg.sender, token, usdcAmount);
            uint scale = IERC20(token).decimals() - 6;
            depositedAmount /= scale > 0 ? 10 ** scale : 1;

            // Swap stable → ETH → USDC through V4 pool
            CORE.swap(sqrtPriceX96, address(this),
            V4.token1isETH(), token, depositedAmount);
            uint ethReceived = address(this).balance;

            WETH.deposit{value: ethReceived}();
            _supplyAAVE(address(WETH), ethReceived,
            address(this), address(HUB) != address(0));
            uint usdcBefore = USDC.balanceOf(address(this));
            CORE.swap(sqrtPriceX96, address(this), !V4.token1isETH(),
                address(USDC), ethReceived); vogueETH += ethReceived;

            usdcAmount = USDC.balanceOf(
            address(this)) - usdcBefore;
        }   uint twapPrice = getTWAP(0);

        require(v3Fair(twapPrice));
        uint targetETH = BasketLib.convert(
           usdcAmount, getTWAP(1800), true);

        (uint inETH, uint spent) = BasketLib.source(
                            _buildContext(), targetETH,
                            usdcAmount, twapPrice, false);
        usdcAmount -= spent;
        require(inETH >= targetETH * 99 / 100);
        USDC.approve(address(AMP), usdcAmount);
        AMP.leverUSD(msg.sender, usdcAmount, inETH);
    }

    /// @notice Convert Basket tokens into dollars
    /// @param amount of tokens to redeem, 1e18...
    function redeem(uint amount) external {
        (uint total,) = get_metrics(false);
        uint price = getTWAP(1800); _syncETH();
        uint ethAvailable = _availableETH();

        uint pooledUSD = CORE.POOLED_USD() * 1e12;
        uint usdAvailable = total > pooledUSD ?
                            total - pooledUSD : 0;

        uint ethExcess = ethAvailable > vogueETH ?
                         ethAvailable - vogueETH : 0;

        uint ethValue = FullMath.mulDiv(
                  ethExcess, price, WAD);

        uint reserved = Math.min(amount,
                usdAvailable + ethValue);

        (uint burned, uint seedBurned) = QUID.turn(
        msg.sender, Math.min(reserved, usdAvailable));

        uint taken = _take(msg.sender, burned,
                    address(QUID), seedBurned);

        if (taken < reserved) {
            uint ethToUse = Math.min(FullMath.mulDiv(
            reserved - taken, WAD, price), ethExcess);

            if (ethToUse > 0) { price = getTWAP(0); require(v3Fair(price));
                uint received = _withdrawAAVE(address(WETH), ethToUse,
                            address(this), address(HUB) != address(0));

                received = BasketLib.sourceExternalUSD(_buildContext(),
                                                      received, price);

                if (received > 0) IERC20(stables[0]).transfer(
                                         msg.sender, received);
            }
        }
    }

    function get_deposits() public
        returns (uint[13] memory amounts) {
        amounts = BasketLib.get_deposits(address(this), address(HUB)
        != address(0) ? address(SPOKE): address(POOL), address(HUB), stables);
        address stable = stables[9]; address vault = vaults[stable];
        (uint spTotal, uint spYieldWeighted) = BasketLib.calcSPValue(vault, address(this),
                      untouchables[stable], BasketLib.SPState(spValue,
                          spTotalYield, spPrincipalTime, spLastUpdate));

        if (spTotal > 0) { amounts[12] += spTotal;
                           amounts[10] = spTotal;
                           amounts[0] += spYieldWeighted; }
    }

    /// @notice Get USYC amount redeemable today
    /// @dev msg.sender in BasketLib will be Aux
    /// @return Redeemable amount scaled to 1e18
    function getUSYCRedeemable() public view returns (uint) {
        address teller = vaults[stables[stables.length - 1]];
        return BasketLib.getUSYCRedeemable(teller);
    }

    // she let me into a conversation, conversation only kate could make
    // breaking into my imagination: whatever's there, was hers to take
    function _take(address who, uint amount, address token, uint seed)
        internal returns (uint sent) { address vault; address skip;
        uint index = toIndex[token];
        uint[13] memory amounts = get_deposits();
        if (token != address(QUID)) { skip = token;
            require(index > 0 && index < 12);
            uint fee = (FeeLib.calcFeeL1WithLookup(token, amounts,
                    stables, address(QUID.HOOK())) * WAD) / 10000;

            uint needed = (fee > 0 && fee < WAD / 10) ?
              FullMath.mulDiv(amount, WAD - fee, WAD) : amount;

            // Haircut applies to ALL redeemers during depeg, not just seed
            { Types.DepegStats memory ds = QUID.HOOK().getDepegStats(token);
              if (ds.depegged) { uint retained = FullMath.mulDiv(
                                needed, QUID.getHaircut(), 10000);
                                needed -= retained;
              }
            } if (seed > 0) { _tip(seed, token, -1);
                sent = _withdraw(who, index, needed);
                return sent;
            }
            sent = _withdraw(who, index, needed);
            amount = needed > sent ? needed - sent : 0;
            sent = BasketLib.scaleTokenAmount(sent, token, true);
            amount = BasketLib.scaleTokenAmount(amount, token, true);
        }
        if (amounts[12] == 0 || amount == 0) return sent;
        uint min = amounts[12]; amount = seed == 0 ?
                  Math.min(min, amount) : amount;
                 // amounts[12] excludes untouchable

        uint haircutBps = QUID.getHaircut();
        address hookAddr = address(QUID.HOOK());
        for (uint i = 1; i <= stables.length; i++) {
            token = stables[i - 1]; if (token == skip) continue;
            amounts[i] = FullMath.mulDiv(amount, FullMath.mulDiv(WAD,
                                       amounts[i], amounts[12]), WAD);

            if (seed > 0) _tip(FullMath.mulDiv(amounts[i],
                                seed, amount), token, -1);
             // Per-token fee: healthy tokens pay high fee during
            // depeg, depegged $ pays BASE: compensates stayers...
            uint feeBps = FeeLib.calcFeeL1(token, i - 1,
                            amounts, stables, hookAddr);
            if (feeBps > 0) {
                uint cut = FullMath.mulDiv(
                 amounts[i], feeBps, 10000); amounts[i] -= cut;
            } // Haircut: depegged token priced below par;
            // captures spread → stayers profit on re-peg
            { Types.DepegStats //
                memory ds = Hook(hookAddr).getDepegStats(token);
                if (ds.depegged) amounts[i] -= FullMath.mulDiv(
                                 amounts[i], haircutBps, 10000);
            } if (amounts[i] > 0) {
            // 6-dec tokens: USDT(i=1), USDC(2), PYUSD(3), USYC(11)
                uint divisor = (i < 4 || i == 11) ? 1e12 : 1;
                amounts[i] = _withdraw(who, i,
                        amounts[i] / divisor);
                sent += amounts[i] * divisor;
            }
        }
    } // don't check sent == passed in...
    function take(address who, uint amount,
        address token, uint seed) public onlyUs
        returns (uint) { address weth = address(WETH);
        return (token == weth) ? _withdrawAAVE(weth,
        amount, who, address(HUB) != address(0)):
                 _take(who, amount, token, seed);
    }

    function _withdraw(address to,
        uint index, uint amount) internal
        returns (uint sent) { address vault;
        // sent is 1e16 for USDC & USDT...
        if (amount == 0) return 0;
        if (index == 10) { address bold = stables[9]; vault = vaults[bold];
            BasketLib.SPWithdrawResult memory r = BasketLib.withdrawFromSP(vault,
                bold, address(WETH), amount, getTWAP(0), BasketLib.SPState(spValue,
                                    spTotalYield, spPrincipalTime, spLastUpdate));

            if (r.boldReceived == 0) return 0;
            spValue = r.newSpValue;
            spTotalYield = r.newSpTotalYield;
            spPrincipalTime = r.newSpPrincipalTime;
            spLastUpdate = r.newSpLastUpdate; sent = r.sent;
            if (r.wethGain > 0) { _supplyAAVE(address(WETH), r.wethGain,
                               address(this), address(HUB) != address(0));
                                                   vogueETH += r.wethGain;
            }
        } else if (index < 5) { vault = stables[index - 1];
            if (index == 2) { (sent,) = BasketLib.withdrawUSYC(
                               vaults[stables[10]], to, amount);
                amount -= sent;
            } if (amount > 0) sent += _withdrawAAVE(vault, amount,
                                    to, address(HUB) != address(0));
        } else if (index != 11) { vault = vaults[stables[index - 1]];
            (amount,) = BasketLib.calculateVaultWithdrawal(vault, amount);
            if (amount == 0) return 0; // skip if no shares to redeem...
                sent = IERC4626(vault).redeem(
                    amount, to, address(this));
        } else (sent,) = BasketLib.withdrawUSYC(
                vaults[stables[10]], to, amount);
    } // there's never an incentive
    // for EOAs to call this since
    // mint() is the only way to
    // get yield for a deposit...
    // so it's assumed only our
    // contracts will call this...
    function deposit(address from,
        address token, uint amount) public
        returns (uint usd) { address vault;
        if (tokens[token] != address(0)
         && token != address(POOL)) {
            amount = Math.min(
                IERC4626(token).convertToShares(amount),
                IERC4626(token).allowance(from, address(this)));
            usd = IERC4626(token).convertToAssets(amount);
            require(usd > 0 && IERC4626(token).transferFrom(from,
                                         address(this), amount));
            token = tokens[token];
        } else { uint index = toIndex[token];
            require(index > 0 && index < 12);
            usd = Math.min(amount, IERC20(token).allowance(
                                      from, address(this)));
            require(IERC20(token).transferFrom(from,
                                  address(this), usd));
            require(usd > 0);
            (usd, amount) = _supply(
                  token, index, usd);
        } uint _target = QUID.target();
        if (untouchable < _target // fee
        && msg.sender == address(QUID)) {
            uint fee = BasketLib.seedFee(usd, untouchable,
                                _target, getAverageYield());
            if (fee > 0) {
                _tip(fee, token, 1);
                if (token == address(USDC) && amount > 0
                && untouchable < _target) _tip(Math.min(
                    FullMath.mulDiv(amount, fee, usd),
                              _target - untouchable),
                    stables[stables.length - 1], 1);
            }
        }
    } function _tip(uint cut, address token, int sign) internal {
        cut = BasketLib.scaleTokenAmount(cut, token, true);
        if (sign > 0) {
            untouchables[token] += cut; untouchable += cut;
        } else {
            cut = Math.min(cut, untouchables[token]);
            untouchables[token] -= cut;
            untouchable -= Math.min(untouchable, cut);
        }
    }
    function _availableETH() internal returns (uint) {
        if (address(SPOKE) == address(0))
            return BasketLib.aaveAvailableV3(
                address(POOL), address(WETH));
        else { uint reserveId = SPOKE.getReserveId(address(HUB),
                                HUB.getAssetId(address(WETH)));
            return SPOKE.getUserSuppliedAssets(
                      reserveId, address(this));
        }
    }

    function _supplyAAVE(address asset, uint amount,
        address to, bool v4) internal returns (uint deposited) {
        if (asset == address(WETH)) _syncETH();
        deposited = BasketLib.supplyAAVE(v4 ? address(SPOKE) : address(POOL),
                                        asset, amount, to, address(HUB));
        if (asset == address(WETH))
            _lastTotalETH = _availableETH();
    }

    function _withdrawAAVE(address asset, uint amount,
        address to, bool v4) internal returns (uint drawn) {
        if (asset == address(WETH)) _syncETH();
        drawn = BasketLib.withdrawAAVE(v4 ? address(SPOKE) : address(POOL),
                                           asset, amount, to, address(HUB));
        if (asset == address(WETH))
            _lastTotalETH = _availableETH();
    }

    function _depositETH(address sender,
        uint amount) internal returns (uint sent) {
        if (msg.value > 0) { sent = msg.value;
            WETH.deposit{value: msg.value}();
        }
        if (amount > 0) { uint available = Math.min(
                WETH.allowance(sender, address(this)),
                WETH.balanceOf(sender));

            uint took = Math.min(amount, available);
            if (took > 0) { WETH.transferFrom(sender,
                                address(this), took);
                                        sent += took;
            }
        } require(sent > 0);
    } /// @param borrower receive tokens & callback
    /// @param token Stable token being borrowed...
    /// @param amount Token amount (native decimals)
    /// @param shareBps LP Profit share signals
    /// higher priority to builders/sequencers
    /// commitment (100 = 1% min, 10000 = 100%)
    /// @param data passed to borrower callback
    function flashLoan(address borrower,
        address token, uint amount, uint shareBps,
        bytes calldata data) external nonReentrant
        returns (bool) { require(msg.sender == JAM);
        uint bal = IERC20(token).balanceOf(address(this));
        uint sent = take(borrower, amount, token, 0);
        bytes32 result = IFlashBorrower(borrower).onFlashLoan(
                        borrower, token, sent, shareBps, data);
        uint repaid = IERC20(token).balanceOf(address(this)) - bal;

        require(result == CALLBACK_SUCCESS &&
            repaid >= (toIndex[token] > 0 &&
                       toIndex[token] < 4 ? sent / 1e12 : sent));
        if (token == address(WETH)) { _supplyAAVE(address(WETH),
            repaid, address(this), address(HUB) != address(0));
            vogueETH += repaid;
        } else _supply(token, toIndex[token], repaid);
        return true; // Block builders don't introspect
    } // they see priority fees, explicit bribes, not
    // internal profit splits. Bebop's orchestrator
    // could score solvers by committed shareBps,
    // routing more flow to generous solvers...
    function _supply(address token, uint index,
        uint usd) internal returns (uint, uint) {
        address vault = vaults[token]; uint amount;
        if (index == 10) // BOLD -> Stability Pool
            (spValue, spPrincipalTime, spLastUpdate) = BasketLib.depositToSP(
                        vault, usd, BasketLib.SPState(spValue, spTotalYield,
                                            spPrincipalTime, spLastUpdate));
        else if (index < 5) { // AAVE: USDT(1), USDC(2), PYUSD(3), GHO(4)
            bool v4 = address(SPOKE) != address(0);
            vault = v4 ? address(SPOKE) : address(POOL);
            if (index == 2) // USDC also deposits to USYC
                (amount, usd) = BasketLib.depositUSYC(vaults[stables[10]],
                                 vault, address(USDC), usd, address(HUB));

            if (usd > 0) _supplyAAVE(token, usd,
                            address(this), v4);
        } // DAI(5), USDS(6), FRAX(7), USDE(8), CRVUSD(9)
        else if (index != 11) // 4626 returns shares...
            usd = IERC4626(vault).convertToAssets(
                    IERC4626(vault).deposit(usd,
                                address(this)));
        return (usd, amount);
    }
}

File 17 of 78 : FixedPoint96.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
    uint8 internal constant RESOLUTION = 96;
    uint256 internal constant Q96 = 0x1000000000000000000000000;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
    using CustomRevert for bytes4;

    error SafeCastOverflow();

    /// @notice Cast a uint256 to a uint160, revert on overflow
    /// @param x The uint256 to be downcasted
    /// @return y The downcasted integer, now type uint160
    function toUint160(uint256 x) internal pure returns (uint160 y) {
        y = uint160(x);
        if (y != x) SafeCastOverflow.selector.revertWith();
    }

    /// @notice Cast a uint256 to a uint128, revert on overflow
    /// @param x The uint256 to be downcasted
    /// @return y The downcasted integer, now type uint128
    function toUint128(uint256 x) internal pure returns (uint128 y) {
        y = uint128(x);
        if (x != y) SafeCastOverflow.selector.revertWith();
    }

    /// @notice Cast a int128 to a uint128, revert on overflow or underflow
    /// @param x The int128 to be casted
    /// @return y The casted integer, now type uint128
    function toUint128(int128 x) internal pure returns (uint128 y) {
        if (x < 0) SafeCastOverflow.selector.revertWith();
        y = uint128(x);
    }

    /// @notice Cast a int256 to a int128, revert on overflow or underflow
    /// @param x The int256 to be downcasted
    /// @return y The downcasted integer, now type int128
    function toInt128(int256 x) internal pure returns (int128 y) {
        y = int128(x);
        if (y != x) SafeCastOverflow.selector.revertWith();
    }

    /// @notice Cast a uint256 to a int256, revert on overflow
    /// @param x The uint256 to be casted
    /// @return y The casted integer, now type int256
    function toInt256(uint256 x) internal pure returns (int256 y) {
        y = int256(x);
        if (y < 0) SafeCastOverflow.selector.revertWith();
    }

    /// @notice Cast a uint256 to a int128, revert on overflow
    /// @param x The uint256 to be downcasted
    /// @return The downcasted integer, now type int128
    function toInt128(uint256 x) internal pure returns (int128) {
        if (x >= 1 << 127) SafeCastOverflow.selector.revertWith();
        return int128(int256(x));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title BitMath
/// @dev This library provides functionality for computing bit properties of an unsigned integer
/// @author Solady (https://github.com/Vectorized/solady/blob/8200a70e8dc2a77ecb074fc2e99a2a0d36547522/src/utils/LibBit.sol)
library BitMath {
    /// @notice Returns the index of the most significant bit of the number,
    ///     where the least significant bit is at index 0 and the most significant bit is at index 255
    /// @param x the value for which to compute the most significant bit, must be greater than 0
    /// @return r the index of the most significant bit
    function mostSignificantBit(uint256 x) internal pure returns (uint8 r) {
        require(x > 0);

        assembly ("memory-safe") {
            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))
            // forgefmt: disable-next-item
            r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
                0x0706060506020500060203020504000106050205030304010505030400000000))
        }
    }

    /// @notice Returns the index of the least significant bit of the number,
    ///     where the least significant bit is at index 0 and the most significant bit is at index 255
    /// @param x the value for which to compute the least significant bit, must be greater than 0
    /// @return r the index of the least significant bit
    function leastSignificantBit(uint256 x) internal pure returns (uint8 r) {
        require(x > 0);

        assembly ("memory-safe") {
            // Isolate the least significant bit.
            x := and(x, sub(0, x))
            // For the upper 3 bits of the result, use a De Bruijn-like lookup.
            // Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
            // forgefmt: disable-next-item
            r := shl(5, shr(252, shl(shl(2, shr(250, mul(x,
                0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))),
                0x8040405543005266443200005020610674053026020000107506200176117077)))
            // For the lower 5 bits of the result, use a De Bruijn lookup.
            // forgefmt: disable-next-item
            r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f),
                0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Library for reverting with custom errors efficiently
/// @notice Contains functions for reverting with custom errors with different argument types efficiently
/// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with
/// `CustomError.selector.revertWith()`
/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
library CustomRevert {
    /// @dev ERC-7751 error for wrapping bubbled up reverts
    error WrappedError(address target, bytes4 selector, bytes reason, bytes details);

    /// @dev Reverts with the selector of a custom error in the scratch space
    function revertWith(bytes4 selector) internal pure {
        assembly ("memory-safe") {
            mstore(0, selector)
            revert(0, 0x04)
        }
    }

    /// @dev Reverts with a custom error with an address argument in the scratch space
    function revertWith(bytes4 selector, address addr) internal pure {
        assembly ("memory-safe") {
            mstore(0, selector)
            mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
            revert(0, 0x24)
        }
    }

    /// @dev Reverts with a custom error with an int24 argument in the scratch space
    function revertWith(bytes4 selector, int24 value) internal pure {
        assembly ("memory-safe") {
            mstore(0, selector)
            mstore(0x04, signextend(2, value))
            revert(0, 0x24)
        }
    }

    /// @dev Reverts with a custom error with a uint160 argument in the scratch space
    function revertWith(bytes4 selector, uint160 value) internal pure {
        assembly ("memory-safe") {
            mstore(0, selector)
            mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff))
            revert(0, 0x24)
        }
    }

    /// @dev Reverts with a custom error with two int24 arguments
    function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure {
        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(fmp, selector)
            mstore(add(fmp, 0x04), signextend(2, value1))
            mstore(add(fmp, 0x24), signextend(2, value2))
            revert(fmp, 0x44)
        }
    }

    /// @dev Reverts with a custom error with two uint160 arguments
    function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure {
        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(fmp, selector)
            mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
            mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
            revert(fmp, 0x44)
        }
    }

    /// @dev Reverts with a custom error with two address arguments
    function revertWith(bytes4 selector, address value1, address value2) internal pure {
        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(fmp, selector)
            mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
            mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
            revert(fmp, 0x44)
        }
    }

    /// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error
    /// @dev this method can be vulnerable to revert data bombs
    function bubbleUpAndRevertWith(
        address revertingContract,
        bytes4 revertingFunctionSelector,
        bytes4 additionalContext
    ) internal pure {
        bytes4 wrappedErrorSelector = WrappedError.selector;
        assembly ("memory-safe") {
            // Ensure the size of the revert data is a multiple of 32 bytes
            let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32)

            let fmp := mload(0x40)

            // Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason
            mstore(fmp, wrappedErrorSelector)
            mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff))
            mstore(
                add(fmp, 0x24),
                and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000)
            )
            // offset revert reason
            mstore(add(fmp, 0x44), 0x80)
            // offset additional context
            mstore(add(fmp, 0x64), add(0xa0, encodedDataSize))
            // size revert reason
            mstore(add(fmp, 0x84), returndatasize())
            // revert reason
            returndatacopy(add(fmp, 0xa4), 0, returndatasize())
            // size additional context
            mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04)
            // additional context
            mstore(
                add(fmp, add(0xc4, encodedDataSize)),
                and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000)
            )
            revert(fmp, add(0xe4, encodedDataSize))
        }
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

import "./IERC20.sol";

/// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
/// https://eips.ethereum.org/EIPS/eip-4626
interface IERC4626 is IERC20 {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    );

    /// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
    /// @dev
    /// - MUST be an ERC-20 token contract.
    /// - MUST NOT revert.
    function asset() external view returns (address assetTokenAddress);

    /// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
    /// @dev
    /// - SHOULD include any compounding that occurs from yield.
    /// - MUST be inclusive of any fees that are charged against assets in the Vault.
    /// - MUST NOT revert.
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
    /// scenario where all the conditions are met.
    /// @dev
    /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
    /// - MUST NOT show any variations depending on the caller.
    /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
    /// - MUST NOT revert.
    ///
    /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
    /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
    /// from.
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
    /// scenario where all the conditions are met.
    /// @dev
    /// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
    /// - MUST NOT show any variations depending on the caller.
    /// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
    /// - MUST NOT revert.
    ///
    /// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
    /// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
    /// from.
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
    /// through a deposit call.
    /// @dev
    /// - MUST return a limited value if receiver is subject to some deposit limit.
    /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
    /// - MUST NOT revert.
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
    /// current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
    ///   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
    ///   in the same transaction.
    /// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
    ///   deposit would be accepted, regardless if the user has enough tokens approved, etc.
    /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
    /// @dev
    /// - MUST emit the Deposit event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   deposit execution, and are accounted for during deposit.
    /// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
    ///   approving enough underlying tokens to the Vault contract, etc).
    ///
    /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
    /// @dev
    /// - MUST return a limited value if receiver is subject to some mint limit.
    /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
    /// - MUST NOT revert.
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
    /// current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
    ///   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
    ///   same transaction.
    /// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
    ///   would be accepted, regardless if the user has enough tokens approved, etc.
    /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by minting.
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
    /// @dev
    /// - MUST emit the Deposit event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
    ///   execution, and are accounted for during mint.
    /// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
    ///   approving enough underlying tokens to the Vault contract, etc).
    ///
    /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
    /// Vault, through a withdrawal call.
    /// @dev
    /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
    /// given current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
    ///   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
    ///   called
    ///   in the same transaction.
    /// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
    ///   the withdrawal would be accepted, regardless if the user has enough shares, etc.
    /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
    /// @dev
    /// - MUST emit the Withdraw event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   withdraw execution, and are accounted for during withdrawal.
    /// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
    ///   not having enough shares, etc).
    ///
    /// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
    /// Those methods should be performed separately.
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
    /// through a redeem call.
    /// @dev
    /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
    /// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
    /// given current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
    ///   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
    ///   same transaction.
    /// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
    ///   redemption would be accepted, regardless if the user has enough shares, etc.
    /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by redeeming.
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
    /// @dev
    /// - MUST emit the Withdraw event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   redeem execution, and are accounted for during redeem.
    /// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
    ///   not having enough shares, etc).
    ///
    /// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
    /// Those methods should be performed separately.
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}

// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.4 <0.9.0;

import {ERC20} from "solmate/src/tokens/ERC20.sol";

contract mock is ERC20 {
    address internal rover;
    modifier onlyVogue {
        require(msg.sender == address(rover), "403"); _;
    }
    constructor(address _rover, uint8 _decimals) 
        ERC20("mock", "mock", _decimals) {
        rover = _rover; // Vogue range...
    }
    function mint(uint amount) onlyVogue external {
        _mint(msg.sender, amount);
    }
    function burn(uint amount) onlyVogue external {
        _burn(msg.sender, amount);   
    }
}

File 26 of 78 : IUniswapV3Pool.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

import "./IUniswapV3PoolImmutables.sol";
import "./IUniswapV3PoolState.sol";
import "./IUniswapV3PoolDerivedState.sol";
import "./IUniswapV3PoolActions.sol";
import "./IUniswapV3PoolOwnerActions.sol";
import "./IUniswapV3PoolEvents.sol";

/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
    IUniswapV3PoolImmutables,
    IUniswapV3PoolState,
    IUniswapV3PoolDerivedState,
    IUniswapV3PoolActions,
    IUniswapV3PoolOwnerActions,
    IUniswapV3PoolEvents
{}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {ImmutableState} from "./ImmutableState.sol";

/// @title Safe Callback
/// @notice A contract that only allows the Uniswap v4 PoolManager to call the unlockCallback
abstract contract SafeCallback is ImmutableState, IUnlockCallback {
    constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {}

    /// @inheritdoc IUnlockCallback
    /// @dev We force the onlyPoolManager modifier by exposing a virtual function after the onlyPoolManager check.
    function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) {
        return _unlockCallback(data);
    }

    /// @dev to be implemented by the child contract, to safely guarantee the logic is only executed by the PoolManager
    function _unlockCallback(bytes calldata data) internal virtual returns (bytes memory);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {SafeCast} from "../libraries/SafeCast.sol";

/// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0
/// and the lower 128 bits represent the amount1.
type BalanceDelta is int256;

using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global;
using BalanceDeltaLibrary for BalanceDelta global;
using SafeCast for int256;

function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) {
    assembly ("memory-safe") {
        balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1))
    }
}

function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
    int256 res0;
    int256 res1;
    assembly ("memory-safe") {
        let a0 := sar(128, a)
        let a1 := signextend(15, a)
        let b0 := sar(128, b)
        let b1 := signextend(15, b)
        res0 := add(a0, b0)
        res1 := add(a1, b1)
    }
    return toBalanceDelta(res0.toInt128(), res1.toInt128());
}

function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
    int256 res0;
    int256 res1;
    assembly ("memory-safe") {
        let a0 := sar(128, a)
        let a1 := signextend(15, a)
        let b0 := sar(128, b)
        let b1 := signextend(15, b)
        res0 := sub(a0, b0)
        res1 := sub(a1, b1)
    }
    return toBalanceDelta(res0.toInt128(), res1.toInt128());
}

function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
    return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b);
}

function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
    return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b);
}

/// @notice Library for getting the amount0 and amount1 deltas from the BalanceDelta type
library BalanceDeltaLibrary {
    /// @notice A BalanceDelta of 0
    BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0);

    function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) {
        assembly ("memory-safe") {
            _amount0 := sar(128, balanceDelta)
        }
    }

    function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) {
        assembly ("memory-safe") {
            _amount1 := signextend(15, balanceDelta)
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {Currency} from "../types/Currency.sol";
import {CurrencyReserves} from "./CurrencyReserves.sol";
import {NonzeroDeltaCount} from "./NonzeroDeltaCount.sol";
import {Lock} from "./Lock.sol";

/// @notice A helper library to provide state getters that use exttload
library TransientStateLibrary {
    /// @notice returns the reserves for the synced currency
    /// @param manager The pool manager contract.

    /// @return uint256 The reserves of the currency.
    /// @dev returns 0 if the reserves are not synced or value is 0.
    /// Checks the synced currency to only return valid reserve values (after a sync and before a settle).
    function getSyncedReserves(IPoolManager manager) internal view returns (uint256) {
        if (getSyncedCurrency(manager).isAddressZero()) return 0;
        return uint256(manager.exttload(CurrencyReserves.RESERVES_OF_SLOT));
    }

    function getSyncedCurrency(IPoolManager manager) internal view returns (Currency) {
        return Currency.wrap(address(uint160(uint256(manager.exttload(CurrencyReserves.CURRENCY_SLOT)))));
    }

    /// @notice Returns the number of nonzero deltas open on the PoolManager that must be zeroed out before the contract is locked
    function getNonzeroDeltaCount(IPoolManager manager) internal view returns (uint256) {
        return uint256(manager.exttload(NonzeroDeltaCount.NONZERO_DELTA_COUNT_SLOT));
    }

    /// @notice Get the current delta for a caller in the given currency
    /// @param target The credited account address
    /// @param currency The currency for which to lookup the delta
    function currencyDelta(IPoolManager manager, address target, Currency currency) internal view returns (int256) {
        bytes32 key;
        assembly ("memory-safe") {
            mstore(0, and(target, 0xffffffffffffffffffffffffffffffffffffffff))
            mstore(32, and(currency, 0xffffffffffffffffffffffffffffffffffffffff))
            key := keccak256(0, 64)
        }
        return int256(uint256(manager.exttload(key)));
    }

    /// @notice Returns whether the contract is unlocked or not
    function isUnlocked(IPoolManager manager) internal view returns (bool) {
        return manager.exttload(Lock.IS_UNLOCKED_SLOT) != 0x0;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";

type Currency is address;

using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;

function equals(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) == Currency.unwrap(other);
}

function greaterThan(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) > Currency.unwrap(other);
}

function lessThan(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) < Currency.unwrap(other);
}

function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) >= Currency.unwrap(other);
}

/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
    /// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
    error NativeTransferFailed();

    /// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
    error ERC20TransferFailed();

    /// @notice A constant to represent the native currency
    Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));

    function transfer(Currency currency, address to, uint256 amount) internal {
        // altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
        // modified custom error selectors

        bool success;
        if (currency.isAddressZero()) {
            assembly ("memory-safe") {
                // Transfer the ETH and revert if it fails.
                success := call(gas(), to, amount, 0, 0, 0, 0)
            }
            // revert with NativeTransferFailed, containing the bubbled up error as an argument
            if (!success) {
                CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
            }
        } else {
            assembly ("memory-safe") {
                // Get a pointer to some free memory.
                let fmp := mload(0x40)

                // Write the abi-encoded calldata into memory, beginning with the function selector.
                mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

                success :=
                    and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), currency, 0, fmp, 68, 0, 32)
                    )

                // Now clean the memory we used
                mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
                mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
                mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
            }
            // revert with ERC20TransferFailed, containing the bubbled up error as an argument
            if (!success) {
                CustomRevert.bubbleUpAndRevertWith(
                    Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
                );
            }
        }
    }

    function balanceOfSelf(Currency currency) internal view returns (uint256) {
        if (currency.isAddressZero()) {
            return address(this).balance;
        } else {
            return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
        }
    }

    function balanceOf(Currency currency, address owner) internal view returns (uint256) {
        if (currency.isAddressZero()) {
            return owner.balance;
        } else {
            return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
        }
    }

    function isAddressZero(Currency currency) internal pure returns (bool) {
        return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
    }

    function toId(Currency currency) internal pure returns (uint256) {
        return uint160(Currency.unwrap(currency));
    }

    // If the upper 12 bytes are non-zero, they will be zero-ed out
    // Therefore, fromId() and toId() are not inverses of each other
    function fromId(uint256 id) internal pure returns (Currency) {
        return Currency.wrap(address(uint160(id)));
    }
}

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

import {Currency} from "../../src/types/Currency.sol";
import {IERC20Minimal} from "../../src/interfaces/external/IERC20Minimal.sol";
import {IPoolManager} from "../../src/interfaces/IPoolManager.sol";

/// @notice Library used to interact with PoolManager.sol to settle any open deltas.
/// To settle a positive delta (a credit to the user), a user may take or mint.
/// To settle a negative delta (a debt on the user), a user make transfer or burn to pay off a debt.
/// @dev Note that sync() is called before any erc-20 transfer in `settle`.
library CurrencySettler {
    /// @notice Settle (pay) a currency to the PoolManager
    /// @param currency Currency to settle
    /// @param manager IPoolManager to settle to
    /// @param payer Address of the payer, the token sender
    /// @param amount Amount to send
    /// @param burn If true, burn the ERC-6909 token, otherwise ERC20-transfer to the PoolManager
    function settle(Currency currency, IPoolManager manager, address payer, uint256 amount, bool burn) internal {
        // for native currencies or burns, calling sync is not required
        // short circuit for ERC-6909 burns to support ERC-6909-wrapped native tokens
        if (burn) {
            manager.burn(payer, currency.toId(), amount);
        } else if (currency.isAddressZero()) {
            manager.settle{value: amount}();
        } else {
            manager.sync(currency);
            if (payer != address(this)) {
                IERC20Minimal(Currency.unwrap(currency)).transferFrom(payer, address(manager), amount);
            } else {
                IERC20Minimal(Currency.unwrap(currency)).transfer(address(manager), amount);
            }
            manager.settle();
        }
    }

    /// @notice Take (receive) a currency from the PoolManager
    /// @param currency Currency to take
    /// @param manager IPoolManager to take from
    /// @param recipient Address of the recipient, the token receiver
    /// @param amount Amount to receive
    /// @param claims If true, mint the ERC-6909 token, otherwise ERC20-transfer from the PoolManager to recipient
    function take(Currency currency, IPoolManager manager, address recipient, uint256 amount, bool claims) internal {
        claims ? manager.mint(recipient, currency.toId(), amount) : manager.take(currency, recipient, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Currency} from "../types/Currency.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {IHooks} from "./IHooks.sol";
import {IERC6909Claims} from "./external/IERC6909Claims.sol";
import {IProtocolFees} from "./IProtocolFees.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {PoolId} from "../types/PoolId.sol";
import {IExtsload} from "./IExtsload.sol";
import {IExttload} from "./IExttload.sol";

/// @notice Interface for the PoolManager
interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload {
    /// @notice Thrown when a currency is not netted out after the contract is unlocked
    error CurrencyNotSettled();

    /// @notice Thrown when trying to interact with a non-initialized pool
    error PoolNotInitialized();

    /// @notice Thrown when unlock is called, but the contract is already unlocked
    error AlreadyUnlocked();

    /// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not
    error ManagerLocked();

    /// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow
    error TickSpacingTooLarge(int24 tickSpacing);

    /// @notice Pools must have a positive non-zero tickSpacing passed to #initialize
    error TickSpacingTooSmall(int24 tickSpacing);

    /// @notice PoolKey must have currencies where address(currency0) < address(currency1)
    error CurrenciesOutOfOrderOrEqual(address currency0, address currency1);

    /// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook,
    /// or on a pool that does not have a dynamic swap fee.
    error UnauthorizedDynamicLPFeeUpdate();

    /// @notice Thrown when trying to swap amount of 0
    error SwapAmountCannotBeZero();

    ///@notice Thrown when native currency is passed to a non native settlement
    error NonzeroNativeValue();

    /// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta.
    error MustClearExactPositiveDelta();

    /// @notice Emitted when a new pool is initialized
    /// @param id The abi encoded hash of the pool key struct for the new pool
    /// @param currency0 The first currency of the pool by address sort order
    /// @param currency1 The second currency of the pool by address sort order
    /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
    /// @param tickSpacing The minimum number of ticks between initialized ticks
    /// @param hooks The hooks contract address for the pool, or address(0) if none
    /// @param sqrtPriceX96 The price of the pool on initialization
    /// @param tick The initial tick of the pool corresponding to the initialized price
    event Initialize(
        PoolId indexed id,
        Currency indexed currency0,
        Currency indexed currency1,
        uint24 fee,
        int24 tickSpacing,
        IHooks hooks,
        uint160 sqrtPriceX96,
        int24 tick
    );

    /// @notice Emitted when a liquidity position is modified
    /// @param id The abi encoded hash of the pool key struct for the pool that was modified
    /// @param sender The address that modified the pool
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param liquidityDelta The amount of liquidity that was added or removed
    /// @param salt The extra data to make positions unique
    event ModifyLiquidity(
        PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt
    );

    /// @notice Emitted for swaps between currency0 and currency1
    /// @param id The abi encoded hash of the pool key struct for the pool that was modified
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param amount0 The delta of the currency0 balance of the pool
    /// @param amount1 The delta of the currency1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of the price of the pool after the swap
    /// @param fee The swap fee in hundredths of a bip
    event Swap(
        PoolId indexed id,
        address indexed sender,
        int128 amount0,
        int128 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick,
        uint24 fee
    );

    /// @notice Emitted for donations
    /// @param id The abi encoded hash of the pool key struct for the pool that was donated to
    /// @param sender The address that initiated the donate call
    /// @param amount0 The amount donated in currency0
    /// @param amount1 The amount donated in currency1
    event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1);

    /// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement
    /// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract.
    /// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee`
    /// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)`
    /// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)`
    function unlock(bytes calldata data) external returns (bytes memory);

    /// @notice Initialize the state for a given pool ID
    /// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
    /// @param key The pool key for the pool to initialize
    /// @param sqrtPriceX96 The initial square root price
    /// @return tick The initial tick of the pool
    function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);

    struct ModifyLiquidityParams {
        // the lower and upper tick of the position
        int24 tickLower;
        int24 tickUpper;
        // how to modify the liquidity
        int256 liquidityDelta;
        // a value to set if you want unique liquidity positions at the same range
        bytes32 salt;
    }

    /// @notice Modify the liquidity for the given pool
    /// @dev Poke by calling with a zero liquidityDelta
    /// @param key The pool to modify liquidity in
    /// @param params The parameters for modifying the liquidity
    /// @param hookData The data to pass through to the add/removeLiquidity hooks
    /// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable
    /// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes
    /// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value
    /// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued)
    /// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
    function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
        external
        returns (BalanceDelta callerDelta, BalanceDelta feesAccrued);

    struct SwapParams {
        /// Whether to swap token0 for token1 or vice versa
        bool zeroForOne;
        /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
        int256 amountSpecified;
        /// The sqrt price at which, if reached, the swap will stop executing
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swap against the given pool
    /// @param key The pool to swap in
    /// @param params The parameters for swapping
    /// @param hookData The data to pass through to the swap hooks
    /// @return swapDelta The balance delta of the address swapping
    /// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified.
    /// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG
    /// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta.
    function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
        external
        returns (BalanceDelta swapDelta);

    /// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
    /// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
    /// Donors should keep this in mind when designing donation mechanisms.
    /// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
    /// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
    /// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
    /// Read the comments in `Pool.swap()` for more information about this.
    /// @param key The key of the pool to donate to
    /// @param amount0 The amount of currency0 to donate
    /// @param amount1 The amount of currency1 to donate
    /// @param hookData The data to pass through to the donate hooks
    /// @return BalanceDelta The delta of the caller after the donate
    function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
        external
        returns (BalanceDelta);

    /// @notice Writes the current ERC20 balance of the specified currency to transient storage
    /// This is used to checkpoint balances for the manager and derive deltas for the caller.
    /// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped
    /// for native tokens because the amount to settle is determined by the sent value.
    /// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle
    /// native funds, this function can be called with the native currency to then be able to settle the native currency
    function sync(Currency currency) external;

    /// @notice Called by the user to net out some value owed to the user
    /// @dev Will revert if the requested amount is not available, consider using `mint` instead
    /// @dev Can also be used as a mechanism for free flash loans
    /// @param currency The currency to withdraw from the pool manager
    /// @param to The address to withdraw to
    /// @param amount The amount of currency to withdraw
    function take(Currency currency, address to, uint256 amount) external;

    /// @notice Called by the user to pay what is owed
    /// @return paid The amount of currency settled
    function settle() external payable returns (uint256 paid);

    /// @notice Called by the user to pay on behalf of another address
    /// @param recipient The address to credit for the payment
    /// @return paid The amount of currency settled
    function settleFor(address recipient) external payable returns (uint256 paid);

    /// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently.
    /// A call to clear will zero out a positive balance WITHOUT a corresponding transfer.
    /// @dev This could be used to clear a balance that is considered dust.
    /// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared.
    function clear(Currency currency, uint256 amount) external;

    /// @notice Called by the user to move value into ERC6909 balance
    /// @param to The address to mint the tokens to
    /// @param id The currency address to mint to ERC6909s, as a uint256
    /// @param amount The amount of currency to mint
    /// @dev The id is converted to a uint160 to correspond to a currency address
    /// If the upper 12 bytes are not 0, they will be 0-ed out
    function mint(address to, uint256 id, uint256 amount) external;

    /// @notice Called by the user to move value from ERC6909 balance
    /// @param from The address to burn the tokens from
    /// @param id The currency address to burn from ERC6909s, as a uint256
    /// @param amount The amount of currency to burn
    /// @dev The id is converted to a uint160 to correspond to a currency address
    /// If the upper 12 bytes are not 0, they will be 0-ed out
    function burn(address from, uint256 id, uint256 amount) external;

    /// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees.
    /// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
    /// @param key The key of the pool to update dynamic LP fees for
    /// @param newDynamicLPFee The new dynamic pool LP fee
    function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

type PoolId is bytes32;

/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
    /// @notice Returns value equal to keccak256(abi.encode(poolKey))
    function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
        assembly ("memory-safe") {
            // 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
            poolId := keccak256(poolKey, 0xa0)
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {PoolId} from "../types/PoolId.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {Position} from "./Position.sol";

/// @notice A helper library to provide state getters that use extsload
library StateLibrary {
    /// @notice index of pools mapping in the PoolManager
    bytes32 public constant POOLS_SLOT = bytes32(uint256(6));

    /// @notice index of feeGrowthGlobal0X128 in Pool.State
    uint256 public constant FEE_GROWTH_GLOBAL0_OFFSET = 1;

    // feeGrowthGlobal1X128 offset in Pool.State = 2

    /// @notice index of liquidity in Pool.State
    uint256 public constant LIQUIDITY_OFFSET = 3;

    /// @notice index of TicksInfo mapping in Pool.State: mapping(int24 => TickInfo) ticks;
    uint256 public constant TICKS_OFFSET = 4;

    /// @notice index of tickBitmap mapping in Pool.State
    uint256 public constant TICK_BITMAP_OFFSET = 5;

    /// @notice index of Position.State mapping in Pool.State: mapping(bytes32 => Position.State) positions;
    uint256 public constant POSITIONS_OFFSET = 6;

    /**
     * @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee
     * @dev Corresponds to pools[poolId].slot0
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision.
     * @return tick The current tick of the pool.
     * @return protocolFee The protocol fee of the pool.
     * @return lpFee The swap fee of the pool.
     */
    function getSlot0(IPoolManager manager, PoolId poolId)
        internal
        view
        returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)
    {
        // slot key of Pool.State value: `pools[poolId]`
        bytes32 stateSlot = _getPoolStateSlot(poolId);

        bytes32 data = manager.extsload(stateSlot);

        //   24 bits  |24bits|24bits      |24 bits|160 bits
        // 0x000000   |000bb8|000000      |ffff75 |0000000000000000fe3aa841ba359daa0ea9eff7
        // ---------- | fee  |protocolfee | tick  | sqrtPriceX96
        assembly ("memory-safe") {
            // bottom 160 bits of data
            sqrtPriceX96 := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            // next 24 bits of data
            tick := signextend(2, shr(160, data))
            // next 24 bits of data
            protocolFee := and(shr(184, data), 0xFFFFFF)
            // last 24 bits of data
            lpFee := and(shr(208, data), 0xFFFFFF)
        }
    }

    /**
     * @notice Retrieves the tick information of a pool at a specific tick.
     * @dev Corresponds to pools[poolId].ticks[tick]
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param tick The tick to retrieve information for.
     * @return liquidityGross The total position liquidity that references this tick
     * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
     * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
     * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
     */
    function getTickInfo(IPoolManager manager, PoolId poolId, int24 tick)
        internal
        view
        returns (
            uint128 liquidityGross,
            int128 liquidityNet,
            uint256 feeGrowthOutside0X128,
            uint256 feeGrowthOutside1X128
        )
    {
        bytes32 slot = _getTickInfoSlot(poolId, tick);

        // read all 3 words of the TickInfo struct
        bytes32[] memory data = manager.extsload(slot, 3);
        assembly ("memory-safe") {
            let firstWord := mload(add(data, 32))
            liquidityNet := sar(128, firstWord)
            liquidityGross := and(firstWord, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            feeGrowthOutside0X128 := mload(add(data, 64))
            feeGrowthOutside1X128 := mload(add(data, 96))
        }
    }

    /**
     * @notice Retrieves the liquidity information of a pool at a specific tick.
     * @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param tick The tick to retrieve liquidity for.
     * @return liquidityGross The total position liquidity that references this tick
     * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
     */
    function getTickLiquidity(IPoolManager manager, PoolId poolId, int24 tick)
        internal
        view
        returns (uint128 liquidityGross, int128 liquidityNet)
    {
        bytes32 slot = _getTickInfoSlot(poolId, tick);

        bytes32 value = manager.extsload(slot);
        assembly ("memory-safe") {
            liquidityNet := sar(128, value)
            liquidityGross := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        }
    }

    /**
     * @notice Retrieves the fee growth outside a tick range of a pool
     * @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param tick The tick to retrieve fee growth for.
     * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
     * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
     */
    function getTickFeeGrowthOutside(IPoolManager manager, PoolId poolId, int24 tick)
        internal
        view
        returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128)
    {
        bytes32 slot = _getTickInfoSlot(poolId, tick);

        // offset by 1 word, since the first word is liquidityGross + liquidityNet
        bytes32[] memory data = manager.extsload(bytes32(uint256(slot) + 1), 2);
        assembly ("memory-safe") {
            feeGrowthOutside0X128 := mload(add(data, 32))
            feeGrowthOutside1X128 := mload(add(data, 64))
        }
    }

    /**
     * @notice Retrieves the global fee growth of a pool.
     * @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @return feeGrowthGlobal0 The global fee growth for token0.
     * @return feeGrowthGlobal1 The global fee growth for token1.
     * @dev Note that feeGrowthGlobal can be artificially inflated
     * For pools with a single liquidity position, actors can donate to themselves to freely inflate feeGrowthGlobal
     * atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
     */
    function getFeeGrowthGlobals(IPoolManager manager, PoolId poolId)
        internal
        view
        returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1)
    {
        // slot key of Pool.State value: `pools[poolId]`
        bytes32 stateSlot = _getPoolStateSlot(poolId);

        // Pool.State, `uint256 feeGrowthGlobal0X128`
        bytes32 slot_feeGrowthGlobal0X128 = bytes32(uint256(stateSlot) + FEE_GROWTH_GLOBAL0_OFFSET);

        // read the 2 words of feeGrowthGlobal
        bytes32[] memory data = manager.extsload(slot_feeGrowthGlobal0X128, 2);
        assembly ("memory-safe") {
            feeGrowthGlobal0 := mload(add(data, 32))
            feeGrowthGlobal1 := mload(add(data, 64))
        }
    }

    /**
     * @notice Retrieves total the liquidity of a pool.
     * @dev Corresponds to pools[poolId].liquidity
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @return liquidity The liquidity of the pool.
     */
    function getLiquidity(IPoolManager manager, PoolId poolId) internal view returns (uint128 liquidity) {
        // slot key of Pool.State value: `pools[poolId]`
        bytes32 stateSlot = _getPoolStateSlot(poolId);

        // Pool.State: `uint128 liquidity`
        bytes32 slot = bytes32(uint256(stateSlot) + LIQUIDITY_OFFSET);

        liquidity = uint128(uint256(manager.extsload(slot)));
    }

    /**
     * @notice Retrieves the tick bitmap of a pool at a specific tick.
     * @dev Corresponds to pools[poolId].tickBitmap[tick]
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param tick The tick to retrieve the bitmap for.
     * @return tickBitmap The bitmap of the tick.
     */
    function getTickBitmap(IPoolManager manager, PoolId poolId, int16 tick)
        internal
        view
        returns (uint256 tickBitmap)
    {
        // slot key of Pool.State value: `pools[poolId]`
        bytes32 stateSlot = _getPoolStateSlot(poolId);

        // Pool.State: `mapping(int16 => uint256) tickBitmap;`
        bytes32 tickBitmapMapping = bytes32(uint256(stateSlot) + TICK_BITMAP_OFFSET);

        // slot id of the mapping key: `pools[poolId].tickBitmap[tick]
        bytes32 slot = keccak256(abi.encodePacked(int256(tick), tickBitmapMapping));

        tickBitmap = uint256(manager.extsload(slot));
    }

    /**
     * @notice Retrieves the position information of a pool without needing to calculate the `positionId`.
     * @dev Corresponds to pools[poolId].positions[positionId]
     * @param poolId The ID of the pool.
     * @param owner The owner of the liquidity position.
     * @param tickLower The lower tick of the liquidity range.
     * @param tickUpper The upper tick of the liquidity range.
     * @param salt The bytes32 randomness to further distinguish position state.
     * @return liquidity The liquidity of the position.
     * @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
     * @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
     */
    function getPositionInfo(
        IPoolManager manager,
        PoolId poolId,
        address owner,
        int24 tickLower,
        int24 tickUpper,
        bytes32 salt
    ) internal view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) {
        // positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
        bytes32 positionKey = Position.calculatePositionKey(owner, tickLower, tickUpper, salt);

        (liquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128) = getPositionInfo(manager, poolId, positionKey);
    }

    /**
     * @notice Retrieves the position information of a pool at a specific position ID.
     * @dev Corresponds to pools[poolId].positions[positionId]
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param positionId The ID of the position.
     * @return liquidity The liquidity of the position.
     * @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
     * @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
     */
    function getPositionInfo(IPoolManager manager, PoolId poolId, bytes32 positionId)
        internal
        view
        returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
    {
        bytes32 slot = _getPositionInfoSlot(poolId, positionId);

        // read all 3 words of the Position.State struct
        bytes32[] memory data = manager.extsload(slot, 3);

        assembly ("memory-safe") {
            liquidity := mload(add(data, 32))
            feeGrowthInside0LastX128 := mload(add(data, 64))
            feeGrowthInside1LastX128 := mload(add(data, 96))
        }
    }

    /**
     * @notice Retrieves the liquidity of a position.
     * @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieiving liquidity as compared to getPositionInfo
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param positionId The ID of the position.
     * @return liquidity The liquidity of the position.
     */
    function getPositionLiquidity(IPoolManager manager, PoolId poolId, bytes32 positionId)
        internal
        view
        returns (uint128 liquidity)
    {
        bytes32 slot = _getPositionInfoSlot(poolId, positionId);
        liquidity = uint128(uint256(manager.extsload(slot)));
    }

    /**
     * @notice Calculate the fee growth inside a tick range of a pool
     * @dev pools[poolId].feeGrowthInside0LastX128 in Position.State is cached and can become stale. This function will calculate the up to date feeGrowthInside
     * @param manager The pool manager contract.
     * @param poolId The ID of the pool.
     * @param tickLower The lower tick of the range.
     * @param tickUpper The upper tick of the range.
     * @return feeGrowthInside0X128 The fee growth inside the tick range for token0.
     * @return feeGrowthInside1X128 The fee growth inside the tick range for token1.
     */
    function getFeeGrowthInside(IPoolManager manager, PoolId poolId, int24 tickLower, int24 tickUpper)
        internal
        view
        returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128)
    {
        (uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) = getFeeGrowthGlobals(manager, poolId);

        (uint256 lowerFeeGrowthOutside0X128, uint256 lowerFeeGrowthOutside1X128) =
            getTickFeeGrowthOutside(manager, poolId, tickLower);
        (uint256 upperFeeGrowthOutside0X128, uint256 upperFeeGrowthOutside1X128) =
            getTickFeeGrowthOutside(manager, poolId, tickUpper);
        (, int24 tickCurrent,,) = getSlot0(manager, poolId);
        unchecked {
            if (tickCurrent < tickLower) {
                feeGrowthInside0X128 = lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
                feeGrowthInside1X128 = lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
            } else if (tickCurrent >= tickUpper) {
                feeGrowthInside0X128 = upperFeeGrowthOutside0X128 - lowerFeeGrowthOutside0X128;
                feeGrowthInside1X128 = upperFeeGrowthOutside1X128 - lowerFeeGrowthOutside1X128;
            } else {
                feeGrowthInside0X128 = feeGrowthGlobal0X128 - lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
                feeGrowthInside1X128 = feeGrowthGlobal1X128 - lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
            }
        }
    }

    function _getPoolStateSlot(PoolId poolId) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(PoolId.unwrap(poolId), POOLS_SLOT));
    }

    function _getTickInfoSlot(PoolId poolId, int24 tick) internal pure returns (bytes32) {
        // slot key of Pool.State value: `pools[poolId]`
        bytes32 stateSlot = _getPoolStateSlot(poolId);

        // Pool.State: `mapping(int24 => TickInfo) ticks`
        bytes32 ticksMappingSlot = bytes32(uint256(stateSlot) + TICKS_OFFSET);

        // slot key of the tick key: `pools[poolId].ticks[tick]
        return keccak256(abi.encodePacked(int256(tick), ticksMappingSlot));
    }

    function _getPositionInfoSlot(PoolId poolId, bytes32 positionId) internal pure returns (bytes32) {
        // slot key of Pool.State value: `pools[poolId]`
        bytes32 stateSlot = _getPoolStateSlot(poolId);

        // Pool.State: `mapping(bytes32 => Position.State) positions;`
        bytes32 positionMapping = bytes32(uint256(stateSlot) + POSITIONS_OFFSET);

        // slot of the mapping key: `pools[poolId].positions[positionId]
        return keccak256(abi.encodePacked(positionId, positionMapping));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {IPoolManager} from "./IPoolManager.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";

/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
    /// @notice The hook called before the state of a pool is initialized
    /// @param sender The initial msg.sender for the initialize call
    /// @param key The key for the pool being initialized
    /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
    /// @return bytes4 The function selector for the hook
    function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);

    /// @notice The hook called after the state of a pool is initialized
    /// @param sender The initial msg.sender for the initialize call
    /// @param key The key for the pool being initialized
    /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
    /// @param tick The current tick after the state of a pool is initialized
    /// @return bytes4 The function selector for the hook
    function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
        external
        returns (bytes4);

    /// @notice The hook called before liquidity is added
    /// @param sender The initial msg.sender for the add liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for adding liquidity
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function beforeAddLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4);

    /// @notice The hook called after liquidity is added
    /// @param sender The initial msg.sender for the add liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for adding liquidity
    /// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
    /// @param feesAccrued The fees accrued since the last time fees were collected from this position
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
    /// @return bytes4 The function selector for the hook
    /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
    function afterAddLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        BalanceDelta delta,
        BalanceDelta feesAccrued,
        bytes calldata hookData
    ) external returns (bytes4, BalanceDelta);

    /// @notice The hook called before liquidity is removed
    /// @param sender The initial msg.sender for the remove liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for removing liquidity
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function beforeRemoveLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4);

    /// @notice The hook called after liquidity is removed
    /// @param sender The initial msg.sender for the remove liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for removing liquidity
    /// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
    /// @param feesAccrued The fees accrued since the last time fees were collected from this position
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
    function afterRemoveLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        BalanceDelta delta,
        BalanceDelta feesAccrued,
        bytes calldata hookData
    ) external returns (bytes4, BalanceDelta);

    /// @notice The hook called before a swap
    /// @param sender The initial msg.sender for the swap call
    /// @param key The key for the pool
    /// @param params The parameters for the swap
    /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    /// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
    /// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4, BeforeSwapDelta, uint24);

    /// @notice The hook called after a swap
    /// @param sender The initial msg.sender for the swap call
    /// @param key The key for the pool
    /// @param params The parameters for the swap
    /// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
    /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    /// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
    function afterSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        BalanceDelta delta,
        bytes calldata hookData
    ) external returns (bytes4, int128);

    /// @notice The hook called before donate
    /// @param sender The initial msg.sender for the donate call
    /// @param key The key for the pool
    /// @param amount0 The amount of token0 being donated
    /// @param amount1 The amount of token1 being donated
    /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function beforeDonate(
        address sender,
        PoolKey calldata key,
        uint256 amount0,
        uint256 amount1,
        bytes calldata hookData
    ) external returns (bytes4);

    /// @notice The hook called after donate
    /// @param sender The initial msg.sender for the donate call
    /// @param key The key for the pool
    /// @param amount0 The amount of token0 being donated
    /// @param amount1 The amount of token1 being donated
    /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function afterDonate(
        address sender,
        PoolKey calldata key,
        uint256 amount0,
        uint256 amount1,
        bytes calldata hookData
    ) external returns (bytes4);
}

File 36 of 78 : PoolKey.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";

using PoolIdLibrary for PoolKey global;

/// @notice Returns the key for identifying a pool
struct PoolKey {
    /// @notice The lower currency of the pool, sorted numerically
    Currency currency0;
    /// @notice The higher currency of the pool, sorted numerically
    Currency currency1;
    /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
    uint24 fee;
    /// @notice Ticks that involve positions must be a multiple of tick spacing
    int24 tickSpacing;
    /// @notice The hooks of the pool
    IHooks hooks;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {IHook, IUMA} from "./UMA.sol";
import {Basket} from "./Basket.sol";
import {Types} from "./imports/Types.sol";
import {FeeLib} from "./imports/FeeLib.sol";
/// @title N-outcome LSMR for "which stablecoin depegs this week"
/// @notice Side 0 = "none depegs". Sides 1..N = each stablecoin.
/// Single market, weekly rounds. Time decay rewards early signal.
/// Rollover positions recommit each round for fresh LMSR entry.
contract Hook is IHook { Types.Market internal _market;
    uint public constant FEE_BPS = 400; // 4% on fresh orders
    uint public constant ROLLOVER_FEE_BPS = 200; // 2% on recommit
    uint public constant MIN_ORDER = 1e18; // 1 QD (18 decimals)
    uint public constant CONSOLATION_BPS = 2000; // 20% to losers
    uint public constant REVEAL_WINDOW = 48 hours;
    int128  public constant INITIAL_B = 10_000e18;
    /// @dev decay lambda for 12-outcome weekly market.
    /// Derived from: λ_base(5) × complexity_adj(0.774)
    /// × duration_factor(0.327) = 1.265, scaled ×100 → 127.
    /// Falls in the linear-quadratic blend zone (100 < λ ≤ 200):
    /// t = 26 → 74% linear + 26% quadratic
    /// Full week (100% participation) → 100% weight
    /// Mid-week entry (50%) → ~43% weight
    /// Day 5 entry  (29%) → ~23% weight
    /// Last minute (~0%) → 10% floor
    struct RevealEntry { uint confidence; bytes32 salt; }
    Basket public immutable QUID; IUMA public immutable UMA;
    uint public constant DECAY_FLOOR = 1000; // 10% min. weight
    uint constant NEUTRAL_CONFIDENCE = 5000; // fifty-fifty
    uint public constant LAMBDA = 127; // same as liveness

    mapping(address => uint8) public stablecoinToSide; // user → side → Position
    mapping(address => mapping(uint8 => Types.Position)) internal _positions;
    mapping(address => mapping(uint8 => Types.PositionEntry[])) internal _entries;

    /// @dev Parallel freeze counters — replace single-assertion bools.
    /// Trading blocked when pendingAssertions > 0.
    /// Sells also blocked when pendingDisputes > 0.
    uint public pendingAssertions;
    uint public pendingDisputes;
    uint public accumulatedFees;

    /// @dev Σ(confidence × capital) per side
    /// this round, accumulated during reveals
    uint[12] internal _confCapAccum;
    /// @dev Total revealed capital per side this round
    uint[12] internal _revealedCapPerSide;
    /// @dev Last round's average confidence
    /// per side — used as Bayesian prior in calcRisk
    uint[12] internal _lastRoundAvgConf;

    event OrderPlaced(address indexed user, uint8 side, uint capital, uint tokens);
    event PositionSold(address indexed user, uint8 side, uint tokens, uint returned);
    event ConfidenceRevealed(address indexed user, uint8 side, uint confidence);
    event StaleWithdrawn(address indexed user, uint8 side, uint capital);
    event PayoutPushed(address indexed user, uint8 side, uint amount);
    event Recommitted(address indexed user, uint8 side, uint tokens);
    event MarketCreated(uint8 numSides); event WeightsCalculated();

    error OnlyUMA(); error InvalidSide(uint8 side, uint8 max);
    error StalePosition(uint posRound, uint mktRound);
    error OrderTooSmall(uint amount, uint minimum);
    error AlreadyRevealed(); error Unauthorized();
    error NotRollover(); error NoPosition();
    constructor(address _uma, address _quid) {
        UMA  = IUMA(_uma); QUID = Basket(_quid);
    }

    function createMarket(address[] calldata stables) external {
        require(msg.sender == address(QUID)
             || msg.sender == address(UMA));

        uint8 n = uint8(stables.length) + 1;
        require(n <= 12, "too many sides");
        require(_market.numSides == 0, "exists");

        _market.numSides = n;
        _market.startTime = block.timestamp;
        _market.roundStartTime = block.timestamp;
        _market.b = INITIAL_B; _market.roundNumber = 1;

        for (uint8 i; i < stables.length; i++)
            stablecoinToSide[stables[i]] = i + 1;

        emit MarketCreated(n);
    }

    modifier onlyUMA() {
        if (msg.sender != address(UMA))
            revert OnlyUMA(); _; }

    function trigger(uint timestamp,
        uint8 winningSide) external
        override onlyUMA { _market.resolved = true;
        _market.resolutionTimestamp = timestamp;
        _market.winningSide = winningSide;
        _market.revealDeadline = block.timestamp + REVEAL_WINDOW;
        if (pendingAssertions > 0) pendingAssertions--;
    }

    function onAssertionFiled() external override onlyUMA {
        pendingAssertions++;
    }

    function onDisputeStarted() external override onlyUMA {
        pendingDisputes++;
    }

    function onAssertionRejected() external override onlyUMA {
        if (pendingAssertions > 0) pendingAssertions--;
        // roundStartTime intentionally NOT reset — early entrants
        // keep their time-decay advantage. Resetting would let a
        // $100 false assertion wipe the entire decay curve.
    }

    function onDisputeEnded() external override onlyUMA {
        if (pendingDisputes > 0) pendingDisputes--;
    }

    function paid() external view override returns (bool) {
        return _market.payoutsComplete;
    }

    /// @notice Resets market-level state for a new round...
    /// Individual positions are NOT iterated. Stale positions
    /// are gated by lastRound checks on every interaction.
    /// Rollover users must call recommit() to re-enter...
    function resetForNewRound() external override onlyUMA {
        // Compute last round's average confidence
        // per side (Bayesian prior for next round)
        for (uint8 s; s < _market.numSides; s++) {
            uint revCap = _revealedCapPerSide[s];
            if (revCap > 0)
                _lastRoundAvgConf[s] = _confCapAccum[s] / revCap;
            else
                _lastRoundAvgConf[s] = 0;

            _confCapAccum[s] = 0;
            _revealedCapPerSide[s] = 0;
        }
        _market.resolved = false;
        _market.winningSide = 0;
        _market.resolutionTimestamp = 0;
        _market.totalCapital = 0;
        _market.positionsTotal = 0;
        _market.positionsRevealed = 0;
        _market.positionsPaidOut = 0;
        _market.positionsWeighed = 0;
        _market.totalWinnerCapital = 0;
        _market.totalLoserCapital = 0;
        _market.totalWinnerWeight = 0;
        _market.totalLoserWeight = 0;
        _market.weightsComplete = false;
        _market.payoutsComplete = false;
        _market.revealDeadline = 0;
        pendingAssertions = 0;
        pendingDisputes = 0;

        delete _market.q;
        delete _market.capitalPerSide;

        _market.roundStartTime = block.timestamp;
        _market.roundNumber++;
    }

    function getMarketCapital() external view
        override returns (uint) {
        return _market.totalCapital;
    }

    /// @notice Rollover positions must recommit each round.
    /// Re-enters existing capital on the fresh LSMR curve
    /// at opening prices (best available — first-mover reward).
    /// 200 bps fee (vs 400 bps fresh) — loyalty discount.
    /// Requires fresh confidence commitment (stale beliefs
    /// from last week shouldn't carry — risk landscape changes).
    function recommit(uint8 side, bytes32 newCommitHash)
        external { require(!_market.resolved, "resolved");
        require(pendingAssertions == 0, "assertion pending");
        require(newCommitHash != bytes32(0), "commit required");
        Types.Position storage pos = _positions[msg.sender][side];
        if (pos.user == address(0)) revert NoPosition();
        if (!pos.autoRollover) revert NotRollover();
        require(pos.lastRound < _market.roundNumber, "already current");
        require(pos.totalCapital > 0, "nothing to roll");

        uint capital = pos.totalCapital;
        // 200 bps rollover fee — cheaper than fresh (400 bps)...
        // because they already paid full freight on initial entry
        uint fee = (capital * ROLLOVER_FEE_BPS) / 10000;
        capital -= fee; accumulatedFees += fee;

        // wipe stale state, keep user + side + autoRollover
        pos.totalCapital = capital; // net after fee
        pos.totalTokens = 0; pos.revealed = false;
        pos.weight = 0; pos.paidOut = false;
        pos.revealedConfidence = 0;

        uint tokens = _buyTokens(_market,
                        side, capital);

        pos.totalTokens = tokens;
        pos.commitmentHash = newCommitHash;
        pos.entryTimestamp = block.timestamp;
        pos.lastRound = _market.roundNumber;

        _market.totalCapital += capital;
        _market.capitalPerSide[side] += capital;
        _market.positionsTotal++;

        delete _entries[msg.sender][side];
        _entries[msg.sender][side].push(Types.PositionEntry({
            capital: capital, tokens: tokens,
            commitmentHash: newCommitHash,
            timestamp: block.timestamp,
            revealedConfidence: 0 }));

        emit Recommitted(msg.sender, side, tokens);
    }

    function placeOrder(uint8 side, uint capital, // denominated in quid...
        bool autoRollover, bytes32 commitHash, address delegate) external {
        if (side >= _market.numSides) revert InvalidSide(side, _market.numSides);
        if (capital < MIN_ORDER) revert OrderTooSmall(capital, MIN_ORDER);
        require(commitHash != bytes32(0), "commit required");

        require(!_market.resolved, "resolved");
        require(pendingDisputes == 0, "dispute pending");
        require(pendingAssertions == 0, "assertion pending");
        QUID.transferFrom(msg.sender, address(this), capital);

        uint fee = (capital * FEE_BPS) / 10000;
        uint net = capital - fee;
        accumulatedFees += fee;
        uint tokens = _buyTokens(
              _market, side, net);

        Types.Position storage pos = _positions[msg.sender][side];
        if (pos.user == address(0)) { // brand new position
            pos.user = msg.sender; pos.side = side;
            pos.lastRound = _market.roundNumber;
            _market.positionsTotal++;
        } else if (pos.lastRound < _market.roundNumber) {
            // Stale position — auto-return old capital before resetting.
            // Prevents phantom capital corruption in downstream math.
            uint staleCapital = pos.totalCapital;
            if (staleCapital > 0)
                QUID.transfer(msg.sender,
                            staleCapital);

            pos.totalCapital = 0;
            pos.totalTokens = 0;
            pos.lastRound = _market.roundNumber;
            pos.revealed = false;
            pos.revealedConfidence  = 0;
            pos.weight = 0;
            pos.paidOut = false;
            _market.positionsTotal++;
            delete _entries[msg.sender][side];
        }
        pos.delegate = delegate;
        pos.totalCapital += net;
        pos.totalTokens += tokens;
        pos.autoRollover = autoRollover;
        pos.commitmentHash = commitHash;
        pos.entryTimestamp = block.timestamp;
        _entries[msg.sender][side].push(Types.PositionEntry({
            capital: net, tokens: tokens,
            commitmentHash: commitHash,
            timestamp: block.timestamp,
            revealedConfidence: 0 }));

        _market.totalCapital += net;
        _market.capitalPerSide[side] += net;
        emit OrderPlaced(msg.sender, side, net, tokens);
    }

    function sellPosition(uint8 side,
        uint tokensToSell) external {
        require(!_market.resolved, "resolved");
        require(pendingDisputes == 0, "dispute pending");
        require(pendingAssertions == 0, "assertion pending");
        Types.Position storage pos = _positions[msg.sender][side];

        if (pos.lastRound != _market.roundNumber)
            revert StalePosition(pos.lastRound, _market.roundNumber);
        require(pos.totalTokens >= tokensToSell);

        uint returned = _sellTokens(
        _market, side, tokensToSell);
        uint totalCapReduced;
        { // scope — marshal, reduce, write back
            Types.PositionEntry[] storage entries = _entries[msg.sender][side];
            uint len = entries.length; uint[] memory caps = new uint[](len);
            uint[] memory toks = new uint[](len);
            for (uint i; i < len; i++) {
                caps[i] = entries[i].capital;
                toks[i] = entries[i].tokens;
            }
            uint[] memory newCaps; uint[] memory newToks;
            (newCaps, newToks, totalCapReduced) = UMA.reduceEntries(caps,
                                    toks, tokensToSell, pos.totalTokens);
            uint writeIdx;
            for (uint i; i < len; i++) { if (newToks[i] == 0) continue;
                if (writeIdx != i) entries[writeIdx] = entries[i];
                entries[writeIdx].capital = newCaps[i];
                entries[writeIdx].tokens  = newToks[i];
                writeIdx++;
            }
            while (entries.length > writeIdx) entries.pop();
        }
        pos.totalTokens  -= tokensToSell;
        pos.totalCapital -= totalCapReduced;
        _market.totalCapital   -= totalCapReduced;
        _market.capitalPerSide[side] -= totalCapReduced;
        if (pos.totalTokens == 0) _market.positionsTotal--;

        // Cap at pro-rata capital to prevent LMSR arbitrage
        // from draining balance needed for round payouts.
        // Users profit from price appreciation via the payout
        // phase (winning side), not from sell-side arbitrage.
        returned = returned > totalCapReduced
                 ? totalCapReduced : returned;

        QUID.transfer(msg.sender, returned);
        emit PositionSold(msg.sender,
        side, tokensToSell, returned);
    }

    /// @notice Reveal confidence for all entries in a position.
    /// Each entry's commitment hash is verified independently.
    /// Weighted average confidence = Σ(entry.capital × conf) / totalCapital
    /// Rollover entries (commitmentHash = 0) get NEUTRAL_CONFIDENCE (5000).
    /// this is because their confidence was already revealed as part of resolution
    /// and it's necessary to re-commit by selling and placing a new order (optional)
    /// @param user The position owner's address. Caller must be user or their delegate.
    function batchReveal(address user, uint8 side,
        RevealEntry[] calldata reveals) external {
        require(_market.resolved, "not resolved");

        Types.Position storage pos = _positions[user][side];
        require(pos.user == user, "no position");
        require(msg.sender == user || msg.sender == pos.delegate, "not owner or delegate");

        if (pos.lastRound != _market.roundNumber)
            revert StalePosition(pos.lastRound, _market.roundNumber);
        if (pos.revealed) revert AlreadyRevealed();

        Types.PositionEntry[] storage entries = _entries[user][side];
        uint revealableCount; uint rolloverCapital;
        for (uint i; i < entries.length; i++) {
            if (entries[i].commitmentHash == bytes32(0)) {
                rolloverCapital += entries[i].capital;
            } else {
                revealableCount++;
            }
        } require(reveals.length == revealableCount, "reveal count mismatch");
        // Verify each entry's hash independently, accumulate weighted sum
        uint weightedConfSum; uint revealIdx;
        for (uint i; i < entries.length; i++) {
            if (entries[i].commitmentHash == bytes32(0)) {
                // Rollover entry — assign neutral confidence
                entries[i].revealedConfidence = NEUTRAL_CONFIDENCE;
                continue;
            } RevealEntry calldata r = reveals[revealIdx];
            require(keccak256(abi.encodePacked(r.confidence, r.salt))
                    == entries[i].commitmentHash, "hash mismatch");
            require(r.confidence >= 100 && r.confidence <= 10000
                    && r.confidence % 100 == 0, "bad confidence");

            entries[i].revealedConfidence = r.confidence;
            weightedConfSum += entries[i].capital * r.confidence;
            revealIdx++;
        }
        // Add rollover capital at neutral confidence
        weightedConfSum += rolloverCapital * NEUTRAL_CONFIDENCE;
        // Weighted average confidence across all entries
        uint weightedAvg = weightedConfSum / pos.totalCapital;

        pos.revealed = true;
        pos.revealedConfidence = weightedAvg;
        _market.positionsRevealed++;

        // Accumulate for Bayesian prior computation at round end
        _confCapAccum[side] += pos.totalCapital * weightedAvg;
        _revealedCapPerSide[side] += pos.totalCapital;

        if (side == _market.winningSide)
            _market.totalWinnerCapital += pos.totalCapital;
        else
            _market.totalLoserCapital += pos.totalCapital;

        emit ConfidenceRevealed(user, side, weightedAvg);
    }

    /// @notice Weights combine confidence quality with per-entry time decay.
    function calculateWeights(address[] calldata users, uint8[] calldata sides)
        external { require(users.length == sides.length && _market.resolved);
        require(block.timestamp >= _market.revealDeadline,
                                   "reveal window open");
        for (uint i; i < users.length; i++) {
            Types.Position storage pos = _positions[users[i]][sides[i]];
            if (!pos.revealed || pos.weight > 0 || pos.paidOut) continue;
            if (pos.lastRound != _market.roundNumber) continue;
            bool isWinner = sides[i] == _market.winningSide; uint w;
            { // scope block — marshal entries, compute, let temporaries fall off...
                Types.PositionEntry[] storage entries = _entries[users[i]][sides[i]];
                uint len = entries.length;
                uint[] memory capitals = new uint[](len);
                uint[] memory timestamps = new uint[](len);
                for (uint j; j < len; j++) {
                    capitals[j] = entries[j].capital;
                    timestamps[j] = entries[j].timestamp;
                }
                w = UMA.computeWeight(capitals, timestamps,
                _market.roundStartTime, _market.resolutionTimestamp,
                LAMBDA, DECAY_FLOOR, pos.revealedConfidence, isWinner);
            } if (w == 0) { // Rounding artifact — finalize immediately.
                // Zero weight means zero payout; skip the payout
                // phase entirely so it can't block round completion.
                pos.paidOut = true;
                _market.positionsPaidOut++;
                _market.positionsWeighed++;
                emit PayoutPushed(users[i],
                     sides[i], 0); continue;
            }
            pos.weight = w;
            if (isWinner) _market.totalWinnerWeight += w;
            else _market.totalLoserWeight += w;
            _market.positionsWeighed++;
        } // mark complete when ALL revealed positions have weights
        if (_market.positionsWeighed >= _market.positionsRevealed)
            _market.weightsComplete = true;
        emit WeightsCalculated();
    }

    function pushPayouts(address[] calldata users,
        uint8[] calldata sides) external {
        require(users.length == sides.length);
        require(_market.weightsComplete);
        uint8 winner = _market.winningSide;
        uint roundNum = _market.roundNumber;

        uint winWeight = _market.totalWinnerWeight;
        uint loseWeight = _market.totalLoserWeight;
        uint loserCap = _market.totalLoserCapital;
        for (uint i; i < users.length; i++) {
            Types.Position storage pos = _positions[users[i]][sides[i]];
            if (pos.paidOut) continue;
            // Stale non-rollover: auto-return capital
            if (pos.lastRound < roundNum) {
                if (!pos.autoRollover && pos.totalCapital > 0) {
                    uint capital = pos.totalCapital;
                    delete _positions[users[i]][sides[i]];
                    delete _entries[users[i]][sides[i]];
                    QUID.transfer(users[i], capital);
                    emit StaleWithdrawn(users[i], sides[i], capital);
                } continue;
            } if (pos.weight == 0) continue;

            pos.paidOut = true;
            _market.positionsPaidOut++;
            bool isWinner = sides[i] == winner;
            uint payout = UMA.computePayout(
                pos.totalCapital, pos.weight,
                winWeight, loseWeight,
                loserCap, CONSOLATION_BPS, isWinner
            );
            // Cap at contract balance — LMSR sell refunds can
            // exceed entry capital, so graceful degradation
            // prevents one underfunded payout from bricking
            // all subsequent payouts and market restart.
            uint bal = QUID.balances(address(this));
            if (payout > bal) payout = bal;
            if (payout > 0 && !pos.autoRollover) {
                QUID.transfer(users[i], payout);
                pos.totalCapital = 0; // QD already sent
            } else pos.totalCapital = payout; // retained
            emit PayoutPushed(users[i], sides[i], payout);
        }
        if (_market.positionsPaidOut >= _market.positionsRevealed)
            _market.payoutsComplete = true;
    }

    function settleAssertion() external {
        UMA.settleAssertion();
    }

    /// @notice True when a depeg round resolved and
    /// haircut vote is required before redemption.
    function depegPending() external view returns (bool) {
        return _market.resolved && _market.winningSide > 0;
    }

    /// @notice Burn accumulated prediction market fees.
    /// Blocked only while resolved market has pending payouts
    /// to prevent balance drain before transfers complete.
    /// Safe during trading (not resolved) since no payouts happening.
    function burnAccumulatedFees() external {
        require(!_market.resolved
        || _market.payoutsComplete);
        uint fees = accumulatedFees;
        require(fees > 0); accumulatedFees = 0;
        QUID.turn(address(this), fees);
    }

    function getLMSRPrice(uint8 side)
        external view returns (uint) {
        return FeeLib.price(_market.q,
        _market.numSides, _market.b, side);
    }

    function getLMSRCost(uint8 side, int128 delta)
        external view returns (uint) {
        return FeeLib.cost(_market.q, _market.numSides,
                              _market.b, side, delta);
    }

    function _buyTokens(Types.Market storage m,
        uint8 side, uint netCap) internal
        returns (uint tokens) { int128 deltaQ;
        (tokens, deltaQ) = UMA.buyTokens(m.q,
            m.numSides, m.b, side, netCap);
                       m.q[side] += deltaQ;
    }

    function _sellTokens(Types.Market storage m, uint8 side, uint tokSell)
        internal returns (uint returned) { int128 deltaQ;
        (returned, deltaQ) = UMA.sellTokens(m.q,
                m.numSides, m.b, side, tokSell);
                            m.q[side] -= deltaQ;
    }

    function getMarket() external
        view returns (Types.Market memory) { return _market; }
    function getPosition(address user, uint8 side) external
        view returns (Types.Position memory) {
            return _positions[user][side];
    }

    function getPositionEntries(address user, uint8 side) external
        view returns (Types.PositionEntry[] memory) {
            return _entries[user][side];
    }

    function getDepegStats(address stablecoin) external
        view returns (Types.DepegStats memory stats) {
        uint8 side = stablecoinToSide[stablecoin];
        if (side == 0) return stats;
        if (_market.numSides == 0) return stats;

        stats.capOnSide = _market.capitalPerSide[side];
        stats.capNone = _market.capitalPerSide[0];
        stats.capTotal = _market.totalCapital;

        stats.depegged = _market.resolved && _market.winningSide == side;
        stats.avgConf = _lastRoundAvgConf[side]; stats.side = side;
    }

    function getAllPrices() external view
        returns (uint[] memory prices) {
        prices = new uint[](_market.numSides);
        for (uint8 i; i < _market.numSides; i++)
            prices[i] = FeeLib.price(_market.q,
                _market.numSides, _market.b, i);
    }

    function getCapitalPerSide() external
        view returns (uint[12] memory) {
        return _market.capitalPerSide;
    }

    function getRoundStartTime() external
        view returns (uint) {
        return _market.roundStartTime;
    }
}

File 38 of 78 : UMA.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Types} from "./imports/Types.sol";
import {FeeLib} from "./imports/FeeLib.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {OptimisticOracleV3Interface} from "./imports/OOV3Interface.sol";

interface IHook {
    function trigger(uint timestamp, uint8 winningSide) external;
    function onAssertionFiled() external;
    function onDisputeStarted() external;
    function onAssertionRejected() external;
    function onDisputeEnded() external;
    function paid() external view returns (bool);
    function resetForNewRound() external;
    function getRoundStartTime() external view returns (uint);
    function getMarketCapital() external view returns (uint);
}

/// @dev Hook calls these pure math functions on UMA via STATICCALL.
///      LMSR internals (cost, exp) inline from BasketLib into UMA's bytecode.
interface IUMA { function settleAssertion() external;
    function getAssertionInfo() external view returns
    (uint8 phase, uint8 claimedSide, uint round, uint8 rejections);
    function buyTokens(int128[12] memory q, uint8 numSides, int128 b, uint8 side, uint netCap) external pure returns (uint tokens, int128 deltaQ);
    function sellTokens(int128[12] memory q, uint8 numSides, int128 b, uint8 side, uint tokSell) external pure returns (uint returned, int128 deltaQ);
    function computeWeight(uint[] memory capitals, uint[] memory timestamps, uint roundStart, uint resTs, uint lambda, uint floor, uint confidence, bool isWinner) external pure returns (uint);
    function computePayout(uint capital, uint weight, uint totalWinWeight, uint totalLoseWeight, uint totalLoserCap, uint consolBps, bool isWinner) external pure returns (uint);
    function reduceEntries(uint[] memory capitals, uint[] memory tokens, uint tokensToSell, uint totalTokens) external pure returns (uint[] memory, uint[] memory, uint);
}

/// @title UMA — OOV3 phase machine for single depeg market
/// @notice Weekly rounds. Asserter claims which side won.
///         Parallel assertions: multiple claimants can file simultaneously.
///         First confirmed assertion wins. Side 0 ("none depegged") is
///         decoupled from OOV3 — uses a permissionless timeout via resolveAsNone().
///         Caller funds the bond via transferFrom — returned on success,
///         lost on rejection. Escalating bond makes griefing exponentially expensive.
contract UMA is Ownable {
    uint public constant LIVENESS = 127 hours;
    uint public constant REVEAL_WINDOW = 48 hours;
    uint public constant BOND_FLOOR = 100e6; // $100 in USDC (6 dec)
    uint public constant BOND_CEILING = 10_000e6; // $10k in USDC (6 dec)

    uint public constant MAX_PARALLEL_ASSERTIONS = 12;
    IERC20 public immutable BOND_TOKEN;
    uint public constant BOND_BPS = 1;
    uint8 constant MAX_SIDES = 12;
    uint constant WAD = 1e18;

    OptimisticOracleV3Interface public immutable OO;
    address public FORWARDER; // CRE KeystoneForwarder
    IHook public HOOK; address public QUID; // Basket
    bool public marketRegistered;
    Types.Market internal market;

    // ═══════════════════════════════════════════════════════════════
    //  Parallel assertion tracking
    //  Multiple asserters can file claims simultaneously.
    //  First confirmed assertion resolves the market.
    //  Rejected assertions decrement the counter; when all are
    //  rejected the market returns to Trading and Hook unfreezes.
    // ═══════════════════════════════════════════════════════════════
    struct AssertionContext {
        address asserter;       // who funded the bond
        uint8   claimedSide;    // 1..N (never 0 — that uses resolveAsNone)
        uint    bond;           // amount transferred from asserter
        uint    requestTimestamp;
        uint    round;          // which market round this belongs to
        bool    disputed;       // set by assertionDisputedCallback
    }
    mapping(bytes32 => AssertionContext) public assertions;
    bytes32[] public pendingAssertionIds;
    uint public activeAssertionCount;
    /// @dev round → side → assertionId. Prevents duplicate per side per round.
    /// Cleared on rejection (re-opens the side). Scoped by round.
    mapping(uint => mapping(uint8 => bytes32)) public sideAssertionId;

    error WrongPhase(Types.Phase actual, Types.Phase expected);
    mapping(bytes32 => Types.ForensicEvidence) public evidence;
    mapping(bytes32 => bool) public evidenceSubmitted;

    uint public watchdogDisputeCount;
    uint8 public minConfidence = 80;
    error NotQUID(); error NotOO();
    error InvalidSide(uint8, uint8);
    error NotForwarder();

    event MarketRegistered(uint8 numSides);
    event ResolutionRequested(bytes32 assertionId,
                    uint8 claimedSide, uint bond);

    event AssertionDisputed();
    event AssertionSettled(
    uint8 winningSide, bool truthful);
    event MarketRestarted(uint round);
    event DisputeForensicsRequested(
        bytes32 indexed assertionId,
        uint8 claimedSide,
        uint requestTimestamp
    );

    event ForensicEvidenceStored(
        bytes32 indexed assertionId,
        uint8 recommendedSide,
        uint8 confidence
    );
    event WatchdogDispute(
        bytes32 indexed assertionId,
        uint8 claimedSide, uint8 recommendedSide,
        int maxDeviationBps, uint8 confidence, bytes32 evidenceHash
    );

    event WatchdogSkipped(bytes32 indexed assertionId, string reason);
    modifier onlyQUID() { if (msg.sender != QUID) revert NotQUID(); _; }
    modifier onlyOO() { if (msg.sender != address(OO)) revert NotOO(); _; }

    modifier onlyForwarder() {
        if (msg.sender != FORWARDER)
            revert NotForwarder(); _; }

    modifier inPhase(Types.Phase p) {
        if (market.phase != p)
            revert WrongPhase(market.phase, p); _;
    }

    constructor(address _oo, address _bond)
        Ownable(msg.sender) {
        OO = OptimisticOracleV3Interface(_oo);
        BOND_TOKEN = IERC20(_bond);
    }

   /// @dev Set the CRE KeystoneForwarder address.
   /// Callable once by owner. Deploy UMA first, then set
   /// forwarder when Chainlink publishes the mainnet address.
   function setForwarder(address _f) external onlyOwner {
       require(FORWARDER == address(0), "already set");
       require(_f != address(0), "zero address");
       FORWARDER = _f;
   }

    /// @dev onlyOwner prevents frontrunning during deployment.
    function setQUID(address _q) external onlyOwner { require(QUID == address(0)); QUID = _q; }
    function setHook(address _h) external onlyOwner { require(address(HOOK) == address(0)); HOOK = IHook(_h); }
    function registerMarket(address[] calldata stables) external onlyQUID {
        require(!marketRegistered, "exists");
        marketRegistered = true;
        uint8 n = uint8(stables.length);
        market.numSides = n + 1;
        market.phase = Types.Phase.Trading;
        market.roundNumber = 1;
        emit MarketRegistered(market.numSides);
    }

    // ═══════════════════════════════════════════════════════════════
    //  Side 0 resolution — permissionless timeout, no OOV3
    //  "None depegged" is the default outcome when a full round
    //  passes without a depeg claim. Anyone can trigger after
    //  MONTH elapsed from round start. No bond needed.
    // ═══════════════════════════════════════════════════════════════

    /// @notice Resolve as "none depegged" — permissionless timeout.
    /// Callable by anyone once MONTH has elapsed since round start.
    /// Bypasses OOV3 entirely since side 0 is the default/null outcome.
    function resolveAsNone() external inPhase(Types.Phase.Trading) {
        require(block.timestamp >=
            HOOK.getRoundStartTime() + FeeLib.MONTH,
            "round not mature");

        market.phase = Types.Phase.Resolved;
        market.winningSide = 0;
        market.revealDeadline = block.timestamp + REVEAL_WINDOW;
        market.consecutiveRejections = 0;

        HOOK.trigger(block.timestamp, 0);
        emit AssertionSettled(0, true);
    }

    // ═══════════════════════════════════════════════════════════════
    //  Depeg assertions (side > 0) — OOV3 backed, caller-funded
    //  Multiple assertions can coexist. First confirmed wins.
    //  Bond pulled from msg.sender → returned on success, lost on
    //  rejection. Escalating bond after consecutive rejections makes
    //  griefing exponentially expensive.
    // ═══════════════════════════════════════════════════════════════

    /// @notice Assert which side won this round.
    /// Side 0 ("none depegged") must use resolveAsNone().
    /// Caller funds the bond — returned on success, lost on rejection.
    /// Multiple asserters may file in parallel; first confirmed wins.
    function requestResolution(uint8 claimedSide)
        external returns (bytes32 assertionId) {
        require(claimedSide > 0, "use resolveAsNone");
        require(market.phase == Types.Phase.Trading
             || market.phase == Types.Phase.Asserting,
                "market not accepting assertions");

        require(activeAssertionCount < MAX_PARALLEL_ASSERTIONS, "too many pending assertions");
        if (claimedSide >= market.numSides) revert InvalidSide(claimedSide, market.numSides);
        require(sideAssertionId[market.roundNumber][claimedSide] == bytes32(0),
                "side already asserted");

        uint bond = getMinimumBond();
        // Pull bond from caller — skin in the game
        BOND_TOKEN.transferFrom(msg.sender, address(this), bond);
        BOND_TOKEN.approve(address(OO), bond);

        bytes memory claim = abi.encodePacked(
            "Depeg market round ", _uint2str(market.roundNumber),
            " outcome: side ", _uint2str(uint(claimedSide))
        );
        // asserter = address(this) → bond returns here on success
        // callbackRecipient = address(this) → callbacks come here
        assertionId = OO.assertTruth(
            claim, address(this), address(this),
            address(0), uint64(LIVENESS), BOND_TOKEN,
            bond, OO.defaultIdentifier(), bytes32(0)
        );
        // Track this assertion
        assertions[assertionId] = AssertionContext({
            asserter: msg.sender,
            claimedSide: claimedSide,
            bond: bond,
            requestTimestamp: block.timestamp,
            round: market.roundNumber,
            disputed: false
        });
        sideAssertionId[market.roundNumber][claimedSide] = assertionId;
        pendingAssertionIds.push(assertionId);
        activeAssertionCount++;

        // First assertion in this round freezes trading
        if (market.phase == Types.Phase.Trading) {
            market.phase = Types.Phase.Asserting;
            HOOK.onAssertionFiled();
        }
        emit ResolutionRequested(
        assertionId, claimedSide, bond);
    }

    /// @notice Settle all pending assertions whose liveness has expired.
    function settleAssertion() external {
        require(market.phase == Types.Phase.Asserting
             || market.phase == Types.Phase.Disputed, "wrong phase");
        for (uint i; i < pendingAssertionIds.length; i++) {
            bytes32 id = pendingAssertionIds[i];
            if (assertions[id].asserter != address(0)) {
                try OO.settleAssertion(id) {} catch {}
            }
        }
    }

    function assertionResolvedCallback(bytes32 assertionId,
        bool truthful) external onlyOO {
        AssertionContext memory ctx = assertions[assertionId];
        if (ctx.asserter == address(0)) return; // unknown / already processed
        // Stale assertion from previous round disputed late;
        // must NOT touch Hook state for the current round...
        // Without this guard, a $0 dispute on a stale assertion
        // sets disputeFrozen=true permanently (no recovery path).
        if (ctx.round != market.roundNumber) {
            delete assertions[assertionId]; return;
        }
        delete assertions[assertionId];
        if (activeAssertionCount > 0) activeAssertionCount--;

        if (truthful) {
            if (market.phase == Types.Phase.Resolved) {
                // Market already resolved by a faster assertion — just refund
                BOND_TOKEN.transfer(ctx.asserter, ctx.bond);
                emit AssertionSettled(market.winningSide, true);
                if (ctx.disputed) HOOK.onDisputeEnded();
                return;
            }
            market.phase = Types.Phase.Resolved;
            market.winningSide = ctx.claimedSide;
            market.revealDeadline = block.timestamp + REVEAL_WINDOW;
            market.consecutiveRejections = 0;

            HOOK.trigger(block.timestamp, ctx.claimedSide);
            // OOV3 returned the bond to us — refund the winning asserter
            BOND_TOKEN.transfer(ctx.asserter, ctx.bond);
        } else {
            // Rejected — asserter loses bond (OOV3 slashed it)
            if (market.consecutiveRejections < 255)
                market.consecutiveRejections++;
            // Re-open this side for a fresh assertion
            if (ctx.round == market.roundNumber)
                sideAssertionId[ctx.round][ctx.claimedSide] = bytes32(0);

            // All assertions exhausted → unfreeze
            if (activeAssertionCount == 0
                && market.phase != Types.Phase.Resolved) {
                market.phase = Types.Phase.Trading;
                // roundStartTime intentionally NOT reset — early entrants
                // keep their time-decay advantage. Resetting would let a
                // $100 false assertion wipe the entire decay curve.
                HOOK.onAssertionRejected();
            }
        }
        emit AssertionSettled(market.winningSide, truthful);
        if (ctx.disputed) HOOK.onDisputeEnded();
    }

    function assertionDisputedCallback
        (bytes32 assertionId) external onlyOO {
        AssertionContext storage ctx = assertions[assertionId];
        require(ctx.asserter != address(0), "unknown assertion");
        ctx.disputed = true;
        if (market.phase == Types.Phase.Asserting)
            market.phase = Types.Phase.Disputed;
        HOOK.onDisputeStarted();
        emit AssertionDisputed();
        // Request deep forensic evidence from CRE (Trigger B).
        // If no CRE workflow is listening, fires into the void.
        emit DisputeForensicsRequested(assertionId,
        ctx.claimedSide, ctx.requestTimestamp);
    }

    function restartMarket() external inPhase(Types.Phase.Resolved) {
        require(block.timestamp >= market.revealDeadline && HOOK.paid());
        market.winningSide = 0;
        market.phase = Types.Phase.Trading;
        market.consecutiveRejections = 0;
        market.revealDeadline = 0;
        market.roundNumber++;
        activeAssertionCount = 0;
        delete pendingAssertionIds;
        HOOK.resetForNewRound();
        emit MarketRestarted(
          market.roundNumber);
    }

    /// @notice Admin-initiated dispute against a specific assertion.
    /// Uses this contract's USDC reserves for the dispute bond.
    /// @param _assertionId The assertion to dispute.
    ///        If bytes32(0), disputes the first pending assertion.
    function executeDispute(bytes32 _assertionId) public onlyOwner {
        if (_assertionId == bytes32(0)) {
            require(pendingAssertionIds.length > 0, "no assertions");
            _assertionId = pendingAssertionIds[0];
        }
        AssertionContext memory ctx = assertions[_assertionId];
        require(ctx.asserter != address(0), "unknown assertion");

        uint bond = ctx.bond; // match the asserter's bond
        require(BOND_TOKEN.balanceOf(address(this)) >= bond, "insufficient bond reserves");
        BOND_TOKEN.approve(address(OO), bond);
        OO.disputeAssertion(_assertionId, address(this));
        // NOTE: assertionDisputedCallback fires synchronously
    }

    /// @notice Backward-compatible overload — disputes first pending.
    function executeDispute() external onlyOwner {
        executeDispute(bytes32(0));
    }

    // ═══════════════════════════════════════════════════════════════
    //  Stores evidence for DVM voters. Advisory only — the DVM
    //  vote is the sole source of truth for market resolution.
    //  Anyone can submit. First write per assertionId wins.
    // ═══════════════════════════════════════════════════════════════
    /// @notice Store forensic evidence for a disputed assertion.
    /// Restricted to CRE forwarder to prevent front-running with garbage.
    /// Idempotent: first submission per assertionId wins.
    function storeEvidence(bytes32 assertionId, uint8 claimedSide,
        uint8 recommendedSide, int maxDeviationBps,
        uint8 confidence, bytes32 evidenceHash) external onlyForwarder {
        _storeEvidence(assertionId, claimedSide, recommendedSide,
                        maxDeviationBps, confidence, evidenceHash);
    }

    function _storeEvidence(bytes32 assertionId,
        uint8 claimedSide, uint8 recommendedSide,
        int maxDeviationBps, uint8 confidence,
        bytes32 evidenceHash) internal {
        if (evidenceSubmitted[assertionId]) return;
        evidenceSubmitted[assertionId] = true;
        evidence[assertionId] = Types.ForensicEvidence({
            claimedSide: claimedSide,
            recommendedSide: recommendedSide,
            maxDeviationBps: maxDeviationBps,
            confidence: confidence,
            evidenceHash: evidenceHash,
            timestamp: block.timestamp
        });
        emit ForensicEvidenceStored(assertionId,
                recommendedSide, confidence);
    }

    function getEvidence(bytes32 assertionId) external
        view returns (Types.ForensicEvidence memory) {
        return evidence[assertionId];
    }

    // ═══════════════════════════════════════════════════════════════
    //  Receives reports from Chainlink CRE via KeystoneForwarder.
    //  If evidence contradicts the assertion with high confidence,
    //  auto-disputes via OOV3 using USDC held by this contract.
    //  DVM vote remains the sole source of truth.
    // ═══════════════════════════════════════════════════════════════
    /// @notice Called by CRE KeystoneForwarder with forensic analysis.
    ///         Stores evidence AND auto-disputes if warranted.
    ///         Implements IReceiver.onReport(bytes metadata, bytes report).
    function onReport(bytes calldata, bytes calldata report)
        external onlyForwarder {
        (bytes32 assertionId, uint8 claimedSide,
            uint8 recommendedSide, int maxDeviationBps,
            uint8 confidence, bytes32 evidenceHash) = abi.decode(report,
                            (bytes32, uint8, uint8, int, uint8, bytes32));
        _storeEvidence(assertionId, claimedSide, recommendedSide,
                        maxDeviationBps, confidence, evidenceHash);

        // Auto-dispute only for known pending undisputed assertions
        AssertionContext memory ctx = assertions[assertionId];
        if (ctx.asserter == address(0)) {
            emit WatchdogSkipped(assertionId, "unknown assertion");
            return;
        }
        if (ctx.disputed) {
            emit WatchdogSkipped(assertionId, "already disputed");
            return;
        }
        if (market.phase == Types.Phase.Resolved) {
            emit WatchdogSkipped(assertionId, "already resolved");
            return;
        }
        if (claimedSide == recommendedSide) {
            emit WatchdogSkipped(assertionId, "evidence agrees");
            return;
        }
        if (confidence < minConfidence) {
            emit WatchdogSkipped(assertionId, "confidence below threshold");
            return;
        }
        uint bond = ctx.bond; // match the asserter's bond
        if (BOND_TOKEN.balanceOf(address(this)) < bond) {
            emit WatchdogSkipped(assertionId, "insufficient bond balance");
            return;
        }
        BOND_TOKEN.approve(address(OO), bond);
        OO.disputeAssertion(assertionId, address(this));
        // NOTE: assertionDisputedCallback fires synchronously above
        watchdogDisputeCount++;
        emit WatchdogDispute(
            assertionId, claimedSide, recommendedSide,
            maxDeviationBps, confidence, evidenceHash
        );
    }

    /// @notice How many auto-disputes this contract can afford.
    /// When assertions are active, uses the current escalated bond.
    /// Otherwise falls back to OOV3's minimum bond.
    function disputeCapacity() external view returns (uint) {
        uint bond = activeAssertionCount > 0 ? getMinimumBond():
                         OO.getMinimumBond(address(BOND_TOKEN));

        if (bond == 0) return type(uint).max;
        return BOND_TOKEN.balanceOf(address(this)) / bond;
    }

    function setMinConfidence(uint8 _min) external onlyOwner {
        require(_min <= 100, "invalid threshold");
        minConfidence = _min;
    }

    function withdrawBond(uint amount,
        address to) external onlyOwner {
        BOND_TOKEN.transfer(to, amount);
    }

    /// @notice Bond = clamp(capital_usdc × BOND_BPS, FLOOR, CEILING) × 2^rejections
    /// First assertion: normal bond. After 1 rejection: 2×. After 2: 4×.
    /// Capped at BOND_CEILING to prevent overflow. Resets on success or new round.
    function getMinimumBond() public view returns (uint) {
        uint capital = HOOK.getMarketCapital();
        uint capitalUSDC = capital / 1e12;
        // WAD (18 dec) → USDC (6 dec)

        uint bp = (capitalUSDC * BOND_BPS) / 10000;
        uint base = bp < BOND_FLOOR ? BOND_FLOOR :
                    bp > BOND_CEILING ? BOND_CEILING : bp;

        uint8 rej = market.consecutiveRejections;
        uint8 shift = rej > 7 ? 7 : rej; // cap at 128×
        uint escalated = base << shift;

        if (escalated > BOND_CEILING) escalated = BOND_CEILING;
        uint ooMin = OO.getMinimumBond(address(BOND_TOKEN));
        return escalated > ooMin ? escalated : ooMin;
    }

    function isTradingEnabled()
        external view returns (bool) {
        Types.Phase p = market.phase;
        return p == Types.Phase.Trading
            || p == Types.Phase.Asserting;
    }

    function isRevealOpen()
        external view returns (bool) {
        return market.phase == Types.Phase.Resolved
        && block.timestamp < market.revealDeadline;
    }

    function getNumSides() external
        view returns (uint8) {
        return market.numSides;
    }

    function getAssertionInfo() external view
        returns (uint8 phase, uint8 claimedSide,
                 uint round, uint8 rejections) {

        return (uint8(market.phase), market.winningSide,
        market.roundNumber, market.consecutiveRejections);
    }

    /// @notice Bond needed to dispute the first pending assertion,
    /// or OOV3 minimum if none are active.
    function getBondForDispute() external view returns (uint) {
        if (pendingAssertionIds.length > 0) {
            bytes32 id = pendingAssertionIds[0];
            AssertionContext memory ctx = assertions[id];
            if (ctx.asserter != address(0)) return ctx.bond;
        }
        return OO.getMinimumBond(address(BOND_TOKEN));
    }

    /// @notice Get details of a specific pending assertion.
    function getAssertion(bytes32 assertionId) external view
        returns (address asserter, uint bond, uint8 claimedSide,
                 uint round, uint requestTimestamp, bool disputed) {
        AssertionContext memory ctx = assertions[assertionId];
        return (ctx.asserter, ctx.bond, ctx.claimedSide,
                ctx.round, ctx.requestTimestamp, ctx.disputed);
    }

    function getPendingAssertionCount() external
        view returns (uint) {
        return activeAssertionCount;
    }

    function _uint2str(uint v) internal
        pure returns (bytes memory) {
        if (v == 0) return "0";
        uint t = v; uint d;
        while (t != 0) {
            d++; t /= 10;
        }
        bytes memory b = new bytes(d);
        while (v != 0) {
            b[--d] = bytes1(uint8(48 + v % 10));
            v /= 10;
        } return b;
    }

    /// @notice Binary search to buy tokens on LMSR curve.
    /// All values in WAD (18-decimal). No scaling needed.
    function buyTokens(int128[MAX_SIDES] memory q,
        uint8 numSides, int128 b, uint8 side,
        uint netCap) external pure
        returns (uint tokens, int128 deltaQ) {
        int128 capWAD = int128(int256(netCap));
        // At minimum price (1/numSides), max tokens ≈ capWAD × numSides.
        // 2× safety margin covers price movement during purchase.
        // _expNorm clamps exp(-x) → 0 for x > 100 to avoid overflow.
        int128 lo; int128 hi = capWAD * int128(int256(
                          uint256(numSides))) * 2;
        for (uint i; i < 64; i++) {
            int128 mid = (lo + hi) / 2;
            uint c = FeeLib.cost(q,
                numSides, b, side, mid);
            if (c <= uint(uint128(capWAD))) lo = mid;
            else hi = mid;
            if (hi - lo <= 1) break; // converge to 1 wei
        } tokens = uint(uint128(lo)); deltaQ = lo;
    }

    /// @notice All values in WAD (18-decimal)...
    function sellTokens(int128[MAX_SIDES] memory q,
        uint8 numSides, int128 b, uint8 side,
        uint tokSell) external pure // LMSR
        returns (uint returned, int128 deltaQ) {
        int128 tokWAD = int128(int256(tokSell));
        uint refund = FeeLib.cost(q,
            numSides, b, side, -tokWAD);
        returned = refund; deltaQ = tokWAD;
    }

    /// @notice Compute a single position's payout weight.
    /// capitals[] are in WAD. decay is in bps (0-10000).
    /// Result: weight = confFactor × Σ(capital × decay) / 10000
    function computeWeight(uint[] memory capitals,
        uint[] memory timestamps, uint roundStart,
        uint resTs, uint lambda, uint floor,
        uint confidence, bool isWinner)
        external pure returns (uint weight) {
        uint timeWeightedCap; uint decay;
        for (uint j; j < capitals.length; j++) {
            if (resTs <= roundStart) decay = 10000;
            else { uint p; // participation in bps
                { // scope: mktD & posD die here (stack relief)
                    uint mktD = resTs - roundStart;
                    uint posD = timestamps[j] <= roundStart ? mktD :
                        (timestamps[j] >= resTs ? 0 : resTs - timestamps[j]);
                    p = Math.min((posD * 10000) / mktD, 10000);
                } // ← mktD, posD freed from stack
                if (lambda <= 100) decay = p;
                else if (lambda <= 200) {
                    uint t = lambda - 100;
                    uint qd = (p * p) / 10000;
                    decay = (p * (100 - t) + qd * t) / 100;
                } else {
                    uint qd = (p * p) / 10000;
                    decay = (qd * p) / 10000;
                }
                if (decay < floor) decay = floor;
            } timeWeightedCap += capitals[j] * decay;
        } uint twcWAD = timeWeightedCap / 10000;
        // timeWeightedCap is WAD × bps. Divide by 10000 to get WAD...
        // confidence is 100-10000 (1-100%). Normalize to WAD fraction.
        uint confNorm = (confidence * WAD) / 10000;
        if (isWinner)
            weight = FullMath.mulDiv(
                confNorm, twcWAD, WAD);

        else { uint inv = WAD - confNorm;
            weight = FullMath.mulDiv(inv > 0 ?
                        inv : 1, twcWAD, WAD);
        }
    }

    /// @notice Compute a single position's payout amount (18-decimal)...
    /// If no winners exist, entire loser capital goes to consolation pool
    /// to prevent permanent capital lock.
    function computePayout(uint capital, uint weight,
        uint totalWinWeight, uint totalLoseWeight, // 20% goes back...
        uint totalLoserCap, uint consolBps, bool isWinner) external pure
        returns (uint payout) { uint winnerPool; uint consolPool;
        if (totalWinWeight == 0) { // No winners: all loser capital
            // → consolation (no one to receive winner pool)
            winnerPool = 0; consolPool = totalLoserCap;
        } else {
            winnerPool = FullMath.mulDiv(totalLoserCap,
                            10000 - consolBps, 10000);
            consolPool = totalLoserCap - winnerPool;
        }
        if (isWinner) {
            uint bonus = totalWinWeight > 0 ? FullMath.mulDiv(
                           winnerPool, weight, totalWinWeight) : 0;

            payout = capital + bonus;
        } else {
            payout = totalLoseWeight > 0 ?
                FullMath.mulDiv(consolPool,
                    weight, totalLoseWeight): 0;
        }
    }

    /// @notice Pro-rata reduce entries selling tokens
    function reduceEntries(uint[] memory capitals,
        uint[] memory tokens, uint tokensToSell,
        uint totalTokens) external pure returns (
        uint[] memory newCapitals,
        uint[] memory newTokens,
        uint totalCapReduced) {
        uint len = capitals.length;
        newCapitals = new uint[](len);
        newTokens = new uint[](len);
        for (uint i; i < len; i++) {
            newCapitals[i] = capitals[i];
            newTokens[i] = tokens[i];
        }
        uint tokensRemaining = tokensToSell;
        for (uint i; i < len; i++) {
            if (newTokens[i] == 0) continue;
            // Last active entry absorbs rounding residual
            uint tokFromEntry;
            if (tokensRemaining >= newTokens[i]) {
                tokFromEntry = newTokens[i];
            } else if (i == len - 1 || _isLastActive(newTokens, i + 1, len)) {
                tokFromEntry = tokensRemaining;
            } else {
                tokFromEntry = (newTokens[i] *
                     tokensToSell) / totalTokens;
                if (tokFromEntry > tokensRemaining)
                    tokFromEntry = tokensRemaining;
            }
            if (tokFromEntry >= newTokens[i]) {
                totalCapReduced += newCapitals[i];
                newCapitals[i] = 0;
                newTokens[i] = 0;
            } else {
                uint capFromEntry = (newCapitals[i]
                      * tokFromEntry) / newTokens[i];

                newTokens[i] -= tokFromEntry;
                newCapitals[i] -= capFromEntry;
                totalCapReduced += capFromEntry;
            }
            tokensRemaining -= tokFromEntry;
            if (tokensRemaining == 0) break;
        }
    }

    /// @dev Check if all entries from `start` to `end` have zero tokens
    function _isLastActive(uint[] memory toks,
        uint start, uint end) internal pure returns (bool) {
        for (uint j = start; j < end; j++)
            if (toks[j] > 0) return false;
        return true;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

library SortedSetLib {
    struct Set {
        uint[] sortedArray;
        mapping(uint => bool) exists;
    }

    /// @notice Inserts a value while maintaining sorting order.
    function insert(Set storage self, uint value) internal {
        if (self.exists[value]) return; // Ignore duplicates

        self.exists[value] = true;
        (uint index, ) = binarySearch(self, value);

        self.sortedArray.push(0); // Expand array

        // Shift elements right to insert in the correct position
        for (uint i = self.sortedArray.length - 1; i > index; i--) {
            self.sortedArray[i] = self.sortedArray[i - 1];
        }
        self.sortedArray[index] = value;
    }

    /// @notice Removes a value and triggers automatic cleanup.
    function remove(Set storage self, uint value) internal {
        require(self.exists[value], "Value does not exist");

        (uint index, ) = binarySearch(self, value);
        require(index < self.sortedArray.length
         && self.sortedArray[index] == value, "Value not found");

        self.sortedArray[index] = type(uint).max; // Mark as deleted
        delete self.exists[value]; // ^ max is just a sentinel value

        compactArray(self); // Cleanup on every removal
    }

    /// @notice Binary search to find index for insertion or lookup.
    function binarySearch(Set storage self, 
        uint value) internal view returns (uint, bool) {
        uint left = 0; uint right = self.sortedArray.length;

        while (left < right) {
            uint mid = left + (right - left) / 2;
            if (self.sortedArray[mid] == value) {
                return (mid, true); // Value found
            } 
            else if (self.sortedArray[mid] < value) {
                left = mid + 1;
            } 
            else {
                right = mid;
            }
        }
        return (left, false); // Value not found, return insertion index
    }

    /// @notice Performs automatic cleanup by removing all `0`s.
    function compactArray(Set storage self)
        internal { uint newLength = 0;
        
        uint[] memory newArray = new uint[](self.sortedArray.length);

        for (uint i = 0; i < self.sortedArray.length; i++) {
            if (self.sortedArray[i] != type(uint).max) {
                newArray[newLength] = self.sortedArray[i];
                newLength++;
            }
        }
        // Resize the array
        self.sortedArray = new uint[](newLength);
        for (uint i = 0; i < newLength; i++) {
            self.sortedArray[i] = newArray[i];
        }
    }

    /// @notice Returns the sorted set.
    function getSortedSet(Set storage self) 
        internal view returns (uint[] memory) {
        return self.sortedArray;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Minimalist and gas efficient standard ERC6909 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol)
abstract contract ERC6909 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OperatorSet(address indexed owner, address indexed operator, bool approved);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);

    event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                             ERC6909 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(address => bool)) public isOperator;

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => mapping(uint256 => uint256))) public allowance;

    /*//////////////////////////////////////////////////////////////
                              ERC6909 LOGIC
    //////////////////////////////////////////////////////////////*/

    function transfer(
        address receiver,
        uint256 id,
        uint256 amount
    ) public virtual returns (bool) {
        balanceOf[msg.sender][id] -= amount;

        balanceOf[receiver][id] += amount;

        emit Transfer(msg.sender, msg.sender, receiver, id, amount);

        return true;
    }

    function transferFrom(
        address sender,
        address receiver,
        uint256 id,
        uint256 amount
    ) public virtual returns (bool) {
        if (msg.sender != sender && !isOperator[sender][msg.sender]) {
            uint256 allowed = allowance[sender][msg.sender][id];
            if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount;
        }

        balanceOf[sender][id] -= amount;

        balanceOf[receiver][id] += amount;

        emit Transfer(msg.sender, sender, receiver, id, amount);

        return true;
    }

    function approve(
        address spender,
        uint256 id,
        uint256 amount
    ) public virtual returns (bool) {
        allowance[msg.sender][spender][id] = amount;

        emit Approval(msg.sender, spender, id, amount);

        return true;
    }

    function setOperator(address operator, bool approved) public virtual returns (bool) {
        isOperator[msg.sender][operator] = approved;

        emit OperatorSet(msg.sender, operator, approved);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(
        address receiver,
        uint256 id,
        uint256 amount
    ) internal virtual {
        balanceOf[receiver][id] += amount;

        emit Transfer(msg.sender, address(0), receiver, id, amount);
    }

    function _burn(
        address sender,
        uint256 id,
        uint256 amount
    ) internal virtual {
        balanceOf[sender][id] -= amount;

        emit Transfer(msg.sender, sender, address(0), id, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Rover} from "./Rover.sol";
import {Types} from "./imports/Types.sol";

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

import {stdMath} from "forge-std/StdMath.sol";
import {BasketLib, AAVEv4, IHub, IAaveOracle} from "./imports/BasketLib.sol";

import {IPool} from "aave-v3/interfaces/IPool.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

import {IUiPoolDataProviderV3} from "aave-v3/helpers/interfaces/IUiPoolDataProviderV3.sol";
import {IPoolAddressesProvider} from "aave-v3/interfaces/IPoolAddressesProvider.sol";

import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {ISwapRouter} from "./imports/v3/ISwapRouter.sol"; // on L1 and Arbitrum
// import {IV3SwapRouter as ISwapRouter} from "./imports/v3/IV3SwapRouter.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";

interface IAux {
    function deposit(address from, address token,
         uint amount) external returns (uint usd);

    function getTWAP(uint32 secondsAgo)
        external view returns (uint);
}

/// @notice Handles AAVE APR/APY
/// @dev Integrates V3 for swaps
contract Amp is Ownable {
    IPoolAddressesProvider ADDR;
    IUiPoolDataProviderV3 DATA;
    uint constant WAD = 1e18;
    uint constant RAY = 1e27;
    uint USDCsharesSnapshot;
    uint wethSharesSnapshot;

    IERC20 USDC; WETH9 weth;
    IUniswapV3Pool v3Pool;
    ISwapRouter v3Router;
    Rover V3; IPool AAVE;
    address public AUX;
    bool token1isWETH;

    AAVEv4 internal SPOKE;
    IHub internal HUB;

    mapping(address => Types.viaAAVE) pledgesOneForZero;
    mapping(address => Types.viaAAVE) pledgesZeroForOne;
    mapping(address => uint) totalBorrowed;

    modifier onlyUs {
        require(msg.sender == address(this)
             || msg.sender == address(V3)
             || msg.sender == AUX, "403"); _;
    }

    constructor(address _aave, address _data,
        address _addr) Ownable(msg.sender) {
        DATA = IUiPoolDataProviderV3(_data);
        ADDR = IPoolAddressesProvider(_addr);
        AAVE = IPool(_aave);
    }

    event LeveragedPositionOpened(
        address indexed user,
        bool indexed isLong,
        uint supplied,
        uint borrowed,
        uint buffer,
        int256 entryPrice,
        uint breakeven,
        uint blockNumber
    );

    event PositionUnwound(
        address indexed user,
        bool indexed isLong,
        int256 exitPrice,
        int256 priceDelta,
        uint blockNumber
    );

    function setup(address payable _rover,
        address _aux) external onlyOwner {
        require(address(Rover(_rover).AMP())
             == address(this)); AUX = _aux;

        renounceOwnership();
        V3 = Rover(_rover);
        USDC = IERC20(V3.USDC());
        weth = WETH9(payable(
          address(V3.weth())));

        v3Pool = IUniswapV3Pool(V3.POOL());
        v3Router = ISwapRouter(V3.ROUTER());
        USDC.approve(AUX, type(uint).max);

        USDC.approve(address(AAVE), type(uint).max);
        USDC.approve(address(v3Router), type(uint).max);
        token1isWETH = v3Pool.token0() == address(USDC);
        weth.approve(address(v3Router), type(uint).max);
        weth.approve(address(AAVE), type(uint).max);
    }

    function setV4(address _hub, address _spoke) external {
        require(msg.sender == AUX
            && address(SPOKE) == address(0));

        (,, uint wethCollat, uint usdcCollat
                        ) = _readV3Positions();

        if (wethCollat > 0) AAVE.withdraw(
            address(weth), wethCollat, address(this));
        if (usdcCollat > 0) AAVE.withdraw(
            address(USDC), usdcCollat, address(this));

        _setupV4(_hub, _spoke);
        uint wethBal = weth.balanceOf(address(this));
        uint usdcBal = USDC.balanceOf(address(this));
        if (wethBal > 0) SPOKE.supply(
            _reserveId(address(weth)),
                  wethBal, address(this));
        if (usdcBal > 0) SPOKE.supply(
            _reserveId(address(USDC)),
                  usdcBal, address(this));
    }

    function hasOpenDebt() external view returns (bool) {
        (uint wethDebt, uint usdcDebt,,) = _readV3Positions();
        return wethDebt > 0 || usdcDebt > 0;
    }

    function _setupV4(address _hub,
        address _spoke) internal {
        SPOKE = AAVEv4(_spoke); HUB = IHub(_hub);
        weth.approve(_hub, type(uint).max);
        USDC.approve(_hub, type(uint).max);
    }

    /// @dev Read actual v3 debt and collateral
    function _readV3Positions() internal view returns (
        uint wethDebt, uint usdcDebt,
        uint wethCollat, uint usdcCollat) {
        ( IUiPoolDataProviderV3.UserReserveData[]
          memory ud, ) = DATA.getUserReservesData(
                                ADDR, address(this));
        ( IUiPoolDataProviderV3.AggregatedReserveData[]
          memory rd, ) = DATA.getReservesData(ADDR);
        if (ud.length > 0 && rd.length > 0) {
            wethDebt = (ud[0].scaledVariableDebt
                * rd[0].variableBorrowIndex) / RAY;
            wethCollat = IERC20(rd[0].aTokenAddress
                            ).balanceOf(address(this));
        } if (ud.length > 3 && rd.length > 3) {
            usdcDebt = (ud[3].scaledVariableDebt
                * rd[3].variableBorrowIndex) / RAY;
            usdcCollat = IERC20(rd[3].aTokenAddress
                            ).balanceOf(address(this));
        }
    }

    function _reserveId(address asset)
        internal returns (uint) {
        return SPOKE.getReserveId(address(HUB),
                    HUB.getAssetId(asset));
    }

    /// @notice leveraged long (borrow weth against USDC)
    /// @dev 70% LTV, excess USDC locked as collateral
    /// @param amount weth amount to deposit
    function leverETH(address who, uint amount,
        uint fromV4) payable external onlyUs {
        uint price =  IAux(AUX).getTWAP(0);
        if (fromV4 > 0) {
            weth.transferFrom(msg.sender,
                address(this), amount);
            USDC.transferFrom(msg.sender,
                address(this), fromV4);
        } else amount = _deposit(amount);
        uint borrowing = amount * 7 / 10;
        uint buffer = amount - borrowing;
        uint totalValue = FullMath.mulDiv(
                        amount, price, WAD);

        // borrow full value of collateral to go long
        // selling the amount borrowed for USDC and
        // depositing the USDC for a future step in
        // unwind which is a basketball crossover
        uint usdcNeeded = totalValue / 1e12;
        uint took = 0;
        if (fromV4 < usdcNeeded) {
            took = usdcNeeded - fromV4;
            if (took > 0) {
                uint got = V3.withdrawUSDC(took);
                require(stdMath.delta(took, got) <= 1e6,
                                        "withdrawUSDC");
                                            took = got;
            }
        } _putUSDC(fromV4 + took); _put(amount);
        totalBorrowed[address(weth)] += borrowing;
        // ^ there will always be enough to pay this
        // back because we are depositting ETH first
        if (address(SPOKE) != address(0))
            SPOKE.borrow(_reserveId(address(weth)),
                            borrowing, address(this));
        else AAVE.borrow(address(weth), borrowing,
                              2, 0, address(this));

        amount = FullMath.mulDiv(borrowing,
                        price, 1e12 * WAD);
        amount = _buyUSDC(borrowing, price);
        // ^ sell borrowed WETH to ad lever

        _putUSDC(amount);
        Types.viaAAVE memory order = Types.viaAAVE({
            breakeven: totalValue, // < "supplied" gets
            // reset; need to remember original value
            // in order to calculate gains eventually
            supplied: took, borrowed: borrowing,
            buffer: buffer, price: int(price) });

        if (token1isWETH) { // check for pre-existing order
            require(pledgesOneForZero[who].breakeven == 0);
            pledgesOneForZero[who] = order;
        }
        else {
            require(pledgesZeroForOne[who].breakeven == 0);
            pledgesZeroForOne[who] = order;
        } emit LeveragedPositionOpened(
            msg.sender, true, took,
            borrowing, buffer, int(price),
            totalValue,block.number);
    }

    /// @notice Open leveraged short position (USDC against weth)
    /// @dev 70% LTV on AAVE, deposited stablecoins as collateral
    /// @param amount Stablecoin amount to deposit
    function leverUSD(address who, uint amount,
        uint fromV4) payable external onlyUs {
        uint price = IAux(AUX).getTWAP(0);
        if (fromV4 > 0)
            weth.transferFrom(msg.sender,
                    address(this), fromV4);

        USDC.transferFrom(msg.sender,
            address(this), amount);
        uint deposited = amount;
        _putUSDC(amount);

        uint inWETH = FullMath.mulDiv(WAD,
                    amount * 1e12, price);
        // ^ convert USDC to 18 decimals

        uint neededFromV3 = 0;
        if (inWETH > fromV4)
            neededFromV3 = V3.take(inWETH - fromV4);
        // borrow WETH from V3, use in AAVE
        // as collateral to borrow dollars
        _put(fromV4 + neededFromV3); // collat
        uint totalWETH = fromV4 + neededFromV3;
        amount = FullMath.mulDiv(totalWETH * 7 / 10,
                                 price, WAD * 1e12);
        // borrow 70% of the WETH value in USDC
        if (address(SPOKE) != address(0))
            SPOKE.borrow(_reserveId(address(USDC)),
                                amount, address(this));
        else AAVE.borrow(address(USDC), amount, 2, 0,
                                         address(this));
        _putUSDC(amount);

        totalBorrowed[address(USDC)] += amount;
        Types.viaAAVE memory order = Types.viaAAVE({
            breakeven: deposited * 1e12, // supplied\
            // reset; need to remember original value
            // in order to calculate gains eventually
            supplied: neededFromV3, borrowed: amount,
            buffer: 0, price: int(price) });

        if (token1isWETH) { // check for pre-existing order
            require(pledgesZeroForOne[who].breakeven == 0);
            pledgesZeroForOne[who] = order;
        }
        else {
            require(pledgesOneForZero[who].breakeven == 0);
            pledgesOneForZero[who] = order;
        }
        emit LeveragedPositionOpened(
            msg.sender, false, neededFromV3,
            amount, 0, int(price),
            deposited, block.number);
    }

    function _getUSDC(uint howMuch) internal
        returns (uint withdrawn) {
        uint amount = Math.min(
        USDCsharesSnapshot, howMuch);
        if (address(SPOKE) != address(0))
            (, withdrawn) = SPOKE.withdraw(
                  _reserveId(address(USDC)),
                     amount, address(this));

        else withdrawn = AAVE.withdraw(address(USDC),
                              amount, address(this));
    }

    function _get(uint howMuch) internal
        returns (uint withdrawn) {
        uint amount = Math.min(
        wethSharesSnapshot, howMuch);
        if (address(SPOKE) != address(0))
            (, withdrawn) = SPOKE.withdraw(
                  _reserveId(address(weth)),
                     amount, address(this));

        else withdrawn = AAVE.withdraw(address(weth),
                              amount, address(this));
    }

    function _put(uint amount) internal {
        if (address(SPOKE) != address(0))
            SPOKE.supply(_reserveId(address(weth)),
                                amount, address(this));
        else { AAVE.supply(address(weth), amount, address(this), 0);
               AAVE.setUserUseReserveAsCollateral(address(weth), true); }
    }
    function _putUSDC(uint amount) internal {
        if (address(SPOKE) != address(0))
            SPOKE.supply(_reserveId(address(USDC)),
                                amount, address(this));
        else { AAVE.supply(address(USDC), amount, address(this), 0);
               AAVE.setUserUseReserveAsCollateral(address(USDC), true); }
    }

    function _buyUSDC(uint howMuch,
        uint price) internal returns (uint) {
        Types.AuxContext memory ctx;
        ctx.v3Pool = address(v3Pool);
        ctx.v3Router = address(v3Router);
        ctx.weth = address(weth);
        ctx.usdc = address(USDC);
        ctx.v3Fee = V3.POOL_FEE();

        return BasketLib.swapWETHtoUSDC(
                    ctx, howMuch, price);
    }

    function _buy(uint howMuch,
        uint price) internal returns (uint) {
        Types.AuxContext memory ctx;
        ctx.v3Pool = address(v3Pool);
        ctx.v3Router = address(v3Router);
        ctx.weth = address(weth);
        ctx.usdc = address(USDC);
        ctx.v3Fee = V3.POOL_FEE();

        return BasketLib.swapUSDCtoWETH(
                    ctx, howMuch, price);
    }

    function _deposit(uint amount) internal returns (uint) {
        if (amount > 0) { weth.transferFrom(msg.sender,
                            address(this), amount);
        } if (msg.value > 0) { weth.deposit{
                            value: msg.value}();
                         amount += msg.value;
        }         return amount;
    } /// @param out token to withdraw
    /// @param borrowed Amount borrowed
    /// @param supplied Amount supplied
    function _unwind(address repay, address out,
        uint borrowed, uint supplied) internal {
        if (borrowed > 0) {
            if (address(SPOKE) != address(0))
                SPOKE.repay(_reserveId(repay),
                        borrowed, address(this));
            else AAVE.repay(repay, borrowed,
                          2, address(this));
        }
        if (out != address(0)) {
            uint tracked = totalBorrowed[repay];
            totalBorrowed[repay] = borrowed >
            tracked ? 0 : tracked - borrowed;
        }
        if (supplied > 0 && out != address(0)) {
            uint withdrawn;
            if (address(SPOKE) != address(0))
                (, withdrawn) = SPOKE.withdraw(
                    _reserveId(out), supplied,
                                  address(this));
            else withdrawn = AAVE.withdraw(out,
                        supplied, address(this));
            require(withdrawn >= supplied - 5,
                    "withdraw slippage");
        }
    }

    /// @notice Calculate APR on AAVE positions
    /// @return repay Interest owed weth borrows
    /// @return repayUSDC owed on USDC borrows
    function _howMuchInterest() internal
        returns (uint repay, uint repayUSDC) {
        if (address(SPOKE) != address(0)) {
            // V4: snapshot collateral via getUserSuppliedAssets
            uint id = _reserveId(address(weth));
            wethSharesSnapshot = SPOKE.getUserSuppliedAssets(
                                            id, address(this));
            id = _reserveId(address(USDC));
            USDCsharesSnapshot = SPOKE.getUserSuppliedAssets(
                                            id, address(this));

            AAVEv4.UserAccountData memory acct =
                SPOKE.getUserAccountData(address(this));
            if (acct.totalDebtValue == 0) return (0, 0);

            IAaveOracle oracle = IAaveOracle(SPOKE.ORACLE());
            // repay/repayUSDC reused as price, then unit, then prinVal
            repay = oracle.getReservePrice(
                  _reserveId(address(weth)));

            repayUSDC = oracle.getReservePrice(
                      _reserveId(address(USDC)));

            // wethPrinVal in `id`, usdcPrinVal in `acct.totalCollateralValue` (reuse)
            { uint wethUnit = 10 ** SPOKE.getReserve(
                            _reserveId(address(weth))).decimals;

              uint usdcUnit = 10 ** SPOKE.getReserve(
                            _reserveId(address(USDC))).decimals;

              uint wethPrinVal = repay > 0
                  ? (totalBorrowed[address(weth)] * repay)
                                              / wethUnit : 0;

              uint usdcPrinVal = repayUSDC > 0
                  ? (totalBorrowed[address(USDC)] * repayUSDC)
                                              / usdcUnit : 0;

              id = wethPrinVal + usdcPrinVal; // totalPrinVal
              if (acct.totalDebtValue > id && id > 0) {
                  uint interest = acct.totalDebtValue - id;
                  wethPrinVal = (interest * wethPrinVal) / id;
                  usdcPrinVal = interest - wethPrinVal;

                  // Convert back: repay still holds wethPrice
                  repay = repay > 0
                      ? (wethPrinVal * wethUnit) / repay : 0;
                  repayUSDC = repayUSDC > 0
                      ? (usdcPrinVal * usdcUnit) / repayUSDC : 0;

              } else { repay = 0; repayUSDC = 0; }
            }
        } else {
            // V3: index-based debt calculation
            ( IUiPoolDataProviderV3.UserReserveData[] memory userData,
            ) = DATA.getUserReservesData(ADDR, address(this)); uint actualDebt;
            ( IUiPoolDataProviderV3.AggregatedReserveData[] memory reserveData,
            ) = DATA.getReservesData(ADDR); uint scaledDebt; uint borrowIndex;
            if (userData.length > 0 && reserveData.length > 0) {
                borrowIndex = reserveData[0].variableBorrowIndex;
                scaledDebt = userData[0].scaledVariableDebt;
                actualDebt = (scaledDebt * borrowIndex) / RAY;
                // index 4 on Arbi and Poly, 0 on L1 and Base,
                wethSharesSnapshot = IERC20(reserveData[0].aTokenAddress).balanceOf(address(this));
                repay = actualDebt > totalBorrowed[address(weth)]
                    ? actualDebt - totalBorrowed[address(weth)] : 0;

                borrowIndex = reserveData[3].variableBorrowIndex;
                scaledDebt = userData[3].scaledVariableDebt;
                actualDebt = (scaledDebt * borrowIndex) / RAY;
                // index 3 on L1, 4 on Base, 12 on Arb, 22 on Polygon
                USDCsharesSnapshot = IERC20(reserveData[3].aTokenAddress).balanceOf(address(this));
                repayUSDC = actualDebt > totalBorrowed[address(USDC)]
                          ? actualDebt - totalBorrowed[address(USDC)] : 0;
            }
        }
    }

    function unwindZeroForOne(
        address[] calldata whose) external {
        int price = int(IAux(AUX).getTWAP(1800));
        Types.viaAAVE memory pledge;

        uint i; uint buffer;
        uint pivot; uint touched;
        (uint repay, uint repayUSDC) = _howMuchInterest();

        while (i < 30 && i < whose.length) {
            address who = whose[i];
            pledge = token1isWETH ? pledgesOneForZero[who] :
                                    pledgesZeroForOne[who];
            if (pledge.price == 0) { i++; continue; }

            int delta = (price - pledge.price) * 1000 / pledge.price;
            if (delta <= -25 || delta >= 25) { touched += 1;
                if (pledge.borrowed > 0) {
                    pivot = _get(pledge.borrowed);
                    require(stdMath.delta(
                    pledge.borrowed, pivot) <= 5);
                    _unwind(address(weth), address(USDC),
                                 pivot, pledge.supplied);

                    if (delta <= -25) {
                        // buy the dip - convert all USDC to WETH
                        buffer = FullMath.mulDiv(pledge.borrowed,
                                uint(pledge.price), WAD * 1e12);

                        pivot = _getUSDC(buffer);
                        require(stdMath.delta(pivot, buffer) <= 5);

                        buffer = pivot + pledge.supplied;
                        pivot = FullMath.mulDiv(WAD,
                            buffer * 1e12, uint(price));
                        buffer = _buy(buffer, uint(price)); // buy ETH
                        buffer += _get(pledge.buffer); _put(buffer);

                        pledge.supplied = buffer;
                        pledge.price = price;
                        pledge.buffer = 0;
                    } else { // Price up...
                        // sell buffer WETH for USDC
                        buffer = _get(pledge.buffer);
                        require(stdMath.delta(buffer, pledge.buffer) <= 5);
                        pivot = FullMath.mulDiv(buffer, uint(price), WAD * 1e12);
                        pivot = _buyUSDC(buffer, uint(price)) + pledge.supplied;
                        pledge.buffer = pivot + FullMath.mulDiv(pledge.borrowed,
                                                uint(pledge.price), WAD * 1e12);
                        pledge.supplied = 0;
                        _putUSDC(pivot);
                    }   pledge.borrowed = 0;

                    if (token1isWETH) pledgesOneForZero[who] = pledge;
                    else pledgesZeroForOne[who] = pledge;
                } else if (delta <= -25 && pledge.buffer > 0) {
                    // Second pivot down - buffer is USDC, buy WETH
                    buffer = _getUSDC(pledge.buffer);
                    require(stdMath.delta(buffer, pledge.buffer) <= 5);
                    pivot = FullMath.mulDiv(WAD, buffer * 1e12, uint(price));
                    buffer = _buy(buffer, uint(price)); // buy ETH
                    pledge.supplied = buffer; _put(buffer);
                    pledge.buffer = 0; pledge.price = price;

                    if (token1isWETH) pledgesOneForZero[who] = pledge;
                    else pledgesZeroForOne[who] = pledge;
                }
                else if (delta >= 25 && pledge.supplied > 0) {
                    // Final exit: supplied is WETH, sell for $
                    buffer = _get(pledge.supplied);

                    // Pay  global
                    // WETH interest
                    if (repay > 0) {
                        pivot = Math.min(
                            buffer, repay);
                        buffer -= pivot;
                        repay -= pivot;
                        _unwind(address(weth),
                        address(0), pivot, 0);
                    }
                    pivot = FullMath.mulDiv(uint(price), buffer, 1e12 * WAD);
                    pivot = _buyUSDC(buffer, uint(price));
                    uint breakeven = pledge.breakeven / 1e12;
                    // Handle underwater positions gracefully
                    if (pivot <= breakeven) {
                        // At a loss - return whatever we have
                        USDC.transfer(who, pivot);
                    } else {
                        uint profit = pivot - breakeven;
                        if (repayUSDC > 0) {
                            buffer = Math.min(
                            profit, repayUSDC);
                            profit -= buffer;
                            repayUSDC -= buffer;
                            _unwind(address(USDC),
                            address(0), buffer, 0);
                        }
                        USDC.transfer(who, breakeven + profit / 2);
                        IAux(AUX).deposit(address(this),
                             address(USDC), profit / 2);
                    }
                    if (token1isWETH)
                         delete pledgesOneForZero[who];
                    else delete pledgesZeroForOne[who];
                } emit PositionUnwound(who, true, price,
                                    delta, block.number);
            } i++;
        }
    }

    function unwindOneForZero(
        address[] calldata whose) external {
        int price = int(IAux(AUX).getTWAP(1800));
        Types.viaAAVE memory pledge;

        uint i; uint buffer;
        uint pivot; uint touched;
        (uint repay, uint repayUSDC) = _howMuchInterest();

        while (i < 30 && i < whose.length) {
            address who = whose[i];
            pledge = token1isWETH ? pledgesZeroForOne[who] :
                                    pledgesOneForZero[who];

            if (pledge.price == 0) { i++; continue; }
            int delta = (price - pledge.price) * 1000 / pledge.price;
            if (delta <= -25 || delta >= 25) { touched += 1;
                if (pledge.borrowed > 0) {
                    pivot = _getUSDC(pledge.borrowed);
                    _unwind(address(USDC), address(weth), pivot, pledge.supplied);
                    if (delta >= 25) { // Price up (bad for short) - sell WETH for USDC
                        pivot = FullMath.mulDiv(pledge.supplied, uint(price), WAD * 1e12);
                        pledge.supplied = _buyUSDC(pledge.supplied, uint(price));
                        _putUSDC(pledge.supplied);
                    } else { // Price down (good for short)
                        pledge.buffer = pledge.supplied;
                        _put(pledge.supplied);
                        pledge.supplied = 0;
                    }   pledge.borrowed = 0;
                        pledge.price = price;

                    if (token1isWETH) pledgesZeroForOne[who] = pledge;
                    else pledgesOneForZero[who] = pledge;
                } else if (delta <= -25 && pledge.supplied > 0) {
                    // Second pivot - supplied is USDC, buy WETH
                    pivot = _getUSDC(pledge.supplied);
                    require(stdMath.delta(pledge.supplied, pivot) <= 5);
                    pivot = FullMath.mulDiv(WAD, pledge.supplied * 1e12, uint(price));
                    pledge.buffer = _buy(pledge.supplied, uint(price));

                    _put(pledge.buffer);
                    pledge.supplied = 0;
                    pledge.price = price;

                    if (token1isWETH) pledgesZeroForOne[who] = pledge;
                    else pledgesOneForZero[who] = pledge;
                } else if (delta >= 25 && pledge.buffer > 0) {
                    // Final exit - buffer is WETH, sell for USDC
                    buffer = _get(pledge.buffer);

                    // Pay down global WETH interest first
                    if (repay > 0) {
                        pivot = Math.min(buffer, repay);
                        buffer -= pivot; repay -= pivot;
                        _unwind(address(weth),
                        address(0), pivot, 0);
                    }
                    pivot = FullMath.mulDiv(uint(price),
                                    buffer, 1e12 * WAD);
                    pivot = _buyUSDC(buffer, uint(price));
                    // longs and shorts store breakeven in 18 dec
                    uint breakeven = pledge.breakeven / 1e12;
                    // Handle underwater positions
                    if (pivot <= breakeven) {
                        USDC.transfer(who, pivot);
                    } else {
                        uint profit = pivot - breakeven;
                        if (repayUSDC > 0) {
                            buffer = Math.min(
                            profit, repayUSDC);
                            profit -= buffer;
                            repayUSDC -= buffer;
                            _unwind(address(USDC),
                            address(0), buffer, 0);
                        }
                        USDC.transfer(who, breakeven + profit / 2);
                        IAux(AUX).deposit(address(this),
                             address(USDC), profit / 2);
                    } // TODO Aux.deposit and we mint more upfront
                    // in leverETH there, maturing in one month !
                    if (token1isWETH)
                         delete pledgesZeroForOne[who];
                    else delete pledgesOneForZero[who];
                } emit PositionUnwound(who, false, price,
                                    delta, block.number);
            } i++;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Amp} from "./Amp.sol";
import {Aux} from "./Aux.sol";

import {WETH} from "solmate/src/tokens/WETH.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";

import {TickMath} from "./imports/v3/TickMath.sol";
import {FullMath} from "./imports/v3/FullMath.sol";

import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {LiquidityAmounts} from "./imports/v3/LiquidityAmounts.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

// import {IV3SwapRouter as ISwapRouter} from "./imports/v3/IV3SwapRouter.sol";
import {INonfungiblePositionManager} from "./imports/v3/INonfungiblePositionManager.sol";
import {ISwapRouter} from "./imports/v3/ISwapRouter.sol"; // on L1, Arbitrum, Polygon

// import {AuxBase as Aux} from "./L2/AuxBase.sol";
// import {AuxArb as Aux} from "./L2/AuxArb.sol";
// import {AuxPoly as Aux} from "./L2/AuxPoly.sol";
// import {AuxUni as Aux} from "./L2/AuxUni.sol";

contract Rover is ReentrancyGuard, Ownable {
    using SafeTransferLib for ERC20;
    using SafeTransferLib for WETH;
    address public immutable USDC;

    WETH public immutable weth;
    uint public YIELD; // %
    uint public ID; // NFT

    uint constant WAD = 1e18;
    bool public token1isWETH;
    uint public totalShares;
    uint public LAST_REPACK;

    int24 public UPPER_TICK;
    int24 public LOWER_TICK;
    int24 public LAST_TICK;
    uint160 public LAST_SQRT_PRICE;
    uint private _deployed;

    int24 constant MAX_TICK = 887220;
    address public AUX; Amp public AMP;
    INonfungiblePositionManager public NFPM;

    mapping(address => uint128) public positions;
    int24 TICK_SPACING; uint24 public POOL_FEE;
    address public POOL; address public ROUTER;

    uint128 public liquidityUnderManagement;
    function fetch(address beneficiary) public
        returns (uint128, uint, uint160) {
        uint128 liq = positions[beneficiary];
        (uint160 sqrtPrice,
         int24 tick,,,,,) = IUniswapV3Pool(POOL).slot0();

        LAST_TICK = tick; LAST_SQRT_PRICE = sqrtPrice;
        uint price = getPrice(sqrtPrice);
        _repackNFT(0, 0, price); return (liq, price, sqrtPrice);
    }   receive() external payable {}

    modifier onlyUs {
        require(msg.sender == AUX
             || msg.sender == address(AMP), "403"); _;
    }

    constructor(address _amp,
        address _weth, address _usdc,
        address _nfpm, address _pool,
        address _router) Ownable(msg.sender) {
        USDC = _usdc; POOL = _pool;
        _deployed = block.timestamp;
        weth = WETH(payable(_weth));

        if (_amp != address(0)) {
            AMP = Amp(payable(_amp));
            ERC20(weth).approve(_amp, type(uint).max);
            ERC20(USDC).approve(_amp, type(uint).max);
        }
        ROUTER = _router; totalShares = 1;
        POOL_FEE = IUniswapV3Pool(POOL).fee();
        TICK_SPACING = IUniswapV3Pool(POOL).tickSpacing();
        address token0 = IUniswapV3Pool(POOL).token0();
        address token1 = IUniswapV3Pool(POOL).token1();
        token1isWETH = (token1 == _weth);

        require((token1isWETH && token0 == _usdc)
            || (!token1isWETH && token1 == _usdc),
            "wrong pool");

        NFPM = INonfungiblePositionManager(_nfpm);
        ERC20(weth).approve(_router, type(uint).max);
        ERC20(USDC).approve(_router, type(uint).max);
        ERC20(weth).approve(_nfpm, type(uint).max);
        ERC20(USDC).approve(_nfpm, type(uint).max);
    }

    function setAux(address _aux) external onlyOwner {
        require(AUX == address(0)); AUX = _aux;
        renounceOwnership();
    }

    function _repackNFT(uint amount0, uint amount1,
             uint price) internal { uint128 liquidity;
        (int24 newLower, int24 newUpper) = _adjustTicks(LAST_TICK);
        if (LAST_REPACK != 0) { // not the first time packing the NFT
            if ((LAST_TICK > UPPER_TICK || LAST_TICK < LOWER_TICK) &&
            // "to improve is to change, to perfect is to change often"
                block.timestamp - LAST_REPACK >= 10 minutes) {
                // we want to make sure that all of the WETH deposited to this
                // contract is always in range (collecting), total range is ~7%
                // below and above tick, as voltage regulators watch currents
                // and control a relay (which turns on & off the alternator,
                // if below or above 12 volts, re-charging battery as such)
                (,,,,,,, liquidity,,,,) = NFPM.positions(ID);
                liquidityUnderManagement = liquidity;
                (uint collected0,
                 uint collected1,) = _withdrawAndCollect(liquidity);
                amount0 += collected0; amount1 += collected1;
                NFPM.burn(ID); ID = 0;
                // Update ticks for new
                // position after burning
                LOWER_TICK = newLower;
                UPPER_TICK = newUpper;
            }
        } else { // First time ever
            LOWER_TICK = newLower;
            UPPER_TICK = newUpper;
        }
        if (liquidity > 0 || ID == 0) {
            if (amount0 == 0 && amount1 == 0) return;
            // Convert to (wethAmount, usdcAmount) for potential _swap
            (uint wethAmount, uint usdcAmount) = token1isWETH ?
                (amount1, amount0) : (amount0, amount1);

            // Only skip _swap if:
            // - We didn't just burn a position (liquidity == 0), AND
            // - We have pre-balanced amounts from deposit() (both > 0)
            // When liquidity > 0, we just burned and collected amounts that
            // are unbalanced for the NEW tick range - must rebalance via _swap
            bool needsSwap = liquidity > 0 || wethAmount == 0 || usdcAmount == 0;
            if (needsSwap) {
                (wethAmount, usdcAmount) = _swap(
                    wethAmount, usdcAmount, price);
            } // Convert back to (amount0, amount1)
            // token0 is always the lower address
            (uint mintAmount0, uint mintAmount1) = token1isWETH ?
                (usdcAmount, wethAmount) : (wethAmount, usdcAmount);

            (ID, liquidityUnderManagement,,) = NFPM.mint(
                INonfungiblePositionManager.MintParams({
                    token0: token1isWETH ? USDC : address(weth),
                    token1: token1isWETH ? address(weth) : USDC,
                    fee: POOL_FEE, tickLower: LOWER_TICK,
                    tickUpper: UPPER_TICK,
                    amount0Desired: mintAmount0,
                    amount1Desired: mintAmount1,
                    amount0Min: mintAmount0 * 980 / 1000,
                    amount1Min: mintAmount1 * 980 / 1000,
                    recipient: address(this),
                    deadline: block.timestamp }));
                    LAST_REPACK = block.timestamp;
        } else {
            (uint collected0, uint collected1) = _collect(price);
            amount0 += collected0; amount1 += collected1;
            if (amount0 > 0 || amount1 > 0) {
                // Try to compound collected fees. May fail if:
                // - Position is in-range but fees are single-sided
                // - Amounts are too small to create any liquidity
                // On fail, tokens stay for next deposit/repack
                try NFPM.increaseLiquidity(
                    INonfungiblePositionManager.IncreaseLiquidityParams(
                        ID, amount0, amount1, amount0 * 980 / 1000,
                        amount1 * 980 / 1000, block.timestamp))
                returns (uint128 addedLiquidity, uint, uint) {
                    liquidityUnderManagement += addedLiquidity;
                } catch { // Silently continue - tokens remain
                }
            }
        }
    } function repackNFT() public nonReentrant
        returns (uint160) { (uint160 sqrtPriceX96,
        int24 tick,,,,,) = IUniswapV3Pool(POOL).slot0();
        LAST_TICK = tick; LAST_SQRT_PRICE = sqrtPriceX96;
        _repackNFT(0, 0, getPrice(sqrtPriceX96));
            return sqrtPriceX96;
    } // from v3-periphery/OracleLibrary
    // Returns price as USDC per WETH...
    function getPrice(uint160 sqrtRatioX96)
        public view returns (uint price) {
        uint casted = uint(sqrtRatioX96);
        uint ratioX128 = FullMath.mulDiv(
                 casted, casted, 1 << 64);

        if (token1isWETH) // sqrtPrice represents token0/token1 = USDC/WETH
            // We want USDC per WETH, so invert: WETH/USDC -> 1/(USDC/WETH)
            price = FullMath.mulDiv(1 << 128, WAD * 1e12, ratioX128);
        else // sqrtPrice represents token0/token1 = WETH/USDC
            // We want USDC per WETH, which is the ratio * decimal adjustment
            price = FullMath.mulDiv(ratioX128, WAD * 1e12, 1 << 128);
    }

    function _collect(uint price) internal
        returns (uint amount0, uint amount1) {
        (amount0, amount1) = NFPM.collect(
            INonfungiblePositionManager.CollectParams(ID,
                address(this), type(uint128).max, type(uint128).max
            )); // "collect calls to the tip sayin' how ya changed"
    } //

    function _withdrawAndCollect(uint128 liquidity) internal
        returns (uint amount0, uint amount1, uint128 liq) {
        // Early return if nothing requested or no position
        if (liquidity == 0 || ID == 0) return (0, 0, 0);
        // actual position liquidity from NFT - this is ground truth
        (,,,,,,, uint128 positionLiquidity,,,,) = NFPM.positions(ID);
        // Cap to actual available in NFT position (prevents NFPM revert)
        if (liquidity > positionLiquidity)
            liquidity = positionLiquidity;

        // Also cap to our tracking variable...
        if (liquidity > liquidityUnderManagement) {
            liquidity = liquidityUnderManagement;
            liquidityUnderManagement = 0;
        } else
            liquidityUnderManagement -= liquidity;

        if (liquidity > 0) {
            (uint160 sqrtLower, uint160 sqrtUpper,
             uint160 sqrtCurrent) = _getTickSqrtPrices();
            (uint exp0, uint exp1) = LiquidityAmounts
                .getAmountsForLiquidity(sqrtCurrent,
                    sqrtLower, sqrtUpper, liquidity);

            NFPM.decreaseLiquidity(// there's liquidity to withdraw
                INonfungiblePositionManager.DecreaseLiquidityParams(
                    ID, liquidity, exp0 * 980 / 1000,
                    exp1 * 980 / 1000, block.timestamp));

            (amount0, amount1) = _collect(0);
            return (amount0, amount1, liquidity);
        } return (0, 0, 0);
    }

    function _adjustToNearestIncrement(int24 input)
        internal view returns (int24) {
        int24 remainder = input % TICK_SPACING;
        if (remainder == 0) return input;
        if (remainder < 0) remainder += TICK_SPACING;

        int24 result = remainder >= TICK_SPACING / 2
            ? input + (TICK_SPACING - remainder)
            : input - remainder;

        return result >  MAX_TICK ?  MAX_TICK :
               result < -MAX_TICK ? -MAX_TICK : result;
    }

    function _adjustTicks(int24 currentTick) internal
        view returns (int24 lower, int24 upper) {
        // Calculate tick delta as ~3.57% of current tick
        int256 tickDelta = (int256(currentTick) * 357) / 10000;
        // Take absolute value - we always want
        // to expand outward from current tick
        if (tickDelta < 0) tickDelta = -tickDelta;
        if (tickDelta < TICK_SPACING) tickDelta = TICK_SPACING;
        // Lower tick is always currentTick - delta (more negative)
        // Upper tick is always currentTick + delta (more positive)
        lower = _adjustToNearestIncrement(currentTick - int24(int256(tickDelta)));
        upper = _adjustToNearestIncrement(currentTick + int24(int256(tickDelta)));
        if (lower > upper) (lower, upper) = (upper, lower);
        // Ensure proper ordering (safety check)
        // Ensure they're not equal
        if (upper == lower)
            upper += TICK_SPACING;
    }

    function _getTickSqrtPrices() internal view returns
        (uint160 sqrtLower, uint160 sqrtUpper, uint160 sqrtCurrent) {
        sqrtLower = TickMath.getSqrtPriceAtTick(LOWER_TICK);
        sqrtUpper = TickMath.getSqrtPriceAtTick(UPPER_TICK);
        sqrtCurrent = LAST_SQRT_PRICE;
    }

    function _swap(uint eth, uint usdc, uint price)
        internal returns (uint, uint) {
        if (eth == 0 && usdc == 0) return (0, 0);
        uint targetETH; uint targetUSDC; usdc *= 1e12;
        { (uint160 sqrtLower, uint160 sqrtUpper,
           uint160 sqrtCurrent) = _getTickSqrtPrices(); uint128 liquidity;
            if (eth > 0) {
                if (token1isWETH) { // ETH token1, getLiquidityForAmount1
                    liquidity = LiquidityAmounts.getLiquidityForAmount1(
                                            sqrtCurrent, sqrtUpper, eth);
                } else { // ETH is token0, use getLiquidityForAmount0
                    liquidity = LiquidityAmounts.getLiquidityForAmount0(
                                            sqrtCurrent, sqrtUpper, eth);
                }
            } else { // We have USDC, calculate liquidity based on $ amount
                if (token1isWETH) { // USDC token0, getLiquidityForAmount0
                    liquidity = LiquidityAmounts.getLiquidityForAmount0(
                                           sqrtLower, sqrtCurrent, usdc);
                } else { // USDC is token1, use getLiquidityForAmount1
                    liquidity = LiquidityAmounts.getLiquidityForAmount1(
                                           sqrtLower, sqrtCurrent, usdc);
                }
            } if (liquidity == 0) return (eth, usdc / 1e12);
            // Get target amounts for this liquidity
            (uint amount0, uint amount1) = LiquidityAmounts.getAmountsForLiquidity(
                                      sqrtCurrent, sqrtLower, sqrtUpper, liquidity);
            if (token1isWETH) {
                targetETH = amount1;
                targetUSDC = amount0;
            } else {
                targetETH = amount0;
                targetUSDC = amount1;
            }
        } if (targetETH == 0
           && targetUSDC == 0)
                return (eth, usdc / 1e12);
        // Assume ETH is X and USDC is Y...
        // the formula is (x - ky)/(1 + kp);
        // we're selling X to buy Y, where
        // p is the price of ETH. So,
        // derivation steps: assume n
        // is amount being swapped...
        // (x - n)/(y + np) = k target
        // x - n = ky + knp
        // x - ky = n + knp
        // x - ky = n(1 + kp)
        targetUSDC *= 1e12;

        if (usdc > targetUSDC)
            usdc = targetUSDC;

        if (eth > targetETH)
            eth = targetETH;

        if (targetUSDC > usdc && eth > 0 && targetUSDC > 0) {
            uint k = FullMath.mulDiv(targetETH, WAD, targetUSDC);
            uint ky = FullMath.mulDiv(k, usdc, WAD);
            if (eth > ky) { uint kp = FullMath.mulDiv(
                                        ky, price, WAD);
                if (kp == 0) kp = 1;
                uint toSwap = FullMath.mulDiv(WAD, eth - ky, WAD + kp);
                if (toSwap > 0 && toSwap <= eth) { eth -= toSwap;
                    uint minUSDC = FullMath.mulDiv(toSwap, price, WAD) / 1e12;
                    minUSDC = minUSDC * 980 / 1000;
                    usdc += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                                          abi.encodePacked(address(weth), POOL_FEE, USDC),
                                          address(this), block.timestamp, toSwap, minUSDC)) * 1e12;
                }
            }
        } if (targetETH > eth && usdc > 0
            && targetUSDC > 0 && targetETH > 0) { uint toSwapScaled;
            uint k = FullMath.mulDiv(targetETH, WAD, targetUSDC);
            if (k == 0) return (eth, usdc / 1e12);
            uint kp = FullMath.mulDiv(k, price, WAD);
            if (kp == 0) return (eth, usdc / 1e12);
            if (eth > 0) {
                uint ethValueInUsdc = FullMath.mulDiv(eth, WAD, k);
                toSwapScaled = usdc > ethValueInUsdc ?
                               usdc - ethValueInUsdc : 0;
            } else
                toSwapScaled = usdc;
            if (toSwapScaled > 0) {
                uint toSwap = FullMath.mulDiv(WAD, toSwapScaled,
                    WAD + FullMath.mulDiv(WAD, WAD, kp)) / 1e12;

                // Cap at available USDC...
                uint maxSwap = usdc / 1e12;
                if (toSwap > maxSwap) toSwap = maxSwap;
                if (toSwap > 0) { usdc -= toSwap * 1e12;
                    uint minETH = FullMath.mulDiv(toSwap * 1e12, WAD, price);
                    minETH = minETH * 980 / 1000;
                    eth += ISwapRouter(ROUTER).exactInput(
                        ISwapRouter.ExactInputParams(abi.encodePacked(USDC, POOL_FEE,
                          address(weth)),address(this), block.timestamp, toSwap, minETH));
                }
            }
        } return (eth, usdc / 1e12);
    }

    function deposit(uint amount)
        external nonReentrant payable {
        (uint128 liq, uint price, uint160 sqrtPrice) = fetch(msg.sender);
        if (amount > 0) weth.transferFrom(msg.sender, address(this), amount);
        if (msg.value > 0) weth.deposit{value: msg.value}(); uint in_dollars;

        (amount, in_dollars) = _swap(amount + msg.value, 0, price);
        (uint amount0, uint amount1) = token1isWETH ?
        (in_dollars, amount) : (amount, in_dollars);

        (uint160 sqrtLower, uint160 sqrtUpper,) = _getTickSqrtPrices();
        uint128 newLiq = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPrice, sqrtLower, sqrtUpper, amount0, amount1);

        uint128 newShares;
        if (totalShares == 0) newShares = newLiq;
        else { // Use max to prevent new depositors from benefiting if NAV decreased
            // - If LUM > totalShares (fees accrued): new depositors get fewer shares
            // - If LUM < totalShares (loss occurred): new depositors get 1:1 (neutral)
            uint denominator = liquidityUnderManagement > totalShares ?
                               liquidityUnderManagement : totalShares;

            newShares = uint128(FullMath.mulDiv(uint(newLiq),
                                   totalShares, denominator));
        } totalShares += newShares;
        _repackNFT(amount0, amount1, price);
        positions[msg.sender] += newShares;
    }

    // LP.liq = user's share (set at deposit, doesn't auto-grow)
    // liquidityUnderManagement = actual V3 position liquidity
    // (grows when fees compound via increaseLiquidity); when
    // fees compound: liquidityUnderManagement += newLiquidity,
    // but NO individual LP.liq changes;
    // totalShares ≠ liquidityUnderManagement
    // because the gap = compounded fees...
    function take(uint amount) public onlyUs
        returns (uint wethAmount) { repackNFT();
        uint128 liquidity; uint usdcAmount;
        (uint160 sqrtLower, uint160 sqrtUpper,
         uint160 sqrtCurrent) = _getTickSqrtPrices();

        liquidity = token1isWETH ? LiquidityAmounts.getLiquidityForAmount1(
                                        sqrtCurrent, sqrtUpper, amount / 2):
                                    LiquidityAmounts.getLiquidityForAmount0(
                                         sqrtCurrent, sqrtUpper, amount / 2);

        (uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);
        if (token1isWETH) { usdcAmount = amount0; wethAmount = amount1; }
        else { wethAmount = amount0; usdcAmount = amount1; }
        if (usdcAmount > 0) {
            uint minETH = FullMath.mulDiv(usdcAmount * 1e12, WAD,
                                          getPrice(LAST_SQRT_PRICE));
            minETH = minETH * 980 / 1000;
            wethAmount += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                                       abi.encodePacked(USDC, POOL_FEE, address(weth)),
                                        address(this), block.timestamp, usdcAmount, minETH));
        }
        if (wethAmount > 0)
            weth.transfer(msg.sender, wethAmount);
    }

    function depositUSDC(uint amount, uint price)
        public onlyUs { repackNFT();
        ERC20(USDC).transferFrom(msg.sender, address(this), amount);
        (uint eth, uint usd) = _swap(0, amount, price);
        (uint amount0, uint amount1) = token1isWETH ? (usd, eth)
                                                    : (eth, usd);
        try NFPM.increaseLiquidity(
            INonfungiblePositionManager.IncreaseLiquidityParams(
                    ID, amount0, amount1, amount0 * 980 / 1000,
                        amount1 * 980 / 1000, block.timestamp))
        returns (uint128 liquidity, uint, uint) {
            liquidityUnderManagement += liquidity;
        } catch {
            // Tokens stay in Rover for next repack/deposit
        }
    }

    function withdrawUSDC(uint amount)
        public onlyUs returns (uint usd) {
        uint eth; repackNFT(); uint128 liquidity;
        (uint160 sqrtLower, uint160 sqrtUpper,
         uint160 sqrtCurrent) = _getTickSqrtPrices();
        liquidity = token1isWETH ?
                     LiquidityAmounts.getLiquidityForAmount0(
                          sqrtLower, sqrtCurrent, amount / 2):
                     LiquidityAmounts.getLiquidityForAmount1(
                          sqrtLower, sqrtCurrent, amount / 2);
        (uint amount0,
         uint amount1, ) = _withdrawAndCollect(liquidity);
        if (token1isWETH) { eth = amount1; usd = amount0; }
        else { eth = amount0; usd = amount1; }
        if (eth > 0) {
            uint minUSDC = FullMath.mulDiv(eth,
                               getPrice(LAST_SQRT_PRICE), WAD) / 1e12;
            minUSDC = minUSDC * 980 / 1000;
            usd += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                        abi.encodePacked(address(weth), POOL_FEE, address(USDC)),
                                        address(this), block.timestamp, eth, minUSDC));
        }
        if (usd > 0)
            ERC20(USDC).transfer(msg.sender, usd);
    }

    // @param (amount) is actually
    // a % of their total liquidity
    // if msg.sender != address(AUX)
    function withdraw(uint amount) public nonReentrant {
        require(amount > 0 && amount <= 1000, "%");
        (uint128 liq, uint price,
         uint160 sqrtPrice) = fetch(msg.sender);
        require(liq > 0, "nothing to withdraw");
        uint128 withdrawingShares = uint128(FullMath.mulDiv(
                                    amount, uint(liq), 1000));

        uint128 liquidity = uint128(FullMath.mulDiv(liquidityUnderManagement,
                                            withdrawingShares, totalShares));

        (uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);
        (uint ethAmount, uint usdAmount) = token1isWETH ? (amount1, amount0)
                                                        : (amount0, amount1);
        if (usdAmount > 0) {
            uint minETH = FullMath.mulDiv(usdAmount * 1e12, WAD, price) * 980 / 1000;
            ethAmount += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                             abi.encodePacked(address(USDC), POOL_FEE, address(weth)),
                                        address(this), block.timestamp, usdAmount, minETH));
        }
        weth.withdraw(ethAmount);
        liq -= withdrawingShares;
        totalShares -= withdrawingShares;
        // LP receives swap output...
        // (bearing the slippage cost)
        (bool success, ) = msg.sender.call{
                          value: ethAmount}("");
                          require(success, "$");

        if (liq > 0) positions[msg.sender] = liq;
        else delete positions[msg.sender];
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {AnalyticMath} from "solidity-math-utils/AnalyticMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Types} from "./Types.sol";

interface IHook {
    function stablecoinToSide(address) external view returns (uint8);
    function getDepegStats(address) external view
        returns (Types.DepegStats memory);
}

library FeeLib {
    uint public constant WAD = 1e18;
    uint public constant MONTH = 2420000;
    uint public constant BASE = 4; // Fixed base fee (bps)
    uint public constant MAX_FEE = 5000;
    uint private constant MIN_MARKET_CAPITAL = 10_000e18;
    uint8 internal constant MAX_SIDES = 12;
    uint constant E_NUM = 2718281828;
    uint constant E_DEN = 1000000000;

    // Oracle type flags
    /* TODO uncomment
    uint8 public constant ORACLE_CHAINLINK = 3;
    uint8 public constant ORACLE_CRV = 2;
    uint8 public constant ORACLE_DSR_RATE = 1;
    error StaleOracle();
    error BadPrice();
    */

    function depositFee(uint amount, uint bps)
        public pure returns (uint) {
        return FullMath.mulDiv(
            amount, bps, 10000);
    }

    /// @dev fee(X) = max(0, totalExposure - riskX)
    ///      where totalExposure = Σ(share_i × risk_i) across all tokens
    ///      Withdrawing the depegged token → fee = 0 (heals basket)
    ///      Withdrawing a healthy token    → fee = basket's depeg exposure
    ///      Equal risk across all tokens   → fee = BASE (no selective advantage)
    /// @param thisRisk calcRisk score for the token being withdrawn (bps)
    function calcFee(uint thisRisk,
        uint totalExposure) public pure returns (uint) {
        if (totalExposure <= thisRisk) return BASE;
        uint fee = totalExposure - thisRisk;
        if (fee < BASE) return BASE;
        return fee > MAX_FEE ? MAX_FEE : fee;
    }

    /// @notice risk score from prediction market data
    /// @dev Simple capital-ratio model matching Hook's Types.DepegStats
    /// @param s Depeg stats from prediction market hook
    /// @return Risk score in basis points
    /// (0 = safe, 10000 = definitely depegging)
    function calcRisk(Types.DepegStats memory s)
        internal pure returns (uint) {
        if (s.depegged) return 10000;

        bool hasPrior = s.avgConf > 0;
        uint prior = hasPrior ? uint(s.avgConf) : 6500;
        if (s.capTotal == 0) return prior;

        uint capitalSignal = (uint(s.capOnSide) * 10000) / uint(s.capTotal);
        uint n = uint(s.capTotal) / MIN_MARKET_CAPITAL;
        if (n == 0) return prior;  // thin market → prior only
        // Thick market with no prior → pure capital signal
        if (!hasPrior) return capitalSignal;

        // Thick market with prior → Bayesian blend
        return (prior + n * capitalSignal) / (1 + n);
    }

    function calcFeeL1(address token, uint idx,
        uint[13] memory deps, address[] memory stables,
        address hook) public view returns (uint) {
        uint totalDeposits = deps[12];
        if (totalDeposits == 0) return BASE;
        // Token without prediction market → no M1 signal, base fee only
        if (IHook(hook).stablecoinToSide(token) == 0) return BASE;
        // Get this token's risk score
        uint thisRisk; uint totalExposure;
        { Types.DepegStats memory stats = IHook(hook).getDepegStats(token);
          thisRisk = stats.side > 0 ? calcRisk(stats) : 0; }
        // Compute basket-wide totalExposure = Σ(share_i × risk_i)
        // Each term: (deps[i+1] / totalDeposits) × risk_i  →  in bps
        for (uint i = 0; i < stables.length; i++) {
            if (deps[i + 1] < 100 * WAD) continue;
            uint8 side = IHook(hook).stablecoinToSide(stables[i]);

            if (side == 0) continue;
            Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
            if (s.side > 0) {
                uint risk = calcRisk(s);
                totalExposure += (deps[i + 1] * risk) / totalDeposits;
            }
        } return calcFee(thisRisk, totalExposure);
    }

    /// @notice Calculate fee with automatic index lookup
    /// @dev Wrapper around calcFeeL1 that finds index internally
    function calcFeeL1WithLookup(address token,
        uint[13] memory deps, address[] memory stables,
        address hook) external view returns (uint) {
        uint len = stables.length;
        for (uint i; i < len;) {
            if (stables[i] == token)
                return calcFeeL1(token,
                i, deps, stables, hook);
            unchecked { ++i; }
        } return 0;
    }

    /// @notice Find token with highest imbalance score (fee)
    /// @dev Higher fee = more overweight + risky = priority to reduce
    /// @return idx Index of most imbalanced token
    /// @return fee Imbalance score (higher = reduce first)
    /// @return excess Amount over equal weight (18 dec)
    function getMostImbalanced(uint[13] memory deps,
        address[] memory stables, address hook) external
        view returns (uint idx, uint fee, uint excess) {
        uint len = stables.length; uint high;
        uint total = deps[12];
        if (total == 0)
        return (0, 0, 0);
        for (uint i; i < len;) {
            if (deps[i + 1] > 100 * WAD) {
                uint f = calcFeeL1(stables[i],
                        i, deps, stables, hook);
                if (f > high) { high = f; idx = i; }
            } unchecked { ++i; }
        } fee = high;
        uint eq = total / len;
        if (deps[idx + 1] > eq)
            excess = deps[idx + 1] - eq;
    }

    // ─── LMSR ────────────────────────────────────

    function price(int128[MAX_SIDES] memory q,
        uint8 n, int128 b, uint8 side) public
        pure returns (uint p) {
        int256 maxQ = _max(q, n);
        uint eSide; uint eSum;
        for (uint8 j; j < n; j++) {
            uint ej = _expNorm(q[j], maxQ, b);
            eSum += ej;
            if (j == side) eSide = ej;
        }
        if (eSum == 0) return WAD / n;
        p = (eSide * WAD) / eSum;
    }

    /// @notice Cost of buying `delta` tokens on `side`
    /// @return c Unsigned cost in WAD
    function cost(int128[MAX_SIDES] memory q, uint8 n,
        int128 b, uint8 side, int128 delta) public
        pure returns (uint c) { int128[MAX_SIDES] memory qA;
        uint lseBefore = _logSumExp(q, n, b);
        for (uint8 j; j < n; j++) qA[j] = q[j];
        qA[side] += delta;
        uint lseAfter = _logSumExp(qA, n, b);
        uint bAbs = uint(int256(b));
        c = lseAfter >= lseBefore
            ? (bAbs * (lseAfter - lseBefore)) / WAD
            : (bAbs * (lseBefore - lseAfter)) / WAD;
    }

    /// @dev exp((q_j − maxQ) / b),
    /// result WAD-scaled. arg ≤ 0.
    function _expNorm(int128 qj,
        int256 maxQ, int128 b)
        internal pure returns (uint) {
        int256 d = int256(qj) - maxQ; // ≤ 0
        if (d == 0) return WAD;
        uint absD = uint(-d);
        uint absB = uint(int256(b));
        // exp(-x) < 1 wei (WAD) when x > ~41.4
        // (18 × ln(10)). Safe margin at 100.
        if (absD / absB > 100) return 0;
        (uint pN, uint pD) =
            AnalyticMath.pow(E_NUM,
             E_DEN, absD, absB);
        if (pN == 0) return 0;
        return (pD * WAD) / pN;
         // 1 / exp(|d|/b)
    }

    /// @dev max/b + ln(Σ exp((q_j−max)/b))  in WAD
    function _logSumExp(int128[MAX_SIDES] memory q,
      uint8 n, int128 b) internal pure returns (uint) {
        int256 maxQ = _max(q, n); uint sum; uint lnWad;
        for (uint8 j; j < n; j++)
            sum += _expNorm(q[j], maxQ, b);
        // ln(sum) where sum is WAD-scaled
        if (sum > WAD) { (uint lnN, uint lnD) =
                   AnalyticMath.log(sum, WAD);
                    lnWad = (lnN * WAD) / lnD;
        } // else ln(≤1) ≤ 0, clamp to 0
        // add back maxQ / b
        uint bAbs = uint(int256(b));
        if (maxQ >= 0)
            lnWad += (uint(maxQ) * WAD) / bAbs;
        else {
            uint sub = (uint(-maxQ) * WAD) / bAbs;
            lnWad = lnWad > sub ? lnWad - sub : 0;
        } return lnWad;
    }

    function _max(int128[MAX_SIDES] memory q, uint8 n)
        internal pure returns (int256 m) {
        m = int256(q[0]);
        for (uint8 j = 1; j < n; j++)
            if (int256(q[j]) > m) m = int256(q[j]);
    }

    // ─── L2 fee helpers (uncomment per deployment) ────────────

    /* TODO uncomment
    struct StakedPairs {
        uint8[4] base;    // Base token indices
        uint8[4] staked;  // Corresponding staked token indices
    }

    function getStakedPrice(address oracle, uint8 oracleType)
        public view returns (uint price) {
        if (oracleType == ORACLE_DSR_RATE) {
            price = IDSRRate(oracle).getRate();
        } else if (oracleType == ORACLE_CRV) {
            price = ISCRVOracle(oracle).pricePerShare();
        } else if (oracleType == ORACLE_CHAINLINK) {
            (, int answer,, uint ts,) = IChainlinkOracle(oracle).latestRoundData();
            price = uint(answer);
            if (ts == 0 || ts > block.timestamp) revert StaleOracle();
        }
        if (price < WAD) revert BadPrice();
    }

    function getBaseIndex(uint idx,
        StakedPairs memory pairs)
        internal pure returns (uint) {
        for (uint i = 0; i < 4; i++)
            if (pairs.staked[i] == idx)
                return pairs.base[i];

        return idx;
    }

    function getCombinedDeposits(uint base, uint[14] memory deps,
        StakedPairs memory pairs) internal pure returns (uint) {
        uint combined = deps[base + 2];
        for (uint i = 0; i < 4; i++)
            if (pairs.base[i] == base) {
                combined += deps[pairs.staked[i] + 2];
                break;
            }

        return combined;
    }

    function isStakedToken(uint idx,
        StakedPairs memory pairs)
        internal pure returns (bool) {
        for (uint i = 0; i < 4; i++)
            if (pairs.staked[i] == idx)
                return true;

        return false;
    }

    function calcFeeWithPairs(uint idx, uint[14] memory deps,
        StakedPairs memory pairs, address[] memory stables,
        address hook) external view returns (uint) {
        uint base = getBaseIndex(idx, pairs);
        if (IHook(hook).stablecoinToSide(stables[base]) == 0) return BASE;

        Types.DepegStats memory stats = IHook(hook).getDepegStats(stables[base]);
        if (stats.side == 0) return BASE;

        uint totalDeposits = deps[1];
        if (totalDeposits == 0) return BASE;

        uint thisRisk = calcRisk(stats);

        uint totalExposure;
        for (uint i = 0; i < stables.length; i++) {
            if (isStakedToken(i, pairs)) continue;
            uint combined = getCombinedDeposits(i, deps, pairs);
            if (combined < 100 * WAD) continue;
            uint8 side = IHook(hook).stablecoinToSide(stables[i]);
            if (side == 0) continue;
            Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
            if (s.side > 0) {
                uint risk = calcRisk(s);
                totalExposure += (combined * risk) / totalDeposits;
            }
        }
        return calcFee(thisRisk, totalExposure);
    }

    function calcFeeUni(uint idx, uint[7] memory deps, address[] memory stables,
        address hook) external view returns (uint) {
        uint base = (idx == 3) ? 2 : idx; // sUSDS maps to USDS
        if (IHook(hook).stablecoinToSide(stables[base]) == 0) return BASE;
        Types.DepegStats memory stats = IHook(hook).getDepegStats(stables[base]);
        if (stats.side == 0 || deps[1] == 0) return BASE;

        uint thisRisk = calcRisk(stats);

        uint totalExposure;
        for (uint i = 0; i < 5; i++) {
            if (i == 3) continue; // skip sUSDS, count USDS
            uint dep = (i == 2) ? deps[4] + deps[5] : deps[i + 2];
            if (dep < 100 * WAD) continue;
            uint8 side = IHook(hook).stablecoinToSide(stables[i]);
            if (side == 0) continue;
            Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
            if (s.side > 0) {
                uint risk = calcRisk(s);
                totalExposure += (dep * risk) / deps[1];
            }
        }
        return calcFee(thisRisk, totalExposure);
    }

    function calcFeePoly(uint idx, uint[8] memory deps, address[] memory stables,
        address hook) external view returns (uint) {
        if (IHook(hook).stablecoinToSide(stables[idx]) == 0) return BASE;
        Types.DepegStats memory stats = IHook(hook).getDepegStats(stables[idx]);
        if (stats.side == 0 || deps[1] == 0) return BASE;

        uint thisRisk = calcRisk(stats);

        uint totalExposure;
        for (uint i = 0; i < 6; i++) {
            if (deps[i + 2] < 100 * WAD) continue;
            uint8 side = IHook(hook).stablecoinToSide(stables[i]);
            if (side == 0) continue;
            Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
            if (s.side > 0) {
                uint risk = calcRisk(s);
                totalExposure += (deps[i + 2] * risk) / deps[1];
            }
        }
        return calcFee(thisRisk, totalExposure);
    }

    function calcWithdrawAmounts(uint amount,
        uint[14] memory deposits, int indexToSkip,
        bool strict, uint[3] memory prices,
        uint sixDecMask) public pure
        returns (uint[14] memory amounts, uint total) {
        uint totalDeposits = deposits[1];
        if (totalDeposits == 0) return (amounts, 0);

        for (uint i = 0; i < 12; i++) {
            if (int(i) == indexToSkip) continue;

            amounts[i + 2] = _calcOne(amount, totalDeposits,
                deposits[i + 2], i, strict, prices, sixDecMask);

            total += amounts[i + 2];
        }
    }

    function _calcOne(uint amount, uint total, uint dep,
        uint i, bool strict, uint[3] memory prices,
        uint sixDecMask) internal pure returns (uint out) {
        if (dep == 0) return 0;
        uint share = FullMath.mulDiv(amount, dep, total);

        if (i < 3 && prices[i] > 0 && prices[i] != WAD) {
            share = FullMath.mulDiv(share, WAD, prices[i]);
        }

        bool isSixDec = (sixDecMask >> i) & 1 == 1;
        if (isSixDec) {
            share = share / 1e12;
            if (strict && share * 1e12 > dep)
                share = dep / 1e12;
        } else if (strict && share > dep) {
            share = dep;
        }

        out = share;
    }
    */
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
    /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
    /// @return The contract address
    function factory() external view returns (address);

    /// @notice The first of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token0() external view returns (address);

    /// @notice The second of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token1() external view returns (address);

    /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
    /// @return The fee
    function fee() external view returns (uint24);

    /// @notice The pool tick spacing
    /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
    /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
    /// This value is an int24 to avoid casting even though it is always positive.
    /// @return The tick spacing
    function tickSpacing() external view returns (int24);

    /// @notice The maximum amount of position liquidity that can use any tick in the range
    /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
    /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
    /// @return The max amount of liquidity per tick
    function maxLiquidityPerTick() external view returns (uint128);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
    /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
    /// when accessed externally.
    /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
    /// tick The current tick of the pool, i.e. according to the last tick transition that was run.
    /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
    /// boundary.
    /// observationIndex The index of the last oracle observation that was written,
    /// observationCardinality The current maximum number of observations stored in the pool,
    /// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
    /// feeProtocol The protocol fee for both tokens of the pool.
    /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
    /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
    /// unlocked Whether the pool is currently locked to reentrancy
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint
    function feeGrowthGlobal0X128() external view returns (uint);

    /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint
    function feeGrowthGlobal1X128() external view returns (uint);

    /// @notice The amounts of token0 and token1 that are owed to the protocol
    /// @dev Protocol fees will never exceed uint128 max in either token
    function protocolFees() external view returns (uint128 token0, uint128 token1);

    /// @notice The currently in range liquidity available to the pool
    /// @dev This value has no relationship to the total liquidity across all ticks
    function liquidity() external view returns (uint128);

    /// @notice Look up information about a specific tick in the pool
    /// @param tick The tick to look up
    /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
    /// tick upper,
    /// liquidityNet how much liquidity changes when the pool price crosses the tick,
    /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
    /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
    /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
    /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
    /// secondsOutside the seconds spent on the other side of the tick from the current tick,
    /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
    /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
    /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
    /// a specific position.
    function ticks(int24 tick)
        external
        view
        returns (
            uint128 liquidityGross,
            int128 liquidityNet,
            uint feeGrowthOutside0X128,
            uint feeGrowthOutside1X128,
            int56 tickCumulativeOutside,
            uint160 secondsPerLiquidityOutsideX128,
            uint32 secondsOutside,
            bool initialized
        );

    /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
    function tickBitmap(int16 wordPosition) external view returns (uint);

    /// @notice Returns the information about a position by the position's key
    /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
    /// @return _liquidity The amount of liquidity in the position,
    /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
    /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
    /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
    /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
    function positions(bytes32 key)
        external
        view
        returns (
            uint128 _liquidity,
            uint feeGrowthInside0LastX128,
            uint feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    /// @notice Returns data about a specific observation index
    /// @param index The element of the observations array to fetch
    /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
    /// ago, rather than at a specific index in the array.
    /// @return blockTimestamp The timestamp of the observation,
    /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
    /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
    /// Returns initialized whether the observation has been initialized and the values are safe to use
    function observations(uint index)
        external
        view
        returns (
            uint32 blockTimestamp,
            int56 tickCumulative,
            uint160 secondsPerLiquidityCumulativeX128,
            bool initialized
        );
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
    /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
    /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
    /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
    /// you must call it with secondsAgos = [3600, 0].
    /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
    /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
    /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
    /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
    /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
    /// timestamp
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

    /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
    /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
    /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
    /// snapshot is taken and the second snapshot is taken.
    /// @param tickLower The lower tick of the range
    /// @param tickUpper The upper tick of the range
    /// @return tickCumulativeInside The snapshot of the tick accumulator for the range
    /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
    /// @return secondsInside The snapshot of seconds per liquidity for the range
    function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
        external
        view
        returns (int56 tickCumulativeInside, uint160 secondsPerLiquidityInsideX128, uint32 secondsInside);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
    /// @notice Sets the initial price for the pool
    /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
    /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
    function initialize(uint160 sqrtPriceX96) external;

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data)
        external
        returns (uint amount0, uint amount1);

    /// @notice Collects tokens owed to a position
    /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
    /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
    /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
    /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
    /// @param recipient The address which should receive the fees collected
    /// @param tickLower The lower tick of the position for which to collect fees
    /// @param tickUpper The upper tick of the position for which to collect fees
    /// @param amount0Requested How much token0 should be withdrawn from the fees owed
    /// @param amount1Requested How much token1 should be withdrawn from the fees owed
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);

    /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
    /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
    /// @dev Fees must be collected separately via a call to #collect
    /// @param tickLower The lower tick of the position for which to burn liquidity
    /// @param tickUpper The upper tick of the position for which to burn liquidity
    /// @param amount How much liquidity to burn
    /// @return amount0 The amount of token0 sent to the recipient
    /// @return amount1 The amount of token1 sent to the recipient
    function burn(int24 tickLower, int24 tickUpper, uint128 amount)
        external
        returns (uint amount0, uint amount1);

    /// @notice Swap token0 for token1, or token1 for token0
    /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
    /// @param recipient The address to receive the output of the swap
    /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
    /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
    /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
    /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
    /// @param data Any data to be passed through to the callback
    /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
    /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);

    /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
    /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
    /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
    /// with 0 amount{0,1} and sending the donation amount(s) from the callback
    /// @param recipient The address which will receive the token0 and token1 amounts
    /// @param amount0 The amount of token0 to send
    /// @param amount1 The amount of token1 to send
    /// @param data Any data to be passed through to the callback
    function flash(address recipient, uint amount0, uint amount1, bytes calldata data) external;

    /// @notice Increase the maximum number of price and liquidity observations that this pool will store
    /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
    /// the input observationCardinalityNext.
    /// @param observationCardinalityNext The desired minimum number of observations for the pool to store
    function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
    /// @notice Set the denominator of the protocol's % share of the fees
    /// @param feeProtocol0 new protocol fee for token0 of the pool
    /// @param feeProtocol1 new protocol fee for token1 of the pool
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;

    /// @notice Collect the protocol fee accrued to the pool
    /// @param recipient The address to which collected protocol fees should be sent
    /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
    /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
    /// @return amount0 The protocol fee collected in token0
    /// @return amount1 The protocol fee collected in token1
    function collectProtocol(address recipient, uint128 amount0Requested, uint128 amount1Requested)
        external
        returns (uint128 amount0, uint128 amount1);
}

File 49 of 78 : IUniswapV3PoolEvents.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
    /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
    /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
    /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
    /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
    event Initialize(uint160 sqrtPriceX96, int24 tick);

    /// @notice Emitted when liquidity is minted for a given position
    /// @param sender The address that minted the liquidity
    /// @param owner The owner of the position and recipient of any minted liquidity
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity minted to the position range
    /// @param amount0 How much token0 was required for the minted liquidity
    /// @param amount1 How much token1 was required for the minted liquidity
    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint amount0,
        uint amount1
    );

    /// @notice Emitted when fees are collected by the owner of a position
    /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
    /// @param owner The owner of the position for which fees are collected
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount0 The amount of token0 fees collected
    /// @param amount1 The amount of token1 fees collected
    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );

    /// @notice Emitted when a position's liquidity is removed
    /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
    /// @param owner The owner of the position for which liquidity is removed
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity to remove
    /// @param amount0 The amount of token0 withdrawn
    /// @param amount1 The amount of token1 withdrawn
    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint amount0,
        uint amount1
    );

    /// @notice Emitted by the pool for any swaps between token0 and token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the output of the swap
    /// @param amount0 The delta of the token0 balance of the pool
    /// @param amount1 The delta of the token1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of price of the pool after the swap
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick
    );

    /// @notice Emitted by the pool for any flashes of token0/token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the tokens from flash
    /// @param amount0 The amount of token0 that was flashed
    /// @param amount1 The amount of token1 that was flashed
    /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
    /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
    event Flash(
        address indexed sender,
        address indexed recipient,
        uint amount0,
        uint amount1,
        uint paid0,
        uint paid1
    );

    /// @notice Emitted by the pool for increases to the number of observations that can be stored
    /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
    /// just before a mint/swap/burn.
    /// @param observationCardinalityNextOld The previous value of the next observation cardinality
    /// @param observationCardinalityNextNew The updated value of the next observation cardinality
    event IncreaseObservationCardinalityNext(
        uint16 observationCardinalityNextOld, uint16 observationCardinalityNextNew
    );

    /// @notice Emitted when the protocol fee is changed by the pool
    /// @param feeProtocol0Old The previous value of the token0 protocol fee
    /// @param feeProtocol1Old The previous value of the token1 protocol fee
    /// @param feeProtocol0New The updated value of the token0 protocol fee
    /// @param feeProtocol1New The updated value of the token1 protocol fee
    event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);

    /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
    /// @param sender The address that collects the protocol fees
    /// @param recipient The address that receives the collected protocol fees
    /// @param amount0 The amount of token0 protocol fees that is withdrawn
    /// @param amount0 The amount of token1 protocol fees that is withdrawn
    event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Interface for the callback executed when an address unlocks the pool manager
interface IUnlockCallback {
    /// @notice Called by the pool manager on `msg.sender` when the manager is unlocked
    /// @param data The data that was passed to the call to unlock
    /// @return Any data that you want to be returned from the unlock call
    function unlockCallback(bytes calldata data) external returns (bytes memory);
}

File 51 of 78 : ImmutableState.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IImmutableState} from "../interfaces/IImmutableState.sol";

/// @title Immutable State
/// @notice A collection of immutable state variables, commonly used across multiple contracts
contract ImmutableState is IImmutableState {
    /// @inheritdoc IImmutableState
    IPoolManager public immutable poolManager;

    /// @notice Thrown when the caller is not PoolManager
    error NotPoolManager();

    /// @notice Only allow calls from the PoolManager contract
    modifier onlyPoolManager() {
        if (msg.sender != address(poolManager)) revert NotPoolManager();
        _;
    }

    constructor(IPoolManager _poolManager) {
        poolManager = _poolManager;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {Currency} from "../types/Currency.sol";
import {CustomRevert} from "./CustomRevert.sol";

library CurrencyReserves {
    using CustomRevert for bytes4;

    /// bytes32(uint256(keccak256("ReservesOf")) - 1)
    bytes32 constant RESERVES_OF_SLOT = 0x1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd95;
    /// bytes32(uint256(keccak256("Currency")) - 1)
    bytes32 constant CURRENCY_SLOT = 0x27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b9;

    function getSyncedCurrency() internal view returns (Currency currency) {
        assembly ("memory-safe") {
            currency := tload(CURRENCY_SLOT)
        }
    }

    function resetCurrency() internal {
        assembly ("memory-safe") {
            tstore(CURRENCY_SLOT, 0)
        }
    }

    function syncCurrencyAndReserves(Currency currency, uint256 value) internal {
        assembly ("memory-safe") {
            tstore(CURRENCY_SLOT, and(currency, 0xffffffffffffffffffffffffffffffffffffffff))
            tstore(RESERVES_OF_SLOT, value)
        }
    }

    function getSyncedReserves() internal view returns (uint256 value) {
        assembly ("memory-safe") {
            value := tload(RESERVES_OF_SLOT)
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

/// @notice This is a temporary library that allows us to use transient storage (tstore/tload)
/// for the nonzero delta count.
/// TODO: This library can be deleted when we have the transient keyword support in solidity.
library NonzeroDeltaCount {
    // The slot holding the number of nonzero deltas. bytes32(uint256(keccak256("NonzeroDeltaCount")) - 1)
    bytes32 internal constant NONZERO_DELTA_COUNT_SLOT =
        0x7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b;

    function read() internal view returns (uint256 count) {
        assembly ("memory-safe") {
            count := tload(NONZERO_DELTA_COUNT_SLOT)
        }
    }

    function increment() internal {
        assembly ("memory-safe") {
            let count := tload(NONZERO_DELTA_COUNT_SLOT)
            count := add(count, 1)
            tstore(NONZERO_DELTA_COUNT_SLOT, count)
        }
    }

    /// @notice Potential to underflow. Ensure checks are performed by integrating contracts to ensure this does not happen.
    /// Current usage ensures this will not happen because we call decrement with known boundaries (only up to the number of times we call increment).
    function decrement() internal {
        assembly ("memory-safe") {
            let count := tload(NONZERO_DELTA_COUNT_SLOT)
            count := sub(count, 1)
            tstore(NONZERO_DELTA_COUNT_SLOT, count)
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

/// @notice This is a temporary library that allows us to use transient storage (tstore/tload)
/// TODO: This library can be deleted when we have the transient keyword support in solidity.
library Lock {
    // The slot holding the unlocked state, transiently. bytes32(uint256(keccak256("Unlocked")) - 1)
    bytes32 internal constant IS_UNLOCKED_SLOT = 0xc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab23;

    function unlock() internal {
        assembly ("memory-safe") {
            // unlock
            tstore(IS_UNLOCKED_SLOT, true)
        }
    }

    function lock() internal {
        assembly ("memory-safe") {
            tstore(IS_UNLOCKED_SLOT, false)
        }
    }

    function isUnlocked() internal view returns (bool unlocked) {
        assembly ("memory-safe") {
            unlocked := tload(IS_UNLOCKED_SLOT)
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Minimal ERC20 interface for Uniswap
/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
interface IERC20Minimal {
    /// @notice Returns an account's balance in the token
    /// @param account The account for which to look up the number of tokens it has, i.e. its balance
    /// @return The number of tokens held by the account
    function balanceOf(address account) external view returns (uint256);

    /// @notice Transfers the amount of token from the `msg.sender` to the recipient
    /// @param recipient The account that will receive the amount transferred
    /// @param amount The number of tokens to send from the sender to the recipient
    /// @return Returns true for a successful transfer, false for an unsuccessful transfer
    function transfer(address recipient, uint256 amount) external returns (bool);

    /// @notice Returns the current allowance given to a spender by an owner
    /// @param owner The account of the token owner
    /// @param spender The account of the token spender
    /// @return The current allowance granted by `owner` to `spender`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
    /// @param spender The account which will be allowed to spend a given amount of the owners tokens
    /// @param amount The amount of tokens allowed to be used by `spender`
    /// @return Returns true for a successful approval, false for unsuccessful
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
    /// @param sender The account from which the transfer will be initiated
    /// @param recipient The recipient of the transfer
    /// @param amount The amount of the transfer
    /// @return Returns true for a successful transfer, false for unsuccessful
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
    /// @param from The account from which the tokens were sent, i.e. the balance decreased
    /// @param to The account to which the tokens were sent, i.e. the balance increased
    /// @param value The amount of tokens that were transferred
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
    /// @param owner The account that approved spending of its tokens
    /// @param spender The account for which the spending allowance was modified
    /// @param value The new allowance from the owner to the spender
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Interface for claims over a contract balance, wrapped as a ERC6909
interface IERC6909Claims {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OperatorSet(address indexed owner, address indexed operator, bool approved);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);

    event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                                 FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @notice Owner balance of an id.
    /// @param owner The address of the owner.
    /// @param id The id of the token.
    /// @return amount The balance of the token.
    function balanceOf(address owner, uint256 id) external view returns (uint256 amount);

    /// @notice Spender allowance of an id.
    /// @param owner The address of the owner.
    /// @param spender The address of the spender.
    /// @param id The id of the token.
    /// @return amount The allowance of the token.
    function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount);

    /// @notice Checks if a spender is approved by an owner as an operator
    /// @param owner The address of the owner.
    /// @param spender The address of the spender.
    /// @return approved The approval status.
    function isOperator(address owner, address spender) external view returns (bool approved);

    /// @notice Transfers an amount of an id from the caller to a receiver.
    /// @param receiver The address of the receiver.
    /// @param id The id of the token.
    /// @param amount The amount of the token.
    /// @return bool True, always, unless the function reverts
    function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);

    /// @notice Transfers an amount of an id from a sender to a receiver.
    /// @param sender The address of the sender.
    /// @param receiver The address of the receiver.
    /// @param id The id of the token.
    /// @param amount The amount of the token.
    /// @return bool True, always, unless the function reverts
    function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);

    /// @notice Approves an amount of an id to a spender.
    /// @param spender The address of the spender.
    /// @param id The id of the token.
    /// @param amount The amount of the token.
    /// @return bool True, always
    function approve(address spender, uint256 id, uint256 amount) external returns (bool);

    /// @notice Sets or removes an operator for the caller.
    /// @param operator The address of the operator.
    /// @param approved The approval status.
    /// @return bool True, always
    function setOperator(address operator, bool approved) external returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Currency} from "../types/Currency.sol";
import {PoolId} from "../types/PoolId.sol";
import {PoolKey} from "../types/PoolKey.sol";

/// @notice Interface for all protocol-fee related functions in the pool manager
interface IProtocolFees {
    /// @notice Thrown when protocol fee is set too high
    error ProtocolFeeTooLarge(uint24 fee);

    /// @notice Thrown when collectProtocolFees or setProtocolFee is not called by the controller.
    error InvalidCaller();

    /// @notice Thrown when collectProtocolFees is attempted on a token that is synced.
    error ProtocolFeeCurrencySynced();

    /// @notice Emitted when the protocol fee controller address is updated in setProtocolFeeController.
    event ProtocolFeeControllerUpdated(address indexed protocolFeeController);

    /// @notice Emitted when the protocol fee is updated for a pool.
    event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee);

    /// @notice Given a currency address, returns the protocol fees accrued in that currency
    /// @param currency The currency to check
    /// @return amount The amount of protocol fees accrued in the currency
    function protocolFeesAccrued(Currency currency) external view returns (uint256 amount);

    /// @notice Sets the protocol fee for the given pool
    /// @param key The key of the pool to set a protocol fee for
    /// @param newProtocolFee The fee to set
    function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external;

    /// @notice Sets the protocol fee controller
    /// @param controller The new protocol fee controller
    function setProtocolFeeController(address controller) external;

    /// @notice Collects the protocol fees for a given recipient and currency, returning the amount collected
    /// @dev This will revert if the contract is unlocked
    /// @param recipient The address to receive the protocol fees
    /// @param currency The currency to withdraw
    /// @param amount The amount of currency to withdraw
    /// @return amountCollected The amount of currency successfully withdrawn
    function collectProtocolFees(address recipient, Currency currency, uint256 amount)
        external
        returns (uint256 amountCollected);

    /// @notice Returns the current protocol fee controller address
    /// @return address The current protocol fee controller address
    function protocolFeeController() external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Interface for functions to access any storage slot in a contract
interface IExtsload {
    /// @notice Called by external contracts to access granular pool state
    /// @param slot Key of slot to sload
    /// @return value The value of the slot as bytes32
    function extsload(bytes32 slot) external view returns (bytes32 value);

    /// @notice Called by external contracts to access granular pool state
    /// @param startSlot Key of slot to start sloading from
    /// @param nSlots Number of slots to load into return value
    /// @return values List of loaded values.
    function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values);

    /// @notice Called by external contracts to access sparse pool state
    /// @param slots List of slots to SLOAD from.
    /// @return values List of loaded values.
    function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @notice Interface for functions to access any transient storage slot in a contract
interface IExttload {
    /// @notice Called by external contracts to access transient storage of the contract
    /// @param slot Key of slot to tload
    /// @return value The value of the slot as bytes32
    function exttload(bytes32 slot) external view returns (bytes32 value);

    /// @notice Called by external contracts to access sparse transient pool state
    /// @param slots List of slots to tload
    /// @return values List of loaded values
    function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {FullMath} from "./FullMath.sol";
import {FixedPoint128} from "./FixedPoint128.sol";
import {LiquidityMath} from "./LiquidityMath.sol";
import {CustomRevert} from "./CustomRevert.sol";

/// @title Position
/// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary
/// @dev Positions store additional state for tracking fees owed to the position
library Position {
    using CustomRevert for bytes4;

    /// @notice Cannot update a position with no liquidity
    error CannotUpdateEmptyPosition();

    // info stored for each user's position
    struct State {
        // the amount of liquidity owned by this position
        uint128 liquidity;
        // fee growth per unit of liquidity as of the last update to liquidity or fees owed
        uint256 feeGrowthInside0LastX128;
        uint256 feeGrowthInside1LastX128;
    }

    /// @notice Returns the State struct of a position, given an owner and position boundaries
    /// @param self The mapping containing all user positions
    /// @param owner The address of the position owner
    /// @param tickLower The lower tick boundary of the position
    /// @param tickUpper The upper tick boundary of the position
    /// @param salt A unique value to differentiate between multiple positions in the same range
    /// @return position The position info struct of the given owners' position
    function get(mapping(bytes32 => State) storage self, address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
        internal
        view
        returns (State storage position)
    {
        bytes32 positionKey = calculatePositionKey(owner, tickLower, tickUpper, salt);
        position = self[positionKey];
    }

    /// @notice A helper function to calculate the position key
    /// @param owner The address of the position owner
    /// @param tickLower the lower tick boundary of the position
    /// @param tickUpper the upper tick boundary of the position
    /// @param salt A unique value to differentiate between multiple positions in the same range, by the same owner. Passed in by the caller.
    function calculatePositionKey(address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
        internal
        pure
        returns (bytes32 positionKey)
    {
        // positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(add(fmp, 0x26), salt) // [0x26, 0x46)
            mstore(add(fmp, 0x06), tickUpper) // [0x23, 0x26)
            mstore(add(fmp, 0x03), tickLower) // [0x20, 0x23)
            mstore(fmp, owner) // [0x0c, 0x20)
            positionKey := keccak256(add(fmp, 0x0c), 0x3a) // len is 58 bytes

            // now clean the memory we used
            mstore(add(fmp, 0x40), 0) // fmp+0x40 held salt
            mstore(add(fmp, 0x20), 0) // fmp+0x20 held tickLower, tickUpper, salt
            mstore(fmp, 0) // fmp held owner
        }
    }

    /// @notice Credits accumulated fees to a user's position
    /// @param self The individual position to update
    /// @param liquidityDelta The change in pool liquidity as a result of the position update
    /// @param feeGrowthInside0X128 The all-time fee growth in currency0, per unit of liquidity, inside the position's tick boundaries
    /// @param feeGrowthInside1X128 The all-time fee growth in currency1, per unit of liquidity, inside the position's tick boundaries
    /// @return feesOwed0 The amount of currency0 owed to the position owner
    /// @return feesOwed1 The amount of currency1 owed to the position owner
    function update(
        State storage self,
        int128 liquidityDelta,
        uint256 feeGrowthInside0X128,
        uint256 feeGrowthInside1X128
    ) internal returns (uint256 feesOwed0, uint256 feesOwed1) {
        uint128 liquidity = self.liquidity;

        if (liquidityDelta == 0) {
            // disallow pokes for 0 liquidity positions
            if (liquidity == 0) CannotUpdateEmptyPosition.selector.revertWith();
        } else {
            self.liquidity = LiquidityMath.addDelta(liquidity, liquidityDelta);
        }

        // calculate accumulated fees. overflow in the subtraction of fee growth is expected
        unchecked {
            feesOwed0 =
                FullMath.mulDiv(feeGrowthInside0X128 - self.feeGrowthInside0LastX128, liquidity, FixedPoint128.Q128);
            feesOwed1 =
                FullMath.mulDiv(feeGrowthInside1X128 - self.feeGrowthInside1LastX128, liquidity, FixedPoint128.Q128);
        }

        // update the position
        self.feeGrowthInside0LastX128 = feeGrowthInside0X128;
        self.feeGrowthInside1LastX128 = feeGrowthInside1X128;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Return type of the beforeSwap hook.
// Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook)
type BeforeSwapDelta is int256;

// Creates a BeforeSwapDelta from specified and unspecified
function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified)
    pure
    returns (BeforeSwapDelta beforeSwapDelta)
{
    assembly ("memory-safe") {
        beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified))
    }
}

/// @notice Library for getting the specified and unspecified deltas from the BeforeSwapDelta type
library BeforeSwapDeltaLibrary {
    /// @notice A BeforeSwapDelta of 0
    BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);

    /// extracts int128 from the upper 128 bits of the BeforeSwapDelta
    /// returned by beforeSwap
    function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) {
        assembly ("memory-safe") {
            deltaSpecified := sar(128, delta)
        }
    }

    /// extracts int128 from the lower 128 bits of the BeforeSwapDelta
    /// returned by beforeSwap and afterSwap
    function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) {
        assembly ("memory-safe") {
            deltaUnspecified := signextend(15, delta)
        }
    }
}

// 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: AGPL-3.0-only
pragma solidity ^0.8.16;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title Optimistic Oracle V3 Interface that callers must use to assert truths about the world.
 */
interface OptimisticOracleV3Interface {
    // Struct grouping together the settings related to the escalation manager stored in the assertion.
    struct EscalationManagerSettings {
        bool arbitrateViaEscalationManager; // False if the DVM is used as an oracle (EscalationManager on True).
        bool discardOracle; // False if Oracle result is used for resolving assertion after dispute.
        bool validateDisputers; // True if the EM isDisputeAllowed should be checked on disputes.
        address assertingCaller; // Stores msg.sender when assertion was made.
        address escalationManager; // Address of the escalation manager (zero address if not configured).
    }

    // Struct for storing properties and lifecycle of an assertion.
    struct Assertion {
        EscalationManagerSettings escalationManagerSettings; // Settings related to the escalation manager.
        address asserter; // Address of the asserter.
        uint64 assertionTime; // Time of the assertion.
        bool settled; // True if the request is settled.
        IERC20 currency; // ERC20 token used to pay rewards and fees.
        uint64 expirationTime; // Unix timestamp marking threshold when the assertion can no longer be disputed.
        bool settlementResolution; // Resolution of the assertion (false till resolved).
        bytes32 domainId; // Optional domain that can be used to relate the assertion to others in the escalationManager.
        bytes32 identifier; // UMA DVM identifier to use for price requests in the event of a dispute.
        uint256 bond; // Amount of currency that the asserter has bonded.
        address callbackRecipient; // Address that receives the callback.
        address disputer; // Address of the disputer.
    }

    // Struct for storing cached currency whitelist.
    struct WhitelistedCurrency {
        bool isWhitelisted; // True if the currency is whitelisted.
        uint256 finalFee; // Final fee of the currency.
    }

    /**
     * @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA
     * DVM or the configured escalation manager for arbitration.
     * @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion.
     * @param assertionId unique identifier for the assertion to dispute.
     * @param disputer receives bonds back at settlement.
     */
    function disputeAssertion(bytes32 assertionId, address disputer) external;

    /**
     * @notice Returns the default identifier used by the Optimistic Oracle V3.
     * @return The default identifier.
     */
    function defaultIdentifier() external view returns (bytes32);

    /**
     * @notice Fetches information about a specific assertion and returns it.
     * @param assertionId unique identifier for the assertion to fetch information for.
     * @return assertion information about the assertion.
     */
    function getAssertion(bytes32 assertionId) external view returns (Assertion memory);

    /**
     * @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or
     * escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage
     * (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency.
     * @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency).
     * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
     * @param asserter receives bonds back at settlement. This could be msg.sender or
     * any other account that the caller wants to receive the bond at settlement time.
     * @return assertionId unique identifier for this assertion.
     */
    function assertTruthWithDefaults(bytes memory claim, address asserter) external returns (bytes32);

    /**
     * @notice Asserts a truth about the world, using a fully custom configuration.
     * @dev The caller must approve this contract to spend at least bond amount of currency.
     * @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
     * @param asserter receives bonds back at settlement. This could be msg.sender or
     * any other account that the caller wants to receive the bond at settlement time.
     * @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and
     * assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The
     * recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked.
     * @param escalationManager if configured, this address will control escalation properties of the assertion. This
     * means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to
     * validate disputes. Combining these, the asserter can define their own security properties for the assertion.
     * escalationManager also _must_ implement the same callbacks as callbackRecipient.
     * @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time.
     * @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved.
     * @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This
     * must be >= getMinimumBond(address(currency)).
     * @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved.
     * @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and
     * can be used by the configured escalationManager to define custom behavior for groups of assertions. This is
     * typically used for "escalation games" by changing bonds or other assertion properties based on the other
     * assertions that have come before. If not needed this value should be 0 to save gas.
     * @return assertionId unique identifier for this assertion.
     */
    function assertTruth(
        bytes memory claim,
        address asserter,
        address callbackRecipient,
        address escalationManager,
        uint64 liveness,
        IERC20 currency,
        uint256 bond,
        bytes32 identifier,
        bytes32 domainId
    ) external returns (bytes32);

    /**
     * @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy
     * of the information within this contract. This is used to save gas when making assertions as we can avoid an
     * external call to the UMA contracts to fetch this.
     * @param identifier identifier to fetch information for and store locally.
     * @param currency currency to fetch information for and store locally.
     */
    function syncUmaParams(bytes32 identifier, address currency) external;

    /**
     * @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the
     * asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle
     * result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an
     * amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of
     * the bond is returned to the asserter or disputer.
     * @param assertionId unique identifier for the assertion to resolve.
     */
    function settleAssertion(bytes32 assertionId) external;

    /**
     * @notice Settles an assertion and returns the resolution.
     * @param assertionId unique identifier for the assertion to resolve and return the resolution for.
     * @return resolution of the assertion.
     */
    function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool);

    /**
     * @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then
     * this will revert. If the assertion was disputed and configured to discard the oracle resolution return false.
     * @param assertionId unique identifier for the assertion to fetch the resolution for.
     * @return resolution of the assertion.
     */
    function getAssertionResult(bytes32 assertionId) external view returns (bool);

    /**
     * @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the
     * currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee.
     * @param currency currency to calculate the minimum bond for.
     * @return minimum bond amount.
     */
    function getMinimumBond(address currency) external view returns (uint256);

    event AssertionMade(
        bytes32 indexed assertionId,
        bytes32 domainId,
        bytes claim,
        address indexed asserter,
        address callbackRecipient,
        address escalationManager,
        address caller,
        uint64 expirationTime,
        IERC20 currency,
        uint256 bond,
        bytes32 indexed identifier
    );

    event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer);

    event AssertionSettled(
        bytes32 indexed assertionId,
        address indexed bondRecipient,
        bool disputed,
        bool settlementResolution,
        address settleCaller
    );

    event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage);
}

File 64 of 78 : IPool.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IPoolAddressesProvider} from './IPoolAddressesProvider.sol';
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';

/**
 * @title IPool
 * @author Aave
 * @notice Defines the basic interface for an Aave Pool.
 */
interface IPool {
  /**
   * @dev Emitted on mintUnbacked()
   * @param reserve The address of the underlying asset of the reserve
   * @param user The address initiating the supply
   * @param onBehalfOf The beneficiary of the supplied assets, receiving the aTokens
   * @param amount The amount of supplied assets
   * @param referralCode The referral code used
   */
  event MintUnbacked(
    address indexed reserve,
    address user,
    address indexed onBehalfOf,
    uint256 amount,
    uint16 indexed referralCode
  );

  /**
   * @dev Emitted on backUnbacked()
   * @param reserve The address of the underlying asset of the reserve
   * @param backer The address paying for the backing
   * @param amount The amount added as backing
   * @param fee The amount paid in fees
   */
  event BackUnbacked(address indexed reserve, address indexed backer, uint256 amount, uint256 fee);

  /**
   * @dev Emitted on supply()
   * @param reserve The address of the underlying asset of the reserve
   * @param user The address initiating the supply
   * @param onBehalfOf The beneficiary of the supply, receiving the aTokens
   * @param amount The amount supplied
   * @param referralCode The referral code used
   */
  event Supply(
    address indexed reserve,
    address user,
    address indexed onBehalfOf,
    uint256 amount,
    uint16 indexed referralCode
  );

  /**
   * @dev Emitted on withdraw()
   * @param reserve The address of the underlying asset being withdrawn
   * @param user The address initiating the withdrawal, owner of aTokens
   * @param to The address that will receive the underlying
   * @param amount The amount to be withdrawn
   */
  event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);

  /**
   * @dev Emitted on borrow() and flashLoan() when debt needs to be opened
   * @param reserve The address of the underlying asset being borrowed
   * @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just
   * initiator of the transaction on flashLoan()
   * @param onBehalfOf The address that will be getting the debt
   * @param amount The amount borrowed out
   * @param interestRateMode The rate mode: 2 for Variable, 1 is deprecated (changed on v3.2.0)
   * @param borrowRate The numeric rate at which the user has borrowed, expressed in ray
   * @param referralCode The referral code used
   */
  event Borrow(
    address indexed reserve,
    address user,
    address indexed onBehalfOf,
    uint256 amount,
    DataTypes.InterestRateMode interestRateMode,
    uint256 borrowRate,
    uint16 indexed referralCode
  );

  /**
   * @dev Emitted on repay()
   * @param reserve The address of the underlying asset of the reserve
   * @param user The beneficiary of the repayment, getting his debt reduced
   * @param repayer The address of the user initiating the repay(), providing the funds
   * @param amount The amount repaid
   * @param useATokens True if the repayment is done using aTokens, `false` if done with underlying asset directly
   */
  event Repay(
    address indexed reserve,
    address indexed user,
    address indexed repayer,
    uint256 amount,
    bool useATokens
  );

  /**
   * @dev Emitted on borrow(), repay() and liquidationCall() when using isolated assets
   * @param asset The address of the underlying asset of the reserve
   * @param totalDebt The total isolation mode debt for the reserve
   */
  event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt);

  /**
   * @dev Emitted when the user selects a certain asset category for eMode
   * @param user The address of the user
   * @param categoryId The category id
   */
  event UserEModeSet(address indexed user, uint8 categoryId);

  /**
   * @dev Emitted on setUserUseReserveAsCollateral()
   * @param reserve The address of the underlying asset of the reserve
   * @param user The address of the user enabling the usage as collateral
   */
  event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);

  /**
   * @dev Emitted on setUserUseReserveAsCollateral()
   * @param reserve The address of the underlying asset of the reserve
   * @param user The address of the user enabling the usage as collateral
   */
  event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);

  /**
   * @dev Emitted on flashLoan()
   * @param target The address of the flash loan receiver contract
   * @param initiator The address initiating the flash loan
   * @param asset The address of the asset being flash borrowed
   * @param amount The amount flash borrowed
   * @param interestRateMode The flashloan mode: 0 for regular flashloan,
   *        1 for Stable (Deprecated on v3.2.0), 2 for Variable
   * @param premium The fee flash borrowed
   * @param referralCode The referral code used
   */
  event FlashLoan(
    address indexed target,
    address initiator,
    address indexed asset,
    uint256 amount,
    DataTypes.InterestRateMode interestRateMode,
    uint256 premium,
    uint16 indexed referralCode
  );

  /**
   * @dev Emitted when a borrower is liquidated.
   * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
   * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
   * @param user The address of the borrower getting liquidated
   * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
   * @param liquidatedCollateralAmount The amount of collateral received by the liquidator
   * @param liquidator The address of the liquidator
   * @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants
   * to receive the underlying collateral asset directly
   */
  event LiquidationCall(
    address indexed collateralAsset,
    address indexed debtAsset,
    address indexed user,
    uint256 debtToCover,
    uint256 liquidatedCollateralAmount,
    address liquidator,
    bool receiveAToken
  );

  /**
   * @dev Emitted when the state of a reserve is updated.
   * @param reserve The address of the underlying asset of the reserve
   * @param liquidityRate The next liquidity rate
   * @param stableBorrowRate The next stable borrow rate @note deprecated on v3.2.0
   * @param variableBorrowRate The next variable borrow rate
   * @param liquidityIndex The next liquidity index
   * @param variableBorrowIndex The next variable borrow index
   */
  event ReserveDataUpdated(
    address indexed reserve,
    uint256 liquidityRate,
    uint256 stableBorrowRate,
    uint256 variableBorrowRate,
    uint256 liquidityIndex,
    uint256 variableBorrowIndex
  );

  /**
   * @dev Emitted when the deficit of a reserve is covered.
   * @param reserve The address of the underlying asset of the reserve
   * @param caller The caller that triggered the DeficitCovered event
   * @param amountCovered The amount of deficit covered
   */
  event DeficitCovered(address indexed reserve, address caller, uint256 amountCovered);

  /**
   * @dev Emitted when the protocol treasury receives minted aTokens from the accrued interest.
   * @param reserve The address of the reserve
   * @param amountMinted The amount minted to the treasury
   */
  event MintedToTreasury(address indexed reserve, uint256 amountMinted);

  /**
   * @dev Emitted when deficit is realized on a liquidation.
   * @param user The user address where the bad debt will be burned
   * @param debtAsset The address of the underlying borrowed asset to be burned
   * @param amountCreated The amount of deficit created
   */
  event DeficitCreated(address indexed user, address indexed debtAsset, uint256 amountCreated);

  /**
   * @notice Mints an `amount` of aTokens to the `onBehalfOf`
   * @param asset The address of the underlying asset to mint
   * @param amount The amount to mint
   * @param onBehalfOf The address that will receive the aTokens
   * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function mintUnbacked(
    address asset,
    uint256 amount,
    address onBehalfOf,
    uint16 referralCode
  ) external;

  /**
   * @notice Back the current unbacked underlying with `amount` and pay `fee`.
   * @param asset The address of the underlying asset to back
   * @param amount The amount to back
   * @param fee The amount paid in fees
   * @return The backed amount
   */
  function backUnbacked(address asset, uint256 amount, uint256 fee) external returns (uint256);

  /**
   * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
   * - E.g. User supplies 100 USDC and gets in return 100 aUSDC
   * @param asset The address of the underlying asset to supply
   * @param amount The amount to be supplied
   * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
   *   wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
   *   is a different wallet
   * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;

  /**
   * @notice Supply with transfer approval of asset to be supplied done via permit function
   * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
   * @param asset The address of the underlying asset to supply
   * @param amount The amount to be supplied
   * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
   *   wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
   *   is a different wallet
   * @param deadline The deadline timestamp that the permit is valid
   * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   * @param permitV The V parameter of ERC712 permit sig
   * @param permitR The R parameter of ERC712 permit sig
   * @param permitS The S parameter of ERC712 permit sig
   */
  function supplyWithPermit(
    address asset,
    uint256 amount,
    address onBehalfOf,
    uint16 referralCode,
    uint256 deadline,
    uint8 permitV,
    bytes32 permitR,
    bytes32 permitS
  ) external;

  /**
   * @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
   * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
   * @param asset The address of the underlying asset to withdraw
   * @param amount The underlying amount to be withdrawn
   *   - Send the value type(uint256).max in order to withdraw the whole aToken balance
   * @param to The address that will receive the underlying, same as msg.sender if the user
   *   wants to receive it on his own wallet, or a different address if the beneficiary is a
   *   different wallet
   * @return The final amount withdrawn
   */
  function withdraw(address asset, uint256 amount, address to) external returns (uint256);

  /**
   * @notice Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
   * already supplied enough collateral, or he was given enough allowance by a credit delegator on the VariableDebtToken
   * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
   *   and 100 variable debt tokens
   * @param asset The address of the underlying asset to borrow
   * @param amount The amount to be borrowed
   * @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0
   * @param referralCode The code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   * @param onBehalfOf The address of the user who will receive the debt. Should be the address of the borrower itself
   * calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
   * if he has been given credit delegation allowance
   */
  function borrow(
    address asset,
    uint256 amount,
    uint256 interestRateMode,
    uint16 referralCode,
    address onBehalfOf
  ) external;

  /**
   * @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
   * - E.g. User repays 100 USDC, burning 100 variable debt tokens of the `onBehalfOf` address
   * @param asset The address of the borrowed underlying asset previously borrowed
   * @param amount The amount to repay
   * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
   * @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0
   * @param onBehalfOf The address of the user who will get his debt reduced/removed. Should be the address of the
   * user calling the function if he wants to reduce/remove his own debt, or the address of any other
   * other borrower whose debt should be removed
   * @return The final amount repaid
   */
  function repay(
    address asset,
    uint256 amount,
    uint256 interestRateMode,
    address onBehalfOf
  ) external returns (uint256);

  /**
   * @notice Repay with transfer approval of asset to be repaid done via permit function
   * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
   * @param asset The address of the borrowed underlying asset previously borrowed
   * @param amount The amount to repay
   * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
   * @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0
   * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
   * user calling the function if he wants to reduce/remove his own debt, or the address of any other
   * other borrower whose debt should be removed
   * @param deadline The deadline timestamp that the permit is valid
   * @param permitV The V parameter of ERC712 permit sig
   * @param permitR The R parameter of ERC712 permit sig
   * @param permitS The S parameter of ERC712 permit sig
   * @return The final amount repaid
   */
  function repayWithPermit(
    address asset,
    uint256 amount,
    uint256 interestRateMode,
    address onBehalfOf,
    uint256 deadline,
    uint8 permitV,
    bytes32 permitR,
    bytes32 permitS
  ) external returns (uint256);

  /**
   * @notice Repays a borrowed `amount` on a specific reserve using the reserve aTokens, burning the
   * equivalent debt tokens
   * - E.g. User repays 100 USDC using 100 aUSDC, burning 100 variable debt tokens
   * @dev  Passing uint256.max as amount will clean up any residual aToken dust balance, if the user aToken
   * balance is not enough to cover the whole debt
   * @param asset The address of the borrowed underlying asset previously borrowed
   * @param amount The amount to repay
   * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
   * @param interestRateMode DEPRECATED in v3.2.0
   * @return The final amount repaid
   */
  function repayWithATokens(
    address asset,
    uint256 amount,
    uint256 interestRateMode
  ) external returns (uint256);

  /**
   * @notice Allows suppliers to enable/disable a specific supplied asset as collateral
   * @param asset The address of the underlying asset supplied
   * @param useAsCollateral True if the user wants to use the supply as collateral, false otherwise
   */
  function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;

  /**
   * @notice Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
   * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
   *   a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
   * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
   * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
   * @param user The address of the borrower getting liquidated
   * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
   * @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants
   * to receive the underlying collateral asset directly
   */
  function liquidationCall(
    address collateralAsset,
    address debtAsset,
    address user,
    uint256 debtToCover,
    bool receiveAToken
  ) external;

  /**
   * @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
   * as long as the amount taken plus a fee is returned.
   * @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
   * into consideration. For further details please visit https://docs.aave.com/developers/
   * @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanReceiver interface
   * @param assets The addresses of the assets being flash-borrowed
   * @param amounts The amounts of the assets being flash-borrowed
   * @param interestRateModes Types of the debt to open if the flash loan is not returned:
   *   0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
   *   1 -> Deprecated on v3.2.0
   *   2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
   * @param onBehalfOf The address  that will receive the debt in the case of using 2 on `modes`
   * @param params Variadic packed params to pass to the receiver as extra information
   * @param referralCode The code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function flashLoan(
    address receiverAddress,
    address[] calldata assets,
    uint256[] calldata amounts,
    uint256[] calldata interestRateModes,
    address onBehalfOf,
    bytes calldata params,
    uint16 referralCode
  ) external;

  /**
   * @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
   * as long as the amount taken plus a fee is returned.
   * @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
   * into consideration. For further details please visit https://docs.aave.com/developers/
   * @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanSimpleReceiver interface
   * @param asset The address of the asset being flash-borrowed
   * @param amount The amount of the asset being flash-borrowed
   * @param params Variadic packed params to pass to the receiver as extra information
   * @param referralCode The code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function flashLoanSimple(
    address receiverAddress,
    address asset,
    uint256 amount,
    bytes calldata params,
    uint16 referralCode
  ) external;

  /**
   * @notice Returns the user account data across all the reserves
   * @param user The address of the user
   * @return totalCollateralBase The total collateral of the user in the base currency used by the price feed
   * @return totalDebtBase The total debt of the user in the base currency used by the price feed
   * @return availableBorrowsBase The borrowing power left of the user in the base currency used by the price feed
   * @return currentLiquidationThreshold The liquidation threshold of the user
   * @return ltv The loan to value of The user
   * @return healthFactor The current health factor of the user
   */
  function getUserAccountData(
    address user
  )
    external
    view
    returns (
      uint256 totalCollateralBase,
      uint256 totalDebtBase,
      uint256 availableBorrowsBase,
      uint256 currentLiquidationThreshold,
      uint256 ltv,
      uint256 healthFactor
    );

  /**
   * @notice Initializes a reserve, activating it, assigning an aToken and debt tokens and an
   * interest rate strategy
   * @dev Only callable by the PoolConfigurator contract
   * @param asset The address of the underlying asset of the reserve
   * @param aTokenAddress The address of the aToken that will be assigned to the reserve
   * @param variableDebtAddress The address of the VariableDebtToken that will be assigned to the reserve
   * @param interestRateStrategyAddress The address of the interest rate strategy contract
   */
  function initReserve(
    address asset,
    address aTokenAddress,
    address variableDebtAddress,
    address interestRateStrategyAddress
  ) external;

  /**
   * @notice Drop a reserve
   * @dev Only callable by the PoolConfigurator contract
   * @dev Does not reset eMode flags, which must be considered when reusing the same reserve id for a different reserve.
   * @param asset The address of the underlying asset of the reserve
   */
  function dropReserve(address asset) external;

  /**
   * @notice Updates the address of the interest rate strategy contract
   * @dev Only callable by the PoolConfigurator contract
   * @param asset The address of the underlying asset of the reserve
   * @param rateStrategyAddress The address of the interest rate strategy contract
   */
  function setReserveInterestRateStrategyAddress(
    address asset,
    address rateStrategyAddress
  ) external;

  /**
   * @notice Accumulates interest to all indexes of the reserve
   * @dev Only callable by the PoolConfigurator contract
   * @dev To be used when required by the configurator, for example when updating interest rates strategy data
   * @param asset The address of the underlying asset of the reserve
   */
  function syncIndexesState(address asset) external;

  /**
   * @notice Updates interest rates on the reserve data
   * @dev Only callable by the PoolConfigurator contract
   * @dev To be used when required by the configurator, for example when updating interest rates strategy data
   * @param asset The address of the underlying asset of the reserve
   */
  function syncRatesState(address asset) external;

  /**
   * @notice Sets the configuration bitmap of the reserve as a whole
   * @dev Only callable by the PoolConfigurator contract
   * @param asset The address of the underlying asset of the reserve
   * @param configuration The new configuration bitmap
   */
  function setConfiguration(
    address asset,
    DataTypes.ReserveConfigurationMap calldata configuration
  ) external;

  /**
   * @notice Returns the configuration of the reserve
   * @param asset The address of the underlying asset of the reserve
   * @return The configuration of the reserve
   */
  function getConfiguration(
    address asset
  ) external view returns (DataTypes.ReserveConfigurationMap memory);

  /**
   * @notice Returns the configuration of the user across all the reserves
   * @param user The user address
   * @return The configuration of the user
   */
  function getUserConfiguration(
    address user
  ) external view returns (DataTypes.UserConfigurationMap memory);

  /**
   * @notice Returns the normalized income of the reserve
   * @param asset The address of the underlying asset of the reserve
   * @return The reserve's normalized income
   */
  function getReserveNormalizedIncome(address asset) external view returns (uint256);

  /**
   * @notice Returns the normalized variable debt per unit of asset
   * @dev WARNING: This function is intended to be used primarily by the protocol itself to get a
   * "dynamic" variable index based on time, current stored index and virtual rate at the current
   * moment (approx. a borrower would get if opening a position). This means that is always used in
   * combination with variable debt supply/balances.
   * If using this function externally, consider that is possible to have an increasing normalized
   * variable debt that is not equivalent to how the variable debt index would be updated in storage
   * (e.g. only updates with non-zero variable debt supply)
   * @param asset The address of the underlying asset of the reserve
   * @return The reserve normalized variable debt
   */
  function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);

  /**
   * @notice Returns the state and configuration of the reserve
   * @param asset The address of the underlying asset of the reserve
   * @return The state and configuration data of the reserve
   */
  function getReserveData(address asset) external view returns (DataTypes.ReserveDataLegacy memory);

  /**
   * @notice Returns the virtual underlying balance of the reserve
   * @param asset The address of the underlying asset of the reserve
   * @return The reserve virtual underlying balance
   */
  function getVirtualUnderlyingBalance(address asset) external view returns (uint128);

  /**
   * @notice Validates and finalizes an aToken transfer
   * @dev Only callable by the overlying aToken of the `asset`
   * @param asset The address of the underlying asset of the aToken
   * @param from The user from which the aTokens are transferred
   * @param to The user receiving the aTokens
   * @param amount The amount being transferred/withdrawn
   * @param balanceFromBefore The aToken balance of the `from` user before the transfer
   * @param balanceToBefore The aToken balance of the `to` user before the transfer
   */
  function finalizeTransfer(
    address asset,
    address from,
    address to,
    uint256 amount,
    uint256 balanceFromBefore,
    uint256 balanceToBefore
  ) external;

  /**
   * @notice Returns the list of the underlying assets of all the initialized reserves
   * @dev It does not include dropped reserves
   * @return The addresses of the underlying assets of the initialized reserves
   */
  function getReservesList() external view returns (address[] memory);

  /**
   * @notice Returns the number of initialized reserves
   * @dev It includes dropped reserves
   * @return The count
   */
  function getReservesCount() external view returns (uint256);

  /**
   * @notice Returns the address of the underlying asset of a reserve by the reserve id as stored in the DataTypes.ReserveData struct
   * @param id The id of the reserve as stored in the DataTypes.ReserveData struct
   * @return The address of the reserve associated with id
   */
  function getReserveAddressById(uint16 id) external view returns (address);

  /**
   * @notice Returns the PoolAddressesProvider connected to this contract
   * @return The address of the PoolAddressesProvider
   */
  function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);

  /**
   * @notice Updates the protocol fee on the bridging
   * @param bridgeProtocolFee The part of the premium sent to the protocol treasury
   */
  function updateBridgeProtocolFee(uint256 bridgeProtocolFee) external;

  /**
   * @notice Updates flash loan premiums. Flash loan premium consists of two parts:
   * - A part is sent to aToken holders as extra, one time accumulated interest
   * - A part is collected by the protocol treasury
   * @dev The total premium is calculated on the total borrowed amount
   * @dev The premium to protocol is calculated on the total premium, being a percentage of `flashLoanPremiumTotal`
   * @dev Only callable by the PoolConfigurator contract
   * @param flashLoanPremiumTotal The total premium, expressed in bps
   * @param flashLoanPremiumToProtocol The part of the premium sent to the protocol treasury, expressed in bps
   */
  function updateFlashloanPremiums(
    uint128 flashLoanPremiumTotal,
    uint128 flashLoanPremiumToProtocol
  ) external;

  /**
   * @notice Configures a new or alters an existing collateral configuration of an eMode.
   * @dev In eMode, the protocol allows very high borrowing power to borrow assets of the same category.
   * The category 0 is reserved as it's the default for volatile assets
   * @param id The id of the category
   * @param config The configuration of the category
   */
  function configureEModeCategory(
    uint8 id,
    DataTypes.EModeCategoryBaseConfiguration memory config
  ) external;

  /**
   * @notice Replaces the current eMode collateralBitmap.
   * @param id The id of the category
   * @param collateralBitmap The collateralBitmap of the category
   */
  function configureEModeCategoryCollateralBitmap(uint8 id, uint128 collateralBitmap) external;

  /**
   * @notice Replaces the current eMode borrowableBitmap.
   * @param id The id of the category
   * @param borrowableBitmap The borrowableBitmap of the category
   */
  function configureEModeCategoryBorrowableBitmap(uint8 id, uint128 borrowableBitmap) external;

  /**
   * @notice Returns the data of an eMode category
   * @dev DEPRECATED use independent getters instead
   * @param id The id of the category
   * @return The configuration data of the category
   */
  function getEModeCategoryData(
    uint8 id
  ) external view returns (DataTypes.EModeCategoryLegacy memory);

  /**
   * @notice Returns the label of an eMode category
   * @param id The id of the category
   * @return The label of the category
   */
  function getEModeCategoryLabel(uint8 id) external view returns (string memory);

  /**
   * @notice Returns the collateral config of an eMode category
   * @param id The id of the category
   * @return The ltv,lt,lb of the category
   */
  function getEModeCategoryCollateralConfig(
    uint8 id
  ) external view returns (DataTypes.CollateralConfig memory);

  /**
   * @notice Returns the collateralBitmap of an eMode category
   * @param id The id of the category
   * @return The collateralBitmap of the category
   */
  function getEModeCategoryCollateralBitmap(uint8 id) external view returns (uint128);

  /**
   * @notice Returns the borrowableBitmap of an eMode category
   * @param id The id of the category
   * @return The borrowableBitmap of the category
   */
  function getEModeCategoryBorrowableBitmap(uint8 id) external view returns (uint128);

  /**
   * @notice Allows a user to use the protocol in eMode
   * @param categoryId The id of the category
   */
  function setUserEMode(uint8 categoryId) external;

  /**
   * @notice Returns the eMode the user is using
   * @param user The address of the user
   * @return The eMode id
   */
  function getUserEMode(address user) external view returns (uint256);

  /**
   * @notice Resets the isolation mode total debt of the given asset to zero
   * @dev It requires the given asset has zero debt ceiling
   * @param asset The address of the underlying asset to reset the isolationModeTotalDebt
   */
  function resetIsolationModeTotalDebt(address asset) external;

  /**
   * @notice Sets the liquidation grace period of the given asset
   * @dev To enable a liquidation grace period, a timestamp in the future should be set,
   *      To disable a liquidation grace period, any timestamp in the past works, like 0
   * @param asset The address of the underlying asset to set the liquidationGracePeriod
   * @param until Timestamp when the liquidation grace period will end
   **/
  function setLiquidationGracePeriod(address asset, uint40 until) external;

  /**
   * @notice Returns the liquidation grace period of the given asset
   * @param asset The address of the underlying asset
   * @return Timestamp when the liquidation grace period will end
   **/
  function getLiquidationGracePeriod(address asset) external view returns (uint40);

  /**
   * @notice Returns the total fee on flash loans
   * @return The total fee on flashloans
   */
  function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);

  /**
   * @notice Returns the part of the bridge fees sent to protocol
   * @return The bridge fee sent to the protocol treasury
   */
  function BRIDGE_PROTOCOL_FEE() external view returns (uint256);

  /**
   * @notice Returns the part of the flashloan fees sent to protocol
   * @return The flashloan fee sent to the protocol treasury
   */
  function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128);

  /**
   * @notice Returns the maximum number of reserves supported to be listed in this Pool
   * @return The maximum number of reserves supported
   */
  function MAX_NUMBER_RESERVES() external view returns (uint16);

  /**
   * @notice Mints the assets accrued through the reserve factor to the treasury in the form of aTokens
   * @param assets The list of reserves for which the minting needs to be executed
   */
  function mintToTreasury(address[] calldata assets) external;

  /**
   * @notice Rescue and transfer tokens locked in this contract
   * @param token The address of the token
   * @param to The address of the recipient
   * @param amount The amount of token to transfer
   */
  function rescueTokens(address token, address to, uint256 amount) external;

  /**
   * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
   * - E.g. User supplies 100 USDC and gets in return 100 aUSDC
   * @dev Deprecated: Use the `supply` function instead
   * @param asset The address of the underlying asset to supply
   * @param amount The amount to be supplied
   * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
   *   wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
   *   is a different wallet
   * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;

  /**
   * @notice It covers the deficit of a specified reserve by burning:
   * - the equivalent aToken `amount` for assets with virtual accounting enabled
   * - the equivalent `amount` of underlying for assets with virtual accounting disabled (e.g. GHO)
   * @dev The deficit of a reserve can occur due to situations where borrowed assets are not repaid, leading to bad debt.
   * @param asset The address of the underlying asset to cover the deficit.
   * @param amount The amount to be covered, in aToken or underlying on non-virtual accounted assets
   */
  function eliminateReserveDeficit(address asset, uint256 amount) external;

  /**
   * @notice Returns the current deficit of a reserve.
   * @param asset The address of the underlying asset of the reserve
   * @return The current deficit of the reserve
   */
  function getReserveDeficit(address asset) external view returns (uint256);

  /**
   * @notice Returns the aToken address of a reserve.
   * @param asset The address of the underlying asset of the reserve
   * @return The address of the aToken
   */
  function getReserveAToken(address asset) external view returns (address);

  /**
   * @notice Returns the variableDebtToken address of a reserve.
   * @param asset The address of the underlying asset of the reserve
   * @return The address of the variableDebtToken
   */
  function getReserveVariableDebtToken(address asset) external view returns (address);

  /**
   * @notice Gets the address of the external FlashLoanLogic
   */
  function getFlashLoanLogic() external view returns (address);

  /**
   * @notice Gets the address of the external BorrowLogic
   */
  function getBorrowLogic() external view returns (address);

  /**
   * @notice Gets the address of the external BridgeLogic
   */
  function getBridgeLogic() external view returns (address);

  /**
   * @notice Gets the address of the external EModeLogic
   */
  function getEModeLogic() external view returns (address);

  /**
   * @notice Gets the address of the external LiquidationLogic
   */
  function getLiquidationLogic() external view returns (address);

  /**
   * @notice Gets the address of the external PoolLogic
   */
  function getPoolLogic() external view returns (address);

  /**
   * @notice Gets the address of the external SupplyLogic
   */
  function getSupplyLogic() external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {IPoolAddressesProvider} from '../../interfaces/IPoolAddressesProvider.sol';
import {DataTypes} from '../../protocol/libraries/types/DataTypes.sol';

interface IUiPoolDataProviderV3 {
  struct AggregatedReserveData {
    address underlyingAsset;
    string name;
    string symbol;
    uint256 decimals;
    uint256 baseLTVasCollateral;
    uint256 reserveLiquidationThreshold;
    uint256 reserveLiquidationBonus;
    uint256 reserveFactor;
    bool usageAsCollateralEnabled;
    bool borrowingEnabled;
    bool isActive;
    bool isFrozen;
    // base data
    uint128 liquidityIndex;
    uint128 variableBorrowIndex;
    uint128 liquidityRate;
    uint128 variableBorrowRate;
    uint40 lastUpdateTimestamp;
    address aTokenAddress;
    address variableDebtTokenAddress;
    address interestRateStrategyAddress;
    //
    uint256 availableLiquidity;
    uint256 totalScaledVariableDebt;
    uint256 priceInMarketReferenceCurrency;
    address priceOracle;
    uint256 variableRateSlope1;
    uint256 variableRateSlope2;
    uint256 baseVariableBorrowRate;
    uint256 optimalUsageRatio;
    // v3 only
    bool isPaused;
    bool isSiloedBorrowing;
    uint128 accruedToTreasury;
    uint128 unbacked;
    uint128 isolationModeTotalDebt;
    bool flashLoanEnabled;
    //
    uint256 debtCeiling;
    uint256 debtCeilingDecimals;
    uint256 borrowCap;
    uint256 supplyCap;
    bool borrowableInIsolation;
    // v3.1
    bool virtualAccActive;
    uint128 virtualUnderlyingBalance;
    // v3.3
    uint128 deficit;
  }

  struct UserReserveData {
    address underlyingAsset;
    uint256 scaledATokenBalance;
    bool usageAsCollateralEnabledOnUser;
    uint256 scaledVariableDebt;
  }

  struct BaseCurrencyInfo {
    uint256 marketReferenceCurrencyUnit;
    int256 marketReferenceCurrencyPriceInUsd;
    int256 networkBaseTokenPriceInUsd;
    uint8 networkBaseTokenPriceDecimals;
  }

  struct Emode {
    uint8 id;
    DataTypes.EModeCategory eMode;
  }

  function getReservesList(
    IPoolAddressesProvider provider
  ) external view returns (address[] memory);

  function getReservesData(
    IPoolAddressesProvider provider
  ) external view returns (AggregatedReserveData[] memory, BaseCurrencyInfo memory);

  function getUserReservesData(
    IPoolAddressesProvider provider,
    address user
  ) external view returns (UserReserveData[] memory, uint8);

  /**
   * @dev Iterates the eModes mapping and returns all eModes found
   * @notice The method assumes for id gaps <= 2 within the eMode definitions
   * @return an array of eModes that were found in the eMode mapping
   */
  function getEModes(IPoolAddressesProvider provider) external view returns (Emode[] memory);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title IPoolAddressesProvider
 * @author Aave
 * @notice Defines the basic interface for a Pool Addresses Provider.
 */
interface IPoolAddressesProvider {
  /**
   * @dev Emitted when the market identifier is updated.
   * @param oldMarketId The old id of the market
   * @param newMarketId The new id of the market
   */
  event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);

  /**
   * @dev Emitted when the pool is updated.
   * @param oldAddress The old address of the Pool
   * @param newAddress The new address of the Pool
   */
  event PoolUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the pool configurator is updated.
   * @param oldAddress The old address of the PoolConfigurator
   * @param newAddress The new address of the PoolConfigurator
   */
  event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the price oracle is updated.
   * @param oldAddress The old address of the PriceOracle
   * @param newAddress The new address of the PriceOracle
   */
  event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the ACL manager is updated.
   * @param oldAddress The old address of the ACLManager
   * @param newAddress The new address of the ACLManager
   */
  event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the ACL admin is updated.
   * @param oldAddress The old address of the ACLAdmin
   * @param newAddress The new address of the ACLAdmin
   */
  event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the price oracle sentinel is updated.
   * @param oldAddress The old address of the PriceOracleSentinel
   * @param newAddress The new address of the PriceOracleSentinel
   */
  event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the pool data provider is updated.
   * @param oldAddress The old address of the PoolDataProvider
   * @param newAddress The new address of the PoolDataProvider
   */
  event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when a new proxy is created.
   * @param id The identifier of the proxy
   * @param proxyAddress The address of the created proxy contract
   * @param implementationAddress The address of the implementation contract
   */
  event ProxyCreated(
    bytes32 indexed id,
    address indexed proxyAddress,
    address indexed implementationAddress
  );

  /**
   * @dev Emitted when a new non-proxied contract address is registered.
   * @param id The identifier of the contract
   * @param oldAddress The address of the old contract
   * @param newAddress The address of the new contract
   */
  event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress);

  /**
   * @dev Emitted when the implementation of the proxy registered with id is updated
   * @param id The identifier of the contract
   * @param proxyAddress The address of the proxy contract
   * @param oldImplementationAddress The address of the old implementation contract
   * @param newImplementationAddress The address of the new implementation contract
   */
  event AddressSetAsProxy(
    bytes32 indexed id,
    address indexed proxyAddress,
    address oldImplementationAddress,
    address indexed newImplementationAddress
  );

  /**
   * @notice Returns the id of the Aave market to which this contract points to.
   * @return The market id
   */
  function getMarketId() external view returns (string memory);

  /**
   * @notice Associates an id with a specific PoolAddressesProvider.
   * @dev This can be used to create an onchain registry of PoolAddressesProviders to
   * identify and validate multiple Aave markets.
   * @param newMarketId The market id
   */
  function setMarketId(string calldata newMarketId) external;

  /**
   * @notice Returns an address by its identifier.
   * @dev The returned address might be an EOA or a contract, potentially proxied
   * @dev It returns ZERO if there is no registered address with the given id
   * @param id The id
   * @return The address of the registered for the specified id
   */
  function getAddress(bytes32 id) external view returns (address);

  /**
   * @notice General function to update the implementation of a proxy registered with
   * certain `id`. If there is no proxy registered, it will instantiate one and
   * set as implementation the `newImplementationAddress`.
   * @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit
   * setter function, in order to avoid unexpected consequences
   * @param id The id
   * @param newImplementationAddress The address of the new implementation
   */
  function setAddressAsProxy(bytes32 id, address newImplementationAddress) external;

  /**
   * @notice Sets an address for an id replacing the address saved in the addresses map.
   * @dev IMPORTANT Use this function carefully, as it will do a hard replacement
   * @param id The id
   * @param newAddress The address to set
   */
  function setAddress(bytes32 id, address newAddress) external;

  /**
   * @notice Returns the address of the Pool proxy.
   * @return The Pool proxy address
   */
  function getPool() external view returns (address);

  /**
   * @notice Updates the implementation of the Pool, or creates a proxy
   * setting the new `pool` implementation when the function is called for the first time.
   * @param newPoolImpl The new Pool implementation
   */
  function setPoolImpl(address newPoolImpl) external;

  /**
   * @notice Returns the address of the PoolConfigurator proxy.
   * @return The PoolConfigurator proxy address
   */
  function getPoolConfigurator() external view returns (address);

  /**
   * @notice Updates the implementation of the PoolConfigurator, or creates a proxy
   * setting the new `PoolConfigurator` implementation when the function is called for the first time.
   * @param newPoolConfiguratorImpl The new PoolConfigurator implementation
   */
  function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;

  /**
   * @notice Returns the address of the price oracle.
   * @return The address of the PriceOracle
   */
  function getPriceOracle() external view returns (address);

  /**
   * @notice Updates the address of the price oracle.
   * @param newPriceOracle The address of the new PriceOracle
   */
  function setPriceOracle(address newPriceOracle) external;

  /**
   * @notice Returns the address of the ACL manager.
   * @return The address of the ACLManager
   */
  function getACLManager() external view returns (address);

  /**
   * @notice Updates the address of the ACL manager.
   * @param newAclManager The address of the new ACLManager
   */
  function setACLManager(address newAclManager) external;

  /**
   * @notice Returns the address of the ACL admin.
   * @return The address of the ACL admin
   */
  function getACLAdmin() external view returns (address);

  /**
   * @notice Updates the address of the ACL admin.
   * @param newAclAdmin The address of the new ACL admin
   */
  function setACLAdmin(address newAclAdmin) external;

  /**
   * @notice Returns the address of the price oracle sentinel.
   * @return The address of the PriceOracleSentinel
   */
  function getPriceOracleSentinel() external view returns (address);

  /**
   * @notice Updates the address of the price oracle sentinel.
   * @param newPriceOracleSentinel The address of the new PriceOracleSentinel
   */
  function setPriceOracleSentinel(address newPriceOracleSentinel) external;

  /**
   * @notice Returns the address of the data provider.
   * @return The address of the DataProvider
   */
  function getPoolDataProvider() external view returns (address);

  /**
   * @notice Updates the address of the data provider.
   * @param newDataProvider The address of the new DataProvider
   */
  function setPoolDataProvider(address newDataProvider) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.5;
pragma abicoder v2;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
    /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint deadline;
        uint amountIn;
        uint amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint deadline;
        uint amountIn;
        uint amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint deadline;
        uint amountOut;
        uint amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint deadline;
        uint amountOut;
        uint amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint amountIn);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    /// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
    error InvalidTick();
    /// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
    error InvalidSqrtPrice();

    /// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = 887272;

    /// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
    int24 internal constant MIN_TICK_SPACING = 1;
    /// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
    int24 internal constant MAX_TICK_SPACING = type(int16).max;

    /// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_PRICE = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
    /// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
    uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
        1461446703485210103287273052203988822378723970342 - 4295128739 - 1;

    /// @notice Given a tickSpacing, compute the maximum usable tick
    function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
        unchecked {
            return (MAX_TICK / tickSpacing) * tickSpacing;
        }
    }

    /// @notice Given a tickSpacing, compute the minimum usable tick
    function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
        unchecked {
            return (MIN_TICK / tickSpacing) * tickSpacing;
        }
    }

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
    /// at the given tick
    function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        unchecked {
            uint256 absTick;
            assembly {
                // mask = 0 if tick >= 0 else -1 (all 1s)
                let mask := sar(255, tick)
                // if tick >= 0, |tick| = tick = 0 ^ tick
                // if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
                // either way, |tick| = mask ^ (tick + mask)
                absTick := xor(mask, add(mask, tick))
            }
            // Equivalent: if (absTick > MAX_TICK) revert InvalidTick();
            assembly {
                if gt(absTick, MAX_TICK) {
                    // store 4-byte selector of "InvalidTick()" at memory [0x1c, 0x20)
                    mstore(0, 0xce8ef7fc)
                    revert(0x1c, 0x04)
                }
            }

            // Equivalent to:
            //     price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
            //     or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
            uint256 price;
            assembly {
                price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
            }
            if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;

            assembly {
                // if (tick > 0) price = type(uint256).max / price;
                if sgt(tick, 0) { price := div(not(0), price) }

                // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
                // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
                // we round up in the division so getTickAtSqrtPrice of the output price is always consistent
                // `sub(shl(32, 1), 1)` is `type(uint32).max`
                // `price + type(uint32).max` will not overflow because `price` fits in 192 bits
                sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
            }
        }
    }

    /// @notice Calculates the greatest tick value such that getPriceAtTick(tick) <= price
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getPriceAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the price is less than or equal to the input price
    function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        unchecked {
            // Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
            // second inequality must be < because the price can never reach the price at the max tick
            assembly {
                // if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
                // if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
                if gt(sub(sqrtPriceX96, MIN_SQRT_PRICE), MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
                    // store 4-byte selector of "InvalidSqrtPrice()" at memory [0x1c, 0x20)
                    mstore(0, 0x31efafe8)
                    revert(0x1c, 0x04)
                }
            }

            uint256 price = uint256(sqrtPriceX96) << 32;

            uint256 r = price;
            uint256 msb = 0;

            assembly {
                let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(5, gt(r, 0xFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(4, gt(r, 0xFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(3, gt(r, 0xFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(2, gt(r, 0xF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(1, gt(r, 0x3))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := gt(r, 0x1)
                msb := or(msb, f)
            }

            if (msb >= 128) r = price >> (msb - 127);
            else r = price << (127 - msb);

            int256 log_2 = (int256(msb) - 128) << 64;

            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(63, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(62, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(61, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(60, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(59, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(58, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(57, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(56, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(55, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(54, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(53, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(52, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(51, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(50, f))
            }

            int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

            int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

            tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;

/// @title Contains 512-bit math functions
library FullMath {
    uint256 constant FIXED_1 = 0x080000000000000000000000000000000;
    uint256 constant FIXED_2 = 0x100000000000000000000000000000000;
    uint256 constant LOG_E_2 = 6931471806;
    uint256 constant BASE = 1e10;
   
    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            result = mulDiv(a, b, denominator);
            if (mulmod(a, b, denominator) != 0) {
                require(++result > 0);
            }
        }
    }
    /// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
    /// calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    // Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
    function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then use the Chinese Remainder Theorem to reconstruct
            // the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2**256 + prod0
            uint256 prod0 = a * b; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(a, b, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (0 - denominator) & denominator;
            // Divide denominator by power of two
            assembly {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly {
                prod0 := div(prod0, twos)
            }
            // Shift in bits from prod1 into prod0. For this we need
            // to flip `twos` such that it is 2**256 / twos.
            // If twos is zero, then it becomes one
            assembly {
                twos := add(div(sub(0, twos), twos), 1)
            }
            prod0 |= prod1 * twos;

            // Invert denominator mod 2**256
            // Now that denominator is an odd number, it has an inverse
            // modulo 2**256 such that denominator * inv = 1 mod 2**256.
            // Compute the inverse by starting with a seed that is correct
            // correct for four bits. That is, denominator * inv = 1 mod 2**4
            uint256 inv = (3 * denominator) ^ 2;
            // Now use Newton-Raphson iteration to improve the precision.
            // Thanks to Hensel's lifting lemma, this also works in modular
            // arithmetic, doubling the correct bits in each step.
            inv *= 2 - denominator * inv; // inverse mod 2**8
            inv *= 2 - denominator * inv; // inverse mod 2**16
            inv *= 2 - denominator * inv; // inverse mod 2**32
            inv *= 2 - denominator * inv; // inverse mod 2**64
            inv *= 2 - denominator * inv; // inverse mod 2**128
            inv *= 2 - denominator * inv; // inverse mod 2**256

            // Because the division is now exact we can divide by multiplying
            // with the modular inverse of denominator. This will give us the
            // correct result modulo 2**256. Since the preconditions guarantee
            // that the outcome is less than 2**256, this is the final result.
            // We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inv;
            return result;
        }
    }

    /// @dev Credit to Paul Razvan Berg https://github.com/hifi-finance/prb-math/blob/main/contracts/PRBMath.sol
    function sqrt(uint256 x) internal 
        pure returns (uint256 result) {
        if (x == 0) { return 0; }

        // Set the initial guess to the closest 
        // power of two that is higher than x.
        uint256 xAux = uint256(x); result = 1;
        
        if (xAux >= 0x100000000000000000000000000000000) {
            xAux >>= 128; result <<= 64;
        }
        if (xAux >= 0x10000000000000000) {
            xAux >>= 64; result <<= 32;
        }
        if (xAux >= 0x100000000) {
            xAux >>= 32; result <<= 16;
        }
        if (xAux >= 0x10000) {
            xAux >>= 16; result <<= 8;
        }
        if (xAux >= 0x100) {
            xAux >>= 8; result <<= 4;
        }
        if (xAux >= 0x10) {
            xAux >>= 4; result <<= 2;
        }
        if (xAux >= 0x8) {
            result <<= 1;
        }
        // The operations can never overflow 
        // because the result is max 2^127
        // when it enters this block...
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1;
        result = (result + x / result) >> 1; 
        // Seven iterations should be enough

        uint256 roundedDownResult = x / result;
        
        return result >= roundedDownResult ? 
                         roundedDownResult : result;
    }

    function floorLog2(uint256 _n) 
        internal pure returns (uint8) {
        uint8 res = 0;
        if (_n < 256) {
            // At most 8 iterations
            while (_n > 1) {
                _n >>= 1;
                res += 1;
            }
        } else {
            // Exactly 8 iterations
            for (uint8 s = 128; s > 0; s >>= 1) {
                if (_n >= (uint256(1) << s)) {
                    _n >>= s;
                    res |= s;
                }
            }
        }

        return res;
    }

    /// @dev credit github.com/ribbon-finance/rvol/blob/master/contracts/libraries/Math.sol

    function ln(uint256 x) internal 
        pure returns (uint256) {
        uint256 res = 0;

        // If x >= 2, then we compute the integer part 
        // of log2(x), which is larger than 0.
        if (x >= FIXED_2) {
            uint8 count = floorLog2(x / FIXED_1);
            x >>= count; // now x < 2
            res = count * FIXED_1;
        }
        // If x > 1, then we compute the fraction part 
        // of log2(x), which is larger than 0.
        if (x > FIXED_1) {
            for (uint8 i = 127; i > 0; --i) {
                x = (x * x) / FIXED_1; // now 1 < x < 4
                if (x >= FIXED_2) {
                    x >>= 1; // now 1 < x < 2
                    res += uint256(1) << (i - 1);
                }
            }
        }
        return (res * LOG_E_2) / BASE;
    }

    function max(uint _a, uint _b) internal
        pure returns (uint) { return (_a > _b) ?
                                      _a : _b;
    }
    function min(uint _a, uint _b) internal
        pure returns (uint) { return (_a < _b) ?
                                      _a : _b;
    }   
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;

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

/// @title Liquidity amount functions
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
    uint8 public constant RESOLUTION = 96;
    uint128 public constant Q96 = 0x1000000000000000000000000;
    // see https://en.wikipedia.org/wiki/Q_(number_format)

    /// @notice Downcasts uint256 to uint128
    /// @param x The uint258 to be downcasted
    /// @return y The passed value, downcasted to uint128
    function toUint128(uint256 x) private pure returns (uint128 y) {
        require((y = uint128(x)) == x);
    }

    /// @notice Computes the amount of liquidity received for a given amount of token0 and price range
    /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount0 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount0(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, Q96);
        return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
    }

    /// @notice Computes the amount of liquidity received for a given amount of token1 and price range
    /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount1 The amount1 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount1(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        return toUint128(FullMath.mulDiv(amount1, Q96, sqrtRatioBX96 - sqrtRatioAX96));
    }

    /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtRatioX96 A sqrt price representing the current pool prices
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount of token0 being sent in
    /// @param amount1 The amount of token1 being sent in
    /// @return liquidity The maximum amount of liquidity received
    function getLiquidityForAmounts(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
        }
    }

    /// @notice Computes the amount of token0 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount0 The amount of token0
    function getAmount0ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return
            FullMath.mulDiv(
                uint256(liquidity) << RESOLUTION,
                sqrtRatioBX96 - sqrtRatioAX96,
                sqrtRatioBX96
            ) / sqrtRatioAX96;
    }

    /// @notice Computes the amount of token1 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount1 The amount of token1
    function getAmount1ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, Q96);
    }

    /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtRatioX96 A sqrt price representing the current pool prices
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function getAmountsForLiquidity(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
        } else {
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.5;
pragma abicoder v2;

/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
interface INonfungiblePositionManager {
    /// @notice Emitted when liquidity is increased for a position NFT
    /// @dev Also emitted when a token is minted
    /// @param tokenId The ID of the token for which liquidity was increased
    /// @param liquidity The amount by which liquidity for the NFT position was increased
    /// @param amount0 The amount of token0 that was paid for the increase in liquidity
    /// @param amount1 The amount of token1 that was paid for the increase in liquidity
    event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    /// @notice Emitted when liquidity is decreased for a position NFT
    /// @param tokenId The ID of the token for which liquidity was decreased
    /// @param liquidity The amount by which liquidity for the NFT position was decreased
    /// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
    /// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
    event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    /// @notice Emitted when tokens are collected for a position NFT
    /// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior
    /// @param tokenId The ID of the token for which underlying tokens were collected
    /// @param recipient The address of the account that received the collected tokens
    /// @param amount0 The amount of token0 owed to the position that was collected
    /// @param amount1 The amount of token1 owed to the position that was collected
    event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1);

    /// @notice Returns the position information associated with a given token ID.
    /// @dev Throws if the token ID is not valid.
    /// @param tokenId The ID of the token that represents the position
    /// @return nonce The nonce for permits
    /// @return operator The address that is approved for spending
    /// @return token0 The address of the token0 for a specific pool
    /// @return token1 The address of the token1 for a specific pool
    /// @return fee The fee associated with the pool
    /// @return tickLower The lower end of the tick range for the position
    /// @return tickUpper The higher end of the tick range for the position
    /// @return liquidity The liquidity of the position
    /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
    /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
    /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
    /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation
    function positions(uint256 tokenId)
        external
        view
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    struct MintParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        address recipient;
        uint256 deadline;
    }

    /// @notice Creates a new position wrapped in a NFT
    /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
    /// a method does not exist, i.e. the pool is assumed to be initialized.
    /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
    /// @return tokenId The ID of the token that represents the minted position
    /// @return liquidity The amount of liquidity for this position
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function mint(MintParams calldata params)
        external
        payable
        returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

    struct IncreaseLiquidityParams {
        uint256 tokenId;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
    /// @param params tokenId The ID of the token for which liquidity is being increased,
    /// amount0Desired The desired amount of token0 to be spent,
    /// amount1Desired The desired amount of token1 to be spent,
    /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
    /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
    /// deadline The time by which the transaction must be included to effect the change
    /// @return liquidity The new liquidity amount as a result of the increase
    /// @return amount0 The amount of token0 to acheive resulting liquidity
    /// @return amount1 The amount of token1 to acheive resulting liquidity
    function increaseLiquidity(IncreaseLiquidityParams calldata params)
        external
        payable
        returns (uint128 liquidity, uint256 amount0, uint256 amount1);

    struct DecreaseLiquidityParams {
        uint256 tokenId;
        uint128 liquidity;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    /// @notice Decreases the amount of liquidity in a position and accounts it to the position
    /// @param params tokenId The ID of the token for which liquidity is being decreased,
    /// amount The amount by which liquidity will be decreased,
    /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,
    /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,
    /// deadline The time by which the transaction must be included to effect the change
    /// @return amount0 The amount of token0 accounted to the position's tokens owed
    /// @return amount1 The amount of token1 accounted to the position's tokens owed
    function decreaseLiquidity(DecreaseLiquidityParams calldata params)
        external
        payable
        returns (uint256 amount0, uint256 amount1);

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
    /// @param params tokenId The ID of the NFT for which tokens are being collected,
    /// recipient The account that should receive the tokens,
    /// amount0Max The maximum amount of token0 to collect,
    /// amount1Max The maximum amount of token1 to collect
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);

    /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens
    /// must be collected first.
    /// @param tokenId The ID of the token that is being burned
    function burn(uint256 tokenId) external payable;
}

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.26;

import "./IntegralMath.sol";

library AnalyticMath {
    // Auto-generated via 'PrintAnalyticMathConstants.py'
    uint8   internal constant SCALE_1 = 0x000000000000000000000000000000007f;
    uint256 internal constant FIXED_1 = 0x0080000000000000000000000000000000;
    uint256 internal constant LN2_MIN = 0x0058b90bfbe8e7bcd5e4f1d9cc01f97b57;
    uint256 internal constant LN2_MAX = 0x0058b90bfbe8e7bcd5e4f1d9cc01f97b58;
    uint256 internal constant LOG_MID = 0x015bf0a8b1457695355fb8ac404e7a79e4;
    uint256 internal constant EXP_MID = 0x0400000000000000000000000000000000;
    uint256 internal constant EXP_MAX = 0x2cb53f09f05cc627c85ddebfccfeb72758;

    /**
      * @dev Compute (a / b) ^ (c / d)
    */
    function pow(uint256 a, uint256 b, uint256 c, uint256 d) internal pure returns (uint256, uint256) { unchecked {
        if (b == 0 || d == 0)
            revert("division by zero");
        if (a == 0 || c == 0)
            return (a ** c, 1);
        if (a > b)
            return (fixedExp(IntegralMath.mulDivF(fixedLog(IntegralMath.mulDivF(FIXED_1, a, b)), c, d)), FIXED_1);
        if (b > a)
            return (FIXED_1, fixedExp(IntegralMath.mulDivF(fixedLog(IntegralMath.mulDivF(FIXED_1, b, a)), c, d)));
        return (1, 1);
    }}

    /**
      * @dev Compute log(a / b)
    */
    function log(uint256 a, uint256 b) internal pure returns (uint256, uint256) { unchecked {
        return (fixedLog(IntegralMath.mulDivF(FIXED_1, a, b)), FIXED_1);
    }}

    /**
      * @dev Compute exp(a / b)
    */
    function exp(uint256 a, uint256 b) internal pure returns (uint256, uint256) { unchecked {
        return (fixedExp(IntegralMath.mulDivF(FIXED_1, a, b)), FIXED_1);
    }}

    /**
      * @dev Compute log(x / FIXED_1) * FIXED_1
      * Input range: FIXED_1 <= x <= 2 ^ 256 - 1
      * Detailed description:
      * - For x < LOG_MID, compute log(x)
      * - For any other x, compute log(x / 2 ^ log2(x)) + log2(x) * log(2)
      * - The value of log(2) is represented as floor(log(2) * FIXED_1)
      * - With k = log2(x), this solution relies on the following identity:
      *   log(x) =
      *   log(x) + log(2 ^ k) - log(2 ^ k) =
      *   log(x) - log(2 ^ k) + log(2 ^ k) =
      *   log(x / log(2 ^ k)) + log(2 ^ k) =
      *   log(x / log(2 ^ k)) + k * log(2)
    */
    function fixedLog(uint256 x) internal pure returns (uint256) { unchecked {
        if (x < FIXED_1)
            revert("fixedLog: x < min");
        if (x < LOG_MID)
            return optimalLog(x);
        uint8 count = IntegralMath.floorLog2(x / FIXED_1);
        return optimalLog(x >> count) + count * LN2_MIN;
    }}

    /**
      * @dev Compute exp(x / FIXED_1) * FIXED_1
      * Input range: 0 <= x <= EXP_MAX - 1
      * Detailed description:
      * - For x < EXP_MID, compute exp(x)
      * - For any other x, compute exp(x % log(2)) * 2 ^ (x / log(2))
      * - The value of log(2) is represented as ceil(log(2) * FIXED_1)
      * - With k = x / log(2), this solution relies on the following identity:
      *   exp(x) =
      *   exp(x) * 2 ^ k / 2 ^ k =
      *   exp(x) * 2 ^ k / exp(k * log(2)) =
      *   exp(x) / exp(k * log(2)) * 2 ^ k =
      *   exp(x - k * log(2)) * 2 ^ k
    */
    function fixedExp(uint256 x) internal pure returns (uint256) { unchecked {
        if (x < EXP_MID)
            return optimalExp(x);
        if (x < EXP_MAX)
            return optimalExp(x % LN2_MAX) << (x / LN2_MAX);
        revert("fixedExp: x > max");
    }}

    /**
      * @dev Compute log(x / FIXED_1) * FIXED_1
      * Input range: FIXED_1 <= x <= FIXED_1 * 4 - 1
      * Auto-generated via 'PrintAnalyticMathOptimalLog.py'
      * Detailed description:
      * - Rewrite the input as a product of natural exponents and a single residual r, such that 1 < r < 2
      * - The natural logarithm of each (pre-calculated) exponent is the degree of the exponent
      * - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1
      * - The natural logarithm of the input is calculated by summing up the intermediate results above
      * - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859)
    */
    function optimalLog(uint256 x) internal pure returns (uint256) { unchecked {
        uint256 res = 0;

        uint256 y;
        uint256 z;
        uint256 w;

        if (x > 0xd3094c70f034de4b96ff7d5b6f99fcd8) {res |= 0x40000000000000000000000000000000; x = x * 0x0bc5ab1b16779be3575bd8f0520a9d5b9 / 0x1368b2fc6f9609fe7aceb46aa619b8003;} // add 2^(-1)
        if (x > 0xa45af1e1f40c333b3de1db4dd55f29a7) {res |= 0x20000000000000000000000000000000; x = x * 0x1368b2fc6f9609fe7aceb46aa619a2ef6 / 0x18ebef9eac820ae8682b9793ac6cffa93;} // add 2^(-2)
        if (x > 0x910b022db7ae67ce76b441c27035c6a1) {res |= 0x10000000000000000000000000000000; x = x * 0x18ebef9eac820ae8682b9793ac6d11622 / 0x1c3d6a24ed82218787d624d3e5eb9a8c6;} // add 2^(-3)
        if (x > 0x88415abbe9a76bead8d00cf112e4d4a8) {res |= 0x08000000000000000000000000000000; x = x * 0x1c3d6a24ed82218787d624d3e5eba8beb / 0x1e0fabfbc702a3ce5e31fe0609358b04d;} // add 2^(-4)
        if (x > 0x84102b00893f64c705e841d5d4064bd3) {res |= 0x04000000000000000000000000000000; x = x * 0x1e0fabfbc702a3ce5e31fe06093589585 / 0x1f03f56a88b5d7914b00abf9776270f29;} // add 2^(-5)
        if (x > 0x8204055aaef1c8bd5c3259f4822735a2) {res |= 0x02000000000000000000000000000000; x = x * 0x1f03f56a88b5d7914b00abf9776267973 / 0x1f80feabfeefa4927d10bdd54ead4eaf0;} // add 2^(-6)
        if (x > 0x810100ab00222d861931c15e39b44e99) {res |= 0x01000000000000000000000000000000; x = x * 0x1f80feabfeefa4927d10bdd54ead599f9 / 0x1fc03fd56aa224f97fcbf133298883271;} // add 2^(-7)
        if (x > 0x808040155aabbbe9451521693554f733) {res |= 0x00800000000000000000000000000000; x = x * 0x1fc03fd56aa224f97fcbf13329886bd10 / 0x1fe00ffaabffbbc71ad1e1184afc01529;} // add 2^(-8)

        z = y = x - FIXED_1;
        w = y * y >> SCALE_1;
        res += z * (0x100000000000000000000000000000000 - y) / 0x100000000000000000000000000000000; z = z * w >> SCALE_1; // add y^01 / 01 - y^02 / 02
        res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) / 0x200000000000000000000000000000000; z = z * w >> SCALE_1; // add y^03 / 03 - y^04 / 04
        res += z * (0x099999999999999999999999999999999 - y) / 0x300000000000000000000000000000000; z = z * w >> SCALE_1; // add y^05 / 05 - y^06 / 06
        res += z * (0x092492492492492492492492492492492 - y) / 0x400000000000000000000000000000000; z = z * w >> SCALE_1; // add y^07 / 07 - y^08 / 08
        res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) / 0x500000000000000000000000000000000; z = z * w >> SCALE_1; // add y^09 / 09 - y^10 / 10
        res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) / 0x600000000000000000000000000000000; z = z * w >> SCALE_1; // add y^11 / 11 - y^12 / 12
        res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) / 0x700000000000000000000000000000000; z = z * w >> SCALE_1; // add y^13 / 13 - y^14 / 14
        res += z * (0x088888888888888888888888888888888 - y) / 0x800000000000000000000000000000000;                       // add y^15 / 15 - y^16 / 16

        return res;
    }}

    /**
      * @dev Compute exp(x / FIXED_1) * FIXED_1
      * Input range: 0 <= x <= EXP_MID - 1
      * Auto-generated via 'PrintAnalyticMathOptimalExp.py'
      * Detailed description:
      * - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
      * - The exponentiation of each binary exponent is given (pre-calculated)
      * - The exponentiation of r is calculated via Taylor series for e^x, where x = r
      * - The exponentiation of the input is calculated by multiplying the intermediate results above
      * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
    */
    function optimalExp(uint256 x) internal pure returns (uint256) { unchecked {
        uint256 res = 0;

        uint256 y;
        uint256 z;

        z = y = x % 0x10000000000000000000000000000000; // get the input modulo 2^(-3)
        z = z * y >> SCALE_1; res += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
        z = z * y >> SCALE_1; res += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
        z = z * y >> SCALE_1; res += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
        z = z * y >> SCALE_1; res += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
        z = z * y >> SCALE_1; res += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
        z = z * y >> SCALE_1; res += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
        z = z * y >> SCALE_1; res += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
        z = z * y >> SCALE_1; res += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
        z = z * y >> SCALE_1; res += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
        z = z * y >> SCALE_1; res += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
        z = z * y >> SCALE_1; res += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
        z = z * y >> SCALE_1; res += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
        z = z * y >> SCALE_1; res += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
        z = z * y >> SCALE_1; res += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
        z = z * y >> SCALE_1; res += z * 0x000000000001c638; // add y^16 * (20! / 16!)
        z = z * y >> SCALE_1; res += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
        z = z * y >> SCALE_1; res += z * 0x000000000000017c; // add y^18 * (20! / 18!)
        z = z * y >> SCALE_1; res += z * 0x0000000000000014; // add y^19 * (20! / 19!)
        z = z * y >> SCALE_1; res += z * 0x0000000000000001; // add y^20 * (20! / 20!)
        res = res / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!

        if ((x & 0x010000000000000000000000000000000) != 0) res = res * 0x1c3d6a24ed82218787d624d3e5eba2a0f / 0x18ebef9eac820ae8682b9793ac6d1883a; // multiply by e^2^(-3)
        if ((x & 0x020000000000000000000000000000000) != 0) res = res * 0x18ebef9eac820ae8682b9793ac6d1e726 / 0x1368b2fc6f9609fe7aceb46aa619bae94; // multiply by e^2^(-2)
        if ((x & 0x040000000000000000000000000000000) != 0) res = res * 0x1368b2fc6f9609fe7aceb46aa6199a7d7 / 0x0bc5ab1b16779be3575bd8f0520a8b756; // multiply by e^2^(-1)
        if ((x & 0x080000000000000000000000000000000) != 0) res = res * 0x0bc5ab1b16779be3575bd8f0520a67cd2 / 0x0454aaa8efe072e7f6ddbab84b409101a; // multiply by e^2^(+0)
        if ((x & 0x100000000000000000000000000000000) != 0) res = res * 0x0454aaa8efe072e7f6ddbab84b408736f / 0x00960aadc109e7a3bf45780996156d0a3; // multiply by e^2^(+1)
        if ((x & 0x200000000000000000000000000000000) != 0) res = res * 0x00960aadc109e7a3bf45780996149ade3 / 0x0002bf84208204f5977f9a8cf01fd8f74; // multiply by e^2^(+2)

        return res;
    }}
}

File 73 of 78 : IImmutableState.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";

/// @title IImmutableState
/// @notice Interface for the ImmutableState contract
interface IImmutableState {
    /// @notice The Uniswap v4 PoolManager contract
    function poolManager() external view returns (IPoolManager);
}

File 74 of 78 : FixedPoint128.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title FixedPoint128
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
library FixedPoint128 {
    uint256 internal constant Q128 = 0x100000000000000000000000000000000;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Math library for liquidity
library LiquidityMath {
    /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows
    /// @param x The liquidity before change
    /// @param y The delta by which liquidity should be changed
    /// @return z The liquidity delta
    function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) {
        assembly ("memory-safe") {
            z := add(and(x, 0xffffffffffffffffffffffffffffffff), signextend(15, y))
            if shr(128, z) {
                // revert SafeCastOverflow()
                mstore(0, 0x93dafdf1)
                revert(0x1c, 0x04)
            }
        }
    }
}

File 76 of 78 : DataTypes.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

library DataTypes {
  /**
   * This exists specifically to maintain the `getReserveData()` interface, since the new, internal
   * `ReserveData` struct includes the reserve's `virtualUnderlyingBalance`.
   */
  struct ReserveDataLegacy {
    //stores the reserve configuration
    ReserveConfigurationMap configuration;
    //the liquidity index. Expressed in ray
    uint128 liquidityIndex;
    //the current supply rate. Expressed in ray
    uint128 currentLiquidityRate;
    //variable borrow index. Expressed in ray
    uint128 variableBorrowIndex;
    //the current variable borrow rate. Expressed in ray
    uint128 currentVariableBorrowRate;
    // DEPRECATED on v3.2.0
    uint128 currentStableBorrowRate;
    //timestamp of last update
    uint40 lastUpdateTimestamp;
    //the id of the reserve. Represents the position in the list of the active reserves
    uint16 id;
    //aToken address
    address aTokenAddress;
    // DEPRECATED on v3.2.0
    address stableDebtTokenAddress;
    //variableDebtToken address
    address variableDebtTokenAddress;
    //address of the interest rate strategy
    address interestRateStrategyAddress;
    //the current treasury balance, scaled
    uint128 accruedToTreasury;
    //the outstanding unbacked aTokens minted through the bridging feature
    uint128 unbacked;
    //the outstanding debt borrowed against this asset in isolation mode
    uint128 isolationModeTotalDebt;
  }

  struct ReserveData {
    //stores the reserve configuration
    ReserveConfigurationMap configuration;
    //the liquidity index. Expressed in ray
    uint128 liquidityIndex;
    //the current supply rate. Expressed in ray
    uint128 currentLiquidityRate;
    //variable borrow index. Expressed in ray
    uint128 variableBorrowIndex;
    //the current variable borrow rate. Expressed in ray
    uint128 currentVariableBorrowRate;
    /// @notice reused `__deprecatedStableBorrowRate` storage from pre 3.2
    // the current accumulate deficit in underlying tokens
    uint128 deficit;
    //timestamp of last update
    uint40 lastUpdateTimestamp;
    //the id of the reserve. Represents the position in the list of the active reserves
    uint16 id;
    //timestamp until when liquidations are not allowed on the reserve, if set to past liquidations will be allowed
    uint40 liquidationGracePeriodUntil;
    //aToken address
    address aTokenAddress;
    // DEPRECATED on v3.2.0
    address __deprecatedStableDebtTokenAddress;
    //variableDebtToken address
    address variableDebtTokenAddress;
    //address of the interest rate strategy
    address interestRateStrategyAddress;
    //the current treasury balance, scaled
    uint128 accruedToTreasury;
    //the outstanding unbacked aTokens minted through the bridging feature
    uint128 unbacked;
    //the outstanding debt borrowed against this asset in isolation mode
    uint128 isolationModeTotalDebt;
    //the amount of underlying accounted for by the protocol
    uint128 virtualUnderlyingBalance;
  }

  struct ReserveConfigurationMap {
    //bit 0-15: LTV
    //bit 16-31: Liq. threshold
    //bit 32-47: Liq. bonus
    //bit 48-55: Decimals
    //bit 56: reserve is active
    //bit 57: reserve is frozen
    //bit 58: borrowing is enabled
    //bit 59: DEPRECATED: stable rate borrowing enabled
    //bit 60: asset is paused
    //bit 61: borrowing in isolation mode is enabled
    //bit 62: siloed borrowing enabled
    //bit 63: flashloaning enabled
    //bit 64-79: reserve factor
    //bit 80-115: borrow cap in whole tokens, borrowCap == 0 => no cap
    //bit 116-151: supply cap in whole tokens, supplyCap == 0 => no cap
    //bit 152-167: liquidation protocol fee
    //bit 168-175: DEPRECATED: eMode category
    //bit 176-211: unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled
    //bit 212-251: debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals
    //bit 252: virtual accounting is enabled for the reserve
    //bit 253-255 unused

    uint256 data;
  }

  struct UserConfigurationMap {
    /**
     * @dev Bitmap of the users collaterals and borrows. It is divided in pairs of bits, one pair per asset.
     * The first bit indicates if an asset is used as collateral by the user, the second whether an
     * asset is borrowed by the user.
     */
    uint256 data;
  }

  // DEPRECATED: kept for backwards compatibility, might be removed in a future version
  struct EModeCategoryLegacy {
    // each eMode category has a custom ltv and liquidation threshold
    uint16 ltv;
    uint16 liquidationThreshold;
    uint16 liquidationBonus;
    // DEPRECATED
    address priceSource;
    string label;
  }

  struct CollateralConfig {
    uint16 ltv;
    uint16 liquidationThreshold;
    uint16 liquidationBonus;
  }

  struct EModeCategoryBaseConfiguration {
    uint16 ltv;
    uint16 liquidationThreshold;
    uint16 liquidationBonus;
    string label;
  }

  struct EModeCategory {
    // each eMode category has a custom ltv and liquidation threshold
    uint16 ltv;
    uint16 liquidationThreshold;
    uint16 liquidationBonus;
    uint128 collateralBitmap;
    string label;
    uint128 borrowableBitmap;
  }

  enum InterestRateMode {
    NONE,
    __DEPRECATED,
    VARIABLE
  }

  struct ReserveCache {
    uint256 currScaledVariableDebt;
    uint256 nextScaledVariableDebt;
    uint256 currLiquidityIndex;
    uint256 nextLiquidityIndex;
    uint256 currVariableBorrowIndex;
    uint256 nextVariableBorrowIndex;
    uint256 currLiquidityRate;
    uint256 currVariableBorrowRate;
    uint256 reserveFactor;
    ReserveConfigurationMap reserveConfiguration;
    address aTokenAddress;
    address variableDebtTokenAddress;
    uint40 reserveLastUpdateTimestamp;
  }

  struct ExecuteLiquidationCallParams {
    uint256 reservesCount;
    uint256 debtToCover;
    address collateralAsset;
    address debtAsset;
    address user;
    bool receiveAToken;
    address priceOracle;
    uint8 userEModeCategory;
    address priceOracleSentinel;
  }

  struct ExecuteSupplyParams {
    address asset;
    uint256 amount;
    address onBehalfOf;
    uint16 referralCode;
  }

  struct ExecuteBorrowParams {
    address asset;
    address user;
    address onBehalfOf;
    uint256 amount;
    InterestRateMode interestRateMode;
    uint16 referralCode;
    bool releaseUnderlying;
    uint256 reservesCount;
    address oracle;
    uint8 userEModeCategory;
    address priceOracleSentinel;
  }

  struct ExecuteRepayParams {
    address asset;
    uint256 amount;
    InterestRateMode interestRateMode;
    address onBehalfOf;
    bool useATokens;
  }

  struct ExecuteWithdrawParams {
    address asset;
    uint256 amount;
    address to;
    uint256 reservesCount;
    address oracle;
    uint8 userEModeCategory;
  }

  struct ExecuteEliminateDeficitParams {
    address asset;
    uint256 amount;
  }

  struct ExecuteSetUserEModeParams {
    uint256 reservesCount;
    address oracle;
    uint8 categoryId;
  }

  struct FinalizeTransferParams {
    address asset;
    address from;
    address to;
    uint256 amount;
    uint256 balanceFromBefore;
    uint256 balanceToBefore;
    uint256 reservesCount;
    address oracle;
    uint8 fromEModeCategory;
  }

  struct FlashloanParams {
    address receiverAddress;
    address[] assets;
    uint256[] amounts;
    uint256[] interestRateModes;
    address onBehalfOf;
    bytes params;
    uint16 referralCode;
    uint256 flashLoanPremiumToProtocol;
    uint256 flashLoanPremiumTotal;
    uint256 reservesCount;
    address addressesProvider;
    address pool;
    uint8 userEModeCategory;
    bool isAuthorizedFlashBorrower;
  }

  struct FlashloanSimpleParams {
    address receiverAddress;
    address asset;
    uint256 amount;
    bytes params;
    uint16 referralCode;
    uint256 flashLoanPremiumToProtocol;
    uint256 flashLoanPremiumTotal;
  }

  struct FlashLoanRepaymentParams {
    uint256 amount;
    uint256 totalPremium;
    uint256 flashLoanPremiumToProtocol;
    address asset;
    address receiverAddress;
    uint16 referralCode;
  }

  struct CalculateUserAccountDataParams {
    UserConfigurationMap userConfig;
    uint256 reservesCount;
    address user;
    address oracle;
    uint8 userEModeCategory;
  }

  struct ValidateBorrowParams {
    ReserveCache reserveCache;
    UserConfigurationMap userConfig;
    address asset;
    address userAddress;
    uint256 amount;
    InterestRateMode interestRateMode;
    uint256 reservesCount;
    address oracle;
    uint8 userEModeCategory;
    address priceOracleSentinel;
    bool isolationModeActive;
    address isolationModeCollateralAddress;
    uint256 isolationModeDebtCeiling;
  }

  struct ValidateLiquidationCallParams {
    ReserveCache debtReserveCache;
    uint256 totalDebt;
    uint256 healthFactor;
    address priceOracleSentinel;
  }

  struct CalculateInterestRatesParams {
    uint256 unbacked;
    uint256 liquidityAdded;
    uint256 liquidityTaken;
    uint256 totalDebt;
    uint256 reserveFactor;
    address reserve;
    bool usingVirtualBalance;
    uint256 virtualUnderlyingBalance;
  }

  struct InitReserveParams {
    address asset;
    address aTokenAddress;
    address variableDebtAddress;
    address interestRateStrategyAddress;
    uint16 reservesCount;
    uint16 maxNumberReserves;
  }
}

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.26;

import "./common/Uint256.sol";

library IntegralMath {
    /**
      * @dev Compute the largest integer smaller than or equal to the binary logarithm of `n`
    */
    function floorLog2(uint256 n) internal pure returns (uint8) { unchecked {
        uint8 x = 0;
        if (n > 0) {
          assembly {
            if gt(n, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { n := shr(128, n) x := 128 }
            if gt(n, 0xFFFFFFFFFFFFFFFF) { n := shr(64, n) x := or(x, 64) }
            if gt(n, 0xFFFFFFFF) { n := shr(32, n) x := or(x, 32) }
            if gt(n, 0xFFFF) { n := shr(16, n) x := or(x, 16) }
            if gt(n, 0xFF) { n := shr(8, n) x := or(x, 8) }
            if gt(n, 0xF) { n := shr(4, n) x := or(x, 4) }
            if gt(n, 0x3) { n := shr(2, n) x := or(x, 2) }
            if gt(n, 0x1) { x := or(x, 1) }
          }
        }
        return x;
    }}

    /**
      * @dev Compute the largest integer smaller than or equal to the square root of `n`
    */
    function floorSqrt(uint256 n) internal pure returns (uint256) { unchecked {
        if (n > 63) {
            uint256 x = n;
            uint256 y = 1 << (floorLog2(n) / 2 + 1);
            while (x > y) {
                x = y;
                y = (x + n / x) >> 1;
            }
            return x;
        }
        return 0x7777777777777776666666666666555555555554444444443333333222221110 >> (n * 4) & 0xf;
    }}

    /**
      * @dev Compute the smallest integer larger than or equal to the square root of `n`
    */
    function ceilSqrt(uint256 n) internal pure returns (uint256) { unchecked {
        uint256 x = floorSqrt(n);
        return x ** 2 == n ? x : x + 1;
    }}

    /**
      * @dev Compute the largest integer smaller than or equal to the cubic root of `n`
    */
    function floorCbrt(uint256 n) internal pure returns (uint256) { unchecked {
        if (n > 84) {
            uint256 x = n;
            uint256 y = 1 << (floorLog2(n) / 3 + 1);
            while (x > y) {
                x = y;
                y = ((x << 1) + n / x ** 2) / 3;
            }
            return x;
        }
        return 0x49249249249249246db6db6db6db6db6db6db6db6db692492492492492249248 >> (n * 3) & 0x7;
    }}

    /**
      * @dev Compute the smallest integer larger than or equal to the cubic root of `n`
    */
    function ceilCbrt(uint256 n) internal pure returns (uint256) { unchecked {
        uint256 x = floorCbrt(n);
        return x ** 3 == n ? x : x + 1;
    }}

    /**
      * @dev Compute the nearest integer (half being rounded upwards) to `n / d`
    */
    function roundDiv(uint256 n, uint256 d) internal pure returns (uint256) { unchecked {
        return n / d + (n % d) / (d - d / 2);
    }}

    /**
      * @dev Compute the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1`
    */
    function minFactor(uint256 x, uint256 y) internal pure returns (uint256) { unchecked {
        (uint256 hi, uint256 lo) = mul512(x, y);
        return hi > ~lo ? hi + 2 : hi + 1;
        // General:
        // - find the smallest integer `z` such that `x * y / z <= 2 ^ 256 - 1`
        // - the value of `x * y` is represented via `2 ^ 256 * hi + lo`
        // - the expression `~lo` is equivalent to `2 ^ 256 - 1 - lo`
        //
        // Safety:
        // - if `x < 2 ^ 256 - 1` or `y < 2 ^ 256 - 1`
        //   then `hi < 2 ^ 256 - 2`
        //   hence neither `hi + 1` nor `hi + 2` overflows
        // - if `x = 2 ^ 256 - 1` and `y = 2 ^ 256 - 1`
        //   then `hi = 2 ^ 256 - 2 = ~lo`
        //   hence `hi + 1`, which does not overflow, is computed
        //
        // Symbols:
        // - let `H` denote `hi`
        // - let `L` denote `lo`
        // - let `N` denote `2 ^ 256 - 1`
        //
        // Inference:
        // `x * y / z <= 2 ^ 256 - 1`     <-->
        // `x * y / (2 ^ 256 - 1) <= z`   <-->
        // `((N + 1) * H + L) / N <= z`   <-->
        // `(N * H + H + L) / N <= z`     <-->
        // `H + (H + L) / N <= z`
        //
        // Inference:
        // `0 <= H <= N && 0 <= L <= N`   <-->
        // `0 <= H + L <= N + N`          <-->
        // `0 <= H + L <= N * 2`          <-->
        // `0 <= (H + L) / N <= 2`
        //
        // Inference:
        // - `0 = (H + L) / N` --> `H + L = 0` --> `x * y = 0` --> `z = 1 = H + 1`
        // - `0 < (H + L) / N <= 1` --> `H + (H + L) / N <= H + 1` --> `z = H + 1`
        // - `1 < (H + L) / N <= 2` --> `H + (H + L) / N <= H + 2` --> `z = H + 2`
        //
        // Implementation:
        // - if `hi > ~lo`:
        //   `~L < H <= N`                         <-->
        //   `N - L < H <= N`                      <-->
        //   `N < H + L <= N + L`                  <-->
        //   `1 < (H + L) / N <= 2`                <-->
        //   `H + 1 < H + (H + L) / N <= H + 2`    <-->
        //   `z = H + 2`
        // - if `hi <= ~lo`:
        //   `H <= ~L`                             <-->
        //   `H <= N - L`                          <-->
        //   `H + L <= N`                          <-->
        //   `(H + L) / N <= 1`                    <-->
        //   `H + (H + L) / N <= H + 1`            <-->
        //   `z = H + 1`
    }}

    /**
      * @dev Compute the largest integer smaller than or equal to `x * y / 2 ^ s`
    */
    function mulShrF(uint256 x, uint256 y, uint8 s) internal pure returns (uint256) { unchecked {
        (uint256 xyh, uint256 xyl) = mul512(x, y);
        require(xyh < 1 << s);
        return (xyh << (256 - s)) | (xyl >> s);
    }}

    /**
      * @dev Compute the smallest integer larger than or equal to `x * y / 2 ^ s`
    */
    function mulShrC(uint256 x, uint256 y, uint8 s) internal pure returns (uint256) { unchecked {
        uint256 w = mulShrF(x, y, s);
        if (mulmod(x, y, 1 << s) > 0)
            return safeAdd(w, 1);
        return w;
    }}

    /**
      * @dev Compute the largest integer smaller than or equal to `x * y / z`
    */
    function mulDivF(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { unchecked {
        (uint256 xyh, uint256 xyl) = mul512(x, y);
        if (xyh == 0) { // `x * y < 2 ^ 256`
            return xyl / z;
        }
        if (xyh < z) { // `x * y / z < 2 ^ 256`
            uint256 m = mulmod(x, y, z);                    // `m = x * y % z`
            (uint256 nh, uint256 nl) = sub512(xyh, xyl, m); // `n = x * y - m` hence `n / z = floor(x * y / z)`
            if (nh == 0) { // `n < 2 ^ 256`
                return nl / z;
            }
            uint256 p = unsafeSub(0, z) & z; // `p` is the largest power of 2 which `z` is divisible by
            uint256 q = div512(nh, nl, p);   // `n` is divisible by `p` because `n` is divisible by `z` and `z` is divisible by `p`
            uint256 r = inv256(z / p);       // `z / p = 1 mod 2` hence `inverse(z / p) = 1 mod 2 ^ 256`
            return unsafeMul(q, r);          // `q * r = (n / p) * inverse(z / p) = n / z`
        }
        revert(); // `x * y / z >= 2 ^ 256`
    }}

    /**
      * @dev Compute the smallest integer larger than or equal to `x * y / z`
    */
    function mulDivC(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { unchecked {
        uint256 w = mulDivF(x, y, z);
        if (mulmod(x, y, z) > 0)
            return safeAdd(w, 1);
        return w;
    }}

    /**
      * @dev Compute the nearest integer (half being rounded upwards) to `x * y / z`
    */
    function mulDivR(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { unchecked {
        uint256 w = mulDivF(x, y, z);
        if (mulmod(x, y, z) > (z - 1) / 2)
            return safeAdd(w, 1);
        return w;
    }}

    /**
      * @dev Compute the largest integer smaller than or equal to `(x * y) / (z * w)`
    */
    function mulDivExF(uint256 x, uint256 y, uint256 z, uint256 w) internal pure returns (uint256) { unchecked {
        (uint256 zwh, uint256 zwl) = mul512(z, w);
        if (zwh > 0) {
            uint256 res = 0;
            (uint256 xyh, uint256 xyl) = mul512(x, y);
            if (xyh > zwh) {
                uint8 xyhn = floorLog2(xyh);
                uint8 zwhn = floorLog2(zwh);
                while (xyhn > zwhn) {
                    uint8 n = xyhn - zwhn - 1;
                    uint256 zwhshl = zwh << n;
                    uint256 zwlshl = zwl << n;
                    uint256 zwlshr = zwl >> (256 - n);
                    res += 1 << n; // set `res = res + 2 ^ n`
                    (xyh, xyl) = sub512Ex(xyh, xyl, zwhshl | zwlshr, zwlshl); // set `xy = xy - zw * 2 ^ n`
                    xyhn = floorLog2(xyh);
                }
            }
            if (xyh > zwh || (xyh == zwh && xyl >= zwl)) // `xy >= zw`
                return res + 1;
            return res;
        }
        return mulDivF(x, y, zwl);
    }}

    /**
      * @dev Compute the smallest integer larger than or equal to `(x * y) / (z * w)`
    */
    function mulDivExC(uint256 x, uint256 y, uint256 z, uint256 w) internal pure returns (uint256) { unchecked {
        uint256 v = mulDivExF(x, y, z, w);
        (uint256 xyh, uint256 xyl) = mul512(x, y);
        (uint256 zwh, uint256 zwl) = mul512(z, w);
        (uint256 vzwlh, uint256 vzwll) = mul512(v, zwl);
        if (xyh == v * zwh + vzwlh && xyl == vzwll)
            return v;
        return safeAdd(v, 1);
    }}

    /**
      * @dev Compute the value of `x * y`
    */
    function mul512(uint256 x, uint256 y) private pure returns (uint256, uint256) { unchecked {
        uint256 p = mulModMax(x, y);
        uint256 q = unsafeMul(x, y);
        if (p >= q)
            return (p - q, q);
        return (unsafeSub(p, q) - 1, q);
    }}

    /**
      * @dev Compute the value of `2 ^ 256 * xh + xl - y`, where `2 ^ 256 * xh + xl >= y`
    */
    function sub512(uint256 xh, uint256 xl, uint256 y) private pure returns (uint256, uint256) { unchecked {
        if (xl >= y)
            return (xh, xl - y);
        return (xh - 1, unsafeSub(xl, y));
    }}

    /**
      * @dev Compute the value of `(2 ^ 256 * xh + xl) / pow2n`, where `xl` is divisible by `pow2n`
    */
    function div512(uint256 xh, uint256 xl, uint256 pow2n) private pure returns (uint256) { unchecked {
        uint256 pow2nInv = unsafeAdd(unsafeSub(0, pow2n) / pow2n, 1); // `1 << (256 - n)`
        return unsafeMul(xh, pow2nInv) | (xl / pow2n); // `(xh << (256 - n)) | (xl >> n)`
    }}

    /**
      * @dev Compute the inverse of `d` modulo `2 ^ 256`, where `d` is congruent to `1` modulo `2`
    */
    function inv256(uint256 d) private pure returns (uint256) { unchecked {
        // approximate the root of `f(x) = 1 / x - d` using the newton–raphson convergence method
        uint256 x = 1;
        for (uint256 i = 0; i < 8; ++i)
            x = unsafeMul(x, unsafeSub(2, unsafeMul(x, d))); // `x = x * (2 - x * d) mod 2 ^ 256`
        return x;
    }}

    /**
      * @dev Compute the value of `(2 ^ 256 * xh + xl) - (2 ^ 256 * yh + yl)`, where `2 ^ 256 * xh + xl >= 2 ^ 256 * yh + yl`
    */
    function sub512Ex(uint256 xh, uint256 xl, uint256 yh, uint256 yl) private pure returns (uint256, uint256) { unchecked {
        if (xl >= yl)
            return (xh - yh, xl - yl);
        return (xh - yh - 1, unsafeSub(xl, yl));
    }}
}

File 78 of 78 : Uint256.sol
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.26;

uint256 constant MAX_VAL = type(uint256).max;

// reverts on overflow
function safeAdd(uint256 x, uint256 y) pure returns (uint256) {
    return x + y;
}

// does not revert on overflow
function unsafeAdd(uint256 x, uint256 y) pure returns (uint256) { unchecked {
    return x + y;
}}

// does not revert on overflow
function unsafeSub(uint256 x, uint256 y) pure returns (uint256) { unchecked {
    return x - y;
}}

// does not revert on overflow
function unsafeMul(uint256 x, uint256 y) pure returns (uint256) { unchecked {
    return x * y;
}}

// does not overflow
function mulModMax(uint256 x, uint256 y) pure returns (uint256) { unchecked {
    return mulmod(x, y, MAX_VAL);
}}

Settings
{
  "remappings": [
    "@uniswap/v4-core/=lib/v4-core/",
    "forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/",
    "forge-std/=lib/v4-core/lib/forge-std/src/",
    "permit2/=lib/v4-periphery/lib/permit2/",
    "solmate/=lib/v4-core/lib/solmate/",
    "v4-core/=lib/v4-core/",
    "v4-periphery/=lib/v4-periphery/",
    "solidity-math-utils/=lib/solidity-math-utils/project/contracts/",
    "aave-v3/=lib/aave-v3-origin/src/contracts/",
    "@openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/",
    "@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
    "aave-v3-origin/=lib/aave-v3-origin/",
    "ds-test/=lib/aave-v3-origin/lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/v4-core/lib/openzeppelin-contracts/lib/erc4626-tests/",
    "halmos-cheatcodes/=lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
    "hardhat/=lib/v4-core/node_modules/hardhat/",
    "openzeppelin-contracts-upgradeable/=lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/v4-core/lib/openzeppelin-contracts/",
    "solady/=lib/solady/src/",
    "solidity-utils/=lib/aave-v3-origin/lib/solidity-utils/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "none",
    "appendCBOR": false
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {
    "src/Vogue.sol": {
      "BasketLib": "0xac2a347ff18bc0ee07c49a2c4faaf18ec54e1cc6"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"ETH_FEES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LAST_REPACK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOWER_TICK","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPPER_TICK","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"USD_FEES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"YIELD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"deltaETH","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"addLiquidityHelper","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"autoManaged","outputs":[{"internalType":"uint256","name":"pooled_eth","type":"uint256"},{"internalType":"uint256","name":"usd_owed","type":"uint256"},{"internalType":"uint256","name":"fees_eth","type":"uint256"},{"internalType":"uint256","name":"fees_usd","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"int24","name":"distance","type":"int24"},{"internalType":"int24","name":"range","type":"int24"}],"name":"outOfRange","outputs":[{"internalType":"uint256","name":"next","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"bool","name":"up","type":"bool"},{"internalType":"uint256","name":"delta","type":"uint256"}],"name":"paddedSqrtPrice","outputs":[{"internalType":"uint160","name":"","type":"uint160"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"pendingRewards","outputs":[{"internalType":"uint256","name":"ethReward","type":"uint256"},{"internalType":"uint256","name":"usdReward","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"positions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"int256","name":"percent","type":"int256"},{"internalType":"address","name":"token","type":"address"}],"name":"pull","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"repack","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"myLiquidity","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"selfManaged","outputs":[{"internalType":"uint256","name":"created","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"int24","name":"lower","type":"int24"},{"internalType":"int24","name":"upper","type":"int24"},{"internalType":"int256","name":"liq","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_quid","type":"address"},{"internalType":"address","name":"_aux","type":"address"},{"internalType":"address","name":"_core","type":"address"}],"name":"setup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"howMuch","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"takeETH","outputs":[{"internalType":"uint256","name":"sent","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token1isETH","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6080604052600180553480156012575f80fd5b503380603757604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b603e816043565b506092565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b613e108061009f5f395ff3fe608060405260043610610141575f3560e01c80637f30560b116100af578063cc5d7a2b1161006b578063cc5d7a2b14610423578063d13615da14610438578063d2e3720114610457578063e3185c6c146104b7578063f2fde38b146104d6578063fc68b6e1146104f557005b80637f30560b146102fd5780638d4f29c61461038d5780638da5cb5b146103a257806395e29ea0146103d2578063b6b55f25146103f1578063c1be66771461040457005b806338078d3f116100fe57806338078d3f146102535780633a98ef39146102835780634a62a2e814610298578063715018a6146102ab57806377b8b1c7146102bf5780637e12119c146102de57005b806312f6d7d7146101435780631dfa45e31461016b5780631e5f257c146101805780632e1a7d4d146101b357806331d7a262146101d25780633351595014610206575b005b34801561014e575f80fd5b5061015860075481565b6040519081526020015b60405180910390f35b348015610176575f80fd5b50610158600a5481565b34801561018b575f80fd5b506003546101a090600160a81b900460020b81565b60405160029190910b8152602001610162565b3480156101be575f80fd5b506101416101cd366004613696565b610515565b3480156101dd575f80fd5b506101f16101ec3660046136c1565b610b4d565b60408051928352602083019190915201610162565b348015610211575f80fd5b5061021a610c2a565b604080516001600160a01b039095168552600293840b60208601529190920b908301526001600160801b03166060820152608001610162565b34801561025e575f80fd5b5060035461027390600160a01b900460ff1681565b6040519015158152602001610162565b34801561028e575f80fd5b50610158600b5481565b6101586102a63660046136ea565b610c91565b3480156102b6575f80fd5b50610141611221565b3480156102ca575f80fd5b506101416102d936600461373a565b611234565b3480156102e9575f80fd5b506101416102f8366004613782565b6115a8565b348015610308575f80fd5b50610356610317366004613696565b600d6020525f908152604090208054600182015460029283015491926001600160a01b03821692600160a01b8304820b92600160b81b900490910b9085565b604080519586526001600160a01b039094166020860152600292830b93850193909352900b6060830152608082015260a001610162565b348015610398575f80fd5b5061015860065481565b3480156103ad575f80fd5b505f546001600160a01b03165b6040516001600160a01b039091168152602001610162565b3480156103dd575f80fd5b506101f16103ec3660046137ad565b6118fb565b6101416103ff366004613696565b611c97565b34801561040f575f80fd5b5061015861041e3660046137cd565b612016565b34801561042e575f80fd5b5061015860055481565b348015610443575f80fd5b506103ba610452366004613804565b612041565b348015610462575f80fd5b506104976104713660046136c1565b600c6020525f908152604090208054600182015460028301546003909301549192909184565b604080519485526020850193909352918301526060820152608001610162565b3480156104c2575f80fd5b506101586104d1366004613842565b6120b3565b3480156104e1575f80fd5b506101416104f03660046136c1565b612108565b348015610500575f80fd5b506003546101a090600160c01b900460020b81565b6001546001146105405760405162461bcd60e51b815260040161053790613870565b60405180910390fd5b60026001555f8080610550612145565b50335f908152600c60209081526040808320600254825163ba6d778360e01b81529251979a50959850939650929491936001600160a01b03169263ba6d778392600480830193928290030181865afa1580156105ae573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105d29190613894565b82549091505f908190156105ef576105e933610b4d565b90925090505b81156106255781845f015f82825461060791906138bf565b9250508190555081600b5f82825461061f91906138bf565b90915550505b600184015461063490826138bf565b905080156106c6575f60018501819055600854604051633c173a4f60e01b8152336004820152602481018490526001600160a01b03909116604482018190526064820192909252633c173a4f906084016020604051808303815f875af11580156106a0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106c49190613894565b505b6106d388855f01546124ca565b97508715610ad4575f806106e78a866124ca565b905080156107f95760028054604051631901a6ef60e01b81528a830b60048201529189900b60248301525f916001600160a01b0390911690631901a6ef90604401606060405180830381865afa158015610743573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061076791906138d2565b925050506001600160801b038116156107f757600254604051632e76f9e760e11b81526001600160a01b0390911690635cedf3ce906107b4908d9086905f908f908f90339060040161391c565b6020604051808303815f875af11580156107d0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107f49190613894565b92505b505b818a1115610aa1575f61080c838c613958565b90505f60025f9054906101000a90046001600160a01b03166001600160a01b031663ba6d77836040518163ffffffff1660e01b8152600401602060405180830381865afa15801561085f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108839190613894565b90505f60095f9054906101000a90046001600160a01b03166001600160a01b0316634aa0c6c46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108fa9190613894565b90505f82821161090a575f610914565b6109148383613958565b905061092084826124ca565b9050801561094d5761093281336124e1565b905061093e81876138bf565b955061094a8185613958565b93505b83156109ed576009546040516343db91af60e01b8152600481018690525f916001600160a01b0316906343db91af906024016020604051808303815f875af115801561099b573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109bf9190613894565b905080156109eb575f6109db6109d583886124ca565b336124e1565b90506109e781896138bf565b9750505b505b6109f7868f6124ca565b9d508d8a5f015f828254610a0b9190613958565b925050819055508d600b5f828254610a239190613958565b9091555050600954604080516312a831b160e21b815290516001600160a01b0390921691634aa0c6c4916004808201926020929091908290030181865afa158015610a70573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a949190613894565b60045550610ad192505050565b89865f015f828254610ab39190613958565b9250508190555089600b5f828254610acb9190613958565b90915550505b50505b83545f03610b0457335f908152600c60205260408120818155600181018290556002810182905560030155610b3f565b610b1c845f0154600754670de0b6b3a76400006126d6565b60028501558354600654610b399190670de0b6b3a76400006126d6565b60038501555b505060018055505050505050565b6001600160a01b0381165f908152600c602090815260408083208151608081018352815480825260018301549482019490945260028201549281019290925260030154606082015282918203610ba857505f93849350915050565b5f610bc1825f0151600754670de0b6b3a76400006126d6565b90505f610bdc835f0151600654670de0b6b3a76400006126d6565b905082604001518211610bef575f610bfe565b6040830151610bfe9083613958565b945082606001518111610c11575f610c20565b6060830151610c209082613958565b9350505050915091565b6009545f908190819081906001600160a01b0316331480610c5557506002546001600160a01b031633145b80610c5f57503330145b610c7b5760405162461bcd60e51b81526004016105379061396b565b610c83612145565b929791965094509092509050565b5f600154600114610cb45760405162461bcd60e51b815260040161053790613870565b60026001819055600a9060649084900b12801590610cd757506103e88360020b13155b8015610cee5750610ce960328461399c565b60020b155b610d4d5760405162461bcd60e51b815260206004820152602a60248201527f52616e6765206d757374206265203130302d3130303020696e20696e6372656d6044820152690656e7473206f662035360b41b6064820152608401610537565b610d5860648561399c565b60020b158015610d6b57508360020b5f14155b8015610d7d5750611387198460020b12155b8015610d8e57506113888460020b13155b610ded5760405162461bcd60e51b815260206004820152602a60248201527f6d757374206265202d3530303020746f203530303020696e20696e6372656d6560448201526906e7473206f66203130360b41b6064820152608401610537565b5f805f610df8612145565b506003549295509093509150600160a01b900460ff16610e1e57610e1b876139bd565b96505b5f80610e2c85878a8c612772565b90925090505f6001600160a01b038b16610ebe57610e4a338d6127e2565b600354909c50600160a01b900460ff1615610e92578360020b8360020b13610e70575f80fd5b610e8b610e7c84612ad8565b610e8584612ad8565b8e612d90565b9050611071565b8460020b8260020b12610ea3575f80fd5b610e8b610eaf84612ad8565b610eb884612ad8565b8e612dd9565b600954604051638340f54960e01b81523360048201526001600160a01b038d81166024830152604482018f905290911690638340f549906064016020604051808303815f875af1158015610f14573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f389190613894565b9b505f8b6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f77573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f9b91906139dd565b90508060ff16600614610ffb5760068160ff1611610fd857610fbe8160066139fd565b610fc990600a613af9565b610fd3908e613b07565b610ff8565b610fe36006826139fd565b610fee90600a613af9565b610ff8908e613b1e565b9c505b600354600160a01b900460ff1615611040578560020b8360020b1261101e575f80fd5b61103961102a85612ad8565b61103385612ad8565b8f612dd9565b915061106f565b8460020b8460020b13611051575f80fd5b61106c61105d85612ad8565b61106685612ad8565b8f612d90565b91505b505b5f6040518060a00160405280438152602001336001600160a01b031681526020018560020b81526020018460020b8152602001836001600160801b03168152509050600e5f81546110c190613b31565b918290555098506001600160801b0382166111075760405162461bcd60e51b815260040161053790602080825260049082015263191d5cdd60e21b604082015260600190565b5f898152600d602090815260408083208451815584830151600180830180548886015160608a01516001600160a01b039586166001600160b81b031990931692909217600160a01b62ffffff928316021762ffffff60b81b1916600160b81b9190921602179055608087015160029384015533808752600f8652848720805492830181558752948620018e905581549251633532736d60e01b815260048101949094526001600160801b038716602485015288820b60448501529087900b6064840152608483019390935290911690633532736d9060a4015f604051808303815f87803b1580156111f6575f80fd5b505af1158015611208573d5f803e3d5ffd5b50506001805550989d9c50505050505050505050505050565b611229612e35565b6112325f612e61565b565b6009546001600160a01b0316156112715760405162461bcd60e51b81526020600482015260016024820152602160f81b6044820152606401610537565b600980546001600160a01b038085166001600160a01b0319928316179092556002805484841690831617905560088054928616929091169190911790556112b6611221565b600854604080516316ce049b60e01b8152905130926001600160a01b0316916316ce049b9160048083019260209291908290030181865afa1580156112fd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113219190613b49565b6001600160a01b03161461135b5760405162461bcd60e51b81526020600482015260016024820152603f60f81b6044820152606401610537565b60095f9054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113ab573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113cf9190613b49565b600380546001600160a01b0319166001600160a01b0392831690811790915560095460405163095ea7b360e01b8152921660048301525f1960248301529063095ea7b3906044016020604051808303815f875af1158015611432573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114569190613b64565b50600254604051631901a6ef60e01b81525f6004820181905260248201819052916001600160a01b031690631901a6ef90604401606060405180830381865afa1580156114a5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114c991906138d2565b5050905060025f9054906101000a90046001600160a01b03166001600160a01b03166338078d3f6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561151d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115419190613b64565b60038054911515600160a01b0260ff60a01b199092169190911790556115688160c8612eb0565b506003805465ffffffffffff60a81b1916600160a81b62ffffff9384160262ffffff60c01b191617600160c01b9490921693909302179091555050505050565b6001546001146115ca5760405162461bcd60e51b815260040161053790613870565b600260019081555f848152600d60205260409020908101546001600160a01b031633146116095760405162461bcd60e51b81526004016105379061396b565b805461161690602f6138bf565b4310156116505760405162461bcd60e51b81526020600482015260086024820152673a37b79039b7b7b760c11b6044820152606401610537565b5f8313801561165f5750606583125b61168f5760405162461bcd60e51b81526020600482015260016024820152602560f81b6044820152606401610537565b5f60648483600201546116a29190613b7f565b6116ac9190613bae565b90505f81136116e65760405162461bcd60e51b815260040161053790602080825260049082015263191d5cdd60e21b604082015260600190565b6001820154335f908152600f602052604081208054600160a01b8404600290810b94600160b81b9004900b929061171d575f61172b565b815461172b90600190613958565b905087606403611807575f898152600d602052604081208181556001810180546001600160d01b03191690556002018190555b818111611801578983828154811061177857611778613bda565b905f5260205f200154036117ef57818110156117c7578282815481106117a0576117a0613bda565b905f5260205f2001548382815481106117bb576117bb613bda565b5f918252602090912001555b828054806117d7576117d7613bee565b600190038181905f5260205f20015f90559055611801565b806117f981613b31565b91505061175e565b5061185b565b84866002015f82825461181a9190613c02565b909155505060028601545f1261185b5760405162461bcd60e51b8152600401610537906020808252600490820152631c1d5b1b60e21b604082015260600190565b6002546001600160a01b0316633532736d3361187688613c28565b6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201526024810191909152600288810b604483015287900b6064820152908a16608482015260a4015f604051808303815f87803b1580156118d6575f80fd5b505af11580156118e8573d5f803e3d5ffd5b5050600180555050505050505050505050565b6009545f9081906001600160a01b031633148061192257506002546001600160a01b031633145b8061192c57503330145b6119485760405162461bcd60e51b81526004016105379061396b565b600954604080516359b44ca960e11b815290515f926001600160a01b03169163b3689952916004808301926101a0929190829003018187875af1158015611991573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119b59190613c56565b90505f60095f9054906101000a90046001600160a01b03166001600160a01b0316635debd2916040518163ffffffff1660e01b8152600401602060405180830381865afa158015611a08573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a2c9190613894565b610160830151610180840151611a429190613958565b611a4c91906138bf565b90505f60025f9054906101000a90046001600160a01b03166001600160a01b0316632671797b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611a9f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ac39190613894565b611ad29064e8d4a51000613b07565b9050818110611ae9575f8094509450505050611c90565b5f611af48284613958565b90505f60095f9054906101000a90046001600160a01b03166001600160a01b0316634aa0c6c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b47573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b6b9190613894565b90505f60025f9054906101000a90046001600160a01b03166001600160a01b031663ba6d77836040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bbe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be29190613894565b90505f818311611bf2575f611bfc565b611bfc8284613958565b90505f611c128c8c670de0b6b3a76400006126d6565b905084811115611c34575083611c3181670de0b6b3a76400008d6126d6565b9b505b818c1115611c5557819b50611c528c8c670de0b6b3a76400006126d6565b90505b5f611c6564e8d4a5100083613b1e565b9050805f03611c82575f809a509a50505050505050505050611c90565b99508b985050505050505050505b9250929050565b600154600114611cb95760405162461bcd60e51b815260040161053790613870565b6002600155600954604051637292084760e11b815261070860048201525f916001600160a01b03169063e524108e90602401602060405180830381865afa158015611d06573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d2a9190613894565b90505f8111611d645760405162461bcd60e51b8152600401610537906020808252600490820152630545741560e41b604082015260600190565b5f8083158015611d72575034155b15611d7f5750505061200f565b335f908152600c60205260408120908080611d98612145565b50925092509250611da933896127e2565b97508760045f828254611dbc91906138bf565b9091555050600754600654855415611e29575f80611dd933610b4d565b9150915081885f015f828254611def91906138bf565b9250508190555080886001015f828254611e0991906138bf565b9250508190555081600b5f828254611e2191906138bf565b909155505050505b6040516304af14f560e51b8152600481018b9052602481018a905230906395e29ea09060440160408051808303815f875af1158015611e6a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e8e9190613cd3565b985096508715611f775787865f015f828254611eaa91906138bf565b9250508190555087600b5f828254611ec291906138bf565b90915550508554611edc9083670de0b6b3a76400006126d6565b60028701558554611ef69082670de0b6b3a76400006126d6565b6003870155600254604051632e76f9e760e11b81526001600160a01b0390911690635cedf3ce90611f359088908c908c908a908a90339060040161391c565b6020604051808303815f875af1158015611f51573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f759190613894565b505b8988101561200557611f8c6109d5898c613958565b5060095f9054906101000a90046001600160a01b03166001600160a01b0316634aa0c6c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611fdd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906120019190613894565b6004555b5050505050505050505b5060018055565b600f602052815f5260405f20818154811061202f575f80fd5b905f5260205f20015f91509150505481565b5f808361207e576120796127106120588582613958565b61206a90670de0b6b3a7640000613b07565b6120749190613b1e565b612fc2565b61208e565b61208e61271061205885826138bf565b90506120a8856001600160a01b031682633b9aca00613066565b9150505b9392505050565b6009545f906001600160a01b03163314806120d857506002546001600160a01b031633145b806120e257503330145b6120fe5760405162461bcd60e51b81526004016105379061396b565b6120ac83836124e1565b612110612e35565b6001600160a01b03811661213957604051631e4fbdf760e01b81525f6004820152602401610537565b61214281612e61565b50565b5f805f80612151613081565b60035460028054604051631901a6ef60e01b8152600160c01b8404830b60048201819052600160a81b90940490920b602483018190529295509193505f916001600160a01b031690631901a6ef90604401606060405180830381865afa1580156121bd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121e191906138d2565b91965090925090505f80808080600288810b9087900b138061220857508860020b8660020b125b156124bd57600954604051637292084760e11b815261070860048201526001600160a01b039091169063e524108e90602401602060405180830381865afa158015612255573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122799190613894565b60035460405163314f364b60e11b81526001600160a01b038d166004820152600160a01b90910460ff161515602482015290915073ac2a347ff18bc0ee07c49a2c4faaf18ec54e1cc69063629e6c9690604401602060405180830381865af41580156122e7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061230b9190613894565b6040516349c4215360e11b815260048101829052602481018390526003604482015290925073ac2a347ff18bc0ee07c49a2c4faaf18ec54e1cc69063938842a690606401602060405180830381865af415801561236a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061238e9190613b64565b1561239e575050505050506124c4565b5f806123ab8c60c8612eb0565b5092505091505f896001600160801b0316111561247c5760028054604051630c0bd30b60e21b81526001600160801b038c1660048201526001600160a01b038f811660248301528e840b60448301528d840b606483015285840b60848301529284900b60a482015291169063302f4c2c9060c40160a0604051808303815f875af115801561243b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061245f9190613cf5565b939a50919850965094509250612478868686868b613159565b600a555b6003805462ffffff838116600160a81b0262ffffff60a81b19918616600160c01b029190911665ffffffffffff60a81b199092169190911717905590995097505b5050505050505b90919293565b5f8183106124d857816120ac565b50815b92915050565b5f478381106124f257839150612673565b5f6124fd8286613958565b6003546040516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa158015612548573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061256c9190613894565b905080821115612609576009545f906001600160a01b03166378cf86116125938486613958565b6040516001600160e01b031960e084901b1681526004810191909152600160248201526044016020604051808303815f875af11580156125d5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125f99190613894565b905061260581836138bf565b9150505b600354604051632e1a7d4d60e01b8152600481018390526001600160a01b0390911690632e1a7d4d906024015f604051808303815f87803b15801561264c575f80fd5b505af115801561265e573d5f803e3d5ffd5b50505050828161266e91906138bf565b935050505b5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f81146126bc576040519150601f19603f3d011682016040523d82523d5f602084013e6126c1565b606091505b50509050806126ce575f92505b505092915050565b5f838302815f19858709828110838203039150508084116126f5575f80fd5b805f03612707575082900490506120ac565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f805f8361277f886132bf565b6127899190613d31565b90505f8460020b12156127bc576127a0818761354f565b92506127b56127af8683613d56565b8761354f565b91506127d8565b6127c6818761354f565b91506127d56127af8683613d31565b92505b5094509492505050565b5f34156128675760035f9054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004015f604051808303818588803b158015612836575f80fd5b505af1158015612848573d5f803e3d5ffd5b505050505034905061285a82346124ca565b6128649083613958565b91505b81156129f557600354604051636eb1769f60e11b81526001600160a01b0385811660048301523060248301525f926129559291169063dd62ed3e90604401602060405180830381865afa1580156128c0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128e49190613894565b6003546040516370a0823160e01b81526001600160a01b038881166004830152909116906370a0823190602401602060405180830381865afa15801561292c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129509190613894565b6124ca565b90505f61296284836124ca565b905080156129f2576003546040516323b872dd60e01b81526001600160a01b03878116600483015230602483015260448201849052909116906323b872dd906064016020604051808303815f875af11580156129c0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129e49190613b64565b506129ef81846138bf565b92505b50505b80156124db576003546040516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa158015612a41573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a659190613894565b6009546040516378cf861160e01b8152600481018390525f60248201529192506001600160a01b0316906378cf8611906044016020604051808303815f875af1158015612ab4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126ce9190613894565b60020b5f60ff82901d80830118620d89e8811115612b0157612b016345c3193d60e11b846135b9565b7001fffcb933bd6fad37aa2d162d1a5940016001821602600160801b186002821615612b3d576ffff97272373d413259a46990580e213a0260801c5b6004821615612b5c576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612b7b576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612b9a576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612bb9576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612bd8576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612bf7576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612c17576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612c37576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612c57576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612c77576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612c97576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612cb7576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612cd7576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612cf7576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612d18576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615612d38576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615612d57576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615612d74576b048a170391f7dc42444e8fa20260801c5b5f841315612d80575f19045b63ffffffff0160201c9392505050565b5f826001600160a01b0316846001600160a01b03161115612daf579192915b612dd1612dcc83600160601b8787036001600160a01b03166126d6565b6135c8565b949350505050565b5f826001600160a01b0316846001600160a01b03161115612df8579192915b5f612e1a856001600160a01b0316856001600160a01b0316600160601b6126d6565b90506120a8612dcc84838888036001600160a01b03166126d6565b5f546001600160a01b031633146112325760405163118cdaa760e01b8152336004820152602401610537565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f805f80612ebf865f87612041565b9250612ecd86600187612041565b9050612edf6401000276a36001613d7b565b6001600160a01b0316836001600160a01b03161015612f2b5760405162461bcd60e51b81526020600482015260086024820152676d696e507269636560c01b6044820152606401610537565b612f4a600173fffd8963efd1fc6a506488495d951d5263988d26613d9a565b6001600160a01b0316816001600160a01b03161115612f965760405162461bcd60e51b81526020600482015260086024820152676d6178507269636560c01b6044820152606401610537565b612fa9612fa2846132bf565b600a61354f565b9350612fb7612fa2826132bf565b915092959194509250565b60b581600160881b8110612fdb5760409190911b9060801c5b69010000000000000000008110612ff75760209190911b9060401c5b65010000000000811061300f5760109190911b9060201c5b630100000081106130255760089190911b9060101c5b62010000010260121c80820401600190811c80830401811c80830401811c80830401811c80830401811c80830401811c80830401901c908190048111900390565b5f825f19048411830215820261307a575f80fd5b5091020490565b6009546040516378cf861160e01b81525f6004820181905260026024830152916001600160a01b0316906378cf8611906044016020604051808303815f875af11580156130d0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130f49190613894565b90505f600454118015613108575060045481115b15613154575f6004548261311c9190613958565b600b54909150156131525761313c81670de0b6b3a7640000600b546126d6565b60075f82825461314c91906138bf565b90915550505b505b600455565b5f8060055490505f805f80600360149054906101000a900460ff1615613189575087925086915089905088613195565b50869250879150889050895b600b54156131f4576131b281670de0b6b3a7640000600b546126d6565b60075f8282546131c291906138bf565b925050819055506131de82670de0b6b3a7640000600b546126d6565b60065f8282546131ee91906138bf565b90915550505b84156132ac575f6132058642613958565b90505f8161321c8a87670de0b6b3a76400006126d6565b61322b8864e8d4a51000613b07565b61323591906138bf565b61323f9190613b07565b905080156132a957670de0b6b3a764000061329c6132668b86670de0b6b3a76400006126d6565b6132758764e8d4a51000613b07565b61327f91906138bf565b61328d906301e13380613b07565b670de0b6b3a7640000846126d6565b6132a69190613b1e565b97505b50505b5050426005555091979650505050505050565b5f73fffd8963efd1fc6a506488495d951d51639616826401000276a21983016001600160a01b031611156132fe576132fe6318521d4960e21b836135ee565b640100000000600160c01b03602083901b16805f61331b82613603565b60ff1690506080811061333657607f810383901c9150613340565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b1461354057886001600160a01b031661352582612ad8565b6001600160a01b0316111561353a5781613542565b80613542565b815b9998505050505050505050565b5f808360020b12801561356d5750613567828461399c565b60020b15155b156135a457818061357e8186613d31565b613589906001613d56565b6135939190613db9565b61359d9190613df1565b90506124db565b816135af8185613db9565b6120ac9190613df1565b815f528060020b60045260245ffd5b806001600160801b03811681146135e9576135e96393dafdf160e01b61368e565b919050565b815f526001600160a01b03811660045260245ffd5b5f80821161360f575f80fd5b507f0706060506020500060203020504000106050205030304010505030400000000601f6f8421084210842108cc6318c6db6d54be6001600160801b03841160071b84811c67ffffffffffffffff1060061b1784811c63ffffffff1060051b1784811c61ffff1060041b1784811c60ff1060031b1793841c1c161a1790565b805f5260045ffd5b5f602082840312156136a6575f80fd5b5035919050565b6001600160a01b0381168114612142575f80fd5b5f602082840312156136d1575f80fd5b81356120ac816136ad565b8060020b8114612142575f80fd5b5f805f80608085870312156136fd575f80fd5b84359350602085013561370f816136ad565b9250604085013561371f816136dc565b9150606085013561372f816136dc565b939692955090935050565b5f805f6060848603121561374c575f80fd5b8335613757816136ad565b92506020840135613767816136ad565b91506040840135613777816136ad565b809150509250925092565b5f805f60608486031215613794575f80fd5b83359250602084013591506040840135613777816136ad565b5f80604083850312156137be575f80fd5b50508035926020909101359150565b5f80604083850312156137de575f80fd5b82356137e9816136ad565b946020939093013593505050565b8015158114612142575f80fd5b5f805f60608486031215613816575f80fd5b8335613821816136ad565b92506020840135613831816137f7565b929592945050506040919091013590565b5f8060408385031215613853575f80fd5b823591506020830135613865816136ad565b809150509250929050565b6020808252600a90820152695245454e5452414e435960b01b604082015260600190565b5f602082840312156138a4575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156124db576124db6138ab565b5f805f606084860312156138e4575f80fd5b83516138ef816136ad565b6020850151909350613900816136dc565b60408501519092506001600160801b0381168114613777575f80fd5b6001600160a01b03968716815260208101959095526040850193909352600291820b6060850152900b608083015290911660a082015260c00190565b818103818111156124db576124db6138ab565b60208082526003908201526234303360e81b604082015260600190565b634e487b7160e01b5f52601260045260245ffd5b5f8260020b806139ae576139ae613988565b808360020b0791505092915050565b5f8160020b627fffff1981036139d5576139d56138ab565b5f0392915050565b5f602082840312156139ed575f80fd5b815160ff811681146120ac575f80fd5b60ff82811682821603908111156124db576124db6138ab565b6001815b6001841115613a5157808504811115613a3557613a356138ab565b6001841615613a4357908102905b60019390931c928002613a1a565b935093915050565b5f82613a67575060016124db565b81613a7357505f6124db565b8160018114613a895760028114613a9357613aaf565b60019150506124db565b60ff841115613aa457613aa46138ab565b50506001821b6124db565b5060208310610133831016604e8410600b8410161715613ad2575081810a6124db565b613ade5f198484613a16565b805f1904821115613af157613af16138ab565b029392505050565b5f6120ac60ff841683613a59565b80820281158282048414176124db576124db6138ab565b5f82613b2c57613b2c613988565b500490565b5f60018201613b4257613b426138ab565b5060010190565b5f60208284031215613b59575f80fd5b81516120ac816136ad565b5f60208284031215613b74575f80fd5b81516120ac816137f7565b8082025f8212600160ff1b84141615613b9a57613b9a6138ab565b81810583148215176124db576124db6138ab565b5f82613bbc57613bbc613988565b600160ff1b82145f1984141615613bd557613bd56138ab565b500590565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b8181035f831280158383131683831282161715613c2157613c216138ab565b5092915050565b5f600160ff1b8201613c3c57613c3c6138ab565b505f0390565b634e487b7160e01b5f52604160045260245ffd5b5f6101a08284031215613c67575f80fd5b82601f830112613c75575f80fd5b6040516101a0810181811067ffffffffffffffff82111715613c9957613c99613c42565b604052806101a0840185811115613cae575f80fd5b845b81811015613cc8578051835260209283019201613cb0565b509195945050505050565b5f8060408385031215613ce4575f80fd5b505080516020909101519092909150565b5f805f805f60a08688031215613d09575f80fd5b5050835160208501516040860151606087015160809097015192989197509594509092509050565b600282810b9082900b03627fffff198112627fffff821317156124db576124db6138ab565b600281810b9083900b01627fffff8113627fffff19821217156124db576124db6138ab565b6001600160a01b0381811683821601908111156124db576124db6138ab565b6001600160a01b0382811682821603908111156124db576124db6138ab565b5f8160020b8360020b80613dcf57613dcf613988565b627fffff1982145f1982141615613de857613de86138ab565b90059392505050565b5f8260020b8260020b028060020b9150808214613c2157613c216138ab56

Deployed Bytecode

0x608060405260043610610141575f3560e01c80637f30560b116100af578063cc5d7a2b1161006b578063cc5d7a2b14610423578063d13615da14610438578063d2e3720114610457578063e3185c6c146104b7578063f2fde38b146104d6578063fc68b6e1146104f557005b80637f30560b146102fd5780638d4f29c61461038d5780638da5cb5b146103a257806395e29ea0146103d2578063b6b55f25146103f1578063c1be66771461040457005b806338078d3f116100fe57806338078d3f146102535780633a98ef39146102835780634a62a2e814610298578063715018a6146102ab57806377b8b1c7146102bf5780637e12119c146102de57005b806312f6d7d7146101435780631dfa45e31461016b5780631e5f257c146101805780632e1a7d4d146101b357806331d7a262146101d25780633351595014610206575b005b34801561014e575f80fd5b5061015860075481565b6040519081526020015b60405180910390f35b348015610176575f80fd5b50610158600a5481565b34801561018b575f80fd5b506003546101a090600160a81b900460020b81565b60405160029190910b8152602001610162565b3480156101be575f80fd5b506101416101cd366004613696565b610515565b3480156101dd575f80fd5b506101f16101ec3660046136c1565b610b4d565b60408051928352602083019190915201610162565b348015610211575f80fd5b5061021a610c2a565b604080516001600160a01b039095168552600293840b60208601529190920b908301526001600160801b03166060820152608001610162565b34801561025e575f80fd5b5060035461027390600160a01b900460ff1681565b6040519015158152602001610162565b34801561028e575f80fd5b50610158600b5481565b6101586102a63660046136ea565b610c91565b3480156102b6575f80fd5b50610141611221565b3480156102ca575f80fd5b506101416102d936600461373a565b611234565b3480156102e9575f80fd5b506101416102f8366004613782565b6115a8565b348015610308575f80fd5b50610356610317366004613696565b600d6020525f908152604090208054600182015460029283015491926001600160a01b03821692600160a01b8304820b92600160b81b900490910b9085565b604080519586526001600160a01b039094166020860152600292830b93850193909352900b6060830152608082015260a001610162565b348015610398575f80fd5b5061015860065481565b3480156103ad575f80fd5b505f546001600160a01b03165b6040516001600160a01b039091168152602001610162565b3480156103dd575f80fd5b506101f16103ec3660046137ad565b6118fb565b6101416103ff366004613696565b611c97565b34801561040f575f80fd5b5061015861041e3660046137cd565b612016565b34801561042e575f80fd5b5061015860055481565b348015610443575f80fd5b506103ba610452366004613804565b612041565b348015610462575f80fd5b506104976104713660046136c1565b600c6020525f908152604090208054600182015460028301546003909301549192909184565b604080519485526020850193909352918301526060820152608001610162565b3480156104c2575f80fd5b506101586104d1366004613842565b6120b3565b3480156104e1575f80fd5b506101416104f03660046136c1565b612108565b348015610500575f80fd5b506003546101a090600160c01b900460020b81565b6001546001146105405760405162461bcd60e51b815260040161053790613870565b60405180910390fd5b60026001555f8080610550612145565b50335f908152600c60209081526040808320600254825163ba6d778360e01b81529251979a50959850939650929491936001600160a01b03169263ba6d778392600480830193928290030181865afa1580156105ae573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105d29190613894565b82549091505f908190156105ef576105e933610b4d565b90925090505b81156106255781845f015f82825461060791906138bf565b9250508190555081600b5f82825461061f91906138bf565b90915550505b600184015461063490826138bf565b905080156106c6575f60018501819055600854604051633c173a4f60e01b8152336004820152602481018490526001600160a01b03909116604482018190526064820192909252633c173a4f906084016020604051808303815f875af11580156106a0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106c49190613894565b505b6106d388855f01546124ca565b97508715610ad4575f806106e78a866124ca565b905080156107f95760028054604051631901a6ef60e01b81528a830b60048201529189900b60248301525f916001600160a01b0390911690631901a6ef90604401606060405180830381865afa158015610743573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061076791906138d2565b925050506001600160801b038116156107f757600254604051632e76f9e760e11b81526001600160a01b0390911690635cedf3ce906107b4908d9086905f908f908f90339060040161391c565b6020604051808303815f875af11580156107d0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107f49190613894565b92505b505b818a1115610aa1575f61080c838c613958565b90505f60025f9054906101000a90046001600160a01b03166001600160a01b031663ba6d77836040518163ffffffff1660e01b8152600401602060405180830381865afa15801561085f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108839190613894565b90505f60095f9054906101000a90046001600160a01b03166001600160a01b0316634aa0c6c46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108d6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108fa9190613894565b90505f82821161090a575f610914565b6109148383613958565b905061092084826124ca565b9050801561094d5761093281336124e1565b905061093e81876138bf565b955061094a8185613958565b93505b83156109ed576009546040516343db91af60e01b8152600481018690525f916001600160a01b0316906343db91af906024016020604051808303815f875af115801561099b573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109bf9190613894565b905080156109eb575f6109db6109d583886124ca565b336124e1565b90506109e781896138bf565b9750505b505b6109f7868f6124ca565b9d508d8a5f015f828254610a0b9190613958565b925050819055508d600b5f828254610a239190613958565b9091555050600954604080516312a831b160e21b815290516001600160a01b0390921691634aa0c6c4916004808201926020929091908290030181865afa158015610a70573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a949190613894565b60045550610ad192505050565b89865f015f828254610ab39190613958565b9250508190555089600b5f828254610acb9190613958565b90915550505b50505b83545f03610b0457335f908152600c60205260408120818155600181018290556002810182905560030155610b3f565b610b1c845f0154600754670de0b6b3a76400006126d6565b60028501558354600654610b399190670de0b6b3a76400006126d6565b60038501555b505060018055505050505050565b6001600160a01b0381165f908152600c602090815260408083208151608081018352815480825260018301549482019490945260028201549281019290925260030154606082015282918203610ba857505f93849350915050565b5f610bc1825f0151600754670de0b6b3a76400006126d6565b90505f610bdc835f0151600654670de0b6b3a76400006126d6565b905082604001518211610bef575f610bfe565b6040830151610bfe9083613958565b945082606001518111610c11575f610c20565b6060830151610c209082613958565b9350505050915091565b6009545f908190819081906001600160a01b0316331480610c5557506002546001600160a01b031633145b80610c5f57503330145b610c7b5760405162461bcd60e51b81526004016105379061396b565b610c83612145565b929791965094509092509050565b5f600154600114610cb45760405162461bcd60e51b815260040161053790613870565b60026001819055600a9060649084900b12801590610cd757506103e88360020b13155b8015610cee5750610ce960328461399c565b60020b155b610d4d5760405162461bcd60e51b815260206004820152602a60248201527f52616e6765206d757374206265203130302d3130303020696e20696e6372656d6044820152690656e7473206f662035360b41b6064820152608401610537565b610d5860648561399c565b60020b158015610d6b57508360020b5f14155b8015610d7d5750611387198460020b12155b8015610d8e57506113888460020b13155b610ded5760405162461bcd60e51b815260206004820152602a60248201527f6d757374206265202d3530303020746f203530303020696e20696e6372656d6560448201526906e7473206f66203130360b41b6064820152608401610537565b5f805f610df8612145565b506003549295509093509150600160a01b900460ff16610e1e57610e1b876139bd565b96505b5f80610e2c85878a8c612772565b90925090505f6001600160a01b038b16610ebe57610e4a338d6127e2565b600354909c50600160a01b900460ff1615610e92578360020b8360020b13610e70575f80fd5b610e8b610e7c84612ad8565b610e8584612ad8565b8e612d90565b9050611071565b8460020b8260020b12610ea3575f80fd5b610e8b610eaf84612ad8565b610eb884612ad8565b8e612dd9565b600954604051638340f54960e01b81523360048201526001600160a01b038d81166024830152604482018f905290911690638340f549906064016020604051808303815f875af1158015610f14573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f389190613894565b9b505f8b6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f77573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f9b91906139dd565b90508060ff16600614610ffb5760068160ff1611610fd857610fbe8160066139fd565b610fc990600a613af9565b610fd3908e613b07565b610ff8565b610fe36006826139fd565b610fee90600a613af9565b610ff8908e613b1e565b9c505b600354600160a01b900460ff1615611040578560020b8360020b1261101e575f80fd5b61103961102a85612ad8565b61103385612ad8565b8f612dd9565b915061106f565b8460020b8460020b13611051575f80fd5b61106c61105d85612ad8565b61106685612ad8565b8f612d90565b91505b505b5f6040518060a00160405280438152602001336001600160a01b031681526020018560020b81526020018460020b8152602001836001600160801b03168152509050600e5f81546110c190613b31565b918290555098506001600160801b0382166111075760405162461bcd60e51b815260040161053790602080825260049082015263191d5cdd60e21b604082015260600190565b5f898152600d602090815260408083208451815584830151600180830180548886015160608a01516001600160a01b039586166001600160b81b031990931692909217600160a01b62ffffff928316021762ffffff60b81b1916600160b81b9190921602179055608087015160029384015533808752600f8652848720805492830181558752948620018e905581549251633532736d60e01b815260048101949094526001600160801b038716602485015288820b60448501529087900b6064840152608483019390935290911690633532736d9060a4015f604051808303815f87803b1580156111f6575f80fd5b505af1158015611208573d5f803e3d5ffd5b50506001805550989d9c50505050505050505050505050565b611229612e35565b6112325f612e61565b565b6009546001600160a01b0316156112715760405162461bcd60e51b81526020600482015260016024820152602160f81b6044820152606401610537565b600980546001600160a01b038085166001600160a01b0319928316179092556002805484841690831617905560088054928616929091169190911790556112b6611221565b600854604080516316ce049b60e01b8152905130926001600160a01b0316916316ce049b9160048083019260209291908290030181865afa1580156112fd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113219190613b49565b6001600160a01b03161461135b5760405162461bcd60e51b81526020600482015260016024820152603f60f81b6044820152606401610537565b60095f9054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113ab573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113cf9190613b49565b600380546001600160a01b0319166001600160a01b0392831690811790915560095460405163095ea7b360e01b8152921660048301525f1960248301529063095ea7b3906044016020604051808303815f875af1158015611432573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114569190613b64565b50600254604051631901a6ef60e01b81525f6004820181905260248201819052916001600160a01b031690631901a6ef90604401606060405180830381865afa1580156114a5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114c991906138d2565b5050905060025f9054906101000a90046001600160a01b03166001600160a01b03166338078d3f6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561151d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115419190613b64565b60038054911515600160a01b0260ff60a01b199092169190911790556115688160c8612eb0565b506003805465ffffffffffff60a81b1916600160a81b62ffffff9384160262ffffff60c01b191617600160c01b9490921693909302179091555050505050565b6001546001146115ca5760405162461bcd60e51b815260040161053790613870565b600260019081555f848152600d60205260409020908101546001600160a01b031633146116095760405162461bcd60e51b81526004016105379061396b565b805461161690602f6138bf565b4310156116505760405162461bcd60e51b81526020600482015260086024820152673a37b79039b7b7b760c11b6044820152606401610537565b5f8313801561165f5750606583125b61168f5760405162461bcd60e51b81526020600482015260016024820152602560f81b6044820152606401610537565b5f60648483600201546116a29190613b7f565b6116ac9190613bae565b90505f81136116e65760405162461bcd60e51b815260040161053790602080825260049082015263191d5cdd60e21b604082015260600190565b6001820154335f908152600f602052604081208054600160a01b8404600290810b94600160b81b9004900b929061171d575f61172b565b815461172b90600190613958565b905087606403611807575f898152600d602052604081208181556001810180546001600160d01b03191690556002018190555b818111611801578983828154811061177857611778613bda565b905f5260205f200154036117ef57818110156117c7578282815481106117a0576117a0613bda565b905f5260205f2001548382815481106117bb576117bb613bda565b5f918252602090912001555b828054806117d7576117d7613bee565b600190038181905f5260205f20015f90559055611801565b806117f981613b31565b91505061175e565b5061185b565b84866002015f82825461181a9190613c02565b909155505060028601545f1261185b5760405162461bcd60e51b8152600401610537906020808252600490820152631c1d5b1b60e21b604082015260600190565b6002546001600160a01b0316633532736d3361187688613c28565b6040516001600160e01b031960e085901b1681526001600160a01b0392831660048201526024810191909152600288810b604483015287900b6064820152908a16608482015260a4015f604051808303815f87803b1580156118d6575f80fd5b505af11580156118e8573d5f803e3d5ffd5b5050600180555050505050505050505050565b6009545f9081906001600160a01b031633148061192257506002546001600160a01b031633145b8061192c57503330145b6119485760405162461bcd60e51b81526004016105379061396b565b600954604080516359b44ca960e11b815290515f926001600160a01b03169163b3689952916004808301926101a0929190829003018187875af1158015611991573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119b59190613c56565b90505f60095f9054906101000a90046001600160a01b03166001600160a01b0316635debd2916040518163ffffffff1660e01b8152600401602060405180830381865afa158015611a08573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a2c9190613894565b610160830151610180840151611a429190613958565b611a4c91906138bf565b90505f60025f9054906101000a90046001600160a01b03166001600160a01b0316632671797b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611a9f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ac39190613894565b611ad29064e8d4a51000613b07565b9050818110611ae9575f8094509450505050611c90565b5f611af48284613958565b90505f60095f9054906101000a90046001600160a01b03166001600160a01b0316634aa0c6c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b47573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b6b9190613894565b90505f60025f9054906101000a90046001600160a01b03166001600160a01b031663ba6d77836040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bbe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be29190613894565b90505f818311611bf2575f611bfc565b611bfc8284613958565b90505f611c128c8c670de0b6b3a76400006126d6565b905084811115611c34575083611c3181670de0b6b3a76400008d6126d6565b9b505b818c1115611c5557819b50611c528c8c670de0b6b3a76400006126d6565b90505b5f611c6564e8d4a5100083613b1e565b9050805f03611c82575f809a509a50505050505050505050611c90565b99508b985050505050505050505b9250929050565b600154600114611cb95760405162461bcd60e51b815260040161053790613870565b6002600155600954604051637292084760e11b815261070860048201525f916001600160a01b03169063e524108e90602401602060405180830381865afa158015611d06573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d2a9190613894565b90505f8111611d645760405162461bcd60e51b8152600401610537906020808252600490820152630545741560e41b604082015260600190565b5f8083158015611d72575034155b15611d7f5750505061200f565b335f908152600c60205260408120908080611d98612145565b50925092509250611da933896127e2565b97508760045f828254611dbc91906138bf565b9091555050600754600654855415611e29575f80611dd933610b4d565b9150915081885f015f828254611def91906138bf565b9250508190555080886001015f828254611e0991906138bf565b9250508190555081600b5f828254611e2191906138bf565b909155505050505b6040516304af14f560e51b8152600481018b9052602481018a905230906395e29ea09060440160408051808303815f875af1158015611e6a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e8e9190613cd3565b985096508715611f775787865f015f828254611eaa91906138bf565b9250508190555087600b5f828254611ec291906138bf565b90915550508554611edc9083670de0b6b3a76400006126d6565b60028701558554611ef69082670de0b6b3a76400006126d6565b6003870155600254604051632e76f9e760e11b81526001600160a01b0390911690635cedf3ce90611f359088908c908c908a908a90339060040161391c565b6020604051808303815f875af1158015611f51573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f759190613894565b505b8988101561200557611f8c6109d5898c613958565b5060095f9054906101000a90046001600160a01b03166001600160a01b0316634aa0c6c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611fdd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906120019190613894565b6004555b5050505050505050505b5060018055565b600f602052815f5260405f20818154811061202f575f80fd5b905f5260205f20015f91509150505481565b5f808361207e576120796127106120588582613958565b61206a90670de0b6b3a7640000613b07565b6120749190613b1e565b612fc2565b61208e565b61208e61271061205885826138bf565b90506120a8856001600160a01b031682633b9aca00613066565b9150505b9392505050565b6009545f906001600160a01b03163314806120d857506002546001600160a01b031633145b806120e257503330145b6120fe5760405162461bcd60e51b81526004016105379061396b565b6120ac83836124e1565b612110612e35565b6001600160a01b03811661213957604051631e4fbdf760e01b81525f6004820152602401610537565b61214281612e61565b50565b5f805f80612151613081565b60035460028054604051631901a6ef60e01b8152600160c01b8404830b60048201819052600160a81b90940490920b602483018190529295509193505f916001600160a01b031690631901a6ef90604401606060405180830381865afa1580156121bd573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121e191906138d2565b91965090925090505f80808080600288810b9087900b138061220857508860020b8660020b125b156124bd57600954604051637292084760e11b815261070860048201526001600160a01b039091169063e524108e90602401602060405180830381865afa158015612255573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122799190613894565b60035460405163314f364b60e11b81526001600160a01b038d166004820152600160a01b90910460ff161515602482015290915073ac2a347ff18bc0ee07c49a2c4faaf18ec54e1cc69063629e6c9690604401602060405180830381865af41580156122e7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061230b9190613894565b6040516349c4215360e11b815260048101829052602481018390526003604482015290925073ac2a347ff18bc0ee07c49a2c4faaf18ec54e1cc69063938842a690606401602060405180830381865af415801561236a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061238e9190613b64565b1561239e575050505050506124c4565b5f806123ab8c60c8612eb0565b5092505091505f896001600160801b0316111561247c5760028054604051630c0bd30b60e21b81526001600160801b038c1660048201526001600160a01b038f811660248301528e840b60448301528d840b606483015285840b60848301529284900b60a482015291169063302f4c2c9060c40160a0604051808303815f875af115801561243b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061245f9190613cf5565b939a50919850965094509250612478868686868b613159565b600a555b6003805462ffffff838116600160a81b0262ffffff60a81b19918616600160c01b029190911665ffffffffffff60a81b199092169190911717905590995097505b5050505050505b90919293565b5f8183106124d857816120ac565b50815b92915050565b5f478381106124f257839150612673565b5f6124fd8286613958565b6003546040516370a0823160e01b81523060048201529192505f916001600160a01b03909116906370a0823190602401602060405180830381865afa158015612548573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061256c9190613894565b905080821115612609576009545f906001600160a01b03166378cf86116125938486613958565b6040516001600160e01b031960e084901b1681526004810191909152600160248201526044016020604051808303815f875af11580156125d5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125f99190613894565b905061260581836138bf565b9150505b600354604051632e1a7d4d60e01b8152600481018390526001600160a01b0390911690632e1a7d4d906024015f604051808303815f87803b15801561264c575f80fd5b505af115801561265e573d5f803e3d5ffd5b50505050828161266e91906138bf565b935050505b5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f81146126bc576040519150601f19603f3d011682016040523d82523d5f602084013e6126c1565b606091505b50509050806126ce575f92505b505092915050565b5f838302815f19858709828110838203039150508084116126f5575f80fd5b805f03612707575082900490506120ac565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f805f8361277f886132bf565b6127899190613d31565b90505f8460020b12156127bc576127a0818761354f565b92506127b56127af8683613d56565b8761354f565b91506127d8565b6127c6818761354f565b91506127d56127af8683613d31565b92505b5094509492505050565b5f34156128675760035f9054906101000a90046001600160a01b03166001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004015f604051808303818588803b158015612836575f80fd5b505af1158015612848573d5f803e3d5ffd5b505050505034905061285a82346124ca565b6128649083613958565b91505b81156129f557600354604051636eb1769f60e11b81526001600160a01b0385811660048301523060248301525f926129559291169063dd62ed3e90604401602060405180830381865afa1580156128c0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128e49190613894565b6003546040516370a0823160e01b81526001600160a01b038881166004830152909116906370a0823190602401602060405180830381865afa15801561292c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129509190613894565b6124ca565b90505f61296284836124ca565b905080156129f2576003546040516323b872dd60e01b81526001600160a01b03878116600483015230602483015260448201849052909116906323b872dd906064016020604051808303815f875af11580156129c0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129e49190613b64565b506129ef81846138bf565b92505b50505b80156124db576003546040516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa158015612a41573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a659190613894565b6009546040516378cf861160e01b8152600481018390525f60248201529192506001600160a01b0316906378cf8611906044016020604051808303815f875af1158015612ab4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126ce9190613894565b60020b5f60ff82901d80830118620d89e8811115612b0157612b016345c3193d60e11b846135b9565b7001fffcb933bd6fad37aa2d162d1a5940016001821602600160801b186002821615612b3d576ffff97272373d413259a46990580e213a0260801c5b6004821615612b5c576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612b7b576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612b9a576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612bb9576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612bd8576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612bf7576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612c17576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612c37576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612c57576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612c77576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612c97576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612cb7576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612cd7576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612cf7576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612d18576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615612d38576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615612d57576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615612d74576b048a170391f7dc42444e8fa20260801c5b5f841315612d80575f19045b63ffffffff0160201c9392505050565b5f826001600160a01b0316846001600160a01b03161115612daf579192915b612dd1612dcc83600160601b8787036001600160a01b03166126d6565b6135c8565b949350505050565b5f826001600160a01b0316846001600160a01b03161115612df8579192915b5f612e1a856001600160a01b0316856001600160a01b0316600160601b6126d6565b90506120a8612dcc84838888036001600160a01b03166126d6565b5f546001600160a01b031633146112325760405163118cdaa760e01b8152336004820152602401610537565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f805f80612ebf865f87612041565b9250612ecd86600187612041565b9050612edf6401000276a36001613d7b565b6001600160a01b0316836001600160a01b03161015612f2b5760405162461bcd60e51b81526020600482015260086024820152676d696e507269636560c01b6044820152606401610537565b612f4a600173fffd8963efd1fc6a506488495d951d5263988d26613d9a565b6001600160a01b0316816001600160a01b03161115612f965760405162461bcd60e51b81526020600482015260086024820152676d6178507269636560c01b6044820152606401610537565b612fa9612fa2846132bf565b600a61354f565b9350612fb7612fa2826132bf565b915092959194509250565b60b581600160881b8110612fdb5760409190911b9060801c5b69010000000000000000008110612ff75760209190911b9060401c5b65010000000000811061300f5760109190911b9060201c5b630100000081106130255760089190911b9060101c5b62010000010260121c80820401600190811c80830401811c80830401811c80830401811c80830401811c80830401811c80830401901c908190048111900390565b5f825f19048411830215820261307a575f80fd5b5091020490565b6009546040516378cf861160e01b81525f6004820181905260026024830152916001600160a01b0316906378cf8611906044016020604051808303815f875af11580156130d0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130f49190613894565b90505f600454118015613108575060045481115b15613154575f6004548261311c9190613958565b600b54909150156131525761313c81670de0b6b3a7640000600b546126d6565b60075f82825461314c91906138bf565b90915550505b505b600455565b5f8060055490505f805f80600360149054906101000a900460ff1615613189575087925086915089905088613195565b50869250879150889050895b600b54156131f4576131b281670de0b6b3a7640000600b546126d6565b60075f8282546131c291906138bf565b925050819055506131de82670de0b6b3a7640000600b546126d6565b60065f8282546131ee91906138bf565b90915550505b84156132ac575f6132058642613958565b90505f8161321c8a87670de0b6b3a76400006126d6565b61322b8864e8d4a51000613b07565b61323591906138bf565b61323f9190613b07565b905080156132a957670de0b6b3a764000061329c6132668b86670de0b6b3a76400006126d6565b6132758764e8d4a51000613b07565b61327f91906138bf565b61328d906301e13380613b07565b670de0b6b3a7640000846126d6565b6132a69190613b1e565b97505b50505b5050426005555091979650505050505050565b5f73fffd8963efd1fc6a506488495d951d51639616826401000276a21983016001600160a01b031611156132fe576132fe6318521d4960e21b836135ee565b640100000000600160c01b03602083901b16805f61331b82613603565b60ff1690506080811061333657607f810383901c9150613340565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b1461354057886001600160a01b031661352582612ad8565b6001600160a01b0316111561353a5781613542565b80613542565b815b9998505050505050505050565b5f808360020b12801561356d5750613567828461399c565b60020b15155b156135a457818061357e8186613d31565b613589906001613d56565b6135939190613db9565b61359d9190613df1565b90506124db565b816135af8185613db9565b6120ac9190613df1565b815f528060020b60045260245ffd5b806001600160801b03811681146135e9576135e96393dafdf160e01b61368e565b919050565b815f526001600160a01b03811660045260245ffd5b5f80821161360f575f80fd5b507f0706060506020500060203020504000106050205030304010505030400000000601f6f8421084210842108cc6318c6db6d54be6001600160801b03841160071b84811c67ffffffffffffffff1060061b1784811c63ffffffff1060051b1784811c61ffff1060041b1784811c60ff1060031b1793841c1c161a1790565b805f5260045ffd5b5f602082840312156136a6575f80fd5b5035919050565b6001600160a01b0381168114612142575f80fd5b5f602082840312156136d1575f80fd5b81356120ac816136ad565b8060020b8114612142575f80fd5b5f805f80608085870312156136fd575f80fd5b84359350602085013561370f816136ad565b9250604085013561371f816136dc565b9150606085013561372f816136dc565b939692955090935050565b5f805f6060848603121561374c575f80fd5b8335613757816136ad565b92506020840135613767816136ad565b91506040840135613777816136ad565b809150509250925092565b5f805f60608486031215613794575f80fd5b83359250602084013591506040840135613777816136ad565b5f80604083850312156137be575f80fd5b50508035926020909101359150565b5f80604083850312156137de575f80fd5b82356137e9816136ad565b946020939093013593505050565b8015158114612142575f80fd5b5f805f60608486031215613816575f80fd5b8335613821816136ad565b92506020840135613831816137f7565b929592945050506040919091013590565b5f8060408385031215613853575f80fd5b823591506020830135613865816136ad565b809150509250929050565b6020808252600a90820152695245454e5452414e435960b01b604082015260600190565b5f602082840312156138a4575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156124db576124db6138ab565b5f805f606084860312156138e4575f80fd5b83516138ef816136ad565b6020850151909350613900816136dc565b60408501519092506001600160801b0381168114613777575f80fd5b6001600160a01b03968716815260208101959095526040850193909352600291820b6060850152900b608083015290911660a082015260c00190565b818103818111156124db576124db6138ab565b60208082526003908201526234303360e81b604082015260600190565b634e487b7160e01b5f52601260045260245ffd5b5f8260020b806139ae576139ae613988565b808360020b0791505092915050565b5f8160020b627fffff1981036139d5576139d56138ab565b5f0392915050565b5f602082840312156139ed575f80fd5b815160ff811681146120ac575f80fd5b60ff82811682821603908111156124db576124db6138ab565b6001815b6001841115613a5157808504811115613a3557613a356138ab565b6001841615613a4357908102905b60019390931c928002613a1a565b935093915050565b5f82613a67575060016124db565b81613a7357505f6124db565b8160018114613a895760028114613a9357613aaf565b60019150506124db565b60ff841115613aa457613aa46138ab565b50506001821b6124db565b5060208310610133831016604e8410600b8410161715613ad2575081810a6124db565b613ade5f198484613a16565b805f1904821115613af157613af16138ab565b029392505050565b5f6120ac60ff841683613a59565b80820281158282048414176124db576124db6138ab565b5f82613b2c57613b2c613988565b500490565b5f60018201613b4257613b426138ab565b5060010190565b5f60208284031215613b59575f80fd5b81516120ac816136ad565b5f60208284031215613b74575f80fd5b81516120ac816137f7565b8082025f8212600160ff1b84141615613b9a57613b9a6138ab565b81810583148215176124db576124db6138ab565b5f82613bbc57613bbc613988565b600160ff1b82145f1984141615613bd557613bd56138ab565b500590565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52603160045260245ffd5b8181035f831280158383131683831282161715613c2157613c216138ab565b5092915050565b5f600160ff1b8201613c3c57613c3c6138ab565b505f0390565b634e487b7160e01b5f52604160045260245ffd5b5f6101a08284031215613c67575f80fd5b82601f830112613c75575f80fd5b6040516101a0810181811067ffffffffffffffff82111715613c9957613c99613c42565b604052806101a0840185811115613cae575f80fd5b845b81811015613cc8578051835260209283019201613cb0565b509195945050505050565b5f8060408385031215613ce4575f80fd5b505080516020909101519092909150565b5f805f805f60a08688031215613d09575f80fd5b5050835160208501516040860151606087015160809097015192989197509594509092509050565b600282810b9082900b03627fffff198112627fffff821317156124db576124db6138ab565b600281810b9083900b01627fffff8113627fffff19821217156124db576124db6138ab565b6001600160a01b0381811683821601908111156124db576124db6138ab565b6001600160a01b0382811682821603908111156124db576124db6138ab565b5f8160020b8360020b80613dcf57613dcf613988565b627fffff1982145f1982141615613de857613de86138ab565b90059392505050565b5f8260020b8260020b028060020b9150808214613c2157613c216138ab56

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

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