ETH Price: $3,365.03 (+1.61%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

TokenTracker

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Mint241463272026-01-02 10:23:2313 days ago1767349403IN
0xf09C42C0...1d38E43F4
0 ETH0.000222140.16725576
0x60c06040241419412026-01-01 19:42:3513 days ago1767296555IN
 Contract Creation
0 ETH0.000501750.09604451
VIEW ADVANCED FILTER
Age:180D
Reset Filter

Advanced mode:
Parent Transaction Hash Method Block
From
To

There are no matching entries

Update your filters to view other transactions

View All Internal Transactions
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

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

Contract Name:
Basket

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 100 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

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

import {Aux} from  "./Aux.sol";
import {Court} from "./Court.sol";
import {Jury} from "./Jury.sol";
import {Vogue} from  "./Vogue.sol";
import {BasketLib} from "./BasketLib.sol";

import {OFT} from "./imports/OFT.sol";
import {Origin} from "./imports/oapp/OApp.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";
import {OFTMsgCodec} from "./imports/oapp/libs/OFTMsgCodec.sol";
import {SendParam} from "./imports/oapp/interfaces/IOFT.sol";
import {MessagingReceipt, MessagingFee} from "./imports/oapp/OAppSender.sol";
import {OFTComposeMsgCodec} from "./imports/oapp/libs/OFTComposeMsgCodec.sol";

import {SortedSetLib} from "./imports/SortedSet.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 {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

contract Basket is OFT,
    ERC6909, ReentrancyGuard {
    using OFTMsgCodec for bytes;
    using OFTMsgCodec for bytes32;
    using SortedSetLib for SortedSetLib.Set;

    Aux public AUX;
    uint private _deployed;
    address private _deployer;
    uint constant WAD = 1e18; // a dollar
    uint constant three = WAD * 3000000;
    uint constant tranche = three / 60;
    uint public seeded; // fundraise
    address payable public V4; // Uni
    address payable public court;
    address public jury;

    uint32 public constant SOL_MAINNET_EID = 30168;
    mapping(bytes32 => bool) public processedGuids;
    // ^ this is just for safety (replay protection)
    mapping(bytes32 => bool) public processedMessages;
    uint public latest_holder; address[] public holders;

    mapping(address => bool) private untouchables;
    mapping(uint => uint) public totalSupplies;
    mapping(address => uint) public juryLocked;
    mapping(address => uint) public holderIndex;

    address constant LZ = 0x1a44076050125825900e736c501f859c50fE728c;
    event SentToSolana(bytes32 indexed guid, uint8 msgType, uint length);

    modifier onlyUs {
        require(auth(msg.sender), "404"); _;
    }

    function auth(address who) public view returns (bool) {
        return who == address(AUX) || who == jury ||
                      who == court || who == V4;
    }

    constructor(address _rover, address _aux)
        OFT("QU!D", "QD", LZ, msg.sender)
        Ownable(msg.sender) {
        _deployer = msg.sender;
        _deployed = block.timestamp;
        AUX = Aux(payable(_aux));
        V4 = payable(_rover);
    } // ^ where it's at...

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

    function setup(address _court, address _jury)
        external onlyUs { court = payable(_court);
        jury = _jury;
    }

    function lockForJury(address juror,
        uint amount) external onlyUs {
        juryLocked[juror] += amount;
    }

    function unlockFromJury(address juror,
        uint amount) external onlyUs {
        juryLocked[juror] -= Math.min(
            juryLocked[juror], amount);
    }

    function _debit(uint _amountLD, uint _minAmountLD,
        uint32 _dstEid) internal override returns
        (uint amountSentLD, uint amountReceivedLD) {
        // keep OFT safety math (fees, min amount)
        (amountSentLD, amountReceivedLD) = _debitView(
                     _amountLD, _minAmountLD, _dstEid);
        // pull basket payload from send(...) calldata
        bytes memory payload = BasketLib.extract(msg.data);
        require(payload.length > 0, "LZ: no data");

        (uint[] memory ids,
         uint[] memory amounts) = abi.decode(
                    payload, (uint[], uint[]));

        require(ids.length == amounts.length
             && ids.length > 0, "LZ: bad data");

        for (uint i = 0; i < ids.length; ++i)
            _burn(msg.sender, ids[i], amounts[i]);
    }

    function _lzReceive(Origin calldata _origin,
        bytes32 _guid, bytes calldata _message,
        address, bytes calldata) internal override {
        require(_origin.srcEid == SOL_MAINNET_EID, "sol");
        require(_origin.sender == peers[_origin.srcEid], "403");
        require(!processedGuids[_guid], "409");
        processedGuids[_guid] = true;

        uint64 amountSD = _message.amountSD();
        uint amountReceivedLD = _toLD(amountSD);
        bytes memory composeMsg = _message.composeMsg();
        address to = _message.sendTo().bytes32ToAddress();
        uint8 msgType = MessageCodec.getMessageType(composeMsg);

        if (msgType == MessageCodec.RESOLUTION_REQUEST) {
            Court(court).receiveResolutionRequest(composeMsg);
            emit OFTReceived(_guid, _origin.srcEid, court, 0);
        } else if (msgType == MessageCodec.JURY_COMPENSATION) {
            _handleJuryCompensation(_guid, _origin.srcEid, composeMsg);
        } else {
            _handleBasketTransfer(_origin, _guid, _message,
                          composeMsg, to, amountReceivedLD);
        }
    }

    function _handleJuryCompensation(bytes32 _guid,
        uint32 srcEid, bytes memory composeMsg) internal {
        (uint64 marketId, uint64 amountSolana) =
            MessageCodec.decodeJuryCompensation(composeMsg);

        uint amount = MessageCodec.toEthereumAmount(amountSolana);
        _mint(jury, currentMonth() + 1, amount);

        // Pass raw message (Jury can extract depeg stats if present)
        Jury(jury).receiveJuryFunds(marketId, amount, composeMsg);
        emit OFTReceived(_guid, srcEid, jury, amount);
    }

    function _handleBasketTransfer(Origin calldata _origin,
        bytes32 _guid, bytes calldata _message,
        bytes memory composeMsg, address to,
        uint amountReceivedLD) internal {
        (uint[] memory ids,
         uint[] memory amounts) = abi.decode(
                composeMsg, (uint[], uint[]));

        require(ids.length == amounts.length
             && ids.length > 0, "LZ: data");

        for (uint i = 0; i < ids.length; ++i)
            _mint(to, ids[i], amounts[i]);

        if (_message.isComposed()) {
            bytes memory wrappedCompose = OFTComposeMsgCodec.encode(
            _origin.nonce, _origin.srcEid, amountReceivedLD, composeMsg);
            endpoint.sendCompose(to, _guid, 0, wrappedCompose);
        }
    }

    function sendToSolana(bytes memory composeMsg)
        external payable onlyUs returns (bytes32) {
        require(composeMsg.length > 0, "empty message");
        require(address(endpoint) != address(0), "endpoint");
        uint8 msgType = MessageCodec.getMessageType(composeMsg);
        require(msgType == MessageCodec.FINAL_RULING, "invalid");

        uint32 dstEid = SOL_MAINNET_EID;
        bytes memory options = BasketLib.buildOptions(msgType);
        MessagingFee memory fee = _quote(dstEid, composeMsg,
                                          options, false);

        require(msg.value >= fee.nativeFee, "Insufficient fee");
        MessagingReceipt memory receipt = _lzSend(dstEid,
             composeMsg, options, fee, msg.sender);

        processedMessages[receipt.guid] = true;
        emit SentToSolana(receipt.guid,
              msgType, block.timestamp);

        if (msg.value > fee.nativeFee)
            payable(msg.sender).transfer(
                msg.value - fee.nativeFee);

        return receipt.guid;
    }

    // extends standard ERC6909 mint
    function _mint(address receiver,
        uint id, uint amount)
        internal override {
        totalSupplies[id] += amount;
        perMonth[receiver].insert(id);
        super._update(address(0),
                receiver, amount);

        balanceOf[receiver][id] += amount;
        uint bal = super.balanceOf(receiver);
        if (holderIndex[receiver] == 0
            && bal > 400 * WAD && bal < 4 * tranche) {
            holderIndex[receiver] = holders.length + 1;
            holders.push(receiver);
            latest_holder = holders.length;
        }
        emit Transfer(msg.sender, address(0),
                        receiver, id, amount);
    }

    function mint(address pledge, uint amount, address token,
        uint when) external nonReentrant returns (uint) {
        uint nextMonth = currentMonth() + 1;
        if (msg.sender == V4) { _mint(pledge,
                    nextMonth, amount);
                        return amount;
        } uint deposited = AUX.deposit(
                 pledge, token, amount);

        uint decimals = IERC20(token).decimals();
        uint normalized = decimals < 18 ? deposited
              * (10 ** (18 - decimals)) : deposited;

        uint month = Math.max(when, nextMonth);
        if (month - nextMonth > 12) { // seed
            require(normalized / tranche >= 2
                 && normalized % tranche == 0, "tranche");
            seeded += normalized; require(seeded < three);
            _mint(_deployer, nextMonth - 1, normalized);

            // "we all wanna be big special, but
            // I'll start small with potential"
            normalized *= 3; // don't transfer it
            untouchables[pledge] = true; // (3,3)
        } else { uint yield = AUX.getAverageYield();
            normalized += FullMath.mulDiv(normalized * yield,
                            month - currentMonth(), WAD * 12);
        } _mint(pledge, month, normalized); return normalized;
    }

    function transferFrom(address from,
        address to, uint value) public
        override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transferHelper(from, to, value); return true;
    }

    function turn(address from, uint value)
        external returns (uint sent) {
        bool isUntouchable = !auth(msg.sender);
        if (isUntouchable)
            require(from == msg.sender
                && untouchables[from], "403");

        // allows slashed tokens to be redistributed to correct jurors
        address destination = (msg.sender == jury) ? jury : address(0);
        sent = _transferHelper(from,
                destination, value);

        if (isUntouchable)
            AUX.take(msg.sender, sent,
                address(this), true);
    }

    function _transferHelper(address from,
        address to, uint amount) internal returns (uint sent) {
        require(super.balanceOf(from) - juryLocked[from] >= amount);
        uint[] memory batches = perMonth[from].getSortedSet();


        bool toZero = to == address(0);
        bool burning = toZero || to == V4;

        int i = toZero ? BasketLib.matureBatches(
             batches, block.timestamp, _deployed):
                          int(batches.length - 1);

        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 (!burning) {
                    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; // can become -1
        } // -1 means "no mature batches"
        if (sent > 0) {
            super._update(from, to, sent);
            amount = super.balanceOf(from);
            if ((amount < 400 * WAD
             || amount > tranche * 4)
             && holderIndex[from] != 0) {
                uint index = holderIndex[from] - 1;
                uint lastIndex = holders.length - 1;
                if (index != lastIndex) {
                    address lastHolder = holders[lastIndex];
                    holderIndex[lastHolder] = index + 1;
                    holders[index] = lastHolder;
                } holderIndex[from] = 0; holders.pop();
                latest_holder = holders.length;
            }
            amount = super.balanceOf(to);
            if (to != address(0) && holderIndex[to] == 0 &&
                amount > 400 * WAD && amount < tranche * 4) {
                holderIndex[to] = holders.length + 1;
                holders.push(to);
                latest_holder = holders.length;
            }
        }
    }

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

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

import {Amp} from "./Amp.sol";
import {Vogue} from "./Vogue.sol";
import {Jury} from "./Jury.sol";
import {Rover} from "./Rover.sol";
import {Basket} from "./Basket.sol";
import {BasketLib} from "./BasketLib.sol";
import {Types} from "./imports/Types.sol";
import {VogueCore} from "./VogueCore.sol";

import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {DataTypes} from "aave-v3/protocol/libraries/types/DataTypes.sol";

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

contract Aux is
    Ownable, ReentrancyGuard {
    Metrics public metrics;
    bool public token1isWETH;
    address[] public stables;

    IERC20 USDC; Basket QUID;
    Vogue V4; VogueCore CORE;
    WETH9 public WETH; Rover V3;

    struct Metrics { uint total;
        uint last; uint yield;
        uint trackingStart;
        uint yieldAccum;
    }
    mapping(address => bool) public isVault;
    mapping(address => bool) public isStable;
    mapping(address => address) public vaults;
    mapping(address => uint) public untouchables;

    uint public untouchable;
    // ^ in vault shares, 1e18
    Amp public AMP; IPool AAVE;

    IERC4626 public wethVault;
    IUniswapV3Pool v3PoolWETH;

    uint constant WAD = 1e18;
    uint constant RAY = 1e27;

    ISwapRouter v3Router;
    Jury public jury;

    uint public lastBlock;

    error Unauthorized();
    error NotInitialized();
    error AlreadySet();
    error InvalidToken();
    error Manipulation();
    error Untouchable();
    error NoLiquidity();
    error NoDeposit();
    error LengthMismatch();
    error TransferFailed();

    modifier onlyUs {
        if (msg.sender != address(V4)
             && msg.sender != address(CORE)
             && msg.sender != address(QUID)) revert Unauthorized(); _;
    }
    modifier onlyVogue {
        if (msg.sender != address(V4)) revert Unauthorized(); _;
    }
    modifier onlyAmped {
        if (address(AMP) == address(0)) revert NotInitialized(); _;
    }

    /// @notice init (plug) Aux with addresses
    /// @dev optional: V3 rover & AAVE amp...
    /// @param _vogue UniV4 rover  address...
    /// @param _wethVault Morpho for WETH deposits
    /// @param _v3poolWETH V3 pool
    /// @param _v3router V3 router for swaps
    /// @param _v3 our wrapper around UniV3
    /// @param _amp AAVE yield-amplifier...
    constructor(address _vogue, address _core,
        address _wethVault, address _amp,
        address _aave, address _v3poolWETH,
        address _v3router, address _v3,
        address[] memory _stables,
        address[] memory _vaults)
        Ownable(msg.sender) {
        AAVE = IPool(_aave);
        lastBlock = block.number - 1;

        v3Router = ISwapRouter(_v3router);
        v3PoolWETH = IUniswapV3Pool(_v3poolWETH);
        address token0 = v3PoolWETH.token0();
        address token1 = v3PoolWETH.token1();
        wethVault = IERC4626(_wethVault);

        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);
        }
        V4 = Vogue(payable(_vogue));
        CORE = VogueCore(_core);
        if (_amp != address(0))
            AMP = Amp(payable(_amp));
        if (_v3 != address(0))
            V3 = Rover(payable(_v3));

        metrics.trackingStart = block.timestamp; metrics.last = 1;
        if (_stables.length != _vaults.length) revert LengthMismatch();
        uint len = _vaults.length;
        stables = _stables;
        for (uint i; i < len;) {
            address stable = _stables[i];
            address vault = _vaults[i];
            isVault[vault] = true; vaults[stable] = vault;
            isStable[stable] = true;
            unchecked { ++i; }
        }
    } fallback() external payable {}


    /// @notice Get TWAP price
    /// @param period Seconds to look back.
    // 0 = V3 TWAP (1800s), >0 = V4 TWAP
    /// @return price ETH price in USD 1e18
    function getTWAP(uint32 period)
        public view returns (uint price) {
        uint32[] memory secondsAgos = new uint32[](2);
        int56[] memory tickCumulatives; bool token0isUSD;
        if (period == 0) {
            // V3 TWAP - for leverage functions that compare against V3 spot
            secondsAgos[0] = 1800; secondsAgos[1] = 0;
            (tickCumulatives, ) = v3PoolWETH.observe(secondsAgos);
            period = 1800; token0isUSD = token1isWETH;
        } else {
            // V4 TWAP - for V4 operations (deposits, swaps, repack)
            secondsAgos[0] = period; secondsAgos[1] = 0;
            tickCumulatives = CORE.observe(secondsAgos);
            token0isUSD = V4.token1isETH();
        }
        int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
        int24 averageTick = int24(tickCumulativesDelta / int56(uint56(period)));
        uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(averageTick);
        price = BasketLib.getPrice(sqrtPriceX96, token0isUSD);
    }

    function setQuid(address _quid, address _jury,
        address _court) external onlyOwner {
        if (address(QUID) != address(0)) revert AlreadySet();
        renounceOwnership(); QUID = Basket(_quid);
        QUID.setup(_court, _jury); jury = Jury(_jury);

        USDC.approve(address(v3Router), type(uint).max);
        WETH.approve(address(wethVault), type(uint).max);
        WETH.approve(address(v3Router), type(uint).max);

        if (address(AMP) != address(0)) {
            WETH.approve(address(AMP), type(uint).max);
            USDC.approve(address(AMP), type(uint).max);
        }
        if (address(V3) != address(0)) {
            WETH.approve(address(V3), type(uint).max);
            USDC.approve(address(V3), type(uint).max);
        }
    } // In order to prevent sandwich attacks, implemented
    // simple form of ASS: process buys first, then sells!
    // Minimum trade size is a form of DoS/spam protection.
    // It's not possible to go through each swap one by one
    // and execute them in sequence, because it would cause
    // race conditons within the lock mechanism; therefore,
    // we clear the entire batch as 1 swap, looping only to
    // distribute the output pro rata (as a % of the total).

    /// @dev Routes small swaps directly, large swaps through
    /// @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 waitable Max blocks to wait for clearing
    /// @return blockNumber Block when trade will clear
    function swap(address token, bool forETH, uint amount,
        uint waitable) public payable returns (uint blockNumber) {
        (uint160 sqrtPriceX96,,,) = V4.repack(); bool sensitive;
        uint price = BasketLib.getPrice(
         sqrtPriceX96, V4.token1isETH());

        bool stable = isVault[token];
        if (stable)
            amount = _withdraw(address(this),
                            token, amount);

        else stable = isStable[token];
        // ^ if this is true user cares
        // about their output being all
        // in 1 specific token, so they
        // won't get multiple tokens...
        bool zeroForOne;
        if (!forETH) { // < trying to sell ETH for dollars
            if (token != address(QUID) && !stable) revert InvalidToken();
            zeroForOne = V4.token1isETH() ? false : true;
            amount = _depositETH(amount);
            wethVault.deposit(amount, address(V4));
            sensitive = FullMath.mulDiv(amount,
                             price, WAD) >= 5000 * WAD;
        } else {
            zeroForOne = V4.token1isETH() ? true : false;
            amount = deposit(msg.sender, token, amount);
            uint scale = IERC20(token).decimals() - 6;
            amount /= scale > 0 ? 10 ** scale : 1;
            sensitive = amount >= 5000 * 1e6;
        }
        if (sensitive) {
            // no sandwiches, slower...
            Types.Trade memory current;
            current.sender = msg.sender;
            current.token = token;
            current.amount = amount;
            blockNumber = V4.pushSwap(zeroForOne,
                                current, waitable);

        } else { blockNumber = block.number;
            // Executes instantly, no batching,
            // no sandwich protection...cheaper,
            // reliably scalable...suitable for:
            // small trades, routine flow, etc.
            CORE.swap(sqrtPriceX96, msg.sender,
                    zeroForOne, token, amount);
        }
    }

    /// @notice Public function to trigger batch clearing
    /// @dev Anyone can call to process pending swaps
    function clearSwaps() external nonReentrant {
        (uint160 sqrtPriceX96,,,) = V4.repack();
        uint v4Price = getTWAP(1800);  // V4 TWAP for V4 execution
        uint v3Price = getTWAP(0);     // V3 TWAP for V3/external execution
        _clearSwaps(sqrtPriceX96, v4Price, v3Price);
    }

    /// @notice Internal ASS clearing logic
    /// @dev Processes buys first, then sells
    /// @param sqrtPriceX96 Current pool price
    /// @param v4Price ETH price from V4 TWAP
    /// @param v3Price ETH price from V3 TWAP
    function _clearSwaps(uint160 sqrtPriceX96, uint v4Price, uint v3Price) internal {
      if (lastBlock + 1 >= block.number) return;
      uint endBlock = block.number - 1;
      if (endBlock > lastBlock + 10)
          endBlock = lastBlock + 10;

      for (uint blockToProcess = lastBlock + 1;
          blockToProcess <= endBlock; blockToProcess++) {
          (Types.Batch memory forUSD,
          Types.Batch memory forETH) = V4.getSwapsETH(blockToProcess);
          if (forUSD.total == 0 && forETH.total == 0) continue;
          uint pooled_usd = CORE.POOLED_USD() * 1e12;
          uint pooled_eth = CORE.POOLED_ETH();
          uint splitForUSD; uint splitForETH;
          uint gotForETH; uint gotForUSD;
          if (forUSD.total > 0) {
              pooled_usd = FullMath.mulDiv(forUSD.total, v4Price, WAD);
              // USD value of ETH we need to source externally...
              // shortfall in dollar terms that's covered by ETH
              if (pooled_usd > CORE.POOLED_USD() * 1e12) {
                  pooled_eth = pooled_usd - (CORE.POOLED_USD() * 1e12);
                  splitForUSD = FullMath.mulDiv(pooled_eth, WAD, v4Price);
                  pooled_usd = V4.takeETH(splitForUSD, address(this));
                  if (address(V3) != address(0)) {
                      gotForETH = V3.withdrawUSDC(pooled_eth / 1e12);
                      if (gotForETH > 0) {
                          pooled_eth -= gotForETH * 1e12;
                          V3.deposit(FullMath.mulDiv(WAD * 1e12, gotForETH, v3Price));
                          pooled_usd -= FullMath.mulDiv(WAD * 1e12, gotForETH, v3Price);
                      }
                  } if ((address(V3) == address(0) || pooled_eth > 0) && pooled_usd > 0) {
                      WETH.deposit{value: pooled_usd}();
                      gotForETH += _getUSDC(pooled_usd/*, v3Price*/);
                  }
                  if (gotForETH > 0) {
                      address vault = vaults[address(USDC)];
                      USDC.approve(vault, gotForETH);
                      pooled_eth = IERC4626(vault).deposit(
                                  gotForETH, address(this));
                  }
              }
          } pooled_eth = CORE.POOLED_ETH();
          if (forETH.total > 0) {
              pooled_usd = FullMath.mulDiv(forETH.total,
                                      WAD * 1e12, v4Price);

              if (pooled_usd > pooled_eth) {
                  pooled_usd -= pooled_eth;
                  splitForETH = _take(address(this),
                      forETH.total - FullMath.mulDiv(pooled_eth,
                      v4Price, WAD * 1e12), address(USDC), true);

                  if (address(V3) != address(0)) {
                      pooled_eth = V3.take(pooled_usd);
                      if (pooled_eth > 0) {
                          V3.depositUSDC(FullMath.mulDiv(pooled_eth,
                                       v3Price, WAD * 1e12), v3Price);
                          splitForETH -= FullMath.mulDiv(
                          pooled_eth, v3Price, WAD * 1e12);
                          gotForUSD += pooled_eth;
                          pooled_usd -= pooled_eth;
                      }
                  }
                  if (address(V3) == address(0) || splitForETH > 0) {
                      gotForUSD += _getWETH(splitForETH/*, v3Price*/);
                  }
                  wethVault.deposit(gotForUSD, address(V4));
              }
          } (pooled_usd,
             pooled_eth) = CORE.batchSwap(sqrtPriceX96, blockToProcess,
                                          splitForUSD, splitForETH);

          V4.distributeBatchSwaps(blockToProcess, v4Price, v3Price,
              pooled_usd, gotForUSD, pooled_eth, gotForETH);
      }
      lastBlock = endBlock;
      _rebalancePool();
  }

    function _rebalancePool() internal {
        uint pooledETH = CORE.POOLED_ETH();
        uint totalShares = V4.totalShares();
        if (pooledETH >= totalShares) return;
        uint shortfall = totalShares - pooledETH;
        if (shortfall * 100 < totalShares) return;
        // Proactive: deposit to vault to restore pool backing
        _arbETH(shortfall, true);
    }

    function arbETH(uint shortfall) external onlyVogue returns (uint) {
        // Reactive: send ETH directly to Vogue for user withdrawal
        return _arbETH(shortfall, false);
    }

    function _arbETH(uint shortfall, bool depositToVault)
        internal returns (uint got) { uint price = getTWAP(0);
        uint usdNeeded = FullMath.mulDiv(shortfall, price, WAD) / 1e12;
        uint took = _take(address(this), usdNeeded, address(USDC), false);
        if (took == 0) return 0;
        got = _getWETH(took/*, price*/);
        if (depositToVault) {
            wethVault.deposit(got, address(V4));
        }
        else {
            // Reactive withdrawal - send ETH to Vogue
            WETH.withdraw(got);
            (bool success,) = address(V4).call{value: got}("");
            if (!success) revert TransferFailed();
        }
    }

    /// @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 onlyAmped external {
        amount = _depositETH(amount);
        (uint160 sqrtPriceX96,,,,,, ) = v3PoolWETH.slot0();
        uint spotPrice = BasketLib.getPrice(
                  sqrtPriceX96, token1isWETH);

        uint twapPrice = getTWAP(0);
        uint deviation = spotPrice > twapPrice
                       ? spotPrice - twapPrice
                       : twapPrice - spotPrice;

        if (twapPrice * 3 < deviation * 100) revert Manipulation();
        uint totalValue = FullMath.mulDiv(amount, twapPrice, WAD);
        uint took = _take(address(this), totalValue / 1e12,
                          address(USDC), false);

        if (totalValue / 1e12 > took + 1) {
            uint needed = totalValue / 1e12 - took;
            uint selling = FullMath.mulDiv(needed,
                            WAD * 1e12, getTWAP(1800));
            uint gotETH = V4.takeETH(selling, address(this));
            if (gotETH > 0) { WETH.deposit{value: gotETH}();
                uint gotUSDC = _getUSDC(gotETH/*, twapPrice*/);
                took += gotUSDC; amount -= gotETH;
                needed = needed > gotUSDC ? needed - gotUSDC : 0;
            }
            if (needed > 0 && amount > 0) {
                selling = FullMath.mulDiv(needed, WAD * 1e12, twapPrice);
                selling = Math.min(selling, amount);
                if (selling > 0) {
                    took += _getUSDC(selling/*, twapPrice*/);
                    amount -= selling;
                }
            }
        }
        AMP.leverETH(msg.sender, amount, took);
    }

    function leverUSD(uint amount,
        address token) external onlyAmped
        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;
            bool zeroForOne = V4.token1isETH() ? true : false;
            CORE.swap(sqrtPriceX96, address(this),
                zeroForOne, token, depositedAmount);

            uint ethReceived = address(this).balance;
            WETH.deposit{value: ethReceived}();
            wethVault.deposit(ethReceived, address(V4));

            zeroForOne = V4.token1isETH() ? false : true;
            uint usdcBefore = USDC.balanceOf(address(this));
            CORE.swap(sqrtPriceX96, address(this),
            zeroForOne, address(USDC), ethReceived);
            usdcAmount = USDC.balanceOf(address(this)) - usdcBefore;
        }
        (sqrtPriceX96,,,,,, ) = v3PoolWETH.slot0();
        uint spotPrice = BasketLib.getPrice(
                 sqrtPriceX96, token1isWETH);

        uint twapPrice = getTWAP(0);
        uint deviation = spotPrice > twapPrice
                       ? spotPrice - twapPrice
                       : twapPrice - spotPrice;

        if (twapPrice * 3 < deviation * 100) revert Manipulation();
        uint targetETH = FullMath.mulDiv(WAD,
            usdcAmount * 1e12, getTWAP(1800));

        uint inETH = V4.takeETH(targetETH,
                            address(this));
        if (inETH > 0)
            WETH.deposit{value: inETH}();

        if (inETH < targetETH && usdcAmount > 0) {
            uint needed = targetETH - inETH;
            uint usdcToSpend = FullMath.mulDiv(needed,
                                twapPrice, WAD * 1e12);
            usdcToSpend = Math.min(usdcToSpend, usdcAmount);
            if (usdcToSpend > 0) {
                uint gotFromV3 = _getWETH(usdcToSpend/*, twapPrice*/);
                inETH += gotFromV3; usdcAmount -= usdcToSpend;
            }
        }
        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 burnable = total - (V4.PENDING_USD() * 1e12
                               + CORE.POOLED_USD() * 1e12);
        if (burnable <= amount) revert Untouchable();
        // it's all about waiting for your turn
        amount = QUID.turn(msg.sender, amount);
        if (amount > 0)
            amount -= _take(msg.sender, amount,
                        address(QUID), false);
    }

    function get_metrics(bool force)
        public returns (uint, uint) {
        Metrics memory stats = metrics;
        uint elapsed = block.timestamp - stats.last;

        if (force || elapsed > 10 minutes) {
            if (stats.last > 0 && stats.trackingStart > 0)
                stats.yieldAccum += stats.yield * elapsed;

            uint[10] memory amounts = get_deposits();
            stats.last = block.timestamp;
            stats.total = amounts[0];

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

            if (stats.total > 0)
                stats.yield = FullMath.mulDiv(WAD,
                    amounts[9], amounts[0]) - WAD;

            metrics = stats;
        }
        return (stats.total, stats.yield);
    }

    function getAverageYield()
        public view returns (uint) {
        Metrics memory stats = metrics;
        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);
    }

    function get_deposits() public view
        returns (uint[10] memory amounts) {
        uint len = stables.length;
        uint ghoIndex = len - 1;
        for (uint i; i < ghoIndex;) {
            uint multiplier = i < 2 ? 1e12 : 1;
            address stable = stables[i];
            address vault = vaults[stable];
            uint shares = IERC4626(vault).balanceOf(address(this));
            shares -= untouchables[vault]; // VC tranche...
            if (shares > 0) {
                uint assets = IERC4626(vault).convertToAssets(shares);
                shares = assets * multiplier;
                amounts[i + 1] = shares;
                amounts[0] += shares;
                if (IERC4626(vault).totalSupply() > 0) {
                    amounts[9] += FullMath.mulDiv(shares,
                        IERC4626(vault).totalAssets() * multiplier,
                        IERC4626(vault).totalSupply());
                }
            }
            unchecked { ++i; }
        } address gho = stables[ghoIndex];

        address vault = vaults[gho];
        if (IERC20(vault).balanceOf(address(this)) > 0) {
            DataTypes.ReserveDataLegacy memory res = AAVE.getReserveData(gho);
            uint128 currentLiquidityRate = res.currentLiquidityRate;
            uint shares = IERC20(vault).balanceOf(address(this));
            shares -= untouchables[vault];
            amounts[0] += shares;

            amounts[len] = shares;
            amounts[9] += FullMath.mulDiv(shares,
                    currentLiquidityRate, RAY);
        }
    }

    function getFee(address token, uint amount)
        public view returns (uint) {
        uint idx = type(uint).max;
        uint len = stables.length;
        for (uint i; i < len;) {
            if (stables[i] == token) { idx = i; break; }
            unchecked { ++i; }
        }
        if (idx == type(uint).max) return 0;
        return BasketLib.calcFeeSimple(token, idx,
            get_deposits(), stables, address(jury));
    }


    // you let me in to a conversation, conversation only we could make
    // breaking into my imagination: whatever's in there, yours to take
    function _take(address who, uint amount, address token,
        bool strict) internal returns (uint sent) {
        address vault; address vaultToSkip;
        // we already check elsewhere that token is a stable
        // and since it's an internal function we don't need
        // to run this check again
        if (token != address(QUID)) { vault = vaults[token];
            uint shares = IERC4626(vault).balanceOf(address(this));
            uint reserved = untouchables[vault];

            // Only exclude reserves if NOT strict
            uint availableShares = strict ? shares :
            (shares > reserved ? shares - reserved : 0);
            uint max = IERC4626(vault).convertToAssets(availableShares);
            if (max == 0) revert NoLiquidity();

            uint fee = (getFee(token, amount) * WAD) / 10000;
            uint amountNeeded = (fee > 0 && fee < WAD / 10) ?
                FullMath.mulDiv(amount, WAD + fee, WAD) : amount;

            if (max >= amountNeeded) {
                uint withdrawn = _withdraw(who, vault, amountNeeded);
                if (strict) { // If strict, decrement untouchables by shares withdrawn
                    uint sharesUsed = IERC4626(vault).convertToShares(withdrawn);
                    untouchables[vault] -= sharesUsed;
                    untouchable -= sharesUsed;
                } if (fee > 0) return FullMath.mulDiv(
                            withdrawn, WAD - fee, WAD);
                else return withdrawn;
            } else {
                uint withdrawn = _withdraw(who, vault, max);
                if (strict) { // If strict, decrement untouchables by shares withdrawn
                    uint sharesUsed = IERC4626(vault).convertToShares(withdrawn);
                    untouchables[vault] -= sharesUsed; untouchable -= sharesUsed;
                }
                if (fee > 0) sent = FullMath.mulDiv(
                          withdrawn, WAD - fee, WAD);
                else sent = withdrawn;
                amount -= withdrawn;
                vaultToSkip = vault;

                if (strict) return sent;
                sent = BasketLib.scaleTokenAmount(
                                sent, token, true);
                amount = BasketLib.scaleTokenAmount(
                                amount, token, true);
            }
        } uint[10] memory amounts = get_deposits();
        uint min = amounts[0]; amount = !strict ?
                 Math.min(min, amount) : amount;

        if (amounts[0] == 0 || amount == 0) return sent;
        uint len = stables.length;
        for (uint i = 1; i <= len;) {
            vault = vaults[stables[i - 1]];
            if (vault == vaultToSkip) { unchecked { ++i; } continue; }
            uint divisor = (i - 1) > 1 ? 1 : 1e12; min = amounts[i];
            amounts[i] = FullMath.mulDiv(amount, FullMath.mulDiv(WAD,
                                       amounts[i], amounts[0]), WAD);
            if (strict) {
                min = i == len ? amounts[i] :
                IERC4626(vault).convertToShares(amounts[i]);
                untouchables[vault] -= min; untouchable -= min;
            } else amounts[i] = Math.min(min, amounts[i]);

            amounts[i] /= divisor;
            if (amounts[i] > 0) {
                amounts[i] = _withdraw(who,
                          vault, amounts[i]);
                sent += amounts[i] * divisor;
            }
            unchecked { ++i; }
        }
    }
    // intentionally don't check that sent == original amount passed in
    function take(address who, uint amount, address token, bool strict)
        public onlyUs returns (uint) { return _take(
                         who, amount, token, strict);
    }

    function _withdraw(address to, // sent is 1e16 for USDC & USDT
        address vault, uint amount) internal returns (uint sent) {
        if (amount == 0) return 0;  // Early return for 0 amounts
        uint len = stables.length;
        if (vault == vaults[stables[len - 1]]) {
            sent = AAVE.withdraw(stables[len - 1], amount, to);
        } else {
            (uint shares,) = BasketLib.calculateVaultWithdrawal(
                                                vault, amount);
            if (shares == 0) return 0;  // Skip if no shares to redeem
            sent = IERC4626(vault).redeem(shares, to,
                                    address(this));
        }
    }

    // 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) {
        uint len = stables.length;
        address GHO = stables[len - 1];
        address SGHO = vaults[GHO]; address vault;
        if (isVault[token] && token != SGHO) {
            amount = Math.min(
                IERC4626(token).allowance(from, address(this)),
                 IERC4626(token).convertToShares(amount));
            usd = IERC4626(token).convertToAssets(amount);
                   IERC4626(token).transferFrom(msg.sender,
                                    address(this), amount);
        }
        else if (isStable[token]) {
            usd = Math.min(amount,
            IERC20(token).allowance(
                from, address(this)));
            IERC20(token).transferFrom(
                from, address(this), usd);
            if (token == GHO) {
                IERC20(token).approve(address(AAVE), usd);
                AAVE.supply(token, usd, address(this), 0);
                token = vaults[token]; amount = usd;
            } else { vault = vaults[token];
                IERC20(token).approve(vault, usd);
                amount = IERC4626(vault).deposit(usd,
                                    address(this));
                                     token = vault;
            }
        } else {
            revert InvalidToken();
        } if (usd == 0) revert NoDeposit();
        if (untouchable < QUID.seeded() * 3) {
            uint cut = amount / 400;
            untouchables[token] += cut; untouchable += cut;
            usd -= IERC4626(token).convertToAssets(cut);
        }
    }

    /// @param howMuch WETH amount to swap
    /// @return Amount of USDC received
    function _getUSDC(uint howMuch/*, uint price*/) internal returns (uint) {
        // uint max = FullMath.mulDiv(USDC.balanceOf(address(v3PoolWETH)) * 1e12, WAD, price);
        return v3Router.exactInput(ISwapRouter.ExactInputParams(
            abi.encodePacked(address(WETH), V3.POOL_FEE(), address(USDC)),
            address(this), block.timestamp, /* Math.min(howMuch, max),*/ howMuch, 0));
    }

    /// @param howMuch USDC amount to swap
    /// @return Amount of WETH received
    function _getWETH(uint howMuch/*, uint price*/) internal returns (uint) {
        // uint max = FullMath.mulDiv(WETH.balanceOf(address(v3PoolWETH)), price, WAD);
        return v3Router.exactInput(ISwapRouter.ExactInputParams(
            abi.encodePacked(address(USDC), V3.POOL_FEE(), address(WETH)),
            address(this), block.timestamp, /* Math.min(howMuch, max),*/ howMuch, 0));
    }

    /// @notice Deposit ETH/WETH from user
    /// @param amount WETH amount to transfer
    /// @return Total amount including msg.value
    function _depositETH(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;
    }
}

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

import {Jury} from "./Jury.sol";
import {Proof} from "./Proof.sol";
import {Basket} from "./Basket.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

contract Court is Ownable, ReentrancyGuard {
    mapping(address => bool) public authorizedBaskets;
    mapping(uint64 => address) public marketToBasket;

    Jury public immutable jury;
    Proof public immutable proof;

    uint constant APPEAL_WINDOW = 7 days;
    uint8 constant EXTENSION_MARKER = 101;

    uint constant MAX_APPEALS = 3;
    uint constant MAX_HUNG_JURIES = 3;
    uint constant MAX_TOTAL_ROUNDS = 10;

    uint constant COMMIT_PERIOD = 4 days;
    uint constant REVEAL_WINDOW = 12 hours;
    uint constant FINALIZE_BLOCK_WINDOW = 50;

    enum AppealGround {
        HUNG_JURY, INCORRECT_VERDICT,
        NEW_EVIDENCE, FABRICATION,
        BIAS, EXCLUSIONARY
    }

    struct Appeal {
        address appellant;
        AppealGround ground;
        uint[] affidavitIds;
        string reasoning;
        uint timestamp;
        bool sustained;
        uint cost;
    }

    struct Resolution {
        uint8 numSides;
        uint8 numWinners;
        bool requiresUnanimous;
        bool requiresAppSignature;
        bool isDepegMarket;
        bool allowsExtensions;
        uint8 currentRound;
        uint8 hungJuryCount;
        uint8 appealCount;
        uint appealCost;
        uint8[] verdict;
        bytes32 resolutionRequester;
        bytes32[] slashingAddresses;
        uint8[] slashingSides;
    }

    mapping(uint64 => bytes) public rulingData;
    mapping(uint64 => Resolution) public resolutions;
    mapping(uint64 => uint) public roundStartTime;
    mapping(uint64 => uint) public verdictTimestamp;
    mapping(uint64 => uint) public finalizeEligibleBlock;
    mapping(uint64 => mapping(uint8 => Appeal)) public appeals;

    event VerdictReached(uint64 indexed marketId, uint8 round, uint8[] verdict);
    event HungJury(uint64 indexed marketId, uint8 round, uint8 count);
    event JurySelectionFailed(uint64 indexed marketId, uint8 round);
    event JurySelectionComplete(uint64 indexed marketId, uint8 round);
    event AppealFiled(uint64 indexed marketId, uint8 round, address appellant,
             AppealGround ground, uint[] affidavitIds, uint8 appealCount);

    event MarketDataEvicted(uint64 indexed marketId, uint gasRefund);
    event AppealResult(uint64 indexed marketId, uint8 round, bool sustained);
    event ResolutionFinalized(uint64 indexed marketId, uint8[] verdict);
    event MarketExtended(uint64 indexed marketId);
    event ForceMajeure(uint64 indexed marketId);
    event BasketAdded(address indexed basket);
    event CourtSealed();
    event MissedWindowRecovery(uint64 indexed marketId, uint8 round);

    error ResolutionActive();
    error CompensationPending();
    error NoVerdictToAppeal();
    error AlreadyFinalized();
    error MaxRoundsExceeded();
    error MaxAppealsReached();
    error Unauthorized();
    error OutsideFinalizeWindow();
    error AppealWindowActive();
    error NoVerdictToExecute();
    error AlreadyExecuted();
    error ZeroAddress();
    error AlreadyAuthorized();
    error NoBasketForMarket();
    error WindowNotExpired();
    error WrongRound();
    error RoundNotStarted();
    error JuryNotSelected();
    error JuryAlreadySelected();
    error NoPendingFinalization();
    error HasVerdict();
    error NoVerdictStored();
    error AppealLate();
    error SpecifyAffidavits();
    error FinalizationIncomplete();
    error TooEarly();

    constructor(address _firstBasket,
        address _jury, address _proof) Ownable(msg.sender) {
        if (_firstBasket == address(0)) revert ZeroAddress();
        authorizedBaskets[_firstBasket] = true;
        emit BasketAdded(_firstBasket);
        jury = Jury(_jury);
        proof = Proof(_proof);
    }

    receive() external payable {}

    function addBasket(address _basket) external onlyOwner {
        if (_basket == address(0)) revert ZeroAddress();
        if (authorizedBaskets[_basket]) revert AlreadyAuthorized();
        authorizedBaskets[_basket] = true;
        emit BasketAdded(_basket);
    }

    function sealAndRenounce() external onlyOwner {
        emit CourtSealed();
        renounceOwnership();
    }

    function registerDepegMarket(uint64 marketId, address stablecoin) external onlyOwner {
        jury.registerDepegMarket(marketId, stablecoin);
    }

    function receiveResolutionRequest(bytes calldata lzMessage) external {
        if (!authorizedBaskets[msg.sender]) revert Unauthorized();

        MessageCodec.ResolutionRequestData memory req = MessageCodec.decodeResolutionRequest(lzMessage);
        Resolution storage res = resolutions[req.marketId];

        // If a previous resolution exists but compensation not yet distributed,
        // block new requests to prevent overwriting state needed for distribution
        if (verdictTimestamp[req.marketId] > 0 && !jury.isCompensationDistributed(req.marketId)) {
            revert CompensationPending();
        }

        // After compensation distributed, clearPostDistribution clears numSides
        // So this check handles both first resolution AND re-resolution
        if (res.numSides != 0) revert ResolutionActive();

        // Reset compensation state for new resolution cycle
        // This handles re-resolution case where distributed=true from previous cycle
        jury.resetCompensation(req.marketId);

        // Reset proof state for new resolution (clears submission counts)
        proof.resetForNewResolution(req.marketId);

        res.requiresAppSignature = req.requiresSignature;
        res.requiresUnanimous = req.requiresUnanimous;
        res.numWinners = req.numWinners;
        res.isDepegMarket = req.isDepegMarket;
        res.allowsExtensions = req.allowsExtensions;
        res.resolutionRequester = req.requester;
        res.appealCost = req.appealCost;
        res.numSides = req.numSides;

        marketToBasket[req.marketId] = msg.sender;
        proof.updateMerkleRoot(req.marketId, req.merkleRoot);
        roundStartTime[req.marketId] = block.timestamp;
    }

    function progressToJurySelection(uint64 marketId, uint8 round,
        bytes[] calldata headers) external nonReentrant {
        Resolution storage res = resolutions[marketId];
        if (res.currentRound != round) revert WrongRound();
        if (roundStartTime[marketId] == 0) revert RoundNotStarted();
        // Prevent re-selection if jury already selected for this round
        if (finalizeEligibleBlock[marketId] != 0) revert JuryAlreadySelected();

        bool success = jury.voirDire(marketId, round, headers);
        if (!success) { res.hungJuryCount++;
            if (res.hungJuryCount >= MAX_HUNG_JURIES) {
                res.verdict = new uint8[](0);
                _sendRuling(marketId);
                emit ForceMajeure(marketId);
                return;
            }
            emit JurySelectionFailed(marketId, round);
            return;
        }
        uint blocksUntilRevealEnd = (COMMIT_PERIOD + REVEAL_WINDOW) / 12;
        finalizeEligibleBlock[marketId] = block.number + blocksUntilRevealEnd;
        emit JurySelectionComplete(marketId, round);
    }

    function finalizeRound(uint64 marketId,
        bytes[] calldata headers) external nonReentrant {
        uint eligible = finalizeEligibleBlock[marketId];
        if (eligible == 0) revert JuryNotSelected();
        if (block.number < eligible || block.number > eligible + FINALIZE_BLOCK_WINDOW) {
            revert OutsideFinalizeWindow();
        }
        Resolution storage res = resolutions[marketId];
        if (verdictTimestamp[marketId] != 0 &&
            block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW)
            revert AlreadyFinalized();

        jury.finalizeRound(marketId, res.currentRound,
            roundStartTime[marketId], headers);

        (uint8[] memory verdict, bool unanimous, bool meetsThreshold) =
            jury.getStoredVerdict(marketId, res.currentRound);

        if (!meetsThreshold || (res.requiresUnanimous && !unanimous)) {
            _handleHungJury(marketId);
            return;
        }
        address appellant = jury.getAppellant(marketId, res.currentRound);
        if (appellant != address(0)) {
            bool sustained = !_verdictMatches(res.verdict, verdict);
            appeals[marketId][res.currentRound].sustained = sustained;
            if (sustained) {
                uint cost = appeals[marketId][res.currentRound].cost;
                jury.refundAppealCost(marketId, cost);
                Basket(marketToBasket[marketId]).transfer(appellant, cost);
            } else if (unanimous) {
                // Failed frivolous appeal with unanimous verdict - finalize immediately
                // Appellant penalty: appeal cost already burned (not refunded)
                _finalize(marketId);
                emit AppealResult(marketId, res.currentRound, false);
                return;
            }
            emit AppealResult(marketId, res.currentRound, sustained);
        }
        res.verdict = verdict;
        verdictTimestamp[marketId] = block.timestamp;

        if (verdict.length == 1) {
            if (verdict[0] == EXTENSION_MARKER) {
                _collectBadAffidavits(marketId);
                _sendRuling(marketId);
                emit MarketExtended(marketId);
                return;
            }
        }
        emit VerdictReached(marketId, res.currentRound, verdict);
    }

    function recoverMissedWindow(uint64 marketId) external nonReentrant {
        uint eligible = finalizeEligibleBlock[marketId];
        if (eligible == 0) revert NoPendingFinalization();
        if (block.number <= eligible + FINALIZE_BLOCK_WINDOW) revert WindowNotExpired();
        // Only block if we have a verdict AND no appeal round is active
        // (roundStartTime > verdictTimestamp means appeal filed, new round started)
        if (verdictTimestamp[marketId] != 0 &&
            roundStartTime[marketId] <= verdictTimestamp[marketId]) revert HasVerdict();

        emit MissedWindowRecovery(marketId, resolutions[marketId].currentRound);
        _handleHungJury(marketId);
    }

    error AppealRoundInProgress();

    function executeVerdict(uint64 marketId) external nonReentrant {
        Resolution storage res = resolutions[marketId];
        if (verdictTimestamp[marketId] == 0) revert NoVerdictToExecute();
        if (block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW) revert AppealWindowActive();
        // Prevent execution during active appeal round
        // If roundStartTime > verdictTimestamp, an appeal was filed and new round started
        if (roundStartTime[marketId] > verdictTimestamp[marketId]) revert AppealRoundInProgress();
        if (rulingData[marketId].length > 0) revert AlreadyExecuted();
        if (res.verdict.length == 0) revert NoVerdictStored();

        // Slashed appeal costs transferred when Jury distributes (via transferSlashedToJury)
        jury.tryDistribute(marketId);
        _finalize(marketId);
    }

    function fileAppeal(uint64 marketId, AppealGround ground,
        uint[] calldata affidavitIds, string calldata reasoning)
        external nonReentrant returns (uint8) {
        Resolution storage res = resolutions[marketId];
        if (verdictTimestamp[marketId] == 0) revert NoVerdictToAppeal();
        if (block.timestamp > verdictTimestamp[marketId] + APPEAL_WINDOW) revert AppealLate();
        // Cannot appeal after ruling already sent (force majeure/extension/frivolous appeal)
        if (rulingData[marketId].length > 0) revert AlreadyExecuted();
        // Prevent filing multiple appeals before jury selection
        // An appeal round is active if roundStartTime > verdictTimestamp
        if (roundStartTime[marketId] > verdictTimestamp[marketId]) revert AppealRoundInProgress();
        if (res.appealCount >= MAX_APPEALS) revert MaxAppealsReached();
        if (ground == AppealGround.FABRICATION || ground == AppealGround.BIAS ||
            ground == AppealGround.EXCLUSIONARY) {
            if (affidavitIds.length == 0) revert SpecifyAffidavits();
        }
        uint cost = _appealCost(marketId, res.appealCount);
        Basket(marketToBasket[marketId]).transferFrom(msg.sender, address(this), cost);
        jury.addSlashedEth(marketId, cost);

        res.appealCount++;
        res.currentRound++;
        if (res.currentRound >= MAX_TOTAL_ROUNDS) revert MaxRoundsExceeded();

        appeals[marketId][res.currentRound] = Appeal({
            appellant: msg.sender,
            ground: ground,
            affidavitIds: affidavitIds,
            reasoning: reasoning,
            timestamp: block.timestamp,
            cost: cost,
            sustained: false
        });
        delete finalizeEligibleBlock[marketId];
        roundStartTime[marketId] = block.timestamp;
        jury.setAppellant(marketId, res.currentRound, msg.sender);

        emit AppealFiled(marketId, res.currentRound,
        msg.sender, ground, affidavitIds, res.appealCount);
        return res.currentRound;
    }

    function _finalize(uint64 marketId) internal {
        _collectBadAffidavits(marketId);
        _sendRuling(marketId); _evictMarketData(marketId);
        emit ResolutionFinalized(marketId, resolutions[marketId].verdict);
    }

    function _evictMarketData(uint64 marketId) internal {
        Resolution storage res = resolutions[marketId];
        uint8 maxRound = res.currentRound;

        // Clear state not needed for jury compensation
        // Preserve: verdict, currentRound, verdictTimestamp, marketToBasket (needed for _tryDistribute)
        // numSides stays non-zero to indicate this market was resolved (blocks duplicate requests)
        res.numWinners = 0;
        res.requiresAppSignature = false;
        res.requiresUnanimous = false;
        res.hungJuryCount = 0;
        res.appealCost = 0;
        res.appealCount = 0;
        res.resolutionRequester = bytes32(0);
        res.isDepegMarket = false;
        res.allowsExtensions = false;

        delete res.slashingSides;
        delete res.slashingAddresses;

        for (uint8 i = 0; i <= maxRound; i++) {
            delete appeals[marketId][i].affidavitIds;
            delete appeals[marketId][i].reasoning;
        }

        delete roundStartTime[marketId];
        delete finalizeEligibleBlock[marketId];
        uint slotsCleared = 10 + maxRound * 5;
        emit MarketDataEvicted(marketId, slotsCleared * 15000);
    }

    function _collectBadAffidavits(uint64 marketId) internal {
        Resolution storage res = resolutions[marketId];
        (bool complete, uint cursor,
        uint total) = proof.getFinalizationStatus(marketId);
        if (total == 0) return;
        if (!complete && cursor == 0) {
            proof.finalizeEvaluations(marketId, res.currentRound);
            (complete,,) = proof.getFinalizationStatus(marketId);
        }
        if (!complete) revert FinalizationIncomplete();
        (bytes32[] memory badAddresses,
           uint8[] memory badSides) = proof.getBadAffidavitAddresses(marketId);

        uint remaining = res.slashingAddresses.length < 100
                 ? 100 - res.slashingAddresses.length : 0;

        uint toAdd = badAddresses.length < remaining ?
                     badAddresses.length : remaining;

        for (uint i = 0; i < toAdd; i++) {
            res.slashingAddresses.push(badAddresses[i]);
            res.slashingSides.push(badSides[i]);
        }   proof.resetFinalization(marketId);
    }

    function _handleHungJury(uint64 marketId) internal {
        Resolution storage res = resolutions[marketId]; res.hungJuryCount++;
        emit HungJury(marketId, res.currentRound, res.hungJuryCount);

        if (res.hungJuryCount < MAX_HUNG_JURIES
         && res.currentRound + 1 < MAX_TOTAL_ROUNDS) {
            // Copy appellant to next round if this was an appeal round
            address currentAppellant = jury.getAppellant(marketId, res.currentRound);
            uint currentAppealCost = appeals[marketId][res.currentRound].cost;
            res.currentRound++;
            if (currentAppellant != address(0)) {
                // Carry forward appellant tracking for hung jury retry
                jury.setAppellant(marketId, res.currentRound, currentAppellant);
                appeals[marketId][res.currentRound].appellant = currentAppellant;
                appeals[marketId][res.currentRound].cost = currentAppealCost;
            }
            delete finalizeEligibleBlock[marketId];
            roundStartTime[marketId] = block.timestamp;
        } else {
            if (res.isDepegMarket && !res.allowsExtensions) {
                // Force majeure: market cancelled, no winners
                // Resolution requester not explicitly slashed - force majeure
                // already punishes all participants via lost positions
                delete res.verdict;
                _collectBadAffidavits(marketId);
                _sendRuling(marketId);
                emit ForceMajeure(marketId);
            } else {
                res.verdict = new uint8[](1);
                res.verdict[0] = EXTENSION_MARKER;
                _collectBadAffidavits(marketId);
                _sendRuling(marketId);
                emit MarketExtended(marketId);
            }
        }
    }

    function _sendRuling(uint64 marketId) internal {
        // Set verdictTimestamp if not already set (for force majeure/extension paths)
        // This ensures _tryDistribute in Jury can proceed after appeal window
        if (verdictTimestamp[marketId] == 0) {
            verdictTimestamp[marketId] = block.timestamp;
        }

        Resolution storage res = resolutions[marketId];
        bytes memory message = MessageCodec.encodeFinalRuling(
            marketId, res.verdict,
            res.slashingAddresses, res.slashingSides);

        rulingData[marketId] = message;
        address originatingBasket = marketToBasket[marketId];
        if (originatingBasket == address(0)) revert NoBasketForMarket();
        Basket(originatingBasket).sendToSolana{value: 0.05 ether}(message);
    }

    function _verdictMatches(uint8[] memory a,
        uint8[] memory b) internal pure returns (bool) {
        if (a.length != b.length) return false;
        for (uint i = 0; i < a.length; i++) {
            if (a[i] != b[i]) return false;
        }
        return true;
    }

    function _appealCost(uint64 marketId,
        uint8 appealIndex) internal view returns (uint) {
        uint base = resolutions[marketId].appealCost;
        for (uint i = 0; i < appealIndex; i++) {
            base = (base * 15000) / 10000;
        }
        return base;
    }

    error RulingNotSent();

    /// @notice Timeout if jury compensation never arrives from Solana
    /// @param marketId The market ID
    function timeoutJuryCompensation(uint64 marketId) external nonReentrant {
        if (verdictTimestamp[marketId] == 0) revert NoVerdictToExecute();
        if (block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW) revert TooEarly();
        // Ruling must have been sent before we can timeout waiting for compensation
        if (rulingData[marketId].length == 0) revert RulingNotSent();
        // If ruling not sent, prevent timeout during active appeal round
        // (But if rulingData exists, resolution is final regardless of roundStartTime)
        if (jury.isCompensationReceived(marketId)) revert AlreadyExecuted();
        // Slashed appeal costs transferred when Jury distributes (via transferSlashedToJury)
        jury.markCompensationTimedOut(marketId);
        jury.tryDistribute(marketId);
    }

    function transferSlashedToJury(uint64 marketId) external {
        require(msg.sender == address(jury), "Only jury");
        uint slashed = jury.getSlashedAmount(marketId);
        if (slashed > 0) {
            Basket(marketToBasket[marketId]).transfer(address(jury), slashed);
            jury.clearSlashedAmount(marketId);
        }
    }

    /// @notice Called by Jury after compensation distributed to clear remaining preserved state
    function clearPostDistribution(uint64 marketId) external {
        require(msg.sender == address(jury), "Only jury");
        // Clear state that was preserved for distribution
        delete verdictTimestamp[marketId];
        delete marketToBasket[marketId];
        delete rulingData[marketId];
        // Clear state that might be stale from force majeure paths
        delete roundStartTime[marketId];
        delete finalizeEligibleBlock[marketId];
        Resolution storage res = resolutions[marketId];
        res.currentRound = 0;
        res.numSides = 0;  // Allow new resolution request
        res.hungJuryCount = 0;  // Reset for new resolution
        res.appealCount = 0;    // Reset for new resolution
        res.appealCost = 0;     // Reset for new resolution
        delete res.verdict;
        // Clear slashing arrays to prevent accumulation across re-resolutions
        delete res.slashingAddresses;
        delete res.slashingSides;
    }

    function isInResolutionPhase(uint64 marketId) external view returns (bool) {
        return roundStartTime[marketId] > 0 && verdictTimestamp[marketId] == 0;
    }

    function getRequiresAppSignature(uint64 marketId)
        external view returns (bool) {
        return resolutions[marketId].requiresAppSignature;
    }

    function getMarketConfig(uint64 marketId) external
        view returns (uint8 numSides, uint8 numWinners,
        bool requiresUnanimous, bool requiresSignature) {
        Resolution storage res = resolutions[marketId];
        return (res.numSides, res.numWinners,
            res.requiresUnanimous,
            res.requiresAppSignature);
    }

    function getRoundStartTime(uint64 marketId)
        external view returns (uint) {
        return roundStartTime[marketId];
    }

    function getCurrentRound(uint64 marketId)
        external view returns (uint8) {
        return resolutions[marketId].currentRound;
    }

    function getVerdictTimestamp(uint64 marketId)
        external view returns (uint) {
        return verdictTimestamp[marketId];
    }

    function getFinalVerdict(uint64 marketId)
        external view returns (uint8[] memory) {
        return resolutions[marketId].verdict;
    }

    function getAppeal(uint64 marketId, uint8 round) external view returns (
        address appellant, AppealGround ground, uint[] memory affidavitIds,
        string memory reasoning, uint timestamp, uint cost, bool sustained) {
        Appeal storage appeal = appeals[marketId][round];
        return (appeal.appellant, appeal.ground, appeal.affidavitIds,
                appeal.reasoning, appeal.timestamp, appeal.cost, appeal.sustained);
    }

    function getFinalizeWindow(uint64 marketId) external
        view returns (uint eligibleBlock, uint windowEnd) {
        eligibleBlock = finalizeEligibleBlock[marketId];
        windowEnd = eligibleBlock + FINALIZE_BLOCK_WINDOW;
    }

    function isReadyForExecution(uint64 marketId)
        external view returns (bool ready, string memory reason) {
        if (verdictTimestamp[marketId] == 0) return (false, "No verdict yet");
        if (block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW) return (false, "Appeal window active");
        if (rulingData[marketId].length > 0) return (false, "Already executed");
        if (resolutions[marketId].verdict.length == 0) return (false, "No verdict stored");
        (bool finalized,, uint total) = proof.getFinalizationStatus(marketId);
        if (total > 0 && !finalized) return (false, "Affidavit finalization not complete");
        return (true, "");
    }
}

File 4 of 113 : Jury.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Basket} from "./Basket.sol";
import {RandaoLib} from "./imports/RandaoLib.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

interface IProof {
    function getAffidavitCount(uint64 marketId) external view returns (uint);
    function evaluationCount(uint64 marketId, address juror) external view returns (uint);
    function getResolutionStartIndex(uint64 marketId) external view returns (uint);
    function getCurrentResolutionEvalCount(uint64 marketId, address juror) external view returns (uint);
}

interface ICourt {
    function getMarketConfig(uint64 marketId) external view returns (
        uint8 numSides, uint8 numWinners, bool requiresUnanimous, bool requiresSignature);
    function getRoundStartTime(uint64 marketId) external view returns (uint);
    function getCurrentRound(uint64 marketId) external view returns (uint8);
    function getVerdictTimestamp(uint64 marketId) external view returns (uint);
    function getFinalVerdict(uint64 marketId) external view returns (uint8[] memory);
    function owner() external view returns (address);
    function transferSlashedToJury(uint64 marketId) external;
    function clearPostDistribution(uint64 marketId) external;
}

contract Jury is Ownable, ReentrancyGuard {
    address public court;
    address public proof;
    address public immutable basket;

    uint constant REVEAL_SIZE = 12;
    uint constant FULL_JURY = 21;
    uint constant COMMIT_PERIOD = 4 days;
    uint constant REVEAL_WINDOW = 12 hours;
    uint constant APPEAL_WINDOW = 7 days;
    uint constant BASE_COMP_TIMEOUT = 7 days;

    struct Round {
        uint8 numSides;
        uint8 numWinners;
        bool requiresUnanimous;
        address appellant;
        address[] jurors;
        uint[] revealedIndices;
        bool finalized;
        uint8[] verdict;
        bool unanimous;
        bool meetsThreshold;
    }

    struct Compensation {
        uint baseFromSolana;
        uint slashedOnEth;
        bool baseReceived;
        bool distributed;
    }

    mapping(uint64 => mapping(uint8 => Round)) public rounds;
    mapping(uint64 => mapping(address => bool)) public hasServed;
    mapping(uint64 => mapping(address => uint)) public lockedStake;
    mapping(uint64 => mapping(uint8 => uint)) public affidavitSnapshot;
    mapping(uint64 => mapping(uint8 => mapping(address => bool))) public revealed;
    mapping(uint64 => mapping(uint8 => mapping(address => uint8[]))) public votes;
    mapping(uint64 => mapping(uint8 => mapping(address => bytes32))) public commits;
    mapping(uint64 => mapping(uint8 => mapping(address => address))) public delegates;

    // Moved from Court
    mapping(uint64 => Compensation) public compensation;
    mapping(uint64 => address) public marketToStablecoin;
    mapping(address => uint64) public stablecoinToMarket;
    mapping(address => MessageCodec.DepegStats) public depegStats;

    event JuryFulfilled(uint64 indexed marketId, uint8 round);
    event InsufficientStakers(uint64 indexed marketId, uint8 round, uint current, uint needed);
    event VoteCommitted(uint64 indexed marketId, uint8 round, address juror);
    event VoteRevealed(uint64 indexed marketId, uint8 round, address juror);
    event RoundFinalized(uint64 indexed marketId, uint8 round);
    event JurorSlashed(address juror, uint amount);
    event JurorCompensated(address juror, uint amount);
    event JuryCompensated(uint64 indexed marketId, uint total);
    event DepegStatsUpdated(address indexed stablecoin, uint64 marketId);

    error OnlyCourt();
    error OnlyBasket();
    error AlreadyCommitted();
    error AlreadyFulfilled();
    error AlreadyFinalized();
    error DoubleSpend();

    modifier onlyCourt() {
        if (court == address(0) || msg.sender != court) revert OnlyCourt();
        _;
    }

    modifier onlyBasket() {
        if (msg.sender != basket) revert OnlyBasket();
        _;
    }

    constructor(address _basket) Ownable(msg.sender) {
        basket = _basket;
    }

    function setup(address _court, address _proof) external onlyOwner {
        court = _court; proof = _proof; renounceOwnership();
    }

    function registerDepegMarket(uint64 marketId, address stablecoin) external onlyCourt {
        require(stablecoin != address(0) && marketToStablecoin[marketId] == address(0));
        marketToStablecoin[marketId] = stablecoin;
        stablecoinToMarket[stablecoin] = marketId;
    }

    function getDepegStats(address stablecoin) external
        view returns (MessageCodec.DepegStats memory) {
        return depegStats[stablecoin];
    }

    // TODO: comment out before mainnet deployment (testing only)
    // function setDepegStatsForTesting(address token,
    //     MessageCodec.DepegStats memory stats) external {
    //     depegStats[token] = stats;
    // }

    function receiveJuryFunds(uint64 marketId, uint amount,
        bytes calldata rawMessage) external nonReentrant onlyBasket {
        Compensation storage comp = compensation[marketId];

        // Get market state to check if there's an active resolution
        (uint8 numSides,,,) = ICourt(court).getMarketConfig(marketId);
        uint verdictTs = ICourt(court).getVerdictTimestamp(marketId);

        // Late compensation detection:
        // If no active resolution (numSides = 0) AND no pending verdict (verdictTimestamp = 0),
        // this is late compensation from a previous resolution → send to treasury
        if (numSides == 0 && verdictTs == 0) {
            Basket(basket).transfer(ICourt(court).owner(), amount);
            return;
        }

        // If already distributed for current resolution → also late, send to treasury
        if (comp.distributed) {
            Basket(basket).transfer(ICourt(court).owner(), amount);
            return;
        }
        if (comp.baseReceived) revert DoubleSpend();
        comp.baseFromSolana = amount;
        comp.baseReceived = true;

        address stablecoin = marketToStablecoin[marketId];
        if (stablecoin != address(0) && MessageCodec.hasDepegStats(rawMessage)) {
            MessageCodec.DepegStats memory stats = MessageCodec.parseDepegStats(rawMessage);
            stats.timestamp = uint40(block.timestamp);
            depegStats[stablecoin] = stats;
            emit DepegStatsUpdated(stablecoin, marketId);
        }
        _tryDistribute(marketId);
    }

    function addSlashedEth(uint64 marketId, uint amount) external onlyCourt {
        compensation[marketId].slashedOnEth += amount;
    }

    function refundAppealCost(uint64 marketId, uint amount) external onlyCourt {
        compensation[marketId].slashedOnEth -= amount;
    }

    function markCompensationTimedOut(uint64 marketId) external onlyCourt {
        Compensation storage comp = compensation[marketId];
        comp.baseFromSolana = 0; comp.baseReceived = true;
    }

    function tryDistribute(uint64 marketId) external nonReentrant {
        _tryDistribute(marketId);
    }

    function _tryDistribute(uint64 marketId) internal {
        Compensation storage comp = compensation[marketId];
        uint verdictTs = ICourt(court).getVerdictTimestamp(marketId);

        if (verdictTs == 0 || block.timestamp <= verdictTs + APPEAL_WINDOW ||
            !comp.baseReceived || comp.distributed) return;

        comp.distributed = true;

        // Capture slashed amount before transfer (transfer will clear tracking)
        uint ethSlashed = comp.slashedOnEth;

        // Pull slashed appeal costs from Court now that we're ready to distribute
        ICourt(court).transferSlashedToJury(marketId);

        uint8[] memory finalVerdict = ICourt(court).getFinalVerdict(marketId);
        uint8 finalRound = ICourt(court).getCurrentRound(marketId);

        uint distributed = _distributeCompensation(marketId, finalRound,
                    finalVerdict, comp.baseFromSolana, ethSlashed
        );
        emit JuryCompensated(marketId, distributed);

        // Clear preserved Court state now that distribution is complete
        ICourt(court).clearPostDistribution(marketId);

        // Don't delete compensation struct - keep distributed = true
        // so late-arriving compensation from Solana goes
        // to treasury (receiveJuryFunds checks this)...
        // Reset amounts to 0 to save gas on future reads
        comp.baseFromSolana = 0;
        comp.slashedOnEth = 0;
    }

    function _distributeCompensation(uint64 marketId, uint8 finalRound,
        uint8[] memory finalVerdict, uint baseComp, uint ethSlashed)
        internal returns (uint distributed) { uint correctCount = 0;
        address[] memory correctJurors = new address[]((finalRound + 1) * FULL_JURY);
        uint pool = baseComp + ethSlashed;
        for (uint8 r = 0; r <= finalRound; r++) {
            (uint slashed, uint correct) = _processRoundJurors(
                marketId, r, finalVerdict, correctJurors, correctCount);

            pool += slashed;
            correctCount = correct;
        }
        if (correctCount > 0) {
            uint perJuror = pool / correctCount;
            for (uint i = 0; i < correctCount; i++) {
                Basket(basket).transfer(correctJurors[i], perJuror);
                distributed += perJuror;
            }
        } else {
            // No correct jurors - send to treasury
            Basket(basket).transfer(owner(), pool);
            distributed = pool;
        } return distributed;
    }

    function isCompensationDistributed(uint64 marketId) external view returns (bool) {
        return compensation[marketId].distributed;
    }

    /// @notice Check if compensation has been received from Solana
    function isCompensationReceived(uint64 marketId) external view returns (bool) {
        return compensation[marketId].baseReceived;
    }

    /// @notice Get the slashed ETH amount Court needs to transfer before distribution
    function getSlashedAmount(uint64 marketId) external view returns (uint) {
        return compensation[marketId].slashedOnEth;
    }

    /// @notice Clear slashed amount after Court transfers tokens
    function clearSlashedAmount(uint64 marketId) external onlyCourt {
        compensation[marketId].slashedOnEth = 0;
    }

    /// @notice Reset compensation struct for new resolution
    /// @dev Called by Court when a new resolution starts (handles re-resolution case)
    function resetCompensation(uint64 marketId) external onlyCourt {
        delete compensation[marketId];
    }

    function voirDire(uint64 marketId, uint8 round,
        bytes[] calldata headers) external onlyCourt returns (bool) {
        Round storage r = rounds[marketId][round];

        // For round 0 with existing data (finalized or not), clear ALL old round data
        // This handles re-resolution after: normal completion, force majeure, missed windows
        // Ensures jurors from old appeal rounds can be selected for new resolution
        if (round == 0 && (r.finalized || r.jurors.length > 0)) {
            for (uint8 oldRound = 0; oldRound < 10; oldRound++) { // MAX_TOTAL_ROUNDS = 10
                Round storage oldR = rounds[marketId][oldRound];
                if (oldR.jurors.length == 0) continue;
                for (uint i = 0; i < oldR.jurors.length; i++) {
                    address juror = oldR.jurors[i];
                    uint stake = lockedStake[marketId][juror];
                    if (stake > 0) {
                        Basket(basket).unlockFromJury(juror, stake);
                        lockedStake[marketId][juror] = 0;
                    }
                    hasServed[marketId][juror] = false;
                    delete commits[marketId][oldRound][juror];
                    delete revealed[marketId][oldRound][juror];
                    delete votes[marketId][oldRound][juror];
                    delete delegates[marketId][oldRound][juror];
                }
                delete rounds[marketId][oldRound];
                delete affidavitSnapshot[marketId][oldRound];
            }
        } else if (r.finalized || r.jurors.length > 0) {
            // RETRY within same resolution: clear only this round's data
            for (uint i = 0; i < r.jurors.length; i++) {
                address juror = r.jurors[i];
                uint stake = lockedStake[marketId][juror];
                if (stake > 0) {
                    Basket(basket).unlockFromJury(juror, stake);
                    lockedStake[marketId][juror] = 0;
                }
                hasServed[marketId][juror] = false;
                delete commits[marketId][round][juror];
                delete revealed[marketId][round][juror];
                delete votes[marketId][round][juror];
                delete delegates[marketId][round][juror];
            }
            delete rounds[marketId][round];
            delete affidavitSnapshot[marketId][round];
        }
        // Re-fetch storage reference after potential delete
        r = rounds[marketId][round];
        if (r.numSides == 0) {
            (r.numSides, r.numWinners,
            r.requiresUnanimous,) = _getMarketConfig(marketId);
        }
        require(ICourt(court).getRoundStartTime(marketId) > 0, "No active resolution");
        require(round == ICourt(court).getCurrentRound(marketId), "Wrong round");
        require(headers.length <= 10);
        require(headers.length >= 3, "need 3 headers");
        bytes32 seed = RandaoLib.getHistoricalRandaoValue(block.number - 1, headers[0]);
        seed = keccak256(abi.encodePacked(seed,
            RandaoLib.getHistoricalRandaoValue(block.number - 2, headers[1]),
            RandaoLib.getHistoricalRandaoValue(block.number - 3, headers[2])));

        for (uint i = 3; i < headers.length; i++) {
            seed = keccak256(abi.encodePacked(seed,
                RandaoLib.getHistoricalRandaoValue(block.number - (i + 1), headers[i])));
        }
        uint poolSize = Basket(basket).latest_holder();
        if (poolSize < FULL_JURY) {
            emit InsufficientStakers(marketId,
                round, poolSize, FULL_JURY); return false;
        }
        uint selected = 0;
        uint maxAttempts = poolSize * 3;
        if (maxAttempts > 100) maxAttempts = 100;
        if (maxAttempts < FULL_JURY * 2) maxAttempts = FULL_JURY * 2;
        for (uint i = 0; i < maxAttempts && selected < FULL_JURY; i++) {
            seed = keccak256(abi.encodePacked(seed, i));
            uint idx = uint(seed) % Basket(basket).latest_holder();
            address candidate = Basket(basket).holders(idx);
            if (_selectAndLockJuror(marketId, candidate, r)) selected++;
        }
        if (selected != FULL_JURY) return false;
        // Snapshot counts only NEW affidavits from current resolution
        // Old affidavits (from previous resolutions) are already finalized
        uint totalAffidavits = IProof(proof).getAffidavitCount(marketId);
        uint startIndex = IProof(proof).getResolutionStartIndex(marketId);
        affidavitSnapshot[marketId][round] = totalAffidavits - startIndex;
        return true;
    }

    function commitVote(uint64 marketId, uint8 round,
        bytes32 commitment, address delegate) external {
        Round storage r = rounds[marketId][round];
        require(!r.finalized, "finalized");
        require(r.jurors.length > 0, "inactive");

        // Enforce commit deadline to prevent late commits after observing reveals
        uint roundStart = ICourt(court).getRoundStartTime(marketId);
        require(roundStart > 0, "round not started");
        require(block.timestamp <= roundStart + COMMIT_PERIOD, "commit period ended");

        bool found = false;
        for (uint i = 0; i < r.jurors.length; i++) {
            if (r.jurors[i] == msg.sender) { found = true; break; }
        }
        require(found, "not juror");

        if (commits[marketId][round][msg.sender] != bytes32(0)) revert AlreadyCommitted();
        if (delegate != address(0)) delegates[marketId][round][msg.sender] = delegate;

        commits[marketId][round][msg.sender] = commitment;
        emit VoteCommitted(marketId, round, msg.sender);
    }

    function revealVote(uint64 marketId, uint8 round,
        uint8[] calldata sides, bytes32 salt, address juror) external {
        Round storage r = rounds[marketId][round];
        require(!r.finalized, "finalized");
        uint snapshot = affidavitSnapshot[marketId][round];
        if (snapshot > 0) {
            // Check evaluations of NEW affidavits only (current resolution)
            require(IProof(proof).getCurrentResolutionEvalCount(marketId, juror) >= snapshot, "evaluate");
        }
        require(msg.sender == delegates[marketId][round][juror] || msg.sender == juror, "403");
        // Fetch roundStart from Court to prevent timing manipulation
        uint roundStart = ICourt(court).getRoundStartTime(marketId);

        require(roundStart > 0, "round not started");
        require(block.timestamp >= roundStart + COMMIT_PERIOD, "early");
        require(block.timestamp <= roundStart + COMMIT_PERIOD + REVEAL_WINDOW, "late");

        require(!revealed[marketId][round][juror], "revealed");
        require(commits[marketId][round][juror] == keccak256(abi.encode(sides, salt)), "invalid");

        revealed[marketId][round][juror] = true;
        votes[marketId][round][juror] = sides;
        emit VoteRevealed(marketId, round, juror);
    }

    function finalizeRound(uint64 marketId, uint8 round,
        uint roundStart, bytes[] calldata headers) external onlyCourt {
        Round storage r = rounds[marketId][round];
        if (r.finalized) revert AlreadyFinalized();
        // Defense-in-depth: verify roundStart matches Court's storage
        require(roundStart == ICourt(court).getRoundStartTime(marketId), "roundStart mismatch");
        if (r.revealedIndices.length == 0) {
            require(block.timestamp > roundStart + COMMIT_PERIOD, "commit active");
            uint commitCount = 0;
            for (uint i = 0; i < r.jurors.length; i++) {
                if (commits[marketId][round][r.jurors[i]] != bytes32(0)) commitCount++;
            }
            require(commitCount >= REVEAL_SIZE, "commits");
            require(headers.length >= 2, "headers");

            bytes32 seed = RandaoLib.getHistoricalRandaoValue(block.number - 1, headers[0]);
            seed = keccak256(abi.encodePacked(seed, RandaoLib.getHistoricalRandaoValue(
                                                        block.number - 2, headers[1])));
            for (uint i = 2; i < headers.length; i++) {
                seed = keccak256(abi.encodePacked(seed,
                    RandaoLib.getHistoricalRandaoValue(
                    block.number - (i + 1), headers[i])));
            }
            address[] memory committed = new address[](commitCount);
            uint idx = 0;
            for (uint i = 0; i < r.jurors.length; i++) {
                if (commits[marketId][round][r.jurors[i]] != bytes32(0)) {
                    committed[idx++] = r.jurors[i];
                }
            } bool[] memory selected = new bool[](commitCount);
            for (uint i = 0; i < REVEAL_SIZE; i++) {
                uint index;
                do {
                    seed = keccak256(abi.encodePacked(seed, i));
                    index = uint(seed) % commitCount;
                } while (selected[index]);
                selected[index] = true;
                for (uint j = 0; j < r.jurors.length; j++) {
                    if (r.jurors[j] == committed[index]) {
                        r.revealedIndices.push(j);
                        break;
                    }
                }
            }
        } uint revealCount = 0;
        for (uint i = 0; i < r.revealedIndices.length; i++) {
            address juror = r.jurors[r.revealedIndices[i]];
            if (revealed[marketId][round][juror]) revealCount++;
        }
        if (r.numWinners > 1) {
            (r.verdict,
             r.unanimous,
             r.meetsThreshold) = _getMultiWinner(marketId, round, r);
        } else {
            uint8[] memory voteCounts = new uint8[](r.numSides);
            for (uint i = 0; i < r.revealedIndices.length; i++) {
                address juror = r.jurors[r.revealedIndices[i]];
                if (revealed[marketId][round][juror]) {
                    uint8[] memory jurorVotes = votes[marketId][round][juror];
                    if (jurorVotes.length > 0 && jurorVotes[0] < r.numSides) {
                        voteCounts[jurorVotes[0]]++;
                    }
                }
            } uint8 maxVotes = 0;
            uint8 winningSide = 0;
            for (uint8 side = 0; side < r.numSides; side++) {
                if (voteCounts[side] > maxVotes) {
                    maxVotes = voteCounts[side];
                    winningSide = side;
                }
            }
            r.unanimous = (revealCount > 0 && maxVotes == revealCount);
            r.meetsThreshold = (revealCount > 0 && maxVotes * 3 >= revealCount * 2);
            r.verdict = new uint8[](1);
            r.verdict[0] = winningSide;
        }   r.finalized = true;

        emit RoundFinalized(marketId, round);
    }

    function getStoredVerdict(uint64 marketId, uint8 round)
        external view returns (uint8[] memory verdict,
        bool unanimous, bool meetsThreshold) {
        Round storage r = rounds[marketId][round];
        return (r.verdict, r.unanimous, r.meetsThreshold);
    }

    function _selectAndLockJuror(uint64 marketId,
        address candidate, Round storage r) internal returns (bool) {
        if (hasServed[marketId][candidate]) return false;
        uint balance = Basket(basket).balanceOf(candidate);
        uint stake = (balance * 2000) / 10000;
        r.jurors.push(candidate);

        hasServed[marketId][candidate] = true;
        lockedStake[marketId][candidate] = stake;
        Basket(basket).lockForJury(candidate, stake);
        return true;
    }

    function _getMultiWinner(uint64 marketId, uint8 round, Round storage r)
        internal view returns (uint8[] memory, bool, bool) {
        uint8[][] memory positionVotes = new uint8[][](r.numWinners);
        for (uint8 pos = 0; pos < r.numWinners; pos++) {
            positionVotes[pos] = new uint8[](r.numSides);
        }
        uint revealCount = 0;
        for (uint i = 0; i < r.revealedIndices.length; i++) {
            address juror = r.jurors[r.revealedIndices[i]];
            if (revealed[marketId][round][juror]) revealCount++;
        }
        for (uint i = 0; i < revealCount; i++) {
            address juror = r.jurors[r.revealedIndices[i]];
            if (revealed[marketId][round][juror]) {
                uint8[] memory ranking = votes[marketId][round][juror];
                for (uint8 pos = 0; pos < r.numWinners && pos < ranking.length; pos++) {
                    if (ranking[pos] < r.numSides) {
                        positionVotes[pos][ranking[pos]]++;
                    }
                }
            }
        } uint8[] memory winners = new uint8[](r.numWinners);
        uint8[] memory winnerVotes = new uint8[](r.numWinners);
        for (uint8 pos = 0; pos < r.numWinners; pos++) {
            uint8 maxVotes = 0;
            for (uint8 side = 0; side < r.numSides; side++) {
                if (positionVotes[pos][side] > maxVotes ||
                    (positionVotes[pos][side] == maxVotes &&
                    uint(keccak256(abi.encodePacked(
                        blockhash(block.number - 1),
                        pos, side))) % 2 == 0)) {
                            maxVotes = positionVotes[pos][side];
                            winners[pos] = side;
                }
            } winnerVotes[pos] = maxVotes;
        }
        if (revealCount == 0) {
            return (winners, false, false);  // Hung jury
        }
        // Check threshold (2/3 majority) for each position
        bool meetsThreshold = true;
        uint threshold = (revealCount * 2) / 3;
        for (uint8 pos = 0; pos < r.numWinners; pos++) {
            if (winnerVotes[pos] < threshold) {
                meetsThreshold = false;
                break;
            }
        }
        // Unanimous = all jurors voted exactly the same ranking
        bool unanimous = true;
        for (uint8 pos = 0; pos < r.numWinners; pos++) {
            if (winnerVotes[pos] != revealCount) {
                unanimous = false;
                break;
            }
        } return (winners, unanimous, meetsThreshold);
    }

    function getCorrectJurors(uint64 marketId,
        uint8 round) external view returns (address[] memory) {
        Round storage r = rounds[marketId][round];
        uint8[] memory finalVerdict = r.verdict;
        uint correct = 0;
        for (uint i = 0; i < r.revealedIndices.length; i++) {
            address juror = r.jurors[r.revealedIndices[i]];
            if (revealed[marketId][round][juror] &&
                _verdictMatches(votes[marketId][round][juror], finalVerdict)) {
                correct++;
            }
        } address[] memory correctJurors = new address[](correct);
        uint idx = 0;
        for (uint i = 0; i < r.revealedIndices.length; i++) {
            address juror = r.jurors[r.revealedIndices[i]];
            if (revealed[marketId][round][juror] &&
                _verdictMatches(votes[marketId][round][juror], finalVerdict)) {
                correctJurors[idx++] = juror;
            }
        } return correctJurors;
    }

    function isJuror(uint64 marketId, uint8 round,
        address addr) external view returns (bool) {
        Round storage r = rounds[marketId][round];
        for (uint i = 0; i < r.jurors.length; i++) {
            if (r.jurors[i] == addr) return true;
        }
        return false;
    }

    function _processRoundJurors(uint64 marketId,
        uint8 r, uint8[] memory finalVerdict,
        address[] memory correctJurors, uint correctCount)
        internal returns (uint slashed, uint newCorrectCount) {
        Round storage round = rounds[marketId][r];
        newCorrectCount = correctCount;

        for (uint i = 0; i < round.revealedIndices.length; i++) {
            uint jurorIndex = round.revealedIndices[i];
            address juror = round.jurors[jurorIndex];
            uint stake = lockedStake[marketId][juror];
            if (stake == 0) continue;
            if (!revealed[marketId][r][juror]) {
                Basket(basket).unlockFromJury(juror, stake);
                Basket(basket).turn(juror, stake);
                slashed += stake;
                emit JurorSlashed(juror, stake);
            } else if (_verdictMatches(votes[marketId][r][juror], finalVerdict)) {
                Basket(basket).unlockFromJury(juror, stake);
                correctJurors[newCorrectCount++] = juror;
            } else {
                // Minority voter: stake returned, no compensation
                // Incentive preserved: majority gets pool share, minority gets nothing
                Basket(basket).unlockFromJury(juror, stake);
            }
            lockedStake[marketId][juror] = 0;
        }
        for (uint i = 0; i < round.jurors.length; i++) {
            address juror = round.jurors[i];
            uint stake = lockedStake[marketId][juror];
            if (stake > 0) {
                Basket(basket).unlockFromJury(juror, stake);
                lockedStake[marketId][juror] = 0;
            }
        }
    }

    function _verdictMatches(uint8[] memory a,
        uint8[] memory b) internal pure returns (bool) {
        if (a.length != b.length) return false;
        for (uint i = 0; i < a.length; i++) {
            if (a[i] != b[i]) return false;
        }
        return true;
    }

    function setAppellant(uint64 marketId,
        uint8 round, address appellant) external onlyCourt {
        rounds[marketId][round].appellant = appellant;
    }

    function getAppellant(uint64 marketId,
        uint8 round) external view returns (address) {
        return rounds[marketId][round].appellant;
    }

    function getJurors(uint64 marketId,
        uint8 round) external view
        returns (address[] memory) {
        return rounds[marketId][round].jurors;
    }

    function _getMarketConfig(uint64 marketId) internal
        view returns (uint8 numSides, uint8 numWinners,
        bool requiresUnanimous, bool requiresSignature) {
        return ICourt(court).getMarketConfig(marketId);
    }
}

// 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 {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.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 {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 {AuxBase as Aux} from "./AuxBase.sol";
// import {AuxArb as Aux} from "./AuxArb.sol";

contract Vogue is // UniV4 rig
    Ownable, ReentrancyGuard {
    IERC4626 public wethVault;
    uint public PENDING_ETH;
    uint public PENDING_USD;
    uint constant WAD = 1e18;
    VogueCore V4; WETH9 WETH;
    bool public token1isETH;
    Basket QUID; Aux AUX;

    // range = between ticks
    int24 public UPPER_TICK;
    int24 public LOWER_TICK;
    uint public LAST_REPACK;
    // ^ timestamp allows us
    // to measure APY% for
    uint public USD_FEES;
    uint public ETH_FEES;
    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...
    uint public totalShares;

    mapping(uint => Types.Batch) public swapsZeroForOne;
    mapping(uint => Types.Batch) public swapsOneForZero;
    mapping(address => Types.Deposit) public autoManaged;

    // ^ price range is managed by our contracts
    mapping(address => uint[]) public positions;
    // ^ allows several selfManaged positions
    mapping(uint => Types.SelfManaged) public selfManaged;
    // ^ key is tokenId of ID++ for that position
    uint internal ID;
    // ^ always grows

    constructor(address _vault) Ownable(msg.sender) {
        wethVault = IERC4626(_vault);
    }   fallback() external payable {}

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

    function setup(address _quid, // < Basket...
        address _aux, address _core) external {
        require(address(AUX) == address(0), "!");

        V4 = VogueCore(_core);
        QUID = Basket(_quid);
        AUX = Aux(payable(_aux));
        renounceOwnership();

        token1isETH = V4.token1isETH();
        require(QUID.V4() == address(this), "?");
        WETH = WETH9(payable(address(AUX.WETH())));
        WETH.approve(address(AUX), type(uint).max);
        WETH.approve(address(wethVault), type(uint).max);
        (uint160 sqrtPriceX96,,) = V4.poolStats(0, 0);
        (LOWER_TICK,, UPPER_TICK,) = _updateTicks(sqrtPriceX96, 200);
    }

    // for queueing batched swaps...
    function pushSwap(bool zeroForOne,
        Types.Trade calldata trade,
        uint waitable) onlyAux public
        returns (uint currentBlock) {
        currentBlock = block.number;
        Types.Batch storage forOne;
        Types.Batch storage forZero;

        forOne = swapsZeroForOne[currentBlock];
        forZero = swapsOneForZero[currentBlock];
        while (forOne.swaps.length + forZero.swaps.length > 30) {
            currentBlock += 1;
            forOne = swapsZeroForOne[currentBlock];
            forZero = swapsOneForZero[currentBlock];
        }
        Types.Batch storage ourBatch;
        require(waitable >= currentBlock - block.number, "time");

        ourBatch = zeroForOne ? forOne : forZero;
        ourBatch.swaps.push(trade);
        ourBatch.total += trade.amount;
        if (token1isETH == zeroForOne) {
            // USD→ETH swap: USD deposited to vaults, waiting for ETH
            PENDING_USD += trade.amount;  // amount is in 1e6
        } else {
            // ETH→USD swap: ETH deposited to wethVault, waiting for USD
            PENDING_ETH += trade.amount;  // amount is in 1e18
        }
    }

    function getSwapsETH(uint blockNumber) public view
        returns (Types.Batch memory, Types.Batch memory) {
        if (token1isETH)
            return (swapsOneForZero[blockNumber],
                    swapsZeroForOne[blockNumber]);
        else
            return (swapsZeroForOne[blockNumber],
                    swapsOneForZero[blockNumber]);
    }

    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,
            "Distance 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(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);
            if (IERC20(token).decimals() > 6)
                amount /= 10 ** (IERC20(token).decimals() - 6);

            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({ owner: msg.sender,
                lower: newLowerTick, upper: newUpperTick, liq: int(uint(liquidity)) });

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

    function distributeBatchSwaps(uint lastBlock, uint v4Price, uint v3Price,
        uint v4ForUSD, uint v3ForUSD, uint v4ForETH, uint v3ForETH) external onlyAux {
        (Types.Batch memory forUSD,
         Types.Batch memory forETH) = getSwapsETH(lastBlock);

        if (forETH.total > 0)
            PENDING_USD = PENDING_USD > forETH.total ?
                        PENDING_USD - forETH.total : 0;

        if (forUSD.total > 0)
            PENDING_ETH = PENDING_ETH > forUSD.total ?
                        PENDING_ETH - forUSD.total : 0;

        uint swappedForUSD = v4ForUSD + v3ForUSD;  // Total ETH for forETH users
        uint swappedForETH = v4ForETH + v3ForETH;  // Total USD for forUSD users

        if (swappedForUSD > 0 && forETH.total > 0) {
            // Users paid forETH.total (USD 6 decimals), received ETH
            // Validate: USD value of ETH received ≈ USD paid
            // Value each portion at its source's TWAP
            uint usdValueReceived = FullMath.mulDiv(v4ForUSD, v4Price, WAD) +
                                    FullMath.mulDiv(v3ForUSD, v3Price, WAD);
            uint usdPaid = forETH.total * 1e12;  // Scale 6 decimals to 18
            require(stdMath.delta(usdValueReceived, usdPaid) <= usdPaid / 50, "slippage");

            for (uint i = 0; i < forETH.swaps.length; i++) {
                uint amount = FullMath.mulDiv(swappedForUSD,
                    forETH.swaps[i].amount, forETH.total);
                // Note: If recipient is a contract that can't receive ETH,
                // their funds stay in contract. This is by design to prevent
                // batch DoS attacks. Users should use EOA addresses.
                _sendETH(amount, forETH.swaps[i].sender);
            }
        }
        if (swappedForETH > 0 && forUSD.total > 0) {
            // Users paid forUSD.total (ETH 18 decimals), received USD
            // Validate: ETH value of USD received ≈ ETH paid
            // Value each portion at its source's TWAP
            uint ethValueReceived = FullMath.mulDiv(v4ForETH * 1e12, WAD, v4Price) +
                                    FullMath.mulDiv(v3ForETH * 1e12, WAD, v3Price);
            uint ethPaid = forUSD.total;
            require(stdMath.delta(ethValueReceived, ethPaid) <= ethPaid / 50, "slippage");

            for (uint i = 0; i < forUSD.swaps.length; i++) {
                address out = forUSD.swaps[i].token;
                uint amount = FullMath.mulDiv(swappedForETH,
                    forUSD.swaps[i].amount, forUSD.total);

                uint scale = IERC20(out).decimals() - 6;
                amount *= scale > 0 ? (10 ** scale) : 1;
                AUX.take(forUSD.swaps[i].sender, amount, out, false);
            }
        } delete swapsOneForZero[lastBlock];
          delete swapsZeroForOne[lastBlock];
    }

    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);

        ethReward = FullMath.mulDiv(LP.pooled_eth,
                      ETH_FEES, WAD) - LP.fees_eth;

        usdReward = FullMath.mulDiv(LP.pooled_eth,
                      USD_FEES, WAD) - LP.fees_usd;
    }

    // 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);

        // Compound ETH rewards
        if (fees_eth > 0) {
            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) {
                sent = V4.modLP(sqrtPriceX96, pulled, 0,
                    tickLower, tickUpper, msg.sender);
            }
            // Arbitrage shortfall from v3 if pool gave less than requested
            if (amount > sent) {
                uint shortfall = amount - sent;
                uint arbed = AUX.arbETH(shortfall);
                if (arbed > 0) _sendETH(Math.min(arbed, shortfall), msg.sender);
                // Only burn shares equal to actual delivered amount
                // Any unrecovered shortfall is socialized across all LPs
                uint delivered = Math.min(sent + arbed, amount);
                LP.pooled_eth -= delivered;
                totalShares -= delivered;
            } 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 {
        if (amount == 0 && msg.value == 0) return;
        amount = _deposit(amount);

        // Deposit to vault FIRST so addLiquidityHelper sees the balance
        wethVault.deposit(amount, address(this));

        Types.Deposit storage LP = autoManaged[msg.sender];
        uint pooled_usd = V4.POOLED_USD();
        uint deltaETH; uint deltaUSD;

        (uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper,) = _repack();
        uint eth_fees = ETH_FEES;
        uint usd_fees = USD_FEES;

        uint price = AUX.getTWAP(1800);
        if (LP.pooled_eth > 0) {
            (uint ethReward, uint usdReward) = pendingRewards(msg.sender);
            LP.pooled_eth += ethReward; LP.usd_owed += usdReward;
            totalShares += ethReward;
        }
        // Calculate how much can actually be paired
        (deltaUSD, deltaETH) = this.addLiquidityHelper(
                             pooled_usd, 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);
    }

    function addLiquidityHelper(uint deltaUSD,
        uint deltaETH, uint price) public
        onlyAux returns (uint, uint) {
        (uint total, ) = AUX.get_metrics(false);
        deltaUSD *= 1e12; // convert to 1e18...

        // Subtract USD in pool + pending swaps...
        uint pendingUSD_scaled = PENDING_USD * 1e12;
        uint committed = deltaUSD + pendingUSD_scaled;
        if (committed > total) return (0, 0);
        uint surplus = total - committed;

        // Check available ETH in vault after pending commitments
        uint availableETH = wethVault.maxWithdraw(address(this));
        if (PENDING_ETH >= availableETH) return (0, 0);
        availableETH -= PENDING_ETH;

        // Start with requested ETH amount
        uint targetUSD = FullMath.mulDiv(
                    deltaETH, price, WAD);

        // Can't exceed surplus USD
        if (targetUSD > surplus) {
            targetUSD = surplus;
            deltaETH = FullMath.mulDiv(
                    surplus, WAD, price);
        }
        // Can't exceed ETH (after pending)
        if (deltaETH > availableETH) {
            deltaETH = availableETH;
            targetUSD = FullMath.mulDiv(
                    deltaETH, price, WAD);
        }
        return (targetUSD / 1e12, deltaETH);
    }

     // pull liquidity from self-managed
    function pull(uint id, int percent,
        address token) external nonReentrant {
        Types.SelfManaged storage position = selfManaged[id];
        require(position.owner == msg.sender, "403");
        require(percent > 0 && percent < 101, "%");
        int liquidity = position.liq * percent / 100;
        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;
            yield = FullMath.mulDiv((usd_fees * 1e12 +
                FullMath.mulDiv(price, fees, WAD)) * 365 days,
                WAD, (deltaUSD * 1e12 + FullMath.mulDiv(
                    price, delta, WAD)) * elapsed) / WAD;
        }
        LAST_REPACK = block.timestamp;
    }

    function takeETH(uint howMuch, address recipient)
        external onlyAux returns (uint) {
        return _sendETH(howMuch, recipient);
    }

    function _takeWETH(uint howMuch) internal returns (uint withdrawn) {
        uint amount = Math.min(wethVault.balanceOf(address(this)),
                               wethVault.convertToShares(howMuch));
        withdrawn = wethVault.redeem(amount, address(this), address(this));
    }

    function _sendETH(uint howMuch,
        address toWhom) internal returns (uint sent) {
        // any unused gas from clearSwaps() lands back in
        // address(this) as residual ETH; re-appropriate:
        uint alreadyInETH = address(this).balance;
        if (alreadyInETH >= howMuch) {
            // Already have enough
            sent = howMuch;
        } else {
            uint needed = howMuch - alreadyInETH;
            uint withdrawn = _takeWETH(needed);
            WETH.withdraw(withdrawn);
            sent = withdrawn + alreadyInETH;
        }
        (bool success, ) = payable(toWhom).call{ value: sent }("");
        // Return 0 if transfer failed so callers can check.
        // For batch swaps, we don't revert to prevent DoS by
        // contract recipients - their ETH stays in contract.
        if (!success) sent = 0;
    }

    function _depositETH(uint amount)
        internal returns (uint) {
        amount = _deposit(amount);
        wethVault.deposit(amount, address(this));
        return amount; // return the total amount
    }

    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;
    }

    function _getPrice(uint160 sqrtPriceX96,
        bool token0isUSD) internal 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 _repack() internal returns (uint160 sqrtPriceX96,
        int24 tickLower, int24 tickUpper, uint128 myLiquidity) {
        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
            delta0 = _getPrice(sqrtPriceX96, token1isETH);
            delta1 = AUX.getTWAP(1800);
            price = delta0 > delta1
                ? delta0 - delta1
                : delta1 - delta0;

            if (price * 100 > delta1 * 3)
                // likely manipulation, skip repack
                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 onlyAux 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));
    }
}

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

import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";

interface IJury {
    function stablecoinToMarket(address) external view returns (uint64);
    function getDepegStats(address) external view returns (MessageCodec.DepegStats memory);
}

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);
}

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

    // Oracle type flags
    uint8 public constant ORACLE_CHAINLINK = 3;
    uint8 public constant ORACLE_CRV = 2;
    uint8 public constant ORACLE_DSR_RATE = 1;

    error StaleOracle();
    error BadPrice();

    /// @notice Calculate pro-rata withdrawal amounts for L2 basket
    /// @dev Split into helper to reduce stack depth
    function calcWithdrawAmounts(uint amount,
        uint[14] memory deposits, int indexToSkip,
        bool strict, uint[3] memory prices,
        uint8[3] memory priceIndices) external
        pure returns (uint[12] memory w) {
        if (deposits[1] == 0 || amount == 0) return w;
        for (uint i = 0; i < 12; i++) {
            if (int(i) == indexToSkip || deposits[i + 2] == 0) continue;
            w[i] = _calcOne(amount, deposits[1], deposits[i + 2],
                              i, strict, prices, priceIndices);
        }
    }

    /// @notice Pro-rata withdrawal for 5-token Unichain basket
    function calcWithdrawAmountsSmall(uint amount, uint[7] memory deposits,
        int indexToSkip, bool strict, uint stakedPrice
    ) external pure returns (uint[5] memory w) {
        if (deposits[1] == 0 || amount == 0) return w;
        for (uint i = 0; i < 5; i++) {
            if (int(i) == indexToSkip || deposits[i + 2] == 0) continue;
            uint dep = deposits[i + 2];
            uint val = (i == 3 && stakedPrice > 0)
                ? FullMath.mulDiv(dep, stakedPrice, WAD) : dep;
            uint share = FullMath.mulDiv(amount,
            FullMath.mulDiv(WAD, val, deposits[1]), WAD);
            if (i == 3 && stakedPrice > 0)
                share = FullMath.mulDiv(share, WAD, stakedPrice);
            if (!strict && share > dep) share = dep;
            if (i < 2) share /= 1e12;
            w[i] = share;
        }
    }

    /// @notice Pro-rata withdrawal for 6-token Polygon basket
    function calcWithdrawAmountsPoly(uint amount, uint[8] memory deposits,
        int indexToSkip, bool strict) external pure returns (uint[6] memory w) {
        if (deposits[1] == 0 || amount == 0) return w;
        for (uint i = 0; i < 6; i++) {
            if (int(i) == indexToSkip || deposits[i + 2] == 0) continue;
            uint dep = deposits[i + 2];
            uint share = FullMath.mulDiv(amount,
            FullMath.mulDiv(WAD, dep, deposits[1]), WAD);
            if (!strict && share > dep) share = dep;
            if (i < 2) share /= 1e12;
            w[i] = share;
        }
    }

    function _calcOne(uint amount, uint total, uint dep,
        uint i, bool strict, uint[3] memory prices,
        uint8[3] memory priceIndices) private pure
        returns (uint share) { uint price = 0;
        if (priceIndices[0] == i) price = prices[0];
        else if (priceIndices[1] == i) price = prices[1];
        else if (priceIndices[2] == i) price = prices[2];

        // Calculate value for share calc
        uint val = price > 0 ? FullMath.mulDiv(dep, price, WAD) : dep;

        // Pro-rata share
        share = FullMath.mulDiv(amount, FullMath.mulDiv(WAD, val, total), WAD);

        // Convert back to RAW if staked
        if (price > 0) share = FullMath.mulDiv(share, WAD, price);

        // Cap at available
        if (!strict && share > dep) share = dep;

        // Apply divisor for 6-decimal tokens
        if (i == 0 || i == 3) share /= 1e12;
    }

    /// @notice Get staked token price from oracle
    /// @param oracle Oracle address
    /// @param oracleType Type of oracle (use ORACLE_* constants)
    /// @return price Price in WAD (1e18)
    function getStakedPrice(address oracle, uint8 oracleType)
        external 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();
    }

    /// @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)
        external 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);
        }
    }

    // Pieralberto formula constants
    uint public constant BASE = 4;                 // Fixed base fee (bps)
    uint private constant ABS_MULT = 40;          // Linear absolute: risk × 0.4
    uint private constant REL_SPREAD_MAX = 120;   // Max relative spread at full intensity
    uint private constant INTENSITY_START = 1200; // 12% max_risk threshold
    uint private constant INTENSITY_RANGE = 3500; // Full intensity at 47%
    uint private constant RISK_WEIGHT = 7;        // 70% weight (÷10)
    uint private constant CONC_WEIGHT = 3;        // 30% weight (÷10)
    uint private constant INTERACTION_COEF = 15;  // 0.15 as integer (÷100)
    uint private constant INTERACTION_CLAMP = 2000; // ±20% as ±2000/10000
    uint private constant MAX_FEE = 420;

    // Anti-manipulation constants
    uint private constant MIN_MARKET_CAPITAL = 10_000e6;  // $10k minimum in prediction market
    uint private constant CONF_DAMPENING_THRESHOLD = 7000; // 70% confidence threshold
    uint private constant CONF_DAMPENING_FACTOR = 5000;    // Halve excess above threshold
    uint128 public constant GAS_FINAL_RULING = 200_000;
    /// @notice risk statistics for fee calculation
    struct BasketStats { uint minRisk; uint maxRisk;
                         uint avgRisk; uint nTokens; }

    /// @notice Staked/Base token pair mappings (exactly 4 pairs per chain)
    struct StakedPairs {
        uint8[4] base;    // Base token indices
        uint8[4] staked;  // Corresponding staked token indices
    }

    /// @notice Get base index for a token (returns idx if already base)
    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;
    }

    /// @notice Get combined deposits for a base token + its staked variant
    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;
    }

    /// @notice Check if token index is a staked variant
    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;
    }

    /// @dev Build basket stats - separated to reduce stack depth
    function _buildBasket(
        uint[14] memory deps,
        StakedPairs memory pairs,
        address[] memory stables,
        address jury
    ) private view returns (BasketStats memory basket) {
        uint[8] memory risks;
        uint validRisks;

        for (uint i = 0; i < stables.length; i++) {
            if (isStakedToken(i, pairs)) continue;
            if (getCombinedDeposits(i, deps, pairs) == 0) continue;
            basket.nTokens++;
            uint64 mktId = IJury(jury).stablecoinToMarket(stables[i]);
            if (mktId != 0) {
                MessageCodec.DepegStats memory s = IJury(jury).getDepegStats(stables[i]);
                if (s.timestamp > 0) {
                    risks[validRisks] = calcRisk(s);
                    validRisks++;
                }
            }
        }

        if (validRisks == 0) {
            basket.minRisk = type(uint).max; // Signal no valid risk data
            return basket;
        }

        basket.minRisk = type(uint).max;
        uint sum;
        for (uint j = 0; j < validRisks; j++) {
            if (risks[j] < basket.minRisk) basket.minRisk = risks[j];
            if (risks[j] > basket.maxRisk) basket.maxRisk = risks[j];
            sum += risks[j];
        }
        basket.avgRisk = sum / validRisks;
    }

    /// @notice Unified fee calculation using StakedPairs config
    function calcFeeWithPairs(uint idx, uint[14] memory deps,
        StakedPairs memory pairs, address[] memory stables,
        address jury) external view returns (uint) {
        uint base = getBaseIndex(idx, pairs);
        if (IJury(jury).stablecoinToMarket(stables[base]) == 0) return 0;

        MessageCodec.DepegStats memory stats = IJury(jury).getDepegStats(stables[base]);
        if (stats.timestamp == 0) return BASE;
        if (deps[1] == 0) return BASE;

        uint concentration = (getCombinedDeposits(base, deps, pairs) * 10000) / deps[1];

        BasketStats memory basket = _buildBasket(deps, pairs, stables, jury);
        if (basket.nTokens == 0 || basket.minRisk == type(uint).max) return BASE;

        return calcFee(stats, concentration, basket);
    }

    /// @notice Fee calc for 5-token Unichain basket (USDS/sUSDS staked pair)
    function calcFeeUni(uint idx, uint[7] memory deps, address[] memory stables,
        address jury) external view returns (uint) {
        uint base = (idx == 3) ? 2 : idx; // sUSDS maps to USDS
        if (IJury(jury).stablecoinToMarket(stables[base]) == 0) return 0;
        MessageCodec.DepegStats memory stats = IJury(jury).getDepegStats(stables[base]);
        if (stats.timestamp == 0 || deps[1] == 0) return BASE;
        uint combined = (idx >= 2 && idx <= 3) ? deps[4] + deps[5] : deps[idx + 2];
        uint conc = (combined * 10000) / deps[1];
        BasketStats memory basket = _buildUni(deps, stables, jury);
        if (basket.nTokens == 0 || basket.minRisk == type(uint).max) return BASE;
        return calcFee(stats, conc, basket);
    }

    function _buildUni(uint[7] memory deps, address[] memory stables,
        address jury) private view returns (BasketStats memory b) {
        uint[5] memory risks; uint valid;
        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 == 0) continue;
            b.nTokens++;
            uint64 mktId = IJury(jury).stablecoinToMarket(stables[i]);
            if (mktId != 0) {
                MessageCodec.DepegStats memory s = IJury(jury).getDepegStats(stables[i]);
                if (s.timestamp > 0) risks[valid++] = calcRisk(s);
            }
        }
        if (valid == 0) { b.minRisk = type(uint).max; return b; }
        b.minRisk = type(uint).max; uint sum;
        for (uint j = 0; j < valid; j++) {
            if (risks[j] < b.minRisk) b.minRisk = risks[j];
            if (risks[j] > b.maxRisk) b.maxRisk = risks[j];
            sum += risks[j];
        }
        b.avgRisk = sum / valid;
    }

    /// @notice Fee calc for 6-token Polygon basket (no staked pairs)
    function calcFeePoly(uint idx, uint[8] memory deps, address[] memory stables,
        address jury) external view returns (uint) {
        if (IJury(jury).stablecoinToMarket(stables[idx]) == 0) return 0;
        MessageCodec.DepegStats memory stats = IJury(jury).getDepegStats(stables[idx]);
        if (stats.timestamp == 0 || deps[1] == 0) return BASE;
        uint conc = (deps[idx + 2] * 10000) / deps[1];
        BasketStats memory basket = _buildPoly(deps, stables, jury);
        if (basket.nTokens == 0 || basket.minRisk == type(uint).max) return BASE;
        return calcFee(stats, conc, basket);
    }

    function _buildPoly(uint[8] memory deps, address[] memory stables,
        address jury) private view returns (BasketStats memory b) {
        uint[6] memory risks; uint valid;
        for (uint i = 0; i < 6; i++) {
            if (deps[i + 2] == 0) continue;
            b.nTokens++;
            uint64 mktId = IJury(jury).stablecoinToMarket(stables[i]);
            if (mktId != 0) {
                MessageCodec.DepegStats memory s = IJury(jury).getDepegStats(stables[i]);
                if (s.timestamp > 0) risks[valid++] = calcRisk(s);
            }
        }
        if (valid == 0) { b.minRisk = type(uint).max; return b; }
        b.minRisk = type(uint).max; uint sum;
        for (uint j = 0; j < valid; j++) {
            if (risks[j] < b.minRisk) b.minRisk = risks[j];
            if (risks[j] > b.maxRisk) b.maxRisk = risks[j];
            sum += risks[j];
        }
        b.avgRisk = sum / valid;
    }

    /// @notice Build LayerZero Type 3 options for cross-chain messages
    /// @param msgType Message type (FINAL_RULING or EXTEND_MARKET)
    /// @return options Encoded LZ options bytes
    function buildOptions(uint8 msgType) external
        pure returns (bytes memory) { uint128 gas;
        if (msgType == 6) gas = GAS_FINAL_RULING;
        else revert("Unknown message type");

        // LayerZero V2 Type 3 Options Format:
        // [type(uint16)][workerID(uint8)][optionLength(uint16)][optionType(uint8)][gas(uint128)][value(uint128)]
        // See: https://docs.layerzero.network/v2/developers/evm/configuration/options
        // OPTION_TYPE_LZRECEIVE contains (uint128 _gas, uint128 _value)

        uint128 value = 10_000_000; // 0.01 SOL in lamports
        // Total size: 2 + 1 + 2 + 1 + 16 + 16 = 38 bytes
        bytes memory options = new bytes(38);

        // Type 3 header (uint16 big-endian)
        options[0] = 0x00;
        options[1] = 0x03;

        // Worker ID: Executor (uint8)
        options[2] = 0x01;

        // Option length: 33 bytes = 1 (option type) + 16 (gas) + 16 (value)
        options[3] = 0x00;
        options[4] = 0x21; // 0x21 = 33

        // Option type: LZRECEIVE (uint8)
        options[5] = 0x01;

        // Gas (uint128 = 16 bytes, big-endian)
        for (uint i = 0; i < 16; i++) {
            options[6 + i] = bytes1(uint8(gas >> (120 - i * 8)));
        }
        // Value (uint128 = 16 bytes, big-endian)
        for (uint i = 0; i < 16; i++) {
            options[22 + i] = bytes1(uint8(value >> (120 - i * 8)));
        }
        return options;
    }

    /// @notice Extract composeMsg from send() calldata
    /// @dev Uses assembly to efficiently parse SendParam struct
    /// @return payload The extracted compose message bytes
    /* ---------------------------------------------------------------*
     * extracts SendParam.composeMsg from calldata...
     * assumes send(SendParam, MessagingFee, address)
     * [0] = dstEid(uint32), [1] = to(bytes32), [2] = amountLD(uint),
     * [3] = minAmountLD(uint), [4] = extraOptions(bytes),
     * [5] = composeMsg(bytes),    [6] = oftCmd(bytes)
     * ---------------------------------------------------------------*/
    function extract(bytes calldata original) external pure returns (bytes memory payload) {
        assembly { let base := original.offset
            let off0 := calldataload(add(base, 4))
            let structStart := add(add(base, 4), off0)
            let composeHeadPos := add(structStart, 0xA0)
            let composeOffset := calldataload(composeHeadPos)
            let composePos := add(structStart, composeOffset)
            let len := calldataload(composePos)

            let ptr := mload(0x40)
            mstore(ptr, len)
            calldatacopy(add(ptr, 0x20), add(composePos, 0x20), len)
            let size := add(0x20, and(add(len, 0x1f), not(0x1f)))
            mstore(0x40, add(ptr, size))
            payload := ptr
        }
    }

    /// @notice Dampen extreme confidence values to reduce manipulation potential
    /// @param conf Confidence value in basis points (0-10000)
    /// @return Dampened confidence value
    function _dampenConfidence(uint conf) internal pure returns (uint) {
        if (conf <= CONF_DAMPENING_THRESHOLD) return conf;
        uint excess = conf - CONF_DAMPENING_THRESHOLD;
        uint dampenedExcess = (excess * CONF_DAMPENING_FACTOR) / 10000;
        return CONF_DAMPENING_THRESHOLD + dampenedExcess;
    }

    /// @notice Calculate fee for depositing a specific stablecoin
    /// @param s Stats for this token from prediction market
    /// @param concentration Token's % of basket (basis points)
    /// @param basket Basket-wide stats
    /// @return Fee in basis points
    function calcFee(MessageCodec.DepegStats memory s, uint concentration,
        BasketStats memory basket) public view returns (uint) {
        // No data or completely stale (>7 days): return base fee
        if (s.timestamp == 0) return BASE;
        uint staleness = block.timestamp - s.timestamp;
        if (staleness > 7 days) return BASE;
        // Confirmed depeg: maximum fee
        if (s.depegged) return MAX_FEE;
        // Calculate risk score with
        // anti-manipulation protections
        uint risk = calcRisk(s);
        // Decay risk toward neutral (5000) after 1 day of staleness
        // This prevents attackers from blocking LayerZero messages to
        // keep favorable risk scores frozen
        if (staleness > 1 days) {
            // Linear decay over 6 days (from day 1 to day 7)
            uint decayProgress = ((staleness - 1 days) * 10000) / 6 days;
            if (decayProgress > 10000) decayProgress = 10000;
            // Interpolate toward neutral (5000)
            if (risk > 5000) {
                risk = risk - FullMath.mulDiv(risk - 5000,
                                    decayProgress, 10000);
            } else {
                risk = risk + FullMath.mulDiv(5000 - risk,
                                    decayProgress, 10000);
            }
        }
        // Calculate absolute premium based on risk
        uint absPremium = (risk * ABS_MULT) / 10000;
        // Check if there's meaningful risk differentiation in the basket
        uint riskRange = basket.maxRisk > basket.minRisk
                      ? basket.maxRisk - basket.minRisk : 1;
        // If risk range is too narrow, just return base + absolute premium
        if (riskRange <= 100) return BASE + absPremium;

        // Calculate position score
        // (combines risk and concentration)
        uint position = _calcPosition(risk,
            concentration, basket, riskRange);
        // Quadratic scaling for position
        uint posSquared = (position * position) / 10000;
        // Intensity scales with max risk in basket
        uint intensity = basket.maxRisk < INTENSITY_START ? 0 :
            (basket.maxRisk >= INTENSITY_START + INTENSITY_RANGE ? 10000
                    : ((basket.maxRisk - INTENSITY_START) * 10000) / INTENSITY_RANGE);
        uint totalSpread = 4 + (intensity * (REL_SPREAD_MAX - 4)) / 10000;
        uint fee = BASE + absPremium + (posSquared * totalSpread) / 10000;
        return fee > MAX_FEE ? MAX_FEE : fee;
    }

    /// @notice Calculate position score
    /// combining risk and concentration
    function _calcPosition(uint risk,
        uint concentration, BasketStats memory basket,
        uint riskRange) internal pure returns (uint) {
        uint riskScore = risk >= basket.minRisk ? (
                    (risk - basket.minRisk) * 10000) / riskRange : 0;
                            if (riskScore > 10000) riskScore = 10000;

        uint avgConc = 10000 / basket.nTokens;
        uint concScore = (concentration * 5000) / avgConc;
        if (concScore > 10000) concScore = 10000;

        uint combined = (RISK_WEIGHT *
            riskScore + CONC_WEIGHT * concScore) / 10;
        int riskDev = int(risk) - int(basket.avgRisk);
        int concDevScaled = int((concentration * 100) / avgConc) - 100;
        int interaction = (riskDev * concDevScaled *
            int(INTERACTION_COEF)) / int(riskRange * 100);

        if (interaction > int(INTERACTION_CLAMP)) interaction = int(INTERACTION_CLAMP);
        if (interaction < -int(INTERACTION_CLAMP)) interaction = -int(INTERACTION_CLAMP);

        int position = int(combined) + interaction;
        if (position < 0) position = 0;
        if (position > 10000) position = 10000;
        return uint(position);
    }

    /// @notice risk score from prediction market data
    /// @dev Includes anti-manipulation: minimum capital
    /// requirement and confidence dampening
    /// @param s Depeg stats from prediction market
    /// @return Risk score in basis points
    /// (0 = safe, 10000 = definitely depegging)
    function calcRisk(MessageCodec.DepegStats memory s)
        public pure returns (uint) {
        // Confirmed depeg: maximum risk
        if (s.depegged) return 10000;

        // Check minimum market capital to prevent cheap manipulation
        // An attacker would need to stake significant capital to move the risk score
        uint totalCapital = uint(s.capPeg) + uint(s.capDepeg);
        if (totalCapital < MIN_MARKET_CAPITAL) {
            return 6500; // Neutral when insufficient liquidity to trust signal
        }
        // Dampen extreme confidence values to reduce manipulation potential
        // Without this, an attacker could stake with 99% confidence to amplify
        // their position's effect on the conviction-weighted calculation
        uint confPeg = _dampenConfidence(uint(s.avgConfPeg));
        uint confDepeg = _dampenConfidence(uint(s.avgConfDepeg));

        // Conviction-weighted calculation: capital × dampened_confidence
        uint convPeg = uint(s.capPeg) * confPeg;
        uint convDepeg = uint(s.capDepeg) * confDepeg;
        uint totalConv = convPeg + convDepeg;

        // No conviction: neutral risk
        if (totalConv == 0) return 5000;

        // Risk = proportion of conviction betting on depeg
        return (convDepeg * 10000) / totalConv;
    }

    /// @notice basket-wide statistics for fee calc
    function calcBasketStats(uint[] memory risks)
        public pure returns (BasketStats memory b) {
        if (risks.length == 0) return b;

        b.nTokens = risks.length;
        b.minRisk = type(uint).max;
        b.maxRisk = 0;
        uint sum = 0;

        for (uint i = 0; i < risks.length; i++) {
            if (risks[i] < b.minRisk) b.minRisk = risks[i];
            if (risks[i] > b.maxRisk) b.maxRisk = risks[i];
            sum += risks[i];
        }
        b.avgRisk = sum / risks.length;
    }

    /// @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;
    }

    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;
    }

    /// @notice Fee calculation for L1 (no staked pairs)
    /// @param token The token to calculate fee for
    /// @param idx Token index in stables array
    /// @param deps Deposits array from get_deposits()
    /// @param stables Array of stablecoin addresses
    /// @param jury Jury contract address
    function calcFeeSimple(address token, uint idx,
        uint[10] memory deps, address[] memory stables,
        address jury) external view returns (uint) {
        if (IJury(jury).stablecoinToMarket(token) == 0) return BASE;

        MessageCodec.DepegStats memory stats = IJury(jury).getDepegStats(token);
        if (stats.timestamp == 0) return BASE;

        uint totalDeposits = deps[0];
        if (totalDeposits == 0) return BASE;

        uint concentration = (deps[idx + 1] * 10000) / totalDeposits;

        uint[] memory risks = new uint[](stables.length);
        uint validRisks = 0;
        uint stablesInBasket = 0;

        for (uint i = 0; i < stables.length; i++) {
            if (deps[i + 1] < 100 * WAD) continue;
            stablesInBasket++;
            uint64 mktId = IJury(jury).stablecoinToMarket(stables[i]);
            if (mktId != 0) {
                MessageCodec.DepegStats memory s = IJury(jury).getDepegStats(stables[i]);
                if (s.timestamp > 0) risks[validRisks++] = calcRisk(s);
            }
        }
        if (validRisks == 0) return BASE;

        uint[] memory validRisksArray = new uint[](validRisks);
        for (uint i = 0; i < validRisks; i++) validRisksArray[i] = risks[i];

        BasketStats memory basket = calcBasketStats(validRisksArray);
        basket.nTokens = stablesInBasket;
        return calcFee(stats, concentration, basket);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IOFT, OFTCore } from "./OFTCore.sol";

/**
 * @title OFT Contract
 * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.
 */
abstract contract OFT is OFTCore, ERC20 {
    /**
     * @dev Constructor for the OFT contract.
     * @param _name The name of the OFT.
     * @param _symbol The symbol of the OFT.
     * @param _lzEndpoint The LayerZero endpoint address.
     * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
     */
    constructor(
        string memory _name,
        string memory _symbol,
        address _lzEndpoint,
        address _delegate
    ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}

    /**
     * @dev Retrieves the address of the underlying ERC20 implementation.
     * @return The address of the OFT token.
     *
     * @dev In the case of OFT, address(this) and erc20 are the same contract.
     */
    function token() external view returns (address) {
        return address(this);
    }

    /**
     * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
     * @return requiresApproval Needs approval of the underlying token implementation.
     *
     * @dev In the case of OFT where the contract IS the token, approval is NOT required.
     */
    function approvalRequired() external pure virtual returns (bool) {
        return false;
    }

    /**
     * @dev Burns tokens from the sender's specified balance.
     * @param _amountLD The amount of tokens to send in local decimals.
     * @param _minAmountLD The minimum amount to send in local decimals.
     * @param _dstEid The destination chain ID.
     * @return amountSentLD The amount sent in local decimals.
     * @return amountReceivedLD The amount received in local decimals on the remote.
     */
    function _debit(
        uint256 _amountLD,
        uint256 _minAmountLD,
        uint32 _dstEid
    ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
        (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);

        // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,
        // therefore amountSentLD CAN differ from amountReceivedLD.

        // @dev Default OFT burns on src.
        _burn(msg.sender, amountSentLD);
    }

    /**
     * @dev Credits tokens to the specified address.
     * @param _to The address to credit the tokens to.
     * @param _amountLD The amount of tokens to credit in local decimals.
     * @dev _srcEid The source chain ID.
     * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.
     */
    function _credit(
        address _to,
        uint256 _amountLD,
        uint32 _srcEid
    ) internal virtual override returns (uint256 amountReceivedLD) {
        // @dev Default OFT mints on dst.
        _mint(_to, _amountLD);
        // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.
        return _amountLD;
    }
}

File 8 of 113 : OApp.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import { OAppSender, MessagingFee, MessagingReceipt } from "./OAppSender.sol";
// @dev Import the 'Origin' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import { OAppReceiver, Origin } from "./OAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";

/**
 * @title OApp
 * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
 */
abstract contract OApp is OAppSender, OAppReceiver {
    /**
     * @dev Constructor to initialize the OApp with the provided endpoint and owner.
     * @param _endpoint The address of the LOCAL LayerZero endpoint.
     * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
     */
    constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}

    /**
     * @notice Retrieves the OApp version information.
     * @return senderVersion The version of the OAppSender.sol implementation.
     * @return receiverVersion The version of the OAppReceiver.sol implementation.
     */
    function oAppVersion()
        public
        pure
        virtual
        override(OAppSender, OAppReceiver)
        returns (uint64 senderVersion, uint64 receiverVersion)
    {
        return (SENDER_VERSION, RECEIVER_VERSION);
    }
}

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

library MessageCodec { // why they plead the 5th?
    uint8 public constant RESOLUTION_REQUEST = 5;
    // 5 reprents arm leg leg arm head, the one
    // that we keep asking for resolutions...

    uint8 public constant FINAL_RULING = 6;
    // "A life is like a book. A book is like
    // a box. A box has six sides. Inside and
    // outside, so, how do you get to what's
    // inside? do you get what's inside, out?"

    uint8 public constant JURY_COMPENSATION = 7;
    // always call the hotline for the stop signs
    uint8 public constant EXTENSION_MARKER = 101;

    error InvalidMessageType();
    error InvalidMessageLength();
    error InvalidMarketId();
    error InvalidSideCount();
    error InvalidAmount();
    error ResolutionTimeInPast();
    error InvalidWinnerCount();
    error InvalidSplits();
    error TooManySlashingAddresses();

    /// @notice Depeg stats from Solana prediction market
    /// (optioally appended to JURY_COMPENSATION)
    struct DepegStats {
        uint64 avgConfPeg;      // Capital-weighted avg confidence for peg side
        uint64 avgConfDepeg;    // Capital-weighted avg confidence for depeg side
        uint64 capPeg;          // Total capital betting peg (from total_capital_per_side[0])
        uint64 capDepeg;        // Total capital betting depeg (from total_capital_per_side[1])
        uint40 timestamp;       // When cached (set by Court on ETH)
        bool depegged;          // Market resolved as depegged
    }

    /// @notice Resolution request data from Solana
    /// @dev Using struct to avoid stack too deep errors
    struct ResolutionRequestData { uint64 marketId;
        uint8 numSides; uint8 numWinners; bytes32 merkleRoot;
        bool requiresUnanimous; bool requiresSignature;
        bool isDepegMarket; bool allowsExtensions;
        uint256 appealCost; bytes32 requester;
    }

    function encodeUint64LE(uint64 value) internal pure returns (bytes memory) {
        bytes memory result = new bytes(8);
        result[0] = bytes1(uint8(value));
        result[1] = bytes1(uint8(value >> 8));
        result[2] = bytes1(uint8(value >> 16));
        result[3] = bytes1(uint8(value >> 24));
        result[4] = bytes1(uint8(value >> 32));
        result[5] = bytes1(uint8(value >> 40));
        result[6] = bytes1(uint8(value >> 48));
        result[7] = bytes1(uint8(value >> 56));
        return result;
    }

    function decodeUint64LE(bytes memory data, uint256 offset)
        internal pure returns (uint64 result) {
        require(data.length >= offset + 8, "Insufficient data for uint64");

        result = uint64(uint8(data[offset]))
            | (uint64(uint8(data[offset + 1])) << 8)
            | (uint64(uint8(data[offset + 2])) << 16)
            | (uint64(uint8(data[offset + 3])) << 24)
            | (uint64(uint8(data[offset + 4])) << 32)
            | (uint64(uint8(data[offset + 5])) << 40)
            | (uint64(uint8(data[offset + 6])) << 48)
            | (uint64(uint8(data[offset + 7])) << 56);
    }

    function decodeUint64LECalldata(bytes calldata data, uint256 offset)
        internal pure returns (uint64) {
        require(data.length >= offset + 8, "Insufficient data for uint64");

        return uint64(uint8(data[offset]))
            | (uint64(uint8(data[offset + 1])) << 8)
            | (uint64(uint8(data[offset + 2])) << 16)
            | (uint64(uint8(data[offset + 3])) << 24)
            | (uint64(uint8(data[offset + 4])) << 32)
            | (uint64(uint8(data[offset + 5])) << 40)
            | (uint64(uint8(data[offset + 6])) << 48)
            | (uint64(uint8(data[offset + 7])) << 56);
    }

    // ============================================================================
    // ENCODING FUNCTIONS - Ethereum → Better call Sol...ana
    // ============================================================================

    /**
     * @notice Encode final ruling message with multi-winner support
     * @dev Message format (variable length):
     *      [0] = FINAL_RULING
     *      [1-8] = marketId (LE)
     *      [9] = numWinners (0 = force majeure, 1 = extension if marker, 2+ = multi-winner)
     *      For each winner:
     *        [offset] = winningSide (1 byte)
     *      Then slashing addresses:
     *      [offset] = numSlashingAddresses
     *      For each slashing address:
     *        [offset..offset+31] = solana public key (32 bytes)
     *        [offset+32] = side (1 byte)
     *
     *      INTERPRETATION:
     *      - Empty winners (length=0) = Force majeure (market cancelled)
     *      - Single winner with EXTENSION_MARKER = Extension granted
     *      - Otherwise = Normal resolution, Solana calculates equal splits
     */
     function encodeFinalRuling(uint64 marketId, uint8[] memory winningSides,
         bytes32[] memory slashingAddresses,
         uint8[] memory slashingSides) internal pure returns (bytes memory) {
         if (marketId == 0) revert InvalidMarketId();
         require(slashingAddresses.length == slashingSides.length, "slash length mismatch");

         bytes memory message = abi.encodePacked(FINAL_RULING,
             encodeUint64LE(marketId), uint8(winningSides.length));

         for (uint i = 0; i < winningSides.length; i++) {
             message = abi.encodePacked(message, winningSides[i]);
         }
         message = abi.encodePacked(message,
            uint8(slashingAddresses.length));

         // 33 bytes per entry: address (32) + side (1)
         for (uint i = 0; i < slashingAddresses.length; i++) {
             message = abi.encodePacked(message,
                slashingAddresses[i], slashingSides[i]);
         }
         return message;
    }

   /**
    * @notice Decode resolution request from Solana
    * @dev Message format:
    *      [0] = RESOLUTION_REQUEST (5)
    *      [1-8] = marketId (uint64, little-endian)
    *      [9] = numSides (uint8)
    *      [10] = numWinners (uint8)
    *      [11-42] = merkleRoot (32 bytes)
    *      [43] = requiresUnanimous (0 or 1)
    *      [44] = requiresSignature (0 or 1)
    *      [45] = isDepegMarket (0 or 1)
    *      [46] = allowsExtensions (0 or 1)
    *      [47-54] = appealCost (uint64, little-endian)
    *      [55-86] = requester (32 bytes, Solana pubkey)
    *
    * @param message The encoded message bytes
    * @return data The decoded resolution request data struct
    */
    function decodeResolutionRequest(bytes calldata message) internal pure
        returns (ResolutionRequestData memory data) {
        uint256 offset = 1;
        data.marketId = decodeUint64LECalldata(message, offset);

        offset += 8;
        data.numSides = uint8(message[offset]);
        offset += 1;

        data.numWinners = uint8(message[offset]);
        offset += 1;

        data.merkleRoot = bytes32(message[offset:offset+32]);
        offset += 32;

        data.requiresUnanimous = uint8(message[offset]) == 1;
        offset += 1;

        data.requiresSignature = uint8(message[offset]) == 1;
        offset += 1;

        data.isDepegMarket = uint8(message[offset]) == 1;
        offset += 1;

        data.allowsExtensions = uint8(message[offset]) == 1;
        offset += 1;

        data.appealCost = uint256(decodeUint64LECalldata(message, offset));
        offset += 8;

        data.requester = bytes32(message[offset:offset+32]);
    }

    /**
     * @notice Decode jury compensation from Solana (with optional depeg stats tail)
     * @dev Base format (17 bytes):
     *      [0] = JURY_COMPENSATION (7)
     *      [1-8] = marketId (LE)
     *      [9-16] = amount (LE, in Solana decimals - 6)
     *
     *      Extended format for depeg markets (+33 bytes = 50 total):
     *      [17-24] = avgConfPeg (LE, u64)
     *      [25-32] = avgConfDepeg (LE, u64)
     *      [33-40] = capPeg (LE, u64)
     *      [41-48] = capDepeg (LE, u64)
     *      [49] = depegged (bool as u8)
     */
    function decodeJuryCompensation(bytes memory data)
        internal pure returns (uint64 marketId, uint64 amount) {
        if (data.length < 17) revert InvalidMessageLength();
        if (uint8(data[0]) != JURY_COMPENSATION) revert InvalidMessageType();

        marketId = decodeUint64LE(data, 1);
        amount = decodeUint64LE(data, 9);

        if (marketId == 0) revert InvalidMarketId();
        if (amount == 0) revert InvalidAmount();
        require(amount <= 1_000_000 * 1e6,
          "Compensation exceeds maximum");
    }

    /**
     * @notice Check if jury compensation message has depeg stats appended
     */
    function hasDepegStats(bytes memory data) internal pure returns (bool) {
        return data.length >= 50; // 17 base + 33 stats
    }

    /**
     * @notice Parse depeg stats from JURY_COMPENSATION message tail
     * @dev Only call after verifying hasDepegStats() returns true
     */
    function parseDepegStats(bytes memory data)
        internal pure returns (DepegStats memory s) {
        if (data.length < 50) return s; // No stats present

        s.avgConfPeg = decodeUint64LE(data, 17);
        s.avgConfDepeg = decodeUint64LE(data, 25);
        s.capPeg = decodeUint64LE(data, 33);
        s.capDepeg = decodeUint64LE(data, 41);
        s.depegged = uint8(data[49]) == 1;
        // timestamp set by Court when caching
    }

    function getMessageType(bytes memory data) internal pure returns (uint8) {
        if (data.length == 0) revert InvalidMessageLength();
        return uint8(data[0]);
    }

    function toEthereumAmount(uint64 solanaAmount) internal pure returns (uint256) {
        return uint256(solanaAmount) * 1e12;
    }

    function isForceMajeure(uint8[] memory verdict) internal pure returns (bool) {
        return verdict.length == 0;
    }

    function isExtension(uint8[] memory verdict) internal pure returns (bool) {
        return verdict.length == 1 && verdict[0] == EXTENSION_MARKER;
    } // this extension marker was chosen specifically because VoIP
    // office phone systems start extensions at 100, with the first
} // human extension being 101, and humans have to vote for this...

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

library OFTMsgCodec {
    // Offset constants for encoding and decoding OFT messages
    uint8 private constant SEND_TO_OFFSET = 32;
    uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;

    /**
     * @dev Encodes an OFT LayerZero message.
     * @param _sendTo The recipient address.
     * @param _amountShared The amount in shared decimals.
     * @param _composeMsg The composed message.
     * @return _msg The encoded message.
     * @return hasCompose A boolean indicating whether the message has a composed payload.
     */
    function encode(
        bytes32 _sendTo,
        uint64 _amountShared,
        bytes memory _composeMsg
    ) internal view returns (bytes memory _msg, bool hasCompose) {
        hasCompose = _composeMsg.length > 0;
        // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.
        _msg = hasCompose
            ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)
            : abi.encodePacked(_sendTo, _amountShared);
    }

    /**
     * @dev Checks if the OFT message is composed.
     * @param _msg The OFT message.
     * @return A boolean indicating whether the message is composed.
     */
    function isComposed(bytes calldata _msg) internal pure returns (bool) {
        return _msg.length > SEND_AMOUNT_SD_OFFSET;
    }

    /**
     * @dev Retrieves the recipient address from the OFT message.
     * @param _msg The OFT message.
     * @return The recipient address.
     */
    function sendTo(bytes calldata _msg) internal pure returns (bytes32) {
        return bytes32(_msg[:SEND_TO_OFFSET]);
    }

    /**
     * @dev Retrieves the amount in shared decimals from the OFT message.
     * @param _msg The OFT message.
     * @return The amount in shared decimals.
     */
    function amountSD(bytes calldata _msg) internal pure returns (uint64) {
        return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));
    }

    /**
     * @dev Retrieves the composed message from the OFT message.
     * @param _msg The OFT message.
     * @return The composed message.
     */
    function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
        return _msg[SEND_AMOUNT_SD_OFFSET:];
    }

    /**
     * @dev Converts an address to bytes32.
     * @param _addr The address to convert.
     * @return The bytes32 representation of the address.
     */
    function addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));
    }

    /**
     * @dev Converts bytes32 to an address.
     * @param _b The bytes32 value to convert.
     * @return The address representation of bytes32.
     */
    function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
        return address(uint160(uint256(_b)));
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { MessagingReceipt, MessagingFee } from "../OAppSender.sol";

/**
 * @dev Struct representing token parameters for the OFT send() operation.
 */
struct SendParam {
    uint32 dstEid; // Destination endpoint ID.
    bytes32 to; // Recipient address.
    uint256 amountLD; // Amount to send in local decimals.
    uint256 minAmountLD; // Minimum amount to send in local decimals.
    bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
    bytes composeMsg; // The composed message for the send() operation.
    bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
}

/**
 * @dev Struct representing OFT limit information.
 * @dev These amounts can change dynamically and are up the the specific oft implementation.
 */
struct OFTLimit {
    uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.
    uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.
}

/**
 * @dev Struct representing OFT receipt information.
 */
struct OFTReceipt {
    uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.
    // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.
    uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.
}

/**
 * @dev Struct representing OFT fee details.
 * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
 */
struct OFTFeeDetail {
    int256 feeAmountLD; // Amount of the fee in local decimals.
    string description; // Description of the fee.
}

/**
 * @title IOFT
 * @dev Interface for the OftChain (OFT) token.
 * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.
 * @dev This specific interface ID is '0x02e49c2c'.
 */
interface IOFT {
    // Custom error messages
    error InvalidLocalDecimals();
    error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);

    // Events
    event OFTSent(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 dstEid, // Destination Endpoint ID.
        address indexed fromAddress, // Address of the sender on the src chain.
        uint256 amountSentLD, // Amount of tokens sent in local decimals.
        uint256 amountReceivedLD // Amount of tokens received in local decimals.
    );
    event OFTReceived(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 srcEid, // Source Endpoint ID.
        address indexed toAddress, // Address of the recipient on the dst chain.
        uint256 amountReceivedLD // Amount of tokens received in local decimals.
    );

    /**
     * @notice Retrieves interfaceID and the version of the OFT.
     * @return interfaceId The interface ID.
     * @return version The version.
     *
     * @dev interfaceId: This specific interface ID is '0x02e49c2c'.
     * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.
     * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.
     * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)
     */
    function oftVersion() external view returns (bytes4 interfaceId, uint64 version);

    /**
     * @notice Retrieves the address of the token associated with the OFT.
     * @return token The address of the ERC20 token implementation.
     */
    function token() external view returns (address);

    /**
     * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
     * @return requiresApproval Needs approval of the underlying token implementation.
     *
     * @dev Allows things like wallet implementers to determine integration requirements,
     * without understanding the underlying token implementation.
     */
    function approvalRequired() external view returns (bool);

    /**
     * @notice Retrieves the shared decimals of the OFT.
     * @return sharedDecimals The shared decimals of the OFT.
     */
    function sharedDecimals() external view returns (uint8);

    /**
     * @notice Provides a quote for OFT-related operations.
     * @param _sendParam The parameters for the send operation.
     * @return limit The OFT limit information.
     * @return oftFeeDetails The details of OFT fees.
     * @return receipt The OFT receipt information.
     */
    function quoteOFT(
        SendParam calldata _sendParam
    ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);

    /**
     * @notice Provides a quote for the send() operation.
     * @param _sendParam The parameters for the send() operation.
     * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
     * @return fee The calculated LayerZero messaging fee from the send() operation.
     *
     * @dev MessagingFee: LayerZero msg fee
     *  - nativeFee: The native fee.
     *  - lzTokenFee: The lzToken fee.
     */
    function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);

    /**
     * @notice Executes the send() operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The fee information supplied by the caller.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess funds from fees etc. on the src.
     * @return receipt The LayerZero messaging receipt from the send() operation.
     * @return oftReceipt The OFT receipt information.
     *
     * @dev MessagingReceipt: LayerZero msg receipt
     *  - guid: The unique identifier for the sent message.
     *  - nonce: The nonce of the sent message.
     *  - fee: The LayerZero fee incurred for the message.
     */
    function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable returns (MessagingReceipt memory, OFTReceipt memory);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { MessagingParams, MessagingFee, MessagingReceipt } from "./interfaces/ILayerZeroEndpointV2.sol";
import { OAppCore } from "./OAppCore.sol";

/**
 * @title OAppSender
 * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.
 */
abstract contract OAppSender is OAppCore {
    using SafeERC20 for IERC20;

    // Custom error messages
    error NotEnoughNative(uint256 msgValue);
    error LzTokenUnavailable();

    // @dev The version of the OAppSender implementation.
    // @dev Version is bumped when changes are made to this contract.
    uint64 internal constant SENDER_VERSION = 1;

    /**
     * @notice Retrieves the OApp version information.
     * @return senderVersion The version of the OAppSender.sol contract.
     * @return receiverVersion The version of the OAppReceiver.sol contract.
     *
     * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.
     * ie. this is a SEND only OApp.
     * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions
     */
    function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
        return (SENDER_VERSION, 0);
    }

    /**
     * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.
     * @param _dstEid The destination endpoint ID.
     * @param _message The message payload.
     * @param _options Additional options for the message.
     * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.
     * @return fee The calculated MessagingFee for the message.
     *      - nativeFee: The native fee for the message.
     *      - lzTokenFee: The LZ token fee for the message.
     */
    function _quote(
        uint32 _dstEid,
        bytes memory _message,
        bytes memory _options,
        bool _payInLzToken
    ) internal view virtual returns (MessagingFee memory fee) {
        return
            endpoint.quote(
                MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
                address(this)
            );
    }

    /**
     * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.
     * @param _dstEid The destination endpoint ID.
     * @param _message The message payload.
     * @param _options Additional options for the message.
     * @param _fee The calculated LayerZero fee for the message.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess fee values sent to the endpoint.
     * @return receipt The receipt for the sent message.
     *      - guid: The unique identifier for the sent message.
     *      - nonce: The nonce of the sent message.
     *      - fee: The LayerZero fee incurred for the message.
     */
    function _lzSend(
        uint32 _dstEid,
        bytes memory _message,
        bytes memory _options,
        MessagingFee memory _fee,
        address _refundAddress
    ) internal virtual returns (MessagingReceipt memory receipt) {
        // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
        uint256 messageValue = _payNative(_fee.nativeFee);
        if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);

        return
            // solhint-disable-next-line check-send-result
            endpoint.send{ value: messageValue }(
                MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),
                _refundAddress
            );
    }

    /**
     * @dev Internal function to pay the native fee associated with the message.
     * @param _nativeFee The native fee to be paid.
     * @return nativeFee The amount of native currency paid.
     *
     * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,
     * this will need to be overridden because msg.value would contain multiple lzFees.
     * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.
     * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.
     * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.
     */
    function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
        if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);
        return _nativeFee;
    }

    /**
     * @dev Internal function to pay the LZ token fee associated with the message.
     * @param _lzTokenFee The LZ token fee to be paid.
     *
     * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.
     * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().
     */
    function _payLzToken(uint256 _lzTokenFee) internal virtual {
        // @dev Cannot cache the token because it is not immutable in the endpoint.
        address lzToken = endpoint.lzToken();
        if (lzToken == address(0)) revert LzTokenUnavailable();

        // Pay LZ token fee by sending tokens to the endpoint.
        IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

library OFTComposeMsgCodec {
    // Offset constants for decoding composed messages
    uint8 private constant NONCE_OFFSET = 8;
    uint8 private constant SRC_EID_OFFSET = 12;
    uint8 private constant AMOUNT_LD_OFFSET = 44;
    uint8 private constant COMPOSE_FROM_OFFSET = 76;

    /**
     * @dev Encodes a OFT composed message.
     * @param _nonce The nonce value.
     * @param _srcEid The source endpoint ID.
     * @param _amountLD The amount in local decimals.
     * @param _composeMsg The composed message.
     * @return _msg The encoded Composed message.
     */
    function encode(
        uint64 _nonce,
        uint32 _srcEid,
        uint256 _amountLD,
        bytes memory _composeMsg // 0x[composeFrom][composeMsg]
    ) internal pure returns (bytes memory _msg) {
        _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);
    }

    /**
     * @dev Retrieves the nonce from the composed message.
     * @param _msg The message.
     * @return The nonce value.
     */
    function nonce(bytes calldata _msg) internal pure returns (uint64) {
        return uint64(bytes8(_msg[:NONCE_OFFSET]));
    }

    /**
     * @dev Retrieves the source endpoint ID from the composed message.
     * @param _msg The message.
     * @return The source endpoint ID.
     */
    function srcEid(bytes calldata _msg) internal pure returns (uint32) {
        return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));
    }

    /**
     * @dev Retrieves the amount in local decimals from the composed message.
     * @param _msg The message.
     * @return The amount in local decimals.
     */
    function amountLD(bytes calldata _msg) internal pure returns (uint256) {
        return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));
    }

    /**
     * @dev Retrieves the composeFrom value from the composed message.
     * @param _msg The message.
     * @return The composeFrom value.
     */
    function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {
        return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);
    }

    /**
     * @dev Retrieves the composed message.
     * @param _msg The message.
     * @return The composed message.
     */
    function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
        return _msg[COMPOSE_FROM_OFFSET:];
    }

    /**
     * @dev Converts an address to bytes32.
     * @param _addr The address to convert.
     * @return The bytes32 representation of the address.
     */
    function addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));
    }

    /**
     * @dev Converts bytes32 to an address.
     * @param _b The bytes32 value to convert.
     * @return The address representation of bytes32.
     */
    function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
        return address(uint160(uint256(_b)));
    }
}

// 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.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);
}

// 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: 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: 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
// 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);
    }
}

File 21 of 113 : 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
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 {IERC20} from "forge-std/interfaces/IERC20.sol";

import {IPool} from "aave-v3/interfaces/IPool.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 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;

    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());

        token1isWETH = v3Pool.token0() == address(USDC);
        USDC.approve(address(V3), type(uint).max);
        USDC.approve(address(AAVE), type(uint).max);
        USDC.approve(address(v3Router), type(uint).max);
        weth.approve(address(v3Router), type(uint).max);
        weth.approve(address(AAVE), type(uint).max);
    }

    /// @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 {
        uint160 sqrtPriceX96 = V3.repackNFT();
        uint price = V3.getPrice(sqrtPriceX96);

         if (fromV4 > 0) { // msg.sender is Aux
            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;
            }
        }
        this.putUSDC(fromV4 + took); this.put(amount);
        totalBorrowed[address(weth)] += borrowing;
        // ^ there will always be enough to pay this
        // back because we are depositting ETH first
        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

        this.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 {
        uint160 sqrtPriceX96 = V3.repackNFT();
        uint price = V3.getPrice(sqrtPriceX96);
        if (fromV4 > 0)
            weth.transferFrom(msg.sender,
                    address(this), fromV4);

        USDC.transferFrom(msg.sender,
            address(this), amount);

        uint deposited = amount;
        this.putUSDC(amount);

        uint inWETH = FullMath.mulDiv(WAD,
                        amount * 1e12, price);
        // ^ convert USDC to 18 decimals first

        uint neededFromV3 = 0;
        if (inWETH > fromV4)
            neededFromV3 = V3.take(inWETH - fromV4);
        // borrow WETH from V3, use in AAVE
        // as collateral to borrow dollars
        this.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
        AAVE.borrow(address(USDC), amount, 2, 0,
        address(this)); this.putUSDC(amount);

        totalBorrowed[address(USDC)] += amount;
        Types.viaAAVE memory order = Types.viaAAVE({
            breakeven: deposited * 1e12, // supplied gets reset
            // 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);
        withdrawn = AAVE.withdraw(address(USDC),
                          amount, address(this));
    }

    function put(uint amount) public onlyUs {
        AAVE.supply(address(weth), amount, address(this), 0);
        AAVE.setUserUseReserveAsCollateral(address(weth), true);
    }
    function putUSDC(uint amount) public onlyUs {
        AAVE.supply(address(USDC), amount, address(this), 0);
        AAVE.setUserUseReserveAsCollateral(address(USDC), true);
    }
    fallback() external payable {}
    function get(uint howMuch) onlyUs
        public returns (uint withdrawn) {
        uint amount = Math.min(wethSharesSnapshot, howMuch);
        withdrawn = AAVE.withdraw(address(weth),
                        amount, address(this));

        if (msg.sender != address(this))
            weth.transfer(msg.sender, withdrawn);
    }

    // Arbitrum, Polygon, and L1
    function _buyUSDC(uint howMuch, uint price) internal returns (uint) {
        uint max = FullMath.mulDiv(USDC.balanceOf(address(v3Pool)) * 1e12, WAD, price);
        return v3Router.exactInput(ISwapRouter.ExactInputParams(abi.encodePacked(
            address(weth), V3.POOL_FEE(), address(USDC)), address(this),
            block.timestamp, Math.min(howMuch, max), 0));
    }

    function _buy(uint howMuch, uint price) internal returns (uint) {
        uint max = FullMath.mulDiv(weth.balanceOf(address(v3Pool)), price, WAD);
        return v3Router.exactInput(ISwapRouter.ExactInputParams(abi.encodePacked(
            address(USDC), V3.POOL_FEE(), address(weth)), address(this),
            block.timestamp, Math.min(howMuch, max), 0));
    }

    /*
    function _buyUSDC(uint howMuch, uint price) internal returns (uint) {
        uint max = FullMath.mulDiv(USDC.balanceOf(address(v3Pool)) * 1e12, WAD, price);
        return v3Router.exactInput(ISwapRouter.ExactInputParams(abi.encodePacked(
            address(weth), V3.POOL_FEE(), address(USDC)), address(this),
            Math.min(howMuch, max), 0));
    } // this version is only on Base and Unichain

    function _buy(uint howMuch, uint price) internal returns (uint) {
        uint max = FullMath.mulDiv(weth.balanceOf(address(v3Pool)), price, WAD);
        return v3Router.exactInput(ISwapRouter.ExactInputParams(abi.encodePacked(
            address(USDC), V3.POOL_FEE(), address(weth)), address(this),
            Math.min(howMuch, max), 0));
    } */

    function send(uint howMuch, address toWhom)
        public onlyUs { _send(howMuch, toWhom); }

    function _send(uint howMuch, address toWhom) internal {
        // any unused gas from clearSwaps() lands back in
        // address(this) as residual ETH; re-appropriate:
        uint already = address(this).balance;
        uint fromBalance = Math.min(howMuch, already);
        uint fromAAVE = howMuch - fromBalance;
        if (fromAAVE > 0) fromAAVE = get(fromAAVE);
        (bool _success, ) = payable(toWhom).call{
                value: fromBalance + fromAAVE }("");
                                   assert(_success);
    }

    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) AAVE.repay(repay,
                borrowed, 2, address(this));

        if (supplied > 0 && out != address(0)) {
            uint tracked = totalBorrowed[repay];
            totalBorrowed[repay] = borrowed >
            tracked ? 0 : tracked - borrowed;
            uint 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) {

        ( IUiPoolDataProviderV3.UserReserveData[] memory userData,
        ) = DATA.getUserReservesData(ADDR, address(this));

        ( IUiPoolDataProviderV3.AggregatedReserveData[] memory reserveData,
        ) = DATA.getReservesData(ADDR);
        uint scaledDebt; uint borrowIndex; uint actualDebt;
        if (userData.length > 0 && reserveData.length > 0) {
            borrowIndex = reserveData[4].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));
            // Safe subtraction - if tracking drifted, just return 0
            repay = actualDebt > totalBorrowed[address(weth)]
                ? actualDebt - totalBorrowed[address(weth)] : 0;

            borrowIndex = reserveData[3].variableBorrowIndex;
            scaledDebt = userData[3].scaledVariableDebt;
            actualDebt = (scaledDebt * borrowIndex) / RAY;
            USDCsharesSnapshot = IERC20(// index 3 on L1, 4 on Base, 12 on Arb
                // 22 on Polygon
                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.buffer = 0;
                        pledge.price = price;
                    } 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;
                        this.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; this.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 USDC
                    buffer = get(pledge.supplied);

                    // 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));
                    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);
                        V3.depositUSDC(profit / 2, uint(price));
                    }
                    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));
                        this.putUSDC(pledge.supplied);
                    } else {
                        // Price down (good for short) - hold WETH in buffer
                        pledge.buffer = pledge.supplied;
                        this.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));

                    this.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);
                        V3.depositUSDC(profit / 2, uint(price));
                    }
                    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 {AuxArb as Aux} from "./AuxArb.sol";
import {Aux} from "./Aux.sol";
import {WETH} from "solmate/src/tokens/WETH.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";

import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
import {TickMath} from "./imports/v3/TickMath.sol";
import {FullMath} from "./imports/v3/FullMath.sol";

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {LiquidityAmounts} from "./imports/v3/LiquidityAmounts.sol";
import {INonfungiblePositionManager} from "./imports/v3/INonfungiblePositionManager.sol";
// import {IV3SwapRouter as ISwapRouter} from "./imports/v3/IV3SwapRouter.sol";
import {ISwapRouter} from "./imports/v3/ISwapRouter.sol"; // on L1 and Arbitrum

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; // V3 NFT
    uint public LAST_REPACK;
    uint private _deployed;
    struct Deposit {
        // Masterchef-style
        // snapshots of fees
        // that are owed...
        uint fees_eth;
        uint fees_usd;
        uint128 liq;
    } Amp public AMP;
    address public AUX;
    bool public token1isWETH;

    uint public totalShares;
    int24 public UPPER_TICK;
    int24 public LOWER_TICK;
    int24 public LAST_TICK;
    uint constant WAD = 1e18;

    int24 constant MAX_TICK = 887220;
    INonfungiblePositionManager public NFPM;
    mapping(address => Deposit) positions;

    int24 TICK_SPACING; uint24 public POOL_FEE;
    address public POOL; address public ROUTER;
    uint128 public liquidityUnderManagement;
    uint public USD_FEES; uint public ETH_FEES;

    function fetch(address beneficiary) public
        returns (Deposit memory, uint, uint160) {
        Deposit memory LP = positions[beneficiary];
        (uint160 sqrtPrice,
         int24 tick,,,,,) = IUniswapV3Pool(POOL).slot0();
        LAST_TICK = tick; uint price = getPrice(sqrtPrice);
        _repackNFT(0, 0, price); return (LP, 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));
        AMP = Amp(payable(_amp));
        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(_amp, type(uint).max);
        ERC20(USDC).approve(_amp, type(uint).max);
        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);
                (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 - set initial ticks
            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) for mint
            // 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: 0, amount1Min: 0, recipient: address(this),
                    deadline: block.timestamp
            })); LAST_REPACK = block.timestamp;
        } // metrics at the expense of sometimes doing 1 extra swap:
        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 (from prior out-of-range period)
                // - Amounts are too small to create any liquidity
                // On failure, tokens stay in contract for next deposit/repack
                try NFPM.increaseLiquidity(
                    INonfungiblePositionManager.IncreaseLiquidityParams(
                            ID, amount0, amount1, 0, 0, block.timestamp))
                returns (uint128 addedLiquidity, uint, uint) {
                    liquidityUnderManagement += addedLiquidity;
                } catch {
                    // Silently continue - tokens remain in contract
                }
            }
        }
    } function repackNFT() public nonReentrant
        returns (uint160) { (uint160 sqrtPriceX96,
        int24 tick,,,,,) = IUniswapV3Pool(POOL).slot0();
        LAST_TICK = tick; _repackNFT(0, 0,
              getPrice(sqrtPriceX96));
                  return sqrtPriceX96;
    }

    // from v3-periphery/OracleLibrary...
    // Returns price as USDC per WETH (scaled to 18 decimals)
    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 pendingRewards(address user) public
        view returns (uint fees_eth, uint fees_usd) {
        Deposit memory LP = positions[user];
        if (LP.liq == 0) return (0, 0);
        fees_eth = FullMath.mulDiv(LP.liq,
            ETH_FEES, WAD) - LP.fees_eth;
        fees_usd = FullMath.mulDiv(LP.liq,
            USD_FEES, WAD) - LP.fees_usd;
    }

    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);
        // Get 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;
        }
        // Only proceed if there's actually liquidity to withdraw
        if (liquidity > 0) {
            NFPM.decreaseLiquidity(
                INonfungiblePositionManager.DecreaseLiquidityParams(
                    ID, liquidity, 0, 0, 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;

        // Handle negative remainders
        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;
    }

    // Fixed _adjustTicks: handles negative ticks correctly
    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;

        // Ensure minimum spacing
        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)));

        // Ensure proper ordering (safety check)
        if (lower > upper) {
            (lower, upper) = (upper, lower);
        }
        // Ensure they're not equal
        if (upper == lower) {
            upper += TICK_SPACING;
        }
    }

    // Helper to get sqrt prices for tick bounds
    function _getTickSqrtPrices() internal view
        returns (uint160 sqrtLower, uint160 sqrtUpper, uint160 sqrtCurrent) {
        sqrtLower = TickMath.getSqrtPriceAtTick(LOWER_TICK);
        sqrtUpper = TickMath.getSqrtPriceAtTick(UPPER_TICK);
        sqrtCurrent = TickMath.getSqrtPriceAtTick(LAST_TICK);
    }

    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) {
                // We have ETH, calculate liquidity based on ETH amount
                if (token1isWETH) {
                    // ETH is token1, use 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 USDC amount
                if (token1isWETH) {
                    // USDC is token0, use 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
            );
            // Convert to (ETH, USDC) based on token ordering
            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, the
        // 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)
        // Send excess to AMP
        targetUSDC *= 1e12;
        if (usdc > targetUSDC) {
            usdc -= targetUSDC;
            usdc /= 1e12;
            if (usdc > 0) {
                ERC20(USDC).transfer(address(AMP), usdc);
                AMP.putUSDC(usdc);
            }
            usdc = targetUSDC;
        }
        if (eth > targetETH) {
            eth -= targetETH;
            if (eth > 0) {
                weth.transfer(address(AMP), eth);
                AMP.put(eth);
            }
            eth = targetETH;
        }
        // Swap ETH → USDC if we need more USDC
        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;
                    usdc += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                                          abi.encodePacked(address(weth), POOL_FEE, USDC),
                                          address(this), block.timestamp, toSwap, 0)) * 1e12;
                }
            }
        }
        // Swap USDC → ETH if we need more ETH
        if (targetETH > eth && usdc > 0 && targetUSDC > 0 && targetETH > 0) {
            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);
            // Calculate how much USDC to swap
            uint toSwapScaled;
            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;
                    eth += ISwapRouter(ROUTER).exactInput(
                        ISwapRouter.ExactInputParams(abi.encodePacked(USDC, POOL_FEE,
                          address(weth)),address(this), block.timestamp, toSwap, 0));
                }
            }
        } return (eth, usdc / 1e12);
    }

    function deposit(uint amount) external nonReentrant payable {
        (Deposit memory LP, 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);

        // Convert to (amount0, amount1) for liquidity calculation
        (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 (value moved to AMP): new depositors get 1:1 (neutral)
            uint denominator = liquidityUnderManagement > totalShares
                ? liquidityUnderManagement
                : totalShares;
            newShares = uint128(FullMath.mulDiv(uint(newLiq), totalShares, denominator));
        }
        LP.liq += newShares;
        totalShares += newShares;
        _repackNFT(amount0, amount1, price);
        LP.fees_eth = FullMath.mulDiv(LP.liq, ETH_FEES, WAD);
        LP.fees_usd = FullMath.mulDiv(LP.liq, USD_FEES, WAD);
        positions[msg.sender] = LP;
    }

    // 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;
        (uint160 sqrtLower, uint160 sqrtUpper, uint160 sqrtCurrent) = _getTickSqrtPrices();
        // Calculate liquidity needed for this ETH amount
        if (token1isWETH) {
            // ETH is token1
            liquidity = LiquidityAmounts.getLiquidityForAmount1(sqrtCurrent, sqrtUpper, amount / 2);
        } else {
            // ETH is token0
            liquidity = LiquidityAmounts.getLiquidityForAmount0(sqrtCurrent, sqrtUpper, amount / 2);
        }
        (uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);
        // Convert to (USDC, ETH) based on token ordering
        uint usdcAmount;
        if (token1isWETH) {
            usdcAmount = amount0;
            wethAmount = amount1;
        } else {
            wethAmount = amount0;
            usdcAmount = amount1;
        }
        if (usdcAmount > 0)
            wethAmount += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                                       abi.encodePacked(USDC, POOL_FEE, address(weth)),
                                      address(this), block.timestamp, usdcAmount, 0));
        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);
        // Convert to (amount0, amount1) for increaseLiquidity
        (uint amount0, uint amount1) = token1isWETH ? (usd, eth) : (eth, usd);
        (uint128 liquidity, , ) = NFPM.increaseLiquidity(
            INonfungiblePositionManager.IncreaseLiquidityParams(
                    ID, amount0, amount1, 0, 0, block.timestamp));
                            liquidityUnderManagement += liquidity;
    }

    function withdrawUSDC(uint amount) public onlyUs
        returns (uint usd) { uint eth; repackNFT();
        uint128 liquidity;
        (uint160 sqrtLower, uint160 sqrtUpper, uint160 sqrtCurrent) = _getTickSqrtPrices();
        // Calculate liquidity needed for this USDC amount
        if (token1isWETH) {
            // USDC is token0
            liquidity = LiquidityAmounts.getLiquidityForAmount0(
                              sqrtLower, sqrtCurrent, amount / 2);
        } else {
            // USDC is token1
            liquidity = LiquidityAmounts.getLiquidityForAmount1(
                              sqrtLower, sqrtCurrent, amount / 2);
        }
        (uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);
        // Convert to (ETH, USDC) based on token ordering
        if (token1isWETH) {
            eth = amount1;
            usd = amount0;
        } else {
            eth = amount0;
            usd = amount1;
        }
        if (eth > 0)
            usd += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                        abi.encodePacked(address(weth), POOL_FEE, address(USDC)),
                                        address(this), block.timestamp, eth, 0));
        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, "%");
        (Deposit memory LP,
        uint price, uint160 sqrtPrice) = fetch(msg.sender);
        require(LP.liq > 0, "nothing to withdraw");
        uint128 withdrawingShares = uint128(FullMath.mulDiv(
                                amount, uint(LP.liq), 1000));

        uint128 liquidity = uint128(FullMath.mulDiv(liquidityUnderManagement,
                                            withdrawingShares, totalShares));

        (uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);

        // Convert to (ETH, USDC) based on token ordering
        (uint ethAmount, uint usdAmount) = token1isWETH ? (amount1, amount0)
                                                        : (amount0, amount1);
        uint expected = ethAmount + FullMath.mulDiv(usdAmount, price, WAD);
        if (usdAmount > 0) // Swap USDC to ETH if there's any USDC
            ethAmount += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
                             abi.encodePacked(address(USDC), POOL_FEE, address(weth)),
                                        address(this), block.timestamp, usdAmount, 0));
        weth.withdraw(ethAmount);
        LP.liq -= withdrawingShares;
        totalShares -= withdrawingShares;
        if (expected > ethAmount) {
            uint extra = AMP.get(expected - ethAmount);
            weth.withdraw(extra); ethAmount += extra;
        }
        (bool success, ) = msg.sender.call{
                          value: ethAmount}("");
                          require(success, "$");
        if (LP.liq == 0) {
            delete positions[msg.sender];
        } else {
            LP.fees_eth = FullMath.mulDiv(LP.liq, ETH_FEES, WAD);
            LP.fees_usd = FullMath.mulDiv(LP.liq, USD_FEES, WAD);
            positions[msg.sender] = LP;
        }
    }
}

File 24 of 113 : Types.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

library Types {
    struct SelfManaged {
        address owner;
        int24 lower;
        int24 upper;
        int liq;
    }
    struct viaAAVE {
        uint breakeven;
        uint supplied;
        uint borrowed;
        uint buffer;
        int price;
    }    
    struct Deposit {
        uint pooled_eth;
        uint usd_owed;
        // Masterchef-style
        // snapshots of fees:
        uint fees_eth;
        uint fees_usd;
    }
    struct Trade {
        address sender;
        address token; // receiving
        uint amount; // selling
    }
    struct Batch { 
        uint total; 
        Trade[] swaps; 
    } 
}

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

import {Aux} from "./Aux.sol";
import {Vogue} from "./Vogue.sol";
import {mock} from "./mock.sol";
import {Types} from "./imports/Types.sol";
// import {AuxBase as Aux} from "./AuxBase.sol";
// import {AuxArb as Aux} from "./AuxArb.sol";
// import {VogueArb as Vogue} from "./VogueArb.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;

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

    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;
    PoolKey VANILLA;
    enum Action {
        Swap, BatchSwap,
        Repack, ModLP,
        OutsideRange
    }
    modifier onlyAux {
        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,
        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;

        // PoolKey requires currency0 < currency1 by address
        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 token orderings
        if (token1isETH)
            tickETH *= AUX.token1isWETH() ? int24(1) : int24(-1);
        else
            tickETH *= AUX.token1isWETH() ? int24(-1) : int24(1);

        poolManager.initialize(VANILLA, TickMath.getSqrtPriceAtTick(tickETH));

        // Initialize 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 onlyAux
        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 onlyAux {
        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)
        onlyAux public returns (BalanceDelta delta) {
        delta = abi.decode(poolManager.unlock(abi.encode(Action.Swap,
            sqrtPriceX96, sender, forOne, token, amount)), (BalanceDelta));
    }

    function batchSwap(uint160 sqrtPriceX96, uint blockNumber,
        uint splitForUSD, uint splitForETH) onlyAux public
        returns (uint swappedForUSD, uint swappedForETH) {
        (swappedForUSD, swappedForETH) = abi.decode(
          poolManager.unlock(abi.encode(Action.BatchSwap,
            sqrtPriceX96, blockNumber, // no sandwiches!
            splitForUSD, splitForETH)), (uint, uint));
    }

    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.BatchSwap) {
            return _handleBatchSwap(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);

        // Write observation after swap
        (, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
        _writeObservation(currentTick);

        _handleDelta(delta, true,
           false, sender, token);
        return abi.encode(delta);
    }

    function _handleBatchSwap(bytes calldata data) internal
        returns (bytes memory) {
        (uint160 sqrtPriceX96, uint lastBlock,
         uint splitForUSD, uint splitForETH) = abi.decode(data,
                                    (uint160, uint, uint, uint));
        (Types.Batch memory forUSD,
         Types.Batch memory forETH) = VOGUE.getSwapsETH(lastBlock);

        if (forETH.total > splitForETH) {
            BalanceDelta delta = poolManager.swap(VANILLA, IPoolManager.SwapParams({
                zeroForOne: token1isETH, amountSpecified: -int(forETH.total - splitForETH),
                sqrtPriceLimitX96: VOGUE.paddedSqrtPrice(sqrtPriceX96, !token1isETH, 1000)
            }), ZERO_BYTES);
            if (token1isETH)
                (, splitForETH) = _handleDelta(delta, true, false,
                                            address(0), address(0));
            else
                (splitForETH, ) = _handleDelta(delta, true, false,
                                            address(0), address(0));
        }
        if (forUSD.total > splitForUSD) {
            BalanceDelta delta = poolManager.swap(VANILLA, IPoolManager.SwapParams({
                zeroForOne: !token1isETH, amountSpecified: -int(forUSD.total - splitForUSD),
                sqrtPriceLimitX96: VOGUE.paddedSqrtPrice(sqrtPriceX96, token1isETH, 1000)
            }), ZERO_BYTES);
            if (token1isETH)
                (splitForUSD,) = _handleDelta(delta, true, true,
                                        address(0), address(0));
            else
                (, splitForUSD) = _handleDelta(delta, true, true,
                                        address(0), address(0));
        }

        // Write observation after batch swaps
        (, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
        _writeObservation(currentTick);

        return abi.encode(splitForETH, splitForUSD);
    }

    function _getPrice(uint160 sqrtPriceX96,
        bool token0isUSD) internal 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 _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));

        uint price = _getPrice(sqrtPriceX96, token1isETH);
        if (token1isETH) {
            (delta0, delta1) = VOGUE.addLiquidityHelper(0, delta1, price);
            delta = _modLP(delta0, delta1, newTickLower, newTickUpper, sqrtPriceX96);
        } else {
            (delta1, delta0) = VOGUE.addLiquidityHelper(0, delta0, price);
            delta = _modLP(delta1, delta0, newTickLower, newTickUpper, sqrtPriceX96);
        }
        _handleDelta(delta, true, false, address(0), address(0));

        // Write observation after repack
        (, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
        _writeObservation(currentTick);

        return abi.encode(price, uint(int(fees.amount0())), uint(int(fees.amount1())),
                                uint(int(delta.amount0())), uint(int(delta.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 -= usdAmount;
            if (!keep && token != address(0))
                AUX.take(who, usdAmount, token, false);
        }
        else if (usdDelta < 0) {
            usdAmount = uint(int(-usdDelta));
            mockUSD.mint(usdAmount);
            usdCurrency.settle(poolManager,
            address(this), usdAmount, false);
            if (inRange) POOLED_USD += usdAmount;
        }
        if (ethDelta > 0) {
            ethAmount = uint(int(ethDelta));
            ethCurrency.take(poolManager,
            address(this), ethAmount, false);
            mockETH.burn(ethAmount);
            if (inRange) POOLED_ETH -= ethAmount;
            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) {

        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);

        (BalanceDelta totalDelta, ) = _modifyLiquidity(
            flip * int(uint(liquidity)), tickLower, tickUpper);
        return totalDelta;
    }

    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;  // Update tick for next write
            return;
        }
        // Calculate tick 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 (V3-compatible interface)
    /// @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];
            if (secondsAgos[i] == 0) {
                // Current: extrapolate forward from latest
                uint32 delta = time - latest.blockTimestamp;
                tickCumulatives[i] = latest.tickCumulative + int56(lastTick) * int56(uint56(delta));
            } else if (target <= oldest.blockTimestamp) {
                // Target is before or at oldest observation - extrapolate BACKWARDS
                // This handles "not enough history" case
                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));
            } else if (target >= latest.blockTimestamp) {
                // Target is at or after latest - extrapolate forward
                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);
            }
        }
    }

    /// @notice Get the oldest observation
    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 yet), oldest is at 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;
            // Linear interpolation
            return oldest.tickCumulative + (cumulativeDelta *
                int56(uint56(targetDelta))) / int56(uint56(totalDelta));
        }
        // For more observations, find the bracketing pair
        // linear interpolation between oldest and latest
        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));
    }

    function repack(uint128 myLiquidity,
        uint160 sqrtPriceX96, int24 oldTickLower,
        int24 oldTickUpper, int24 newTickLower, int24 newTickUpper) public onlyAux
        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.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;
        }
    }
}

File 27 of 113 : 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;
  }
}

File 28 of 113 : 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: 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: 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);
}

File 31 of 113 : 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
{}

File 32 of 113 : Proof.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Basket} from "./Basket.sol";
import {ECDSA} from "solady/utils/ECDSA.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";

interface ICourt {
    function getMarketConfig(uint64 marketId) external view returns (
    uint8 numSides, uint8 numWinners, bool requiresUnanimous, bool requiresSignature);
    function isInResolutionPhase(uint64 marketId) external view returns (bool);
    function getRequiresAppSignature(uint64 marketId) external view returns (bool);
    function getCurrentRound(uint64 marketId) external view returns (uint8);
}

interface IJury {
    function isJuror(uint64 marketId, uint8 round, address addr) external view returns (bool);
    function getJurors(uint64 marketId, uint8 round) external view returns (address[] memory);
    function getCorrectJurors(uint64 marketId, uint8 round) external view returns (address[] memory);
}

contract Proof is ReentrancyGuard, Ownable {
    enum EvalType { CONCURRING, MAJORITY, // +
      DISSENTING_RELEVANCY, DISSENTING_ACCURACY } // -
    enum AffidavitQuality { NOT_EVALUATED, GOOD, BAD }

    struct Affidavit {
        address witness;
        string evidenceUrl;
        bytes32 contentHash;
        uint64 timestamp;
        uint8 supportedSide;
        bytes32 solanaKey;
        AffidavitQuality quality;
    }

    struct AffidavitParams {
        uint64 marketId;
        string evidenceUrl;
        bytes32 contentHash;
        uint8 supportedSide;
        bytes32 solanaKey;
        bytes32[] merkleProof;
        bytes ethSig;
        bytes appSig;
        uint64 timestamp;
    }

    struct AffidavitStats {
        uint8 concurringCount;
        uint8 majorityCount;
        uint8 dissRelevancy;
        uint8 dissAccuracy;
    }

    struct Evaluation {
        address evaluator;
        EvalType evalType;
        string reasoning;
        uint64 timestamp;
    }

    struct BatchEvaluation {
        uint affidavitId;
        EvalType evalType;
        string reasoning;
    }

    struct FinalizationState {
        uint cursor; // Next affidavit index to process
        bool complete; // Whether finalization is done
        address[] correctJurors; // Cached for this round
        bytes32[] badAddresses;
        uint8[] badSides;
    }

    address public immutable basket;
    address public court;
    address public jury;

    uint public constant MAX_AFFIDAVITS_PER_ADDRESS = 4;
    uint public constant MAX_BATCH_SIZE = 5000;
    uint public constant FINALIZE_BATCH_SIZE = 50;

    mapping(bytes32 => bool) public approvedAppKeys;
    mapping(uint64 => bytes32) public merkleRoots;
    mapping(uint64 => Affidavit[]) public affidavits;

    mapping(uint64 => mapping(uint => Evaluation[])) public evaluations;
    mapping(uint64 => mapping(address => uint)) public evaluationCount;
    mapping(uint64 => mapping(uint => AffidavitStats)) public stats;
    mapping(uint64 => mapping(address => uint)) public submissionCount;
    mapping(uint64 => mapping(uint => mapping(address => bool))) public hasEvaluated;
    mapping(uint64 => mapping(uint => mapping(address => EvalType))) public evalTypes;
    mapping(uint64 => mapping(address => uint)) public jurorLastResolutionStart;

    mapping(uint64 => FinalizationState) public finalizationState;
    mapping(uint64 => uint) public resolutionStartIndex;
    mapping(uint64 => mapping(address => uint)) public currentResolutionEvalCount;

    event AffidavitSubmitted(uint64 indexed marketId, uint indexed affidavitId, address witness, uint8 supportedSide);
    event BatchEvaluated(uint64 indexed marketId, address indexed juror, uint count);
    event AffidavitQualitySet(uint64 indexed marketId, uint indexed affidavitId, AffidavitQuality quality);
    event FinalizationProgress(uint64 indexed marketId, uint processed, uint total);

    error MaxSubmissionsReached();
    error InvalidMerkleProof();
    error NotJuror();
    error AppSigExpired();
    error SubmissionsClosed();
    error AlreadyEvaluated();
    error CannotEvaluateOwnAffidavit();
    error ReasoningRequired();
    error InvalidAffidavit();
    error BatchTooLarge();
    error FinalizationAlreadyComplete();

    constructor(address _basket) Ownable(msg.sender) {
        basket = _basket;
    }

    function setCourt(address _court) external onlyOwner {
        require(court == address(0), "Already set");
        court = _court;
    }

    function setJury(address _jury) external onlyOwner {
        require(jury == address(0), "Already set");
        jury = _jury;
    }

    function approveAppKey( // no jailbreakers
        bytes32 appKeyHash) external onlyOwner {
        approvedAppKeys[appKeyHash] = true;
    }

    function updateMerkleRoot(
        uint64 marketId, bytes32 merkleRoot) external {
        require(msg.sender == court || msg.sender == basket, "Only court/basket");
        merkleRoots[marketId] = merkleRoot;
    }

    function submitAffidavit(AffidavitParams calldata p)
        external returns (uint affidavitId) {
        require(ICourt(court).isInResolutionPhase(p.marketId), "Not in resolution phase");
        (uint8 numSides,,,) = ICourt(court).getMarketConfig(p.marketId);
        require(p.supportedSide < numSides, "Invalid side");
        uint8 currentRound = ICourt(court).getCurrentRound(p.marketId);
        if (IJury(jury).getJurors(p.marketId, currentRound).length == 21) {
            revert SubmissionsClosed();
        }
        if (submissionCount[p.marketId][msg.sender] >= MAX_AFFIDAVITS_PER_ADDRESS)
            revert MaxSubmissionsReached();

        if (!_verifyMerkleProof(p.merkleProof,
            merkleRoots[p.marketId],
            p.solanaKey, msg.sender))
            revert InvalidMerkleProof();

        _verifySignatures(p.marketId, p.solanaKey, p.contentHash,
            p.supportedSide, p.timestamp, p.ethSig, p.appSig);

        affidavitId = affidavits[p.marketId].length;
        affidavits[p.marketId].push(Affidavit({ witness: msg.sender,
            evidenceUrl: p.evidenceUrl, contentHash: p.contentHash,
            timestamp: uint64(block.timestamp), supportedSide: p.supportedSide,
            solanaKey: p.solanaKey, quality: AffidavitQuality.NOT_EVALUATED
        }));
        submissionCount[p.marketId][msg.sender]++;
        emit AffidavitSubmitted(p.marketId,
          affidavitId, msg.sender, p.supportedSide);
    }

    function _verifySignatures(uint64 marketId, bytes32 solanaKey,
        bytes32 contentHash, uint8 supportedSide, uint64 timestamp,
        bytes calldata ethSig, bytes calldata appSig) internal view {
        uint8 currentRound = ICourt(court).getCurrentRound(marketId);
        bytes32 msgHash = keccak256(abi.encodePacked(
            "QU!D", marketId, currentRound, solanaKey, block.chainid
        ));
        bytes32 ethSigned = keccak256(abi.encodePacked("\x19claro:\n32", msgHash));
        require(ECDSA.recover(ethSigned, ethSig) == msg.sender, "sig");

        if (_requiresAppSignature(marketId)) {
            require(appSig.length > 0, "App signature required");
            if (block.timestamp > timestamp + 1 hours) revert AppSigExpired();
            bytes32 appSigned = keccak256(abi.encodePacked(
                "\x19claro:\n32", msg.sender, msgHash, contentHash, supportedSide, timestamp
            ));
            address appSigner = ECDSA.recover(appSigned, appSig);
            require(approvedAppKeys[keccak256(abi.encodePacked(appSigner))], "sig");
        }
    }

    function _verifyMerkleProof(bytes32[] calldata proof, bytes32 root,
        bytes32 solanaKey, address ethSigner) internal pure returns (bool) {
        bytes32 leaf = keccak256(abi.encodePacked(solanaKey, ethSigner));
        bytes32 computedHash = keccak256(abi.encodePacked(leaf));
        for (uint i = 0; i < proof.length; i++) {
            computedHash = computedHash < proof[i]
                ? keccak256(abi.encodePacked(
                      computedHash, proof[i]))
                : keccak256(abi.encodePacked(
                      proof[i], computedHash));
        }          return computedHash == root;
    }

    function submitBatchEvaluations(uint64 marketId,
        BatchEvaluation[] calldata evals) external {
        uint8 currentRound = ICourt(court).getCurrentRound(marketId);
        if (!IJury(jury).isJuror(marketId, currentRound, msg.sender)) revert NotJuror();
        if (evals.length > MAX_BATCH_SIZE) revert BatchTooLarge();
        for (uint i = 0; i < evals.length; i++)
            _processEvaluation(marketId, evals[i]);

        emit BatchEvaluated(marketId, msg.sender, evals.length);
    }

    function _processEvaluation(uint64 marketId, BatchEvaluation calldata eval) internal {
        if (eval.affidavitId >= affidavits[marketId].length) revert InvalidAffidavit();
        if (hasEvaluated[marketId][eval.affidavitId][msg.sender]) revert AlreadyEvaluated();
        Affidavit storage affidavit = affidavits[marketId][eval.affidavitId];

        if (affidavit.witness == msg.sender) revert CannotEvaluateOwnAffidavit();
        if ((eval.evalType == EvalType.CONCURRING ||
             eval.evalType == EvalType.DISSENTING_ACCURACY) &&
            bytes(eval.reasoning).length == 0) {
            revert ReasoningRequired();
        }
        hasEvaluated[marketId][eval.affidavitId][msg.sender] = true;
        evaluationCount[marketId][msg.sender]++;

        // Track evaluations of NEW affidavits (current resolution) separately
        if (eval.affidavitId >= resolutionStartIndex[marketId]) {
            // Reset count if this is the first evaluation in a new resolution
            if (jurorLastResolutionStart[marketId][msg.sender] != resolutionStartIndex[marketId]) {
                currentResolutionEvalCount[marketId][msg.sender] = 0;
                jurorLastResolutionStart[marketId][msg.sender] = resolutionStartIndex[marketId];
            }
            currentResolutionEvalCount[marketId][msg.sender]++;
        }
        evalTypes[marketId][eval.affidavitId][msg.sender] = eval.evalType;
        AffidavitStats storage s = stats[marketId][eval.affidavitId];
        if (eval.evalType == EvalType.CONCURRING) { s.concurringCount++;
            evaluations[marketId][eval.affidavitId].push(Evaluation({
                evaluator: msg.sender, evalType: eval.evalType,
                reasoning: eval.reasoning, timestamp: uint64(block.timestamp)
            }));
        } else if (eval.evalType == EvalType.MAJORITY) {
            s.majorityCount++;
        } else if (eval.evalType == EvalType.DISSENTING_RELEVANCY) {
            s.dissRelevancy++;
        } else if (eval.evalType == EvalType.DISSENTING_ACCURACY) {
            s.dissAccuracy++;
            evaluations[marketId][eval.affidavitId].push(Evaluation({
                evaluator: msg.sender, evalType: eval.evalType,
                reasoning: eval.reasoning,
                timestamp: uint64(block.timestamp)
            }));
        }
    }

    /// @notice Initialize finalization - called by Court
    /// @dev Sets up state for batched processing
    /// @param marketId The market ID
    /// @param round The round number
    function finalizeEvaluations(uint64 marketId,
        uint8 round) external { require(msg.sender == court, "Only court");
        FinalizationState storage state = finalizationState[marketId];
        if (state.complete) return;
        if (state.correctJurors.length == 0) {
            state.correctJurors = IJury(jury).getCorrectJurors(marketId, round);
            // Start from resolutionStartIndex to skip
            // old affidavits from previous resolutions
            state.cursor = resolutionStartIndex[marketId];
        }
        uint totalAffidavits = affidavits[marketId].length;
        uint endIndex = state.cursor + FINALIZE_BATCH_SIZE;
        if (endIndex > totalAffidavits) endIndex = totalAffidavits;

        for (uint i = state.cursor; i < endIndex; i++) {
            _finalizeAffidavit(marketId, i, state.correctJurors);
        }
        state.cursor = endIndex;
        if (state.cursor >= totalAffidavits) {
            state.complete = true;
        }
        emit FinalizationProgress(marketId,
          state.cursor, totalAffidavits);
    }

    /// @notice Continue finalization - can be called by anyone if not complete
    /// @dev Allows keepers to help process large batches
    /// @param marketId The market ID
    function continueFinalization(uint64 marketId) external {
        FinalizationState storage state = finalizationState[marketId];
        require(state.correctJurors.length > 0, "Not initialized");
        if (state.complete) revert FinalizationAlreadyComplete();

        uint totalAffidavits = affidavits[marketId].length;
        uint endIndex = state.cursor + FINALIZE_BATCH_SIZE;
        if (endIndex > totalAffidavits) endIndex = totalAffidavits;

        for (uint i = state.cursor; i < endIndex; i++) {
            _finalizeAffidavit(marketId, i, state.correctJurors);
        }
        state.cursor = endIndex;
        if (state.cursor >= totalAffidavits) {
            state.complete = true;
        }
        emit FinalizationProgress(marketId,
            state.cursor, totalAffidavits);
    }

    /// @param marketId The market ID
    /// @param affidavitIndex The affidavit index
    /// @param correctJurors Array of jurors who voted correctly
    function _finalizeAffidavit(uint64 marketId,
        uint affidavitIndex, address[] memory correctJurors) internal {
        Affidavit storage affidavit = affidavits[marketId][affidavitIndex];
        if (affidavit.quality != AffidavitQuality.NOT_EVALUATED) return;
        uint relevantEvals = 0; uint dissents = 0; bool isBad = false;
        for (uint j = 0; j < correctJurors.length; j++) {
            if (hasEvaluated[marketId][affidavitIndex][correctJurors[j]]) { relevantEvals++;
                EvalType et = evalTypes[marketId][affidavitIndex][correctJurors[j]];
                if (et == EvalType.DISSENTING_ACCURACY || et == EvalType.DISSENTING_RELEVANCY) {
                    dissents++;
                }
            }
        } FinalizationState storage state = finalizationState[marketId];
        if (relevantEvals == 0) {
            isBad = true;
        } else {
            uint pct = (dissents * 10000) / relevantEvals;
            if (pct >= 5000) {
                isBad = true;
            }
        } if (isBad) {
            affidavit.quality = AffidavitQuality.BAD;
            if (state.badAddresses.length < 100) {
                state.badAddresses.push(affidavit.solanaKey);
                state.badSides.push(affidavit.supportedSide);
            }
        } else {
            affidavit.quality = AffidavitQuality.GOOD;
        }
        emit AffidavitQualitySet(marketId,
        affidavitIndex, affidavit.quality);
    }

    /// @notice Check if finalization is complete
    /// @param marketId The market ID
    /// @return complete Whether finalization is done
    /// @return cursor Current processing position
    /// @return total Total affidavits to process
    function getFinalizationStatus(uint64 marketId) external view
        returns (bool complete, uint cursor, uint total) {
        FinalizationState storage state = finalizationState[marketId];
        return (state.complete, state.cursor, affidavits[marketId].length);
    }

    /// @notice Reset finalization state (for new rounds)
    /// @param marketId The market ID
    function resetFinalization(uint64 marketId) external {
        require(msg.sender == court, "Only court");
        delete finalizationState[marketId];
    }

    /// @notice Reset submission state for re-resolution (called when new resolution starts)
    /// @dev Clears finalization state and tracks starting index for new affidavits
    /// @param marketId The market ID
    function resetForNewResolution(uint64 marketId) external {
        require(msg.sender == court, "Only court");
        delete finalizationState[marketId];
        // Track where new affidavits start - old ones already have quality set
        // This allows efficient iteration in finalizeEvaluations
        resolutionStartIndex[marketId] = affidavits[marketId].length;

        // Note: submissionCount per address is NOT reset - this is intentional
        // Users who submitted bad affidavits in previous resolution shouldn't
        // get unlimited new attempts. The merkle tree also changed, so their
        // Solana positions may no longer be valid anyway.
    }

    function getBadAffidavitAddresses(uint64 marketId) external view
        returns (bytes32[] memory, uint8[] memory) {
        FinalizationState storage state = finalizationState[marketId];
        require(state.complete, "Finalization not complete");
        return (state.badAddresses, state.badSides);
    }

    function _requiresAppSignature(uint64 marketId)
        internal view returns (bool) {
        return ICourt(court).getRequiresAppSignature(marketId);
    }

    function getAffidavitCount(uint64 marketId)
        external view returns (uint) {
        return affidavits[marketId].length;
    }

    function getEvaluations(uint64 marketId, uint affidavitId)
        external view returns (Evaluation[] memory) {
        return evaluations[marketId][affidavitId];
    }

    function getAffidavitStats(uint64 marketId, uint affidavitId)
        external view returns (AffidavitStats memory) {
        return stats[marketId][affidavitId];
    }

    function getResolutionStartIndex(uint64 marketId)
        external view returns (uint) {
        return resolutionStartIndex[marketId];
    }

    function getCurrentResolutionEvalCount(uint64 marketId, address juror)
        external view returns (uint) {
        return currentResolutionEvalCount[marketId][juror];
    }
}

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

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

// Original Author: https://github.com/ethstorage/storage-contracts-v1/blob/main/contracts/RandaoLib.sol
// Functionality: Verification and extraction of RANDAO (mixHash) value from an RLP-encoded block header.
library RandaoLib {
    using RLPDecoder for RLPDecoder.RLPItem;
    using RLPDecoder for RLPDecoder.Iterator;
    using RLPDecoder for bytes;

    // Extracts the RANDAO value from the provided RLP-encoded header
    function getRandaoFromHeader(RLPDecoder.RLPItem memory item) pure internal returns (bytes32) {
        RLPDecoder.Iterator memory iterator = item.iterator();
        for (uint i = 0; i < 13; i++) {
            iterator.next(); // mixHash is at item 13 (0-base index)
        }
        return bytes32(iterator.next().toUint());
    }

    // Verifies the provided RLP-encoded block header matches the known hash of that header
    function verifyHeaderAndGetRandao(bytes32 headerHash, bytes memory headerRlpBytes) pure internal returns (bytes32) {
        RLPDecoder.RLPItem memory item = headerRlpBytes.toRlpItem();
        require(headerHash == item.rlpBytesKeccak256(), "header hash mismatch");
        return getRandaoFromHeader(item);
    }

    // Given a block number and the block header in RLP bytes, returns the RANDAO value after proving it's correct.
    function getHistoricalRandaoValue(uint blockNumber, bytes memory headerRlpBytes) view internal returns (bytes32) {
        bytes32 bh = blockhash(blockNumber); // up to 256 in the past I think?
        require(bh != bytes32(0), "failed to obtain blockhash");
        return verifyHeaderAndGetRandao(bh, headerRlpBytes);
    }    
}

// 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))
        }
    }
}

// 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;
    }
}

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

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { OApp, Origin } from "./oapp/OApp.sol";
import { OAppOptionsType3 } from "./oapp/libs/OAppOptionsType3.sol";
import { IOAppMsgInspector } from "./oapp/interfaces/IOAppMsgInspector.sol";

import { OAppPreCrimeSimulator } from "./oapp/OAppPreCrimeSimulator.sol";

import { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from "./oapp/interfaces/IOFT.sol";
import { OFTMsgCodec } from "./oapp/libs/OFTMsgCodec.sol";
import { OFTComposeMsgCodec } from "./oapp/libs/OFTComposeMsgCodec.sol";

/**
 * @title OFTCore
 * @dev Abstract contract for the OftChain (OFT) token.
 */
abstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {
    using OFTMsgCodec for bytes;
    using OFTMsgCodec for bytes32;

    // @notice Provides a conversion rate when swapping between denominations of SD and LD
    //      - shareDecimals == SD == shared Decimals
    //      - localDecimals == LD == local decimals
    // @dev Considers that tokens have different decimal amounts on various chains.
    // @dev eg.
    //  For a token
    //      - locally with 4 decimals --> 1.2345 => uint(12345)
    //      - remotely with 2 decimals --> 1.23 => uint(123)
    //      - The conversion rate would be 10 ** (4 - 2) = 100
    //  @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,
    //  you can only display 1.23 -> uint(123).
    //  @dev To preserve the dust that would otherwise be lost on that conversion,
    //  we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh
    uint256 public immutable decimalConversionRate;

    // @notice Msg types that are used to identify the various OFT operations.
    // @dev This can be extended in child contracts for non-default oft operations
    // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.
    uint16 public constant SEND = 1;
    uint16 public constant SEND_AND_CALL = 2;

    // Address of an optional contract to inspect both 'message' and 'options'
    address public msgInspector;
    event MsgInspectorSet(address inspector);

    /**
     * @dev Constructor.
     * @param _localDecimals The decimals of the token on the local chain (this chain).
     * @param _endpoint The address of the LayerZero endpoint.
     * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
     */
    constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {
        if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();
        decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());
    }

    /**
     * @notice Retrieves interfaceID and the version of the OFT.
     * @return interfaceId The interface ID.
     * @return version The version.
     *
     * @dev interfaceId: This specific interface ID is '0x02e49c2c'.
     * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.
     * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.
     * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)
     */
    function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {
        return (type(IOFT).interfaceId, 1);
    }

    /**
     * @dev Retrieves the shared decimals of the OFT.
     * @return The shared decimals of the OFT.
     *
     * @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap
     * Lowest common decimal denominator between chains.
     * Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).
     * For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.
     * ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615
     */
    function sharedDecimals() public pure virtual returns (uint8) {
        return 6;
    }

    /**
     * @dev Sets the message inspector address for the OFT.
     * @param _msgInspector The address of the message inspector.
     *
     * @dev This is an optional contract that can be used to inspect both 'message' and 'options'.
     * @dev Set it to address(0) to disable it, or set it to a contract address to enable it.
     */
    function setMsgInspector(address _msgInspector) public virtual onlyOwner {
        msgInspector = _msgInspector;
        emit MsgInspectorSet(_msgInspector);
    }

    /**
     * @notice Provides a quote for OFT-related operations.
     * @param _sendParam The parameters for the send operation.
     * @return oftLimit The OFT limit information.
     * @return oftFeeDetails The details of OFT fees.
     * @return oftReceipt The OFT receipt information.
     */
    function quoteOFT(
        SendParam calldata _sendParam
    )
        external
        view
        virtual
        returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)
    {
        uint256 minAmountLD = 0; // Unused in the default implementation.
        uint256 maxAmountLD = type(uint64).max; // Unused in the default implementation.
        oftLimit = OFTLimit(minAmountLD, maxAmountLD);

        // Unused in the default implementation; reserved for future complex fee details.
        oftFeeDetails = new OFTFeeDetail[](0);

        // @dev This is the same as the send() operation, but without the actual send.
        // - amountSentLD is the amount in local decimals that would be sent from the sender.
        // - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.
        // @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.
        (uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(
            _sendParam.amountLD,
            _sendParam.minAmountLD,
            _sendParam.dstEid
        );
        oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);
    }

    /**
     * @notice Provides a quote for the send() operation.
     * @param _sendParam The parameters for the send() operation.
     * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
     * @return msgFee The calculated LayerZero messaging fee from the send() operation.
     *
     * @dev MessagingFee: LayerZero msg fee
     *  - nativeFee: The native fee.
     *  - lzTokenFee: The lzToken fee.
     */
    function quoteSend(
        SendParam calldata _sendParam,
        bool _payInLzToken
    ) external view virtual returns (MessagingFee memory msgFee) {
        // @dev mock the amount to receive, this is the same operation used in the send().
        // The quote is as similar as possible to the actual send() operation.
        (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);

        // @dev Builds the options and OFT message to quote in the endpoint.
        (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);

        // @dev Calculates the LayerZero fee for the send() operation.
        return _quote(_sendParam.dstEid, message, options, _payInLzToken);
    }

    /**
     * @dev Executes the send operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The calculated fee for the send() operation.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess funds.
     * @return msgReceipt The receipt for the send operation.
     * @return oftReceipt The OFT receipt information.
     *
     * @dev MessagingReceipt: LayerZero msg receipt
     *  - guid: The unique identifier for the sent message.
     *  - nonce: The nonce of the sent message.
     *  - fee: The LayerZero fee incurred for the message.
     */
    function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
        // @dev Applies the token transfers regarding this send() operation.
        // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.
        // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.
        (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(
            _sendParam.amountLD,
            _sendParam.minAmountLD,
            _sendParam.dstEid
        );

        // @dev Builds the options and OFT message to quote in the endpoint.
        (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);

        // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.
        msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
        // @dev Formulate the OFT receipt.
        oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);

        emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);
    }

    /**
     * @dev Internal function to build the message and options.
     * @param _sendParam The parameters for the send() operation.
     * @param _amountLD The amount in local decimals.
     * @return message The encoded message.
     * @return options The encoded options.
     */
    function _buildMsgAndOptions(
        SendParam calldata _sendParam,
        uint256 _amountLD
    ) internal view virtual returns (bytes memory message, bytes memory options) {
        bool hasCompose;
        // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.
        (message, hasCompose) = OFTMsgCodec.encode(
            _sendParam.to,
            _toSD(_amountLD),
            // @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.
            // EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'
            _sendParam.composeMsg
        );
        // @dev Change the msg type depending if its composed or not.
        uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;
        // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.
        options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);

        // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.
        // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean
        if (msgInspector != address(0)) IOAppMsgInspector(msgInspector).inspect(message, options);
    }

    /**
     * @dev Internal function to handle the receive on the LayerZero endpoint.
     * @param _origin The origin information.
     *  - srcEid: The source chain endpoint ID.
     *  - sender: The sender address from the src chain.
     *  - nonce: The nonce of the LayerZero message.
     * @param _guid The unique identifier for the received LayerZero message.
     * @param _message The encoded message.
     * @dev _executor The address of the executor.
     * @dev _extraData Additional data.
     */
    function _lzReceive(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address /*_executor*/, // @dev unused in the default implementation.
        bytes calldata /*_extraData*/ // @dev unused in the default implementation.
    ) internal virtual override {
        // @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)
        // Thus everything is bytes32() encoded in flight.
        address toAddress = _message.sendTo().bytes32ToAddress();
        // @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals
        uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);

        if (_message.isComposed()) {
            // @dev Proprietary composeMsg format for the OFT.
            bytes memory composeMsg = OFTComposeMsgCodec.encode(
                _origin.nonce,
                _origin.srcEid,
                amountReceivedLD,
                _message.composeMsg()
            );

            // @dev Stores the lzCompose payload that will be executed in a separate tx.
            // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.
            // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.
            // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.
            // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.
            endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);
        }

        emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);
    }

    /**
     * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.
     * @param _origin The origin information.
     *  - srcEid: The source chain endpoint ID.
     *  - sender: The sender address from the src chain.
     *  - nonce: The nonce of the LayerZero message.
     * @param _guid The unique identifier for the received LayerZero message.
     * @param _message The LayerZero message.
     * @param _executor The address of the off-chain executor.
     * @param _extraData Arbitrary data passed by the msg executor.
     *
     * @dev Enables the preCrime simulator to mock sending lzReceive() messages,
     * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.
     */
    function _lzReceiveSimulate(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) internal virtual override {
        _lzReceive(_origin, _guid, _message, _executor, _extraData);
    }

    /**
     * @dev Check if the peer is considered 'trusted' by the OApp.
     * @param _eid The endpoint ID to check.
     * @param _peer The peer to check.
     * @return Whether the peer passed is considered 'trusted' by the OApp.
     *
     * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.
     */
    function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {
        return peers[_eid] == _peer;
    }

    /**
     * @dev Internal function to remove dust from the given local decimal amount.
     * @param _amountLD The amount in local decimals.
     * @return amountLD The amount after removing dust.
     *
     * @dev Prevents the loss of dust when moving amounts between chains with different decimals.
     * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).
     */
    function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {
        return (_amountLD / decimalConversionRate) * decimalConversionRate;
    }

    /**
     * @dev Internal function to convert an amount from shared decimals into local decimals.
     * @param _amountSD The amount in shared decimals.
     * @return amountLD The amount in local decimals.
     */
    function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {
        return _amountSD * decimalConversionRate;
    }

    /**
     * @dev Internal function to convert an amount from local decimals into shared decimals.
     * @param _amountLD The amount in local decimals.
     * @return amountSD The amount in shared decimals.
     */
    function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {
        return uint64(_amountLD / decimalConversionRate);
    }

    /**
     * @dev Internal function to mock the amount mutation from a OFT debit() operation.
     * @param _amountLD The amount to send in local decimals.
     * @param _minAmountLD The minimum amount to send in local decimals.
     * @dev _dstEid The destination endpoint ID.
     * @return amountSentLD The amount sent, in local decimals.
     * @return amountReceivedLD The amount to be received on the remote chain, in local decimals.
     *
     * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.
     */
    function _debitView(
        uint256 _amountLD,
        uint256 _minAmountLD,
        uint32 /*_dstEid*/
    ) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {
        // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.
        amountSentLD = _removeDust(_amountLD);
        // @dev The amount to send is the same as amount received in the default implementation.
        amountReceivedLD = amountSentLD;

        // @dev Check for slippage.
        if (amountReceivedLD < _minAmountLD) {
            revert SlippageExceeded(amountReceivedLD, _minAmountLD);
        }
    }

    /**
     * @dev Internal function to perform a debit operation.
     * @param _amountLD The amount to send in local decimals.
     * @param _minAmountLD The minimum amount to send in local decimals.
     * @param _dstEid The destination endpoint ID.
     * @return amountSentLD The amount sent in local decimals.
     * @return amountReceivedLD The amount received in local decimals on the remote.
     *
     * @dev Defined here but are intended to be overriden depending on the OFT implementation.
     * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.
     */
    function _debit(
        uint256 _amountLD,
        uint256 _minAmountLD,
        uint32 _dstEid
    ) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);

    /**
     * @dev Internal function to perform a credit operation.
     * @param _to The address to credit.
     * @param _amountLD The amount to credit in local decimals.
     * @param _srcEid The source endpoint ID.
     * @return amountReceivedLD The amount ACTUALLY received in local decimals.
     *
     * @dev Defined here but are intended to be overriden depending on the OFT implementation.
     * @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.
     */
    function _credit(
        address _to,
        uint256 _amountLD,
        uint32 _srcEid
    ) internal virtual returns (uint256 amountReceivedLD);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { IOAppReceiver, Origin } from "./interfaces/IOAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";

/**
 * @title OAppReceiver
 * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.
 */
abstract contract OAppReceiver is IOAppReceiver, OAppCore {
    // Custom error message for when the caller is not the registered endpoint/
    error OnlyEndpoint(address addr);

    // @dev The version of the OAppReceiver implementation.
    // @dev Version is bumped when changes are made to this contract.
    uint64 internal constant RECEIVER_VERSION = 1;

    /**
     * @notice Retrieves the OApp version information.
     * @return senderVersion The version of the OAppSender.sol contract.
     * @return receiverVersion The version of the OAppReceiver.sol contract.
     *
     * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.
     * ie. this is a RECEIVE only OApp.
     * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.
     */
    function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
        return (0, RECEIVER_VERSION);
    }

    /**
     * @notice Retrieves the address responsible for 'sending' composeMsg's to the Endpoint.
     * @return sender The address responsible for 'sending' composeMsg's to the Endpoint.
     *
     * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.
     * @dev The default sender IS the OApp implementer.
     */
    function composeMsgSender() public view virtual returns (address sender) {
        return address(this);
    }

    /**
     * @notice Checks if the path initialization is allowed based on the provided origin.
     * @param origin The origin information containing the source endpoint and sender address.
     * @return Whether the path has been initialized.
     *
     * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.
     * @dev This defaults to assuming if a peer has been set, its initialized.
     * Can be overridden by the OApp if there is other logic to determine this.
     */
    function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {
        return peers[origin.srcEid] == origin.sender;
    }

    /**
     * @notice Retrieves the next nonce for a given source endpoint and sender address.
     * @dev _srcEid The source endpoint ID.
     * @dev _sender The sender address.
     * @return nonce The next nonce.
     *
     * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.
     * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.
     * @dev This is also enforced by the OApp.
     * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.
     */
    function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {
        return 0;
    }

    /**
     * @dev Entry point for receiving messages or packets from the endpoint.
     * @param _origin The origin information containing the source endpoint and sender address.
     *  - srcEid: The source chain endpoint ID.
     *  - sender: The sender address on the src chain.
     *  - nonce: The nonce of the message.
     * @param _guid The unique identifier for the received LayerZero message.
     * @param _message The payload of the received message.
     * @param _executor The address of the executor for the received message.
     * @param _extraData Additional arbitrary data provided by the corresponding executor.
     *
     * @dev Entry point for receiving msg/packet from the LayerZero endpoint.
     */
    function lzReceive(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) public payable virtual {
        // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
        if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);

        // Ensure that the sender matches the expected peer for the source endpoint.
        if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);

        // Call the internal OApp implementation of lzReceive.
        _lzReceive(_origin, _guid, _message, _executor, _extraData);
    }

    /**
     * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.
     */
    function _lzReceive(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) internal virtual;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppCore, ILayerZeroEndpointV2 } from "./interfaces/IOAppCore.sol";

/**
 * @title OAppCore
 * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.
 */
abstract contract OAppCore is IOAppCore, Ownable {
    // The LayerZero endpoint associated with the given OApp
    ILayerZeroEndpointV2 public immutable endpoint;

    // Mapping to store peers associated with corresponding endpoints
    mapping(uint32 eid => bytes32 peer) public peers;

    /**
     * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.
     * @param _endpoint The address of the LOCAL Layer Zero endpoint.
     * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
     *
     * @dev The delegate typically should be set as the owner of the contract.
     */
    constructor(address _endpoint, address _delegate) {
        endpoint = ILayerZeroEndpointV2(_endpoint);

        if (_delegate == address(0)) revert InvalidDelegate();
        endpoint.setDelegate(_delegate);
    }

    /**
     * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @param _peer The address of the peer to be associated with the corresponding endpoint.
     *
     * @dev Only the owner/admin of the OApp can call this function.
     * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
     * @dev Set this to bytes32(0) to remove the peer address.
     * @dev Peer is a bytes32 to accommodate non-evm chains.
     */
    function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
        peers[_eid] = _peer;
        emit PeerSet(_eid, _peer);
    }

    /**
     * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.
     * ie. the peer is set to bytes32(0).
     * @param _eid The endpoint ID.
     * @return peer The address of the peer associated with the specified endpoint.
     */
    function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
        bytes32 peer = peers[_eid];
        if (peer == bytes32(0)) revert NoPeer(_eid);
        return peer;
    }

    /**
     * @notice Sets the delegate address for the OApp.
     * @param _delegate The address of the delegate to be set.
     *
     * @dev Only the owner/admin of the OApp can call this function.
     * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.
     */
    function setDelegate(address _delegate) public onlyOwner {
        endpoint.setDelegate(_delegate);
    }
}

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

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import { IMessageLibManager } from "./IMessageLibManager.sol";
import { IMessagingComposer } from "./IMessagingComposer.sol";
import { IMessagingChannel } from "./IMessagingChannel.sol";
import { IMessagingContext } from "./IMessagingContext.sol";

struct MessagingParams {
    uint32 dstEid;
    bytes32 receiver;
    bytes message;
    bytes options;
    bool payInLzToken;
}

struct MessagingReceipt {
    bytes32 guid;
    uint64 nonce;
    MessagingFee fee;
}

struct MessagingFee {
    uint256 nativeFee;
    uint256 lzTokenFee;
}

struct Origin {
    uint32 srcEid;
    bytes32 sender;
    uint64 nonce;
}

interface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {
    event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);

    event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);

    event PacketDelivered(Origin origin, address receiver);

    event LzReceiveAlert(
        address indexed receiver,
        address indexed executor,
        Origin origin,
        bytes32 guid,
        uint256 gas,
        uint256 value,
        bytes message,
        bytes extraData,
        bytes reason
    );

    event LzTokenSet(address token);

    event DelegateSet(address sender, address delegate);

    function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);

    function send(
        MessagingParams calldata _params,
        address _refundAddress
    ) external payable returns (MessagingReceipt memory);

    function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;

    function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);

    function initializable(Origin calldata _origin, address _receiver) external view returns (bool);

    function lzReceive(
        Origin calldata _origin,
        address _receiver,
        bytes32 _guid,
        bytes calldata _message,
        bytes calldata _extraData
    ) external payable;

    // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order
    function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;

    function setLzToken(address _lzToken) external;

    function lzToken() external view returns (address);

    function nativeToken() external view returns (address);

    function setDelegate(address _delegate) external;
}

// 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.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: 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.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: 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);   
    }
}

// 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 62 of 113 : 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.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: 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 70 of 113 : 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.4;

/// @notice Gas optimized ECDSA wrapper.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)
///
/// @dev Note:
/// - The recovery functions use the ecrecover precompile (0x1).
/// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure.
///   This is for more safety by default.
///   Use the `tryRecover` variants if you need to get the zero address back
///   upon recovery failure instead.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
///   regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
///   See: https://eips.ethereum.org/EIPS/eip-2098
///   This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT directly use signatures as unique identifiers:
/// - The recovery operations do NOT check if a signature is non-malleable.
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
///   EIP-712 also enables readable signing of typed data for better user safety.
/// - If you need a unique hash from a signature, please use the `canonicalHash` functions.
library ECDSA {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The order of the secp256k1 elliptic curve.
    uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;

    /// @dev `N/2 + 1`. Used for checking the malleability of the signature.
    uint256 private constant _HALF_N_PLUS_1 =
        0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CUSTOM ERRORS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The signature is invalid.
    error InvalidSignature();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    RECOVERY OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
    function recover(bytes32 hash, bytes memory signature) internal view returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            for { let m := mload(0x40) } 1 {
                mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
                revert(0x1c, 0x04)
            } {
                switch mload(signature)
                case 64 {
                    let vs := mload(add(signature, 0x40))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                }
                case 65 {
                    mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
                    mstore(0x60, mload(add(signature, 0x40))) // `s`.
                }
                default { continue }
                mstore(0x00, hash)
                mstore(0x40, mload(add(signature, 0x20))) // `r`.
                result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                if returndatasize() { break }
            }
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
    function recoverCalldata(bytes32 hash, bytes calldata signature)
        internal
        view
        returns (address result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            for { let m := mload(0x40) } 1 {
                mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
                revert(0x1c, 0x04)
            } {
                switch signature.length
                case 64 {
                    let vs := calldataload(add(signature.offset, 0x20))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x40, calldataload(signature.offset)) // `r`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                }
                case 65 {
                    mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
                    calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.
                }
                default { continue }
                mstore(0x00, hash)
                result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                if returndatasize() { break }
            }
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the EIP-2098 short form signature defined by `r` and `vs`.
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x00, hash)
            mstore(0x20, add(shr(255, vs), 27)) // `v`.
            mstore(0x40, r)
            mstore(0x60, shr(1, shl(1, vs))) // `s`.
            result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
            // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
            if iszero(returndatasize()) {
                mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the signature defined by `v`, `r`, `s`.
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        view
        returns (address result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x00, hash)
            mstore(0x20, and(v, 0xff))
            mstore(0x40, r)
            mstore(0x60, s)
            result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20))
            // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
            if iszero(returndatasize()) {
                mstore(0x00, 0x8baa579f) // `InvalidSignature()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   TRY-RECOVER OPERATIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // WARNING!
    // These functions will NOT revert upon recovery failure.
    // Instead, they will return the zero address upon recovery failure.
    // It is critical that the returned address is NEVER compared against
    // a zero address (e.g. an uninitialized address variable).

    /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
    function tryRecover(bytes32 hash, bytes memory signature)
        internal
        view
        returns (address result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            for { let m := mload(0x40) } 1 {} {
                switch mload(signature)
                case 64 {
                    let vs := mload(add(signature, 0x40))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                }
                case 65 {
                    mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
                    mstore(0x60, mload(add(signature, 0x40))) // `s`.
                }
                default { break }
                mstore(0x00, hash)
                mstore(0x40, mload(add(signature, 0x20))) // `r`.
                pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
                mstore(0x60, 0) // Restore the zero slot.
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                result := mload(xor(0x60, returndatasize()))
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`.
    function tryRecoverCalldata(bytes32 hash, bytes calldata signature)
        internal
        view
        returns (address result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            for { let m := mload(0x40) } 1 {} {
                switch signature.length
                case 64 {
                    let vs := calldataload(add(signature.offset, 0x20))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x40, calldataload(signature.offset)) // `r`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                }
                case 65 {
                    mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
                    calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`.
                }
                default { break }
                mstore(0x00, hash)
                pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
                mstore(0x60, 0) // Restore the zero slot.
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                result := mload(xor(0x60, returndatasize()))
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the EIP-2098 short form signature defined by `r` and `vs`.
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs)
        internal
        view
        returns (address result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x00, hash)
            mstore(0x20, add(shr(255, vs), 27)) // `v`.
            mstore(0x40, r)
            mstore(0x60, shr(1, shl(1, vs))) // `s`.
            pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
            mstore(0x60, 0) // Restore the zero slot.
            // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
            result := mload(xor(0x60, returndatasize()))
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the signature defined by `v`, `r`, `s`.
    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        view
        returns (address result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x00, hash)
            mstore(0x20, and(v, 0xff))
            mstore(0x40, r)
            mstore(0x60, s)
            pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20))
            mstore(0x60, 0) // Restore the zero slot.
            // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
            result := mload(xor(0x60, returndatasize()))
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     HASHING OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an Ethereum Signed Message, created from a `hash`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x20, hash) // Store into scratch space for keccak256.
            mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
            result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
        }
    }

    /// @dev Returns an Ethereum Signed Message, created from `s`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    /// Note: Supports lengths of `s` up to 999999 bytes.
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let sLength := mload(s)
            let o := 0x20
            mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
            mstore(0x00, 0x00)
            // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
            for { let temp := sLength } 1 {} {
                o := sub(o, 1)
                mstore8(o, add(48, mod(temp, 10)))
                temp := div(temp, 10)
                if iszero(temp) { break }
            }
            let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
            // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
            returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
            mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
            result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
            mstore(s, sLength) // Restore the length.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  CANONICAL HASH FUNCTIONS                  */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // The following functions return the hash of the signature in its canonicalized format,
    // which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28.
    // If `s` is greater than `N / 2` then it will be converted to `N - s`
    // and the `v` value will be flipped.
    // If the signature has an invalid length, or if `v` is invalid,
    // a uniquely corrupt hash will be returned.
    // These functions are useful for "poor-mans-VRF".

    /// @dev Returns the canonical hash of `signature`.
    function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let l := mload(signature)
            for {} 1 {} {
                mstore(0x00, mload(add(signature, 0x20))) // `r`.
                let s := mload(add(signature, 0x40))
                let v := mload(add(signature, 0x41))
                if eq(l, 64) {
                    v := add(shr(255, s), 27)
                    s := shr(1, shl(1, s))
                }
                if iszero(lt(s, _HALF_N_PLUS_1)) {
                    v := xor(v, 7)
                    s := sub(N, s)
                }
                mstore(0x21, v)
                mstore(0x20, s)
                result := keccak256(0x00, 0x41)
                mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
                break
            }

            // If the length is neither 64 nor 65, return a uniquely corrupted hash.
            if iszero(lt(sub(l, 64), 2)) {
                // `bytes4(keccak256("InvalidSignatureLength"))`.
                result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2)
            }
        }
    }

    /// @dev Returns the canonical hash of `signature`.
    function canonicalHashCalldata(bytes calldata signature)
        internal
        pure
        returns (bytes32 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            for {} 1 {} {
                mstore(0x00, calldataload(signature.offset)) // `r`.
                let s := calldataload(add(signature.offset, 0x20))
                let v := calldataload(add(signature.offset, 0x21))
                if eq(signature.length, 64) {
                    v := add(shr(255, s), 27)
                    s := shr(1, shl(1, s))
                }
                if iszero(lt(s, _HALF_N_PLUS_1)) {
                    v := xor(v, 7)
                    s := sub(N, s)
                }
                mstore(0x21, v)
                mstore(0x20, s)
                result := keccak256(0x00, 0x41)
                mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
                break
            }
            // If the length is neither 64 nor 65, return a uniquely corrupted hash.
            if iszero(lt(sub(signature.length, 64), 2)) {
                calldatacopy(mload(0x40), signature.offset, signature.length)
                // `bytes4(keccak256("InvalidSignatureLength"))`.
                result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2)
            }
        }
    }

    /// @dev Returns the canonical hash of `signature`.
    function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, r) // `r`.
            let v := add(shr(255, vs), 27)
            let s := shr(1, shl(1, vs))
            mstore(0x21, v)
            mstore(0x20, s)
            result := keccak256(0x00, 0x41)
            mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
        }
    }

    /// @dev Returns the canonical hash of `signature`.
    function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, r) // `r`.
            if iszero(lt(s, _HALF_N_PLUS_1)) {
                v := xor(v, 7)
                s := sub(N, s)
            }
            mstore(0x21, v)
            mstore(0x20, s)
            result := keccak256(0x00, 0x41)
            mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes.
    function emptySignature() internal pure returns (bytes calldata signature) {
        /// @solidity memory-safe-assembly
        assembly {
            signature.length := 0
        }
    }
}

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

// Original Author: https://github.com/hamdiallam/Solidity-RLP/blob/master/contracts/RLPReader.sol
library RLPDecoder {
    uint8 constant STRING_SHORT_START = 0x80;
    uint8 constant STRING_LONG_START  = 0xb8;
    uint8 constant LIST_SHORT_START   = 0xc0;
    uint8 constant LIST_LONG_START    = 0xf8;
    uint8 constant WORD_SIZE = 32;

    struct RLPItem {
        uint len;
        uint memPtr;
    }

    struct Iterator {
        RLPItem item;   // Item that's being iterated over.
        uint nextPtr;   // Position of the next item in the list.
    }

    /*
    * @dev Returns the next element in the iteration. Reverts if it has not next element.
    * @param self The iterator.
    * @return The next element in the iteration.
    */
    function next(Iterator memory self) internal pure returns (RLPItem memory) {
        require(hasNext(self));

        uint ptr = self.nextPtr;
        uint itemLength = _itemLength(ptr);
        self.nextPtr = ptr + itemLength;

        return RLPItem(itemLength, ptr);
    }

    /*
    * @dev Returns true if the iteration has more elements.
    * @param self The iterator.
    * @return true if the iteration has more elements.
    */
    function hasNext(Iterator memory self) internal pure returns (bool) {
        RLPItem memory item = self.item;
        return self.nextPtr < item.memPtr + item.len;
    }

    /*
    * @param item RLP encoded bytes
    */
    function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) {
        uint memPtr;
        assembly {
            memPtr := add(item, 0x20)
        }

        return RLPItem(item.length, memPtr);
    }

    /*
    * @dev Create an iterator. Reverts if item is not a list.
    * @param self The RLP item.
    * @return An 'Iterator' over the item.
    */
    function iterator(RLPItem memory self) internal pure returns (Iterator memory) {
        require(isList(self));

        uint ptr = self.memPtr + _payloadOffset(self.memPtr);
        return Iterator(self, ptr);
    }

    /*
    * @param the RLP item.
    */
    function rlpLen(RLPItem memory item) internal pure returns (uint) {
        return item.len;
    }

    /*
     * @param the RLP item.
     * @return (memPtr, len) pair: location of the item's payload in memory.
     */
    function payloadLocation(RLPItem memory item) internal pure returns (uint, uint) {
        uint offset = _payloadOffset(item.memPtr);
        uint memPtr = item.memPtr + offset;
        uint len = item.len - offset; // data length
        return (memPtr, len);
    }

    /*
    * @param the RLP item.
    */
    function payloadLen(RLPItem memory item) internal pure returns (uint) {
        (, uint len) = payloadLocation(item);
        return len;
    }

    /*
    * @param the RLP item containing the encoded list.
    */
    function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) {
        require(isList(item));

        uint items = numItems(item);
        RLPItem[] memory result = new RLPItem[](items);

        uint memPtr = item.memPtr + _payloadOffset(item.memPtr);
        uint dataLen;
        for (uint i = 0; i < items; i++) {
            dataLen = _itemLength(memPtr);
            result[i] = RLPItem(dataLen, memPtr); 
            memPtr = memPtr + dataLen;
        }

        return result;
    }

    // @return indicator whether encoded payload is a list. negate this function call for isData.
    function isList(RLPItem memory item) internal pure returns (bool) {
        if (item.len == 0) return false;

        uint8 byte0;
        uint memPtr = item.memPtr;
        assembly {
            byte0 := byte(0, mload(memPtr))
        }

        if (byte0 < LIST_SHORT_START)
            return false;
        return true;
    }

    /*
     * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory.
     * @return keccak256 hash of RLP encoded bytes.
     */
    function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) {
        uint ptr = item.memPtr;
        uint len = item.len;
        bytes32 result;
        assembly {
            result := keccak256(ptr, len)
        }
        return result;
    }

    /*
     * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory.
     * @return keccak256 hash of the item payload.
     */
    function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) {
        (uint memPtr, uint len) = payloadLocation(item);
        bytes32 result;
        assembly {
            result := keccak256(memPtr, len)
        }
        return result;
    }

    /** RLPItem conversions into data types **/

    // @returns raw rlp encoding in bytes
    function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) {
        bytes memory result = new bytes(item.len);
        if (result.length == 0) return result;
        
        uint ptr;
        assembly {
            ptr := add(0x20, result)
        }

        copy(item.memPtr, ptr, item.len);
        return result;
    }

    // any non-zero byte except "0x80" is considered true
    function toBoolean(RLPItem memory item) internal pure returns (bool) {
        require(item.len == 1);
        uint result;
        uint memPtr = item.memPtr;
        assembly {
            result := byte(0, mload(memPtr))
        }

        // SEE Github Issue #5.
        // Summary: Most commonly used RLP libraries (i.e Geth) will encode
        // "0" as "0x80" instead of as "0". We handle this edge case explicitly
        // here.
        if (result == 0 || result == STRING_SHORT_START) {
            return false;
        } else {
            return true;
        }
    }

    function toAddress(RLPItem memory item) internal pure returns (address) {
        // 1 byte for the length prefix
        require(item.len == 21);

        return address(uint160(toUint(item)));
    }

    function toUint(RLPItem memory item) internal pure returns (uint) {
        require(item.len > 0 && item.len <= 33);

        (uint memPtr, uint len) = payloadLocation(item);

        uint result;
        assembly {
            result := mload(memPtr)

            // shift to the correct location if necessary
            if lt(len, 32) {
                result := div(result, exp(256, sub(32, len)))
            }
        }

        return result;
    }

    // enforces 32 byte length
    function toUintStrict(RLPItem memory item) internal pure returns (uint) {
        // one byte prefix
        require(item.len == 33);

        uint result;
        uint memPtr = item.memPtr + 1;
        assembly {
            result := mload(memPtr)
        }

        return result;
    }

    function toBytes(RLPItem memory item) internal pure returns (bytes memory) {
        require(item.len > 0);

        (uint memPtr, uint len) = payloadLocation(item);
        bytes memory result = new bytes(len);

        uint destPtr;
        assembly {
            destPtr := add(0x20, result)
        }

        copy(memPtr, destPtr, len);
        return result;
    }

    /*
    * Private Helpers
    */

    // @return number of payload items inside an encoded list.
    function numItems(RLPItem memory item) private pure returns (uint) {
        if (item.len == 0) return 0;

        uint count = 0;
        uint currPtr = item.memPtr + _payloadOffset(item.memPtr);
        uint endPtr = item.memPtr + item.len;
        while (currPtr < endPtr) {
           currPtr = currPtr + _itemLength(currPtr); // skip over an item
           count++;
        }

        return count;
    }

    // @return entire rlp item byte length
    function _itemLength(uint memPtr) private pure returns (uint) {
        uint itemLen;
        uint byte0;
        assembly {
            byte0 := byte(0, mload(memPtr))
        }

        if (byte0 < STRING_SHORT_START)
            itemLen = 1;
        
        else if (byte0 < STRING_LONG_START)
            itemLen = byte0 - STRING_SHORT_START + 1;

        else if (byte0 < LIST_SHORT_START) {
            assembly {
                let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
                memPtr := add(memPtr, 1) // skip over the first byte
                
                /* 32 byte word size */
                let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
                itemLen := add(dataLen, add(byteLen, 1))
            }
        }

        else if (byte0 < LIST_LONG_START) {
            itemLen = byte0 - LIST_SHORT_START + 1;
        } 

        else {
            assembly {
                let byteLen := sub(byte0, 0xf7)
                memPtr := add(memPtr, 1)

                let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
                itemLen := add(dataLen, add(byteLen, 1))
            }
        }

        return itemLen;
    }

    // @return number of bytes until the data
    function _payloadOffset(uint memPtr) private pure returns (uint) {
        uint byte0;
        assembly {
            byte0 := byte(0, mload(memPtr))
        }

        if (byte0 < STRING_SHORT_START) 
            return 0;
        else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START))
            return 1;
        else if (byte0 < LIST_SHORT_START)  // being explicit
            return byte0 - (STRING_LONG_START - 1) + 1;
        else
            return byte0 - (LIST_LONG_START - 1) + 1;
    }

    /*
    * @param src Pointer to source
    * @param dest Pointer to destination
    * @param len Amount of memory to copy from the source
    */
    function copy(uint src, uint dest, uint len) private pure {
        if (len == 0) return;

        // copy as many word sizes as possible
        for (; len >= WORD_SIZE; len -= WORD_SIZE) {
            assembly {
                mstore(dest, mload(src))
            }

            src += WORD_SIZE;
            dest += WORD_SIZE;
        }

        if (len > 0) {
            // left over bytes. Mask is used to remove unwanted bytes from the word
            uint mask = 256 ** (WORD_SIZE - len) - 1;
            assembly {
                let srcpart := and(mload(src), not(mask)) // zero out src
                let destpart := and(mload(dest), mask) // retrieve the bytes
                mstore(dest, or(destpart, srcpart))
            }
        }
    }
}

File 73 of 113 : 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.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: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppOptionsType3, EnforcedOptionParam } from "../interfaces/IOAppOptionsType3.sol";

/**
 * @title OAppOptionsType3
 * @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.
 */
abstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {
    uint16 internal constant OPTION_TYPE_3 = 3;

    // @dev The "msgType" should be defined in the child contract.
    mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;

    /**
     * @dev Sets the enforced options for specific endpoint and message type combinations.
     * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.
     *
     * @dev Only the owner/admin of the OApp can call this function.
     * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.
     * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.
     * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay
     * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().
     */
    function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {
        for (uint256 i = 0; i < _enforcedOptions.length; i++) {
            // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.
            _assertOptionsType3(_enforcedOptions[i].options);
            enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;
        }

        emit EnforcedOptionSet(_enforcedOptions);
    }

    /**
     * @notice Combines options for a given endpoint and message type.
     * @param _eid The endpoint ID.
     * @param _msgType The OAPP message type.
     * @param _extraOptions Additional options passed by the caller.
     * @return options The combination of caller specified options AND enforced options.
     *
     * @dev If there is an enforced lzReceive option:
     * - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}
     * - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.
     * @dev This presence of duplicated options is handled off-chain in the verifier/executor.
     */
    function combineOptions(
        uint32 _eid,
        uint16 _msgType,
        bytes calldata _extraOptions
    ) public view virtual returns (bytes memory) {
        bytes memory enforced = enforcedOptions[_eid][_msgType];

        // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.
        if (enforced.length == 0) return _extraOptions;

        // No caller options, return enforced
        if (_extraOptions.length == 0) return enforced;

        // @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.
        if (_extraOptions.length >= 2) {
            _assertOptionsType3(_extraOptions);
            // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.
            return bytes.concat(enforced, _extraOptions[2:]);
        }

        // No valid set of options was found.
        revert InvalidOptions(_extraOptions);
    }

    /**
     * @dev Internal function to assert that options are of type 3.
     * @param _options The options to be checked.
     */
    function _assertOptionsType3(bytes calldata _options) internal pure virtual {
        uint16 optionsType = uint16(bytes2(_options[0:2]));
        if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

/**
 * @title IOAppMsgInspector
 * @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.
 */
interface IOAppMsgInspector {
    // Custom error message for inspection failure
    error InspectionFailed(bytes message, bytes options);

    /**
     * @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.
     * @param _message The message payload to be inspected.
     * @param _options Additional options or parameters for inspection.
     * @return valid A boolean indicating whether the inspection passed (true) or failed (false).
     *
     * @dev Optionally done as a revert, OR use the boolean provided to handle the failure.
     */
    function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IPreCrime } from "./interfaces/IPreCrime.sol";
import { IOAppPreCrimeSimulator, InboundPacket, Origin } from "./interfaces/IOAppPreCrimeSimulator.sol";

/**
 * @title OAppPreCrimeSimulator
 * @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.
 */
abstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {
    // The address of the preCrime implementation.
    address public preCrime;

    /**
     * @dev Retrieves the address of the OApp contract.
     * @return The address of the OApp contract.
     *
     * @dev The simulator contract is the base contract for the OApp by default.
     * @dev If the simulator is a separate contract, override this function.
     */
    function oApp() external view virtual returns (address) {
        return address(this);
    }

    /**
     * @dev Sets the preCrime contract address.
     * @param _preCrime The address of the preCrime contract.
     */
    function setPreCrime(address _preCrime) public virtual onlyOwner {
        preCrime = _preCrime;
        emit PreCrimeSet(_preCrime);
    }

    /**
     * @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.
     * @param _packets An array of InboundPacket objects representing received packets to be delivered.
     *
     * @dev WARNING: MUST revert at the end with the simulation results.
     * @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,
     * WITHOUT actually executing them.
     */
    function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {
        for (uint256 i = 0; i < _packets.length; i++) {
            InboundPacket calldata packet = _packets[i];

            // Ignore packets that are not from trusted peers.
            if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;

            // @dev Because a verifier is calling this function, it doesnt have access to executor params:
            //  - address _executor
            //  - bytes calldata _extraData
            // preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().
            // They are instead stubbed to default values, address(0) and bytes("")
            // @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,
            // which would cause the revert to be ignored.
            this.lzReceiveSimulate{ value: packet.value }(
                packet.origin,
                packet.guid,
                packet.message,
                packet.executor,
                packet.extraData
            );
        }

        // @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().
        revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());
    }

    /**
     * @dev Is effectively an internal function because msg.sender must be address(this).
     * Allows resetting the call stack for 'internal' calls.
     * @param _origin The origin information containing the source endpoint and sender address.
     *  - srcEid: The source chain endpoint ID.
     *  - sender: The sender address on the src chain.
     *  - nonce: The nonce of the message.
     * @param _guid The unique identifier of the packet.
     * @param _message The message payload of the packet.
     * @param _executor The executor address for the packet.
     * @param _extraData Additional data for the packet.
     */
    function lzReceiveSimulate(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) external payable virtual {
        // @dev Ensure ONLY can be called 'internally'.
        if (msg.sender != address(this)) revert OnlySelf();
        _lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);
    }

    /**
     * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.
     * @param _origin The origin information.
     *  - srcEid: The source chain endpoint ID.
     *  - sender: The sender address from the src chain.
     *  - nonce: The nonce of the LayerZero message.
     * @param _guid The GUID of the LayerZero message.
     * @param _message The LayerZero message.
     * @param _executor The address of the off-chain executor.
     * @param _extraData Arbitrary data passed by the msg executor.
     *
     * @dev Enables the preCrime simulator to mock sending lzReceive() messages,
     * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.
     */
    function _lzReceiveSimulate(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) internal virtual;

    /**
     * @dev checks if the specified peer is considered 'trusted' by the OApp.
     * @param _eid The endpoint Id to check.
     * @param _peer The peer to check.
     * @return Whether the peer passed is considered 'trusted' by the OApp.
     */
    function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);
}

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

import { ILayerZeroReceiver, Origin } from "./ILayerZeroReceiver.sol";

interface IOAppReceiver is ILayerZeroReceiver {
    /**
     * @notice Retrieves the address responsible for 'sending' composeMsg's to the Endpoint.
     * @return sender The address responsible for 'sending' composeMsg's to the Endpoint.
     *
     * @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.
     * @dev The default sender IS the OApp implementer.
     */
    function composeMsgSender() external view returns (address sender);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

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

/**
 * @title IOAppCore
 */
interface IOAppCore {
    // Custom error messages
    error OnlyPeer(uint32 eid, bytes32 sender);
    error NoPeer(uint32 eid);
    error InvalidEndpointCall();
    error InvalidDelegate();

    // Event emitted when a peer (OApp) is set for a corresponding endpoint
    event PeerSet(uint32 eid, bytes32 peer);

    /**
     * @notice Retrieves the OApp version information.
     * @return senderVersion The version of the OAppSender.sol contract.
     * @return receiverVersion The version of the OAppReceiver.sol contract.
     */
    function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);

    /**
     * @notice Retrieves the LayerZero endpoint associated with the OApp.
     * @return iEndpoint The LayerZero endpoint as an interface.
     */
    function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);

    /**
     * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @return peer The peer address (OApp instance) associated with the corresponding endpoint.
     */
    function peers(uint32 _eid) external view returns (bytes32 peer);

    /**
     * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @param _peer The address of the peer to be associated with the corresponding endpoint.
     */
    function setPeer(uint32 _eid, bytes32 _peer) external;

    /**
     * @notice Sets the delegate address for the OApp Core.
     * @param _delegate The address of the delegate to be set.
     */
    function setDelegate(address _delegate) external;
}

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

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

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

struct SetConfigParam {
    uint32 eid;
    uint32 configType;
    bytes config;
}

interface IMessageLibManager {
    struct Timeout {
        address lib;
        uint256 expiry;
    }

    event LibraryRegistered(address newLib);
    event DefaultSendLibrarySet(uint32 eid, address newLib);
    event DefaultReceiveLibrarySet(uint32 eid, address newLib);
    event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);
    event SendLibrarySet(address sender, uint32 eid, address newLib);
    event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);
    event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);

    function registerLibrary(address _lib) external;

    function isRegisteredLibrary(address _lib) external view returns (bool);

    function getRegisteredLibraries() external view returns (address[] memory);

    function setDefaultSendLibrary(uint32 _eid, address _newLib) external;

    function defaultSendLibrary(uint32 _eid) external view returns (address);

    function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;

    function defaultReceiveLibrary(uint32 _eid) external view returns (address);

    function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;

    function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);

    function isSupportedEid(uint32 _eid) external view returns (bool);

    function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);

    /// ------------------- OApp interfaces -------------------
    function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;

    function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);

    function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);

    function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;

    function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);

    function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;

    function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);

    function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;

    function getConfig(
        address _oapp,
        address _lib,
        uint32 _eid,
        uint32 _configType
    ) external view returns (bytes memory config);
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface IMessagingComposer {
    event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);
    event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);
    event LzComposeAlert(
        address indexed from,
        address indexed to,
        address indexed executor,
        bytes32 guid,
        uint16 index,
        uint256 gas,
        uint256 value,
        bytes message,
        bytes extraData,
        bytes reason
    );

    function composeQueue(
        address _from,
        address _to,
        bytes32 _guid,
        uint16 _index
    ) external view returns (bytes32 messageHash);

    function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;

    function lzCompose(
        address _from,
        address _to,
        bytes32 _guid,
        uint16 _index,
        bytes calldata _message,
        bytes calldata _extraData
    ) external payable;
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface IMessagingChannel {
    event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);
    event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);
    event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);

    function eid() external view returns (uint32);

    // this is an emergency function if a message cannot be verified for some reasons
    // required to provide _nextNonce to avoid race condition
    function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;

    function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;

    function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;

    function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);

    function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);

    function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);

    function inboundPayloadHash(
        address _receiver,
        uint32 _srcEid,
        bytes32 _sender,
        uint64 _nonce
    ) external view returns (bytes32);

    function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface IMessagingContext {
    function isSendingMessage() external view returns (bool);

    function getSendContext() external view returns (uint32 dstEid, address sender);
}

// 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 90 of 113 : 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

pragma solidity ^0.8.20;

/**
 * @dev Struct representing enforced option parameters.
 */
struct EnforcedOptionParam {
    uint32 eid; // Endpoint ID
    uint16 msgType; // Message Type
    bytes options; // Additional options
}

/**
 * @title IOAppOptionsType3
 * @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.
 */
interface IOAppOptionsType3 {
    // Custom error message for invalid options
    error InvalidOptions(bytes options);

    // Event emitted when enforced options are set
    event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);

    /**
     * @notice Sets enforced options for specific endpoint and message type combinations.
     * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.
     */
    function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;

    /**
     * @notice Combines options for a given endpoint and message type.
     * @param _eid The endpoint ID.
     * @param _msgType The OApp message type.
     * @param _extraOptions Additional options passed by the caller.
     * @return options The combination of caller specified options AND enforced options.
     */
    function combineOptions(
        uint32 _eid,
        uint16 _msgType,
        bytes calldata _extraOptions
    ) external view returns (bytes memory options);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;
struct PreCrimePeer {
    uint32 eid;
    bytes32 preCrime;
    bytes32 oApp;
}

// TODO not done yet
interface IPreCrime {
    error OnlyOffChain();

    // for simulate()
    error PacketOversize(uint256 max, uint256 actual);
    error PacketUnsorted();
    error SimulationFailed(bytes reason);

    // for preCrime()
    error SimulationResultNotFound(uint32 eid);
    error InvalidSimulationResult(uint32 eid, bytes reason);
    error CrimeFound(bytes crime);

    function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);

    function simulate(
        bytes[] calldata _packets,
        uint256[] calldata _packetMsgValues
    ) external payable returns (bytes memory);

    function buildSimulationResult() external view returns (bytes memory);

    function preCrime(
        bytes[] calldata _packets,
        uint256[] calldata _packetMsgValues,
        bytes[] calldata _simulations
    ) external;

    function version() external view returns (uint64 major, uint8 minor);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.
// solhint-disable-next-line no-unused-import
import { InboundPacket, Origin } from "../libs/Packet.sol";

/**
 * @title IOAppPreCrimeSimulator Interface
 * @dev Interface for the preCrime simulation functionality in an OApp.
 */
interface IOAppPreCrimeSimulator {
    // @dev simulation result used in PreCrime implementation
    error SimulationResult(bytes result);
    error OnlySelf();

    /**
     * @dev Emitted when the preCrime contract address is set.
     * @param preCrimeAddress The address of the preCrime contract.
     */
    event PreCrimeSet(address preCrimeAddress);

    /**
     * @dev Retrieves the address of the preCrime contract implementation.
     * @return The address of the preCrime contract.
     */
    function preCrime() external view returns (address);

    /**
     * @dev Retrieves the address of the OApp contract.
     * @return The address of the OApp contract.
     */
    function oApp() external view returns (address);

    /**
     * @dev Sets the preCrime contract address.
     * @param _preCrime The address of the preCrime contract.
     */
    function setPreCrime(address _preCrime) external;

    /**
     * @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.
     * @param _packets An array of LayerZero InboundPacket objects representing received packets.
     */
    function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;

    /**
     * @dev checks if the specified peer is considered 'trusted' by the OApp.
     * @param _eid The endpoint Id to check.
     * @param _peer The peer to check.
     * @return Whether the peer passed is considered 'trusted' by the OApp.
     */
    function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import { Origin } from "./ILayerZeroEndpointV2.sol";

interface ILayerZeroReceiver {
    function allowInitializePath(Origin calldata _origin) external view returns (bool);

    function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);

    function lzReceive(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) external payable;
}

File 105 of 113 : 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 106 of 113 : 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)
            }
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { Origin } from "../interfaces/ILayerZeroEndpointV2.sol";
import { PacketV1Codec } from "./PacketV1Codec.sol";

/**
 * @title InboundPacket
 * @dev Structure representing an inbound packet received by the contract.
 */
struct InboundPacket {
    Origin origin; // Origin information of the packet.
    uint32 dstEid; // Destination endpointId of the packet.
    address receiver; // Receiver address for the packet.
    bytes32 guid; // Unique identifier of the packet.
    uint256 value; // msg.value of the packet.
    address executor; // Executor address for the packet.
    bytes message; // Message payload of the packet.
    bytes extraData; // Additional arbitrary data for the packet.
}

/**
 * @title PacketDecoder
 * @dev Library for decoding LayerZero packets.
 */
library PacketDecoder {
    using PacketV1Codec for bytes;

    /**
     * @dev Decode an inbound packet from the given packet data.
     * @param _packet The packet data to decode.
     * @return packet An InboundPacket struct representing the decoded packet.
     */
    function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {
        packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());
        packet.dstEid = _packet.dstEid();
        packet.receiver = _packet.receiverB20();
        packet.guid = _packet.guid();
        packet.message = _packet.message();
    }

    /**
     * @dev Decode multiple inbound packets from the given packet data and associated message values.
     * @param _packets An array of packet data to decode.
     * @param _packetMsgValues An array of associated message values for each packet.
     * @return packets An array of InboundPacket structs representing the decoded packets.
     */
    function decode(
        bytes[] calldata _packets,
        uint256[] memory _packetMsgValues
    ) internal pure returns (InboundPacket[] memory packets) {
        packets = new InboundPacket[](_packets.length);
        for (uint256 i = 0; i < _packets.length; i++) {
            bytes calldata packet = _packets[i];
            packets[i] = PacketDecoder.decode(packet);
            // @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.
            packets[i].value = _packetMsgValues[i];
        }
    }
}

// SPDX-License-Identifier: LZBL-1.2

pragma solidity ^0.8.20;

import { Packet } from "../interfaces/ISendLib.sol";
import { AddressCast } from "./AddressCast.sol";

library PacketV1Codec {
    using AddressCast for address;
    using AddressCast for bytes32;

    uint8 internal constant PACKET_VERSION = 1;

    // header (version + nonce + path)
    // version
    uint256 private constant PACKET_VERSION_OFFSET = 0;
    //    nonce
    uint256 private constant NONCE_OFFSET = 1;
    //    path
    uint256 private constant SRC_EID_OFFSET = 9;
    uint256 private constant SENDER_OFFSET = 13;
    uint256 private constant DST_EID_OFFSET = 45;
    uint256 private constant RECEIVER_OFFSET = 49;
    // payload (guid + message)
    uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)
    uint256 private constant MESSAGE_OFFSET = 113;

    function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {
        encodedPacket = abi.encodePacked(
            PACKET_VERSION,
            _packet.nonce,
            _packet.srcEid,
            _packet.sender.toBytes32(),
            _packet.dstEid,
            _packet.receiver,
            _packet.guid,
            _packet.message
        );
    }

    function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {
        return
            abi.encodePacked(
                PACKET_VERSION,
                _packet.nonce,
                _packet.srcEid,
                _packet.sender.toBytes32(),
                _packet.dstEid,
                _packet.receiver
            );
    }

    function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {
        return abi.encodePacked(_packet.guid, _packet.message);
    }

    function header(bytes calldata _packet) internal pure returns (bytes calldata) {
        return _packet[0:GUID_OFFSET];
    }

    function version(bytes calldata _packet) internal pure returns (uint8) {
        return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));
    }

    function nonce(bytes calldata _packet) internal pure returns (uint64) {
        return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));
    }

    function srcEid(bytes calldata _packet) internal pure returns (uint32) {
        return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));
    }

    function sender(bytes calldata _packet) internal pure returns (bytes32) {
        return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);
    }

    function senderAddressB20(bytes calldata _packet) internal pure returns (address) {
        return sender(_packet).toAddress();
    }

    function dstEid(bytes calldata _packet) internal pure returns (uint32) {
        return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));
    }

    function receiver(bytes calldata _packet) internal pure returns (bytes32) {
        return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);
    }

    function receiverB20(bytes calldata _packet) internal pure returns (address) {
        return receiver(_packet).toAddress();
    }

    function guid(bytes calldata _packet) internal pure returns (bytes32) {
        return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);
    }

    function message(bytes calldata _packet) internal pure returns (bytes calldata) {
        return bytes(_packet[MESSAGE_OFFSET:]);
    }

    function payload(bytes calldata _packet) internal pure returns (bytes calldata) {
        return bytes(_packet[GUID_OFFSET:]);
    }

    function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {
        return keccak256(payload(_packet));
    }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import { MessagingFee } from "./ILayerZeroEndpointV2.sol";
import { IMessageLib } from "./IMessageLib.sol";

struct Packet {
    uint64 nonce;
    uint32 srcEid;
    address sender;
    uint32 dstEid;
    bytes32 receiver;
    bytes32 guid;
    bytes message;
}

interface ISendLib is IMessageLib {
    function send(
        Packet calldata _packet,
        bytes calldata _options,
        bool _payInLzToken
    ) external returns (MessagingFee memory, bytes memory encodedPacket);

    function quote(
        Packet calldata _packet,
        bytes calldata _options,
        bool _payInLzToken
    ) external view returns (MessagingFee memory);

    function setTreasury(address _treasury) external;

    function withdrawFee(address _to, uint256 _amount) external;

    function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;
}

// SPDX-License-Identifier: LZBL-1.2

pragma solidity ^0.8.20;

library AddressCast {
    error AddressCast_InvalidSizeForAddress();
    error AddressCast_InvalidAddress();

    function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {
        if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();
        result = bytes32(_addressBytes);
        unchecked {
            uint256 offset = 32 - _addressBytes.length;
            result = result >> (offset * 8);
        }
    }

    function toBytes32(address _address) internal pure returns (bytes32 result) {
        result = bytes32(uint256(uint160(_address)));
    }

    function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {
        if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();
        result = new bytes(_size);
        unchecked {
            uint256 offset = 256 - _size * 8;
            assembly {
                mstore(add(result, 32), shl(offset, _addressBytes32))
            }
        }
    }

    function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {
        result = address(uint160(uint256(_addressBytes32)));
    }

    function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {
        if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();
        result = address(bytes20(_addressBytes));
    }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import { SetConfigParam } from "./IMessageLibManager.sol";

enum MessageLibType {
    Send,
    Receive,
    SendAndReceive
}

interface IMessageLib is IERC165 {
    function setConfig(address _oapp, SetConfigParam[] calldata _config) external;

    function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);

    function isSupportedEid(uint32 _eid) external view returns (bool);

    // message libs of same major version are compatible
    function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);

    function messageLibType() external view returns (MessageLibType);
}

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

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/",
    "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": 100
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "none",
    "appendCBOR": false
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {
    "src/Basket.sol": {
      "BasketLib": "0xe88bda54bccb0ac383bcc71843b079175cb480bb"
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_rover","type":"address"},{"internalType":"address","name":"_aux","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidDelegate","type":"error"},{"inputs":[],"name":"InvalidEndpointCall","type":"error"},{"inputs":[],"name":"InvalidLocalDecimals","type":"error"},{"inputs":[],"name":"InvalidMarketId","type":"error"},{"inputs":[],"name":"InvalidMessageLength","type":"error"},{"inputs":[],"name":"InvalidMessageType","type":"error"},{"inputs":[{"internalType":"bytes","name":"options","type":"bytes"}],"name":"InvalidOptions","type":"error"},{"inputs":[],"name":"LzTokenUnavailable","type":"error"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"}],"name":"NoPeer","type":"error"},{"inputs":[{"internalType":"uint256","name":"msgValue","type":"uint256"}],"name":"NotEnoughNative","type":"error"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"OnlyEndpoint","type":"error"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"}],"name":"OnlyPeer","type":"error"},{"inputs":[],"name":"OnlySelf","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"name":"SimulationResult","type":"error"},{"inputs":[{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"}],"name":"SlippageExceeded","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"uint16","name":"msgType","type":"uint16"},{"internalType":"bytes","name":"options","type":"bytes"}],"indexed":false,"internalType":"struct EnforcedOptionParam[]","name":"_enforcedOptions","type":"tuple[]"}],"name":"EnforcedOptionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"inspector","type":"address"}],"name":"MsgInspectorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"guid","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"srcEid","type":"uint32"},{"indexed":true,"internalType":"address","name":"toAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"name":"OFTReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"guid","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"dstEid","type":"uint32"},{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountSentLD","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"name":"OFTSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"OperatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"eid","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"peer","type":"bytes32"}],"name":"PeerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"preCrimeAddress","type":"address"}],"name":"PreCrimeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"guid","type":"bytes32"},{"indexed":false,"internalType":"uint8","name":"msgType","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"length","type":"uint256"}],"name":"SentToSolana","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"AUX","outputs":[{"internalType":"contract Aux","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SEND","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SEND_AND_CALL","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SOL_MAINNET_EID","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"V4","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"origin","type":"tuple"}],"name":"allowInitializePath","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"approvalRequired","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"auth","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_eid","type":"uint32"},{"internalType":"uint16","name":"_msgType","type":"uint16"},{"internalType":"bytes","name":"_extraOptions","type":"bytes"}],"name":"combineOptions","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"composeMsgSender","outputs":[{"internalType":"address","name":"sender","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"court","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentMonth","outputs":[{"internalType":"uint256","name":"month","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimalConversionRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endpoint","outputs":[{"internalType":"contract ILayerZeroEndpointV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"uint16","name":"msgType","type":"uint16"}],"name":"enforcedOptions","outputs":[{"internalType":"bytes","name":"enforcedOption","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"holderIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"holders","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_eid","type":"uint32"},{"internalType":"bytes32","name":"_peer","type":"bytes32"}],"name":"isPeer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"jury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"juryLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latest_holder","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"juror","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lockForJury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"_origin","type":"tuple"},{"internalType":"bytes32","name":"_guid","type":"bytes32"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"_executor","type":"address"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"lzReceive","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"origin","type":"tuple"},{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes32","name":"guid","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"executor","type":"address"},{"internalType":"bytes","name":"message","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct InboundPacket[]","name":"_packets","type":"tuple[]"}],"name":"lzReceiveAndRevert","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"_origin","type":"tuple"},{"internalType":"bytes32","name":"_guid","type":"bytes32"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"_executor","type":"address"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"lzReceiveSimulate","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pledge","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"when","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"msgInspector","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"nextNonce","outputs":[{"internalType":"uint64","name":"nonce","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oApp","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oAppVersion","outputs":[{"internalType":"uint64","name":"senderVersion","type":"uint64"},{"internalType":"uint64","name":"receiverVersion","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"oftVersion","outputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"},{"internalType":"uint64","name":"version","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"}],"name":"peers","outputs":[{"internalType":"bytes32","name":"peer","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preCrime","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"processedGuids","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"processedMessages","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"}],"name":"quoteOFT","outputs":[{"components":[{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"uint256","name":"maxAmountLD","type":"uint256"}],"internalType":"struct OFTLimit","name":"oftLimit","type":"tuple"},{"components":[{"internalType":"int256","name":"feeAmountLD","type":"int256"},{"internalType":"string","name":"description","type":"string"}],"internalType":"struct OFTFeeDetail[]","name":"oftFeeDetails","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amountSentLD","type":"uint256"},{"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"internalType":"struct OFTReceipt","name":"oftReceipt","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"},{"internalType":"bool","name":"_payInLzToken","type":"bool"}],"name":"quoteSend","outputs":[{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"msgFee","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"seeded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"},{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"_fee","type":"tuple"},{"internalType":"address","name":"_refundAddress","type":"address"}],"name":"send","outputs":[{"components":[{"internalType":"bytes32","name":"guid","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"fee","type":"tuple"}],"internalType":"struct MessagingReceipt","name":"msgReceipt","type":"tuple"},{"components":[{"internalType":"uint256","name":"amountSentLD","type":"uint256"},{"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"internalType":"struct OFTReceipt","name":"oftReceipt","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"composeMsg","type":"bytes"}],"name":"sendToSolana","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_delegate","type":"address"}],"name":"setDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"uint16","name":"msgType","type":"uint16"},{"internalType":"bytes","name":"options","type":"bytes"}],"internalType":"struct EnforcedOptionParam[]","name":"_enforcedOptions","type":"tuple[]"}],"name":"setEnforcedOptions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_msgInspector","type":"address"}],"name":"setMsgInspector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_eid","type":"uint32"},{"internalType":"bytes32","name":"_peer","type":"bytes32"}],"name":"setPeer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_preCrime","type":"address"}],"name":"setPreCrime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_court","type":"address"},{"internalType":"address","name":"_jury","type":"address"}],"name":"setup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sharedDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"totalSupplies","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"turn","outputs":[{"internalType":"uint256","name":"sent","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"juror","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unlockFromJury","outputs":[],"stateMutability":"nonpayable","type":"function"}]

0x60c06040526001600d55348015610014575f80fd5b50604051615ee9380380615ee98339810160408190526100339161027c565b604051806040016040528060048152602001631455485160e21b81525060405180604001604052806002815260200161145160f21b815250731a44076050125825900e736c501f859c50fe728c33838361009161020860201b60201c565b84848181818133806100bc57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100c58161020d565b506001600160a01b0380831660805281166100f357604051632d618d8160e21b815260040160405180910390fd5b60805160405163ca5eb5e160e01b81526001600160a01b0383811660048301529091169063ca5eb5e1906024015f604051808303815f87803b158015610137575f80fd5b505af1158015610149573d5f803e3d5ffd5b505050505050505061015f61025c60201b60201c565b60ff168360ff161015610185576040516301e9714b60e41b815260040160405180910390fd5b6101906006846102c1565b61019b90600a6103c3565b60a05250600891506101af90508382610470565b5060096101bc8282610470565b5050601080546001600160a01b0319908116331790915542600f55600e80546001600160a01b0398891690831617905560128054989097169716969096179094555061052a9350505050565b601290565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600690565b80516001600160a01b0381168114610277575f80fd5b919050565b5f806040838503121561028d575f80fd5b61029683610261565b91506102a460208401610261565b90509250929050565b634e487b7160e01b5f52601160045260245ffd5b60ff82811682821603908111156102da576102da6102ad565b92915050565b6001815b600184111561031b578085048111156102ff576102ff6102ad565b600184161561030d57908102905b60019390931c9280026102e4565b935093915050565b5f82610331575060016102da565b8161033d57505f6102da565b8160018114610353576002811461035d57610379565b60019150506102da565b60ff84111561036e5761036e6102ad565b50506001821b6102da565b5060208310610133831016604e8410600b841016171561039c575081810a6102da565b6103a85f1984846102e0565b805f19048211156103bb576103bb6102ad565b029392505050565b5f6103d160ff841683610323565b9392505050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061040057607f821691505b60208210810361041e57634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561046b57805f5260205f20601f840160051c810160208510156104495750805b601f840160051c820191505b81811015610468575f8155600101610455565b50505b505050565b81516001600160401b03811115610489576104896103d8565b61049d8161049784546103ec565b84610424565b6020601f8211600181146104cf575f83156104b85750848201515b5f19600385901b1c1916600184901b178455610468565b5f84815260208120601f198516915b828110156104fe57878501518255602094850194600190920191016104de565b508482101561051b57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b60805160a0516159516105985f395f81816109db015281816135d10152818161362c0152613dac01525f818161085a01528181610f01015281816111ee0152818161210301528181612d4c01528181612e46015281816138b701528181613cc60152613d7d01526159515ff3fe608060405260043610610386575f3560e01c80635e280f11116101d9578063b731ea0a11610100578063cd5d211811610099578063cd5d211814610bb0578063d045a0dc14610bcf578063d424388514610be2578063dd62ed3e14610c01578063e9bbb04014610c20578063f2fde38b14610c4b578063fc0c546a14610769578063fe99049a14610c6a578063ff7bd03d14610c89575f80fd5b8063b731ea0a14610ac0578063b92d0eff14610769578063b98bd07014610adf578063bb0b6a5314610afe578063bc70b35414610b29578063bd815db014610b48578063c7c7f5b314610b5b578063ca5eb5e114610b7c578063ccb3afea14610b9b575f80fd5b806388ba16ab1161017257806388ba16ab1461096c5780638da5cb5b1461099a57806395d89b41146109b6578063963efcaa146109ca5780639f68b964146109fd578063a9059cbb14610a0f578063b40d18fb14610a2e578063b54fa47414610a5c578063b6363cf214610a87575f80fd5b80635e280f11146108495780636100d69a1461087c578063696c5ded1461089b5780636fc1b31e146108ba57806370a08231146108d9578063715018a6146108f85780637d25a05e1461090c578063857749b014610945578063862a4d4714610958575f80fd5b806323b872dd116102bd5780633b6f743b116102565780633b6f743b146106ff5780633c173a4f1461072b578063426a84931461074a57806352ae2879146107695780635535d4611461077b578063558a72971461079a578063598af9e7146107b95780635a0dfe4d146107f55780635cb0fd151461082a575f80fd5b806323b872dd146105f05780632a11ced01461060f5780632d34ba791461062e5780632eb9921f1461064d5780632ff42ab614610678578063313ce5671461069757806331ba5c0f146106b8578063334041a6146106cb5780633400288b146106e0575f80fd5b80630d35b4151161032f5780630d35b415146104da578063111ecdad1461050857806313137d6514610527578063134d4f251461053c578063156a0d0f1461056357806316ce049b1461058957806317442b70146105a857806318160ddd146105c85780631f5e1334146105dc575f80fd5b8062971a301461038a578062fdd58e146103bf57806301ffc9a7146104035780630455a1a11461043257806306fdde031461045c578063095bcdb61461047d578063095ea7b31461049c57806309fd3f6e146104bb575b5f80fd5b348015610395575f80fd5b506013546103a9906001600160a01b031681565b6040516103b69190614634565b60405180910390f35b3480156103ca575f80fd5b506103f56103d936600461465c565b600b60209081525f928352604080842090915290825290205481565b6040519081526020016103b6565b34801561040e575f80fd5b5061042261041d366004614686565b610ca8565b60405190151581526020016103b6565b34801561043d575f80fd5b506104476175d881565b60405163ffffffff90911681526020016103b6565b348015610467575f80fd5b50610470610cde565b6040516103b691906146db565b348015610488575f80fd5b506104226104973660046146ed565b610d6e565b3480156104a7575f80fd5b506104226104b636600461465c565b610e1d565b3480156104c6575f80fd5b506014546103a9906001600160a01b031681565b3480156104e5575f80fd5b506104f96104f4366004614735565b610e34565b6040516103b693929190614766565b348015610513575f80fd5b506004546103a9906001600160a01b031681565b61053a61053536600461484e565b610eff565b005b348015610547575f80fd5b50610550600281565b60405161ffff90911681526020016103b6565b34801561056e575f80fd5b506040805162b9270b60e21b815260016020820152016103b6565b348015610594575f80fd5b506012546103a9906001600160a01b031681565b3480156105b3575f80fd5b506040805160018082526020820152016103b6565b3480156105d3575f80fd5b506007546103f5565b3480156105e7575f80fd5b50610550600181565b3480156105fb575f80fd5b5061042261060a3660046148e8565b610fc3565b34801561061a575f80fd5b506103a9610629366004614926565b610fe7565b348015610639575f80fd5b5061053a61064836600461493d565b61100f565b348015610658575f80fd5b506103f5610667366004614974565b601b6020525f908152604090205481565b348015610683575f80fd5b506103f561069236600461465c565b611062565b3480156106a2575f80fd5b5060125b60405160ff90911681526020016103b6565b6103f56106c6366004614a21565b611186565b3480156106d6575f80fd5b506103f560115481565b3480156106eb575f80fd5b5061053a6106fa366004614aad565b611423565b34801561070a575f80fd5b5061071e610719366004614ad4565b611480565b6040516103b69190614b17565b348015610736575f80fd5b506103f5610745366004614b2e565b6114e4565b348015610755575f80fd5b506104226107643660046146ed565b611889565b348015610774575f80fd5b50306103a9565b348015610786575f80fd5b50610470610795366004614b84565b6118ed565b3480156107a5575f80fd5b506104226107b4366004614bb5565b61198f565b3480156107c4575f80fd5b506103f56107d33660046148e8565b600c60209081525f938452604080852082529284528284209052825290205481565b348015610800575f80fd5b5061042261080f366004614aad565b63ffffffff919091165f908152600160205260409020541490565b348015610835575f80fd5b5061053a61084436600461465c565b6119fe565b348015610854575f80fd5b506103a97f000000000000000000000000000000000000000000000000000000000000000081565b348015610887575f80fd5b5061053a61089636600461465c565b611a75565b3480156108a6575f80fd5b50600e546103a9906001600160a01b031681565b3480156108c5575f80fd5b5061053a6108d4366004614974565b611ac1565b3480156108e4575f80fd5b506103f56108f3366004614974565b611b1f565b348015610903575f80fd5b5061053a611b39565b348015610917575f80fd5b5061092d610926366004614aad565b5f92915050565b6040516001600160401b0390911681526020016103b6565b348015610950575f80fd5b5060066106a6565b348015610963575f80fd5b506103f5611b4c565b348015610977575f80fd5b50610422610986366004614926565b60166020525f908152604090205460ff1681565b3480156109a5575f80fd5b505f546001600160a01b03166103a9565b3480156109c1575f80fd5b50610470611b6e565b3480156109d5575f80fd5b506103f57f000000000000000000000000000000000000000000000000000000000000000081565b348015610a08575f80fd5b505f610422565b348015610a1a575f80fd5b50610422610a2936600461465c565b611b7d565b348015610a39575f80fd5b50610422610a48366004614926565b60156020525f908152604090205460ff1681565b348015610a67575f80fd5b506103f5610a76366004614926565b601a6020525f908152604090205481565b348015610a92575f80fd5b50610422610aa136600461493d565b600a60209081525f928352604080842090915290825290205460ff1681565b348015610acb575f80fd5b506002546103a9906001600160a01b031681565b348015610aea575f80fd5b5061053a610af9366004614c21565b611b9c565b348015610b09575f80fd5b506103f5610b18366004614c5f565b60016020525f908152604090205481565b348015610b34575f80fd5b50610470610b43366004614c78565b611cf8565b61053a610b56366004614c21565b611e6d565b610b6e610b69366004614cd4565b611fed565b6040516103b6929190614d3c565b348015610b87575f80fd5b5061053a610b96366004614974565b6120e4565b348015610ba6575f80fd5b506103f560175481565b348015610bbb575f80fd5b50610422610bca366004614974565b612168565b61053a610bdd36600461484e565b6121c6565b348015610bed575f80fd5b5061053a610bfc366004614974565b6121f5565b348015610c0c575f80fd5b506103f5610c1b36600461493d565b612248565b348015610c2b575f80fd5b506103f5610c3a366004614974565b601c6020525f908152604090205481565b348015610c56575f80fd5b5061053a610c65366004614974565b612272565b348015610c75575f80fd5b50610422610c84366004614d8d565b6122af565b348015610c94575f80fd5b50610422610ca3366004614dd0565b612412565b5f6301ffc9a760e01b6001600160e01b031983161480610cd85750630f632fb360e01b6001600160e01b03198316145b92915050565b606060088054610ced90614dea565b80601f0160208091040260200160405190810160405280929190818152602001828054610d1990614dea565b8015610d645780601f10610d3b57610100808354040283529160200191610d64565b820191905f5260205f20905b815481529060010190602001808311610d4757829003601f168201915b5050505050905090565b335f908152600b60209081526040808320858452909152812080548391908390610d99908490614e30565b90915550506001600160a01b0384165f908152600b6020908152604080832086845290915281208054849290610dd0908490614e43565b9250508190555082846001600160a01b0316336001600160a01b03165f805160206159318339815191523386604051610e0a929190614e56565b60405180910390a45060015b9392505050565b5f33610e2a818585612446565b5060019392505050565b604080518082019091525f80825260208201526060610e6460405180604001604052805f81526020015f81525090565b6040805180820182525f8082526001600160401b03602080840182905284518381529081019094529195509182610ebd565b604080518082019091525f815260606020820152815260200190600190039081610e965790505b5093505f80610ee1604089013560608a0135610edc60208c018c614c5f565b612458565b60408051808201909152918252602082015296989597505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610f5357336040516391ac5e4f60e01b8152600401610f4a9190614634565b60405180910390fd5b60208701803590610f6d90610f68908a614c5f565b61249b565b14610fab57610f7f6020880188614c5f565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610f4a565b610fba878787878787876124d6565b50505050505050565b5f33610fd085828561272c565b610fdb85858561277c565b50600195945050505050565b60188181548110610ff6575f80fd5b5f918252602090912001546001600160a01b0316905081565b61101833612168565b6110345760405162461bcd60e51b8152600401610f4a90614e6f565b601380546001600160a01b039384166001600160a01b03199182161790915560148054929093169116179055565b5f8061106d33612168565b15905080156110c1576001600160a01b038416331480156110a557506001600160a01b0384165f9081526019602052604090205460ff165b6110c15760405162461bcd60e51b8152600401610f4a90614e8c565b6014545f906001600160a01b031633146110db575f6110e8565b6014546001600160a01b03165b90506110f585828661277c565b9250811561117e57600e5460405163daf8629560e01b815233600482015260248101859052306044820152600160648201526001600160a01b039091169063daf86295906084016020604051808303815f875af1158015611158573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061117c9190614ea9565b505b505092915050565b5f61119033612168565b6111ac5760405162461bcd60e51b8152600401610f4a90614e6f565b5f8251116111ec5760405162461bcd60e51b815260206004820152600d60248201526c656d707479206d65737361676560981b6044820152606401610f4a565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661124d5760405162461bcd60e51b8152602060048201526008602482015267195b991c1bda5b9d60c21b6044820152606401610f4a565b5f61125783612cf5565b905060ff81166006146112965760405162461bcd60e51b81526020600482015260076024820152661a5b9d985b1a5960ca1b6044820152606401610f4a565b60405163815d56a960e01b815260ff821660048201526175d8905f9073e88bda54bccb0ac383bcc71843b079175cb480bb9063815d56a9906024015f60405180830381865af41580156112eb573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526113129190810190614ec0565b90505f6113218387845f612d37565b80519091503410156113685760405162461bcd60e51b815260206004820152601060248201526f496e73756666696369656e742066656560801b6044820152606401610f4a565b5f6113768488858533612e15565b80515f90815260166020908152604091829020805460ff191660011790558251825160ff8a16815242928101929092529293507fd23c04eb80d47f722670c30a453e7989a870fca6f0610d995f87b38b73473969910160405180910390a2815134111561141657815133906108fc906113ef9034614e30565b6040518115909202915f818181858888f19350505050158015611414573d5f803e3d5ffd5b505b519450505050505b919050565b61142b612f1b565b63ffffffff82165f81815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b91015b60405180910390a15050565b604080518082019091525f80825260208201525f6114ae60408501356060860135610edc6020880188614c5f565b9150505f806114bd8684612f47565b90925090506114da6114d26020880188614c5f565b838388612d37565b9695505050505050565b5f600d546001146115245760405162461bcd60e51b815260206004820152600a6024820152695245454e5452414e435960b01b6044820152606401610f4a565b6002600d555f611532611b4c565b61153d906001614e43565b6012549091506001600160a01b031633036115665761155d868287613066565b8491505061187c565b600e54604051638340f54960e01b81525f916001600160a01b031690638340f5499061159a908a9089908b90600401614f34565b6020604051808303815f875af11580156115b6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115da9190614ea9565b90505f856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611619573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061163d9190614f58565b60ff1690505f601282106116515782611671565b61165c826012614e30565b61166790600a615053565b611671908461505e565b90505f61167e878661321d565b9050600c61168c8683614e30565b11156117b8576002603c6116ab670de0b6b3a7640000622dc6c061505e565b6116b59190615089565b6116bf9084615089565b101580156116f55750603c6116df670de0b6b3a7640000622dc6c061505e565b6116e99190615089565b6116f3908361509c565b155b61172b5760405162461bcd60e51b81526020600482015260076024820152667472616e63686560c81b6044820152606401610f4a565b8160115f82825461173c9190614e43565b909155506117579050670de0b6b3a7640000622dc6c061505e565b60115410611763575f80fd5b601054611784906001600160a01b031661177e600188614e30565b84613066565b61178f60038361505e565b6001600160a01b038b165f908152601960205260409020805460ff19166001179055915061186a565b600e546040805163949b9c6960e01b815290515f926001600160a01b03169163949b9c699160048083019260209291908290030181865afa1580156117ff573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118239190614ea9565b905061185c611832828561505e565b61183a611b4c565b6118449085614e30565b611857670de0b6b3a7640000600c61505e565b613232565b6118669084614e43565b9250505b6118758a8284613066565b5093505050505b6001600d55949350505050565b335f818152600c602090815260408083206001600160a01b03881680855290835281842087855290925280832085905551919285927fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a790610e0a9087815260200190565b600360209081525f92835260408084209091529082529020805461191090614dea565b80601f016020809104026020016040519081016040528092919081815260200182805461193c90614dea565b80156119875780601f1061195e57610100808354040283529160200191611987565b820191905f5260205f20905b81548152906001019060200180831161196a57829003601f168201915b505050505081565b335f818152600a602090815260408083206001600160a01b038716808552908352818420805460ff191687151590811790915591519182529293917fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267910160405180910390a350600192915050565b611a0733612168565b611a235760405162461bcd60e51b8152600401610f4a90614e6f565b6001600160a01b0382165f908152601b6020526040902054611a4590826132ce565b6001600160a01b0383165f908152601b602052604081208054909190611a6c908490614e30565b90915550505050565b611a7e33612168565b611a9a5760405162461bcd60e51b8152600401610f4a90614e6f565b6001600160a01b0382165f908152601b602052604081208054839290611a6c908490614e43565b611ac9612f1b565b600480546001600160a01b0319166001600160a01b0383161790556040517ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d441419790611b14908390614634565b60405180910390a150565b6001600160a01b03165f9081526005602052604090205490565b611b41612f1b565b611b4a5f6132dc565b565b5f6224ed20600f5442611b5f9190614e30565b611b699190615089565b905090565b606060098054610ced90614dea565b5f611b8933848461277c565b8214611b93575f80fd5b50600192915050565b611ba4612f1b565b5f5b81811015611cc657611be8838383818110611bc357611bc36150af565b9050602002810190611bd591906150c3565b611be39060408101906150e1565b61332b565b828282818110611bfa57611bfa6150af565b9050602002810190611c0c91906150c3565b611c1a9060408101906150e1565b60035f868686818110611c2f57611c2f6150af565b9050602002810190611c4191906150c3565b611c4f906020810190614c5f565b63ffffffff1663ffffffff1681526020019081526020015f205f868686818110611c7b57611c7b6150af565b9050602002810190611c8d91906150c3565b611c9e906040810190602001615123565b61ffff16815260208101919091526040015f2091611cbd919083615180565b50600101611ba6565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b6748282604051611474929190615261565b63ffffffff84165f90815260036020908152604080832061ffff87168452909152812080546060929190611d2b90614dea565b80601f0160208091040260200160405190810160405280929190818152602001828054611d5790614dea565b8015611da25780601f10611d7957610100808354040283529160200191611da2565b820191905f5260205f20905b815481529060010190602001808311611d8557829003601f168201915b5050505050905080515f03611df05783838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250929450611e659350505050565b5f839003611dff579050611e65565b60028310611e4857611e11848461332b565b80611e1f8460028188615346565b604051602001611e3193929190615384565b604051602081830303815290604052915050611e65565b8383604051639a6d49cd60e01b8152600401610f4a9291906153a2565b949350505050565b5f5b81811015611f705736838383818110611e8a57611e8a6150af565b9050602002810190611e9c91906153b5565b9050611ece611eae6020830183614c5f565b602083013563ffffffff919091165f908152600160205260409020541490565b611ed85750611f68565b3063d045a0dc60c08301358360a0810135611ef76101008301836150e1565b611f08610100890160e08a01614974565b611f166101208a018a6150e1565b6040518963ffffffff1660e01b8152600401611f3897969594939291906153de565b5f604051808303818588803b158015611f4f575f80fd5b505af1158015611f61573d5f803e3d5ffd5b5050505050505b600101611e6f565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b81526004015f60405180830381865afa158015611fac573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611fd39190810190614ec0565b604051638351eea760e01b8152600401610f4a91906146db565b611ff561459d565b604080518082019091525f80825260208201525f806120296040880135606089013561202460208b018b614c5f565b61336c565b915091505f806120398984612f47565b909250905061206561204e60208b018b614c5f565b838361205f368d90038d018d615463565b8b612e15565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a906120b3908d018d614c5f565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6120ec612f1b565b60405163ca5eb5e160e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e190612138908490600401614634565b5f604051808303815f87803b15801561214f575f80fd5b505af1158015612161573d5f803e3d5ffd5b5050505050565b600e545f906001600160a01b038381169116148061219357506014546001600160a01b038381169116145b806121ab57506013546001600160a01b038381169116145b80610cd85750506012546001600160a01b0390811691161490565b3330146121e65760405163029a949d60e31b815260040160405180910390fd5b610fba87878787878787610fab565b6121fd612f1b565b600280546001600160a01b0319166001600160a01b0383161790556040517fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090611b14908390614634565b6001600160a01b039182165f90815260066020908152604080832093909416825291909152205490565b61227a612f1b565b6001600160a01b0381166122a3575f604051631e4fbdf760e01b8152600401610f4a9190614634565b6122ac816132dc565b50565b5f336001600160a01b038616148015906122ec57506001600160a01b0385165f908152600a6020908152604080832033845290915290205460ff16155b1561235c576001600160a01b0385165f908152600c6020908152604080832033845282528083208684529091529020545f19811461235a5761232e8382614e30565b6001600160a01b0387165f908152600c6020908152604080832033845282528083208884529091529020555b505b6001600160a01b0385165f908152600b602090815260408083208684529091528120805484929061238e908490614e30565b90915550506001600160a01b0384165f908152600b60209081526040808320868452909152812080548492906123c5908490614e43565b9250508190555082846001600160a01b0316866001600160a01b03165f8051602061593183398151915233866040516123ff929190614e56565b60405180910390a4506001949350505050565b5f6020820180359060019083906124299086614c5f565b63ffffffff16815260208101919091526040015f20541492915050565b61245383838360016134fc565b505050565b5f80612463856135ce565b915081905083811015612493576040516371c4efed60e01b81526004810182905260248101859052604401610f4a565b935093915050565b63ffffffff81165f9081526001602052604081205480610cd85760405163f6ff4fb760e01b815263ffffffff84166004820152602401610f4a565b6175d86124e66020890189614c5f565b63ffffffff161461251f5760405162461bcd60e51b81526020600482015260036024820152621cdbdb60ea1b6044820152606401610f4a565b60015f61252f60208a018a614c5f565b63ffffffff1663ffffffff1681526020019081526020015f205487602001351461256b5760405162461bcd60e51b8152600401610f4a90614e8c565b5f8681526015602052604090205460ff16156125af5760405162461bcd60e51b815260206004820152600360248201526234303960e81b6044820152606401610f4a565b5f868152601560205260408120805460ff191660011790556125d18686613604565b90505f6125dd82613626565b90505f6125ea888861365a565b90505f6125fd6125fa8a8a6136a4565b90565b90505f61260983612cf5565b905060041960ff8216016126e757601354604051620b465d60e71b81526001600160a01b03909116906305a32e80906126469086906004016146db565b5f604051808303815f87803b15801561265d575f80fd5b505af115801561266f573d5f803e3d5ffd5b5050505060135f9054906101000a90046001600160a01b03166001600160a01b03168b7fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c8e5f0160208101906126c59190614c5f565b6040805163ffffffff90921682525f60208301520160405180910390a361271e565b60061960ff82160161270f5761270a8b61270460208f018f614c5f565b856136bb565b61271e565b61271e8c8c8c8c87878a6137b9565b505050505050505050505050565b5f6127378484612248565b90505f198114612776578181101561276857828183604051637dc7a0d960e11b8152600401610f4a93929190615494565b61277684848484035f6134fc565b50505050565b6001600160a01b0383165f908152601b6020526040812054829061279f86611b1f565b6127a99190614e30565b10156127b3575f80fd5b6001600160a01b0384165f908152601d602052604081206127d39061392c565b90506001600160a01b038416155f81806127fa57506012546001600160a01b038781169116145b90505f8261281557600184516128109190614e30565b612891565b600f5460405163c6dbdf1d60e01b815273e88bda54bccb0ac383bcc71843b079175cb480bb9163c6dbdf1d916128529188914291906004016154b5565b602060405180830381865af415801561286d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128919190614ea9565b90505b5f861180156128a357505f8112155b15612a36575f8482815181106128bb576128bb6150af565b6020908102919091018101516001600160a01b038b165f908152600b8352604080822083835290935291909120549091508015612a22576128fc88826132ce565b6001600160a01b038b165f908152600b60209081526040808320868452909152812080549293508392909190612933908490614e30565b9091555084905061299c576001600160a01b0389165f908152601d6020526040902061295f9083613985565b6001600160a01b0389165f908152600b6020908152604080832085845290915281208054839290612991908490614e43565b909155506129bf9050565b5f828152601a6020526040812080548392906129b9908490614e30565b90915550505b6001600160a01b038a165f908152600b602090815260408083208584529091528120549003612a09576001600160a01b038a165f908152601d60205260409020612a099083613a76565b612a138189614e30565b9750612a1f8188614e43565b96505b612a2d600184615503565b92505050612894565b8415612cea57612a47888887613b89565b612a5088611b1f565b9550612a66670de0b6b3a764000061019061505e565b861080612a9d5750603c612a85670de0b6b3a7640000622dc6c061505e565b612a8f9190615089565b612a9a90600461505e565b86115b8015612abf57506001600160a01b0388165f908152601c602052604090205415155b15612be8576001600160a01b0388165f908152601c6020526040812054612ae890600190614e30565b6018549091505f90612afc90600190614e30565b9050808214612b96575f60188281548110612b1957612b196150af565b5f918252602090912001546001600160a01b03169050612b3a836001614e43565b6001600160a01b0382165f908152601c60205260409020556018805482919085908110612b6957612b696150af565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b03160217905550505b6001600160a01b038a165f908152601c60205260408120556018805480612bbf57612bbf615529565b5f8281526020902081015f1990810180546001600160a01b031916905501905550506018546017555b612bf187611b1f565b95506001600160a01b03871615801590612c2057506001600160a01b0387165f908152601c6020526040902054155b8015612c3e5750612c3b670de0b6b3a764000061019061505e565b86115b8015612c745750603c612c5c670de0b6b3a7640000622dc6c061505e565b612c669190615089565b612c7190600461505e565b86105b15612cea57601854612c87906001614e43565b6001600160a01b0388165f818152601c602052604081209290925560188054600181018255928190527fb13d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e90920180546001600160a01b0319169091179055546017555b505050509392505050565b5f81515f03612d1757604051638d0242c960e01b815260040160405180910390fd5b815f81518110612d2957612d296150af565b016020015160f81c92915050565b604080518082019091525f80825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff168152602001612d998961249b565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401612dce92919061553d565b6040805180830381865afa158015612de8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e0c91906155e4565b95945050505050565b612e1d61459d565b5f612e2a845f0151613c9c565b602085015190915015612e4457612e448460200151613cc3565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001612e948c61249b565b81526020018a81526020018981526020015f8960200151111515815250866040518463ffffffff1660e01b8152600401612ecf92919061553d565b60806040518083038185885af1158015612eeb573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190612f1091906155fe565b979650505050505050565b5f546001600160a01b03163314611b4a573360405163118cdaa760e01b8152600401610f4a9190614634565b6060805f612fa28560200135612f5c86613da6565b612f6960a08901896150e1565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250613dd192505050565b90935090505f81612fb4576001612fb7565b60025b9050612fd7612fc96020880188614c5f565b82610b4360808a018a6150e1565b6004549093506001600160a01b03161561305d576004805460405163043a78eb60e01b81526001600160a01b039091169163043a78eb9161301c918891889101615665565b602060405180830381865afa158015613037573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061305b9190615689565b505b50509250929050565b5f828152601a602052604081208054839290613083908490614e43565b90915550506001600160a01b0383165f908152601d602052604090206130a99083613985565b6130b45f8483613b89565b6001600160a01b0383165f908152600b60209081526040808320858452909152812080548392906130e6908490614e43565b909155505f90506130f684611b1f565b6001600160a01b0385165f908152601c6020526040902054909150158015613130575061312d670de0b6b3a764000061019061505e565b81115b80156131665750603c61314e670de0b6b3a7640000622dc6c061505e565b6131589190615089565b61316390600461505e565b81105b156131dc57601854613179906001614e43565b6001600160a01b0385165f818152601c602052604081209290925560188054600181018255928190527fb13d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e90920180546001600160a01b0319169091179055546017555b82846001600160a01b03165f6001600160a01b03165f80516020615931833981519152338660405161320f929190614e56565b60405180910390a450505050565b5f81831161322b5781610e16565b5090919050565b5f838302815f1985870982811083820303915050808411613251575f80fd5b805f0361326357508290049050610e16565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f81831061322b5781610e16565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f6133396002828486615346565b613342916156a4565b60f01c905060038114612453578282604051639a6d49cd60e01b8152600401610f4a9291906153a2565b5f80613379858585612458565b6040516323a9655f60e21b815291935091505f9073e88bda54bccb0ac383bcc71843b079175cb480bb90638ea5957c906133b990849036906004016153a2565b5f60405180830381865af41580156133d3573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526133fa9190810190614ec0565b90505f81511161343a5760405162461bcd60e51b815260206004820152600b60248201526a4c5a3a206e6f206461746160a81b6044820152606401610f4a565b5f8082806020019051810190613450919061574f565b915091508051825114801561346557505f8251115b6134a05760405162461bcd60e51b815260206004820152600c60248201526b4c5a3a20626164206461746160a01b6044820152606401610f4a565b5f5b82518110156134f0576134e8338483815181106134c1576134c16150af565b60200260200101518484815181106134db576134db6150af565b6020026020010151613e4b565b6001016134a2565b50505050935093915050565b6001600160a01b038416613525575f60405163e602df0560e01b8152600401610f4a9190614634565b6001600160a01b03831661354e575f604051634a1406b160e11b8152600401610f4a9190614634565b6001600160a01b038085165f908152600660209081526040808320938716835292905220829055801561277657826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516135c091815260200190565b60405180910390a350505050565b5f7f00000000000000000000000000000000000000000000000000000000000000006135fa8184615089565b610cd8919061505e565b5f613613602860208486615346565b61361c916157b2565b60c01c9392505050565b5f610cd87f00000000000000000000000000000000000000000000000000000000000000006001600160401b03841661505e565b60606136698260288186615346565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250929695505050505050565b5f6136b26020828486615346565b610e16916157e8565b5f806136c683613ec4565b915091505f6136d482613ff4565b601454909150613700906001600160a01b03166136ef611b4c565b6136fa906001614e43565b83613066565b60145460405163d7c109a960e01b81526001600160a01b039091169063d7c109a99061373490869085908990600401615805565b5f604051808303815f87803b15801561374b575f80fd5b505af115801561375d573d5f803e3d5ffd5b50506014546040805163ffffffff8a168152602081018690526001600160a01b0390921693508992507fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c910160405180910390a3505050505050565b5f80848060200190518101906137cf919061574f565b91509150805182511480156137e457505f8251115b61381b5760405162461bcd60e51b81526020600482015260086024820152674c5a3a206461746160c01b6044820152606401610f4a565b5f5b825181101561386b576138638584838151811061383c5761383c6150af565b6020026020010151848481518110613856576138566150af565b6020026020010151613066565b60010161381d565b506028861115613921575f61389d61388960608c0160408d0161582c565b61389660208d018d614c5f565b868961400d565b604051633e5ac80960e11b81529091506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690637cb59012906138f29088908d905f908790600401615847565b5f604051808303815f87803b158015613909575f80fd5b505af115801561391b573d5f803e3d5ffd5b50505050505b505050505050505050565b6060815f0180548060200260200160405190810160405280929190818152602001828054801561397957602002820191905f5260205f20905b815481526020019060010190808311613965575b50505050509050919050565b5f81815260018301602052604090205460ff16156139a1575050565b5f818152600183810160205260408220805460ff191690911790556139c6838361403f565b508354600181810186555f86815260208120909201829055855492935090916139ef9190614e30565b90505b81811115613a505783613a06600183614e30565b81548110613a1657613a166150af565b905f5260205f200154845f018281548110613a3357613a336150af565b5f9182526020909120015580613a4881615877565b9150506139f2565b5081835f018281548110613a6657613a666150af565b5f91825260209091200155505050565b5f81815260018301602052604090205460ff16613acc5760405162461bcd60e51b815260206004820152601460248201527315985b1d5948191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610f4a565b5f613ad7838361403f565b50835490915081108015613b06575081835f018281548110613afb57613afb6150af565b905f5260205f200154145b613b445760405162461bcd60e51b815260206004820152600f60248201526e15985b1d59481b9bdd08199bdd5b99608a1b6044820152606401610f4a565b5f19835f018281548110613b5a57613b5a6150af565b5f9182526020808320909101929092558381526001850190915260409020805460ff19169055612453836140f2565b6001600160a01b038316613bb3578060075f828254613ba89190614e43565b90915550613c109050565b6001600160a01b0383165f9081526005602052604090205481811015613bf25783818360405163391434e360e21b8152600401610f4a93929190615494565b6001600160a01b0384165f9081526005602052604090209082900390555b6001600160a01b038216613c2c57600780548290039055613c4a565b6001600160a01b0382165f9081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051613c8f91815260200190565b60405180910390a3505050565b5f813414613cbf576040516304fb820960e51b8152346004820152602401610f4a565b5090565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015613d20573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613d44919061588c565b90506001600160a01b038116613d6d576040516329b99a9560e11b815260040160405180910390fd5b613da26001600160a01b038216337f00000000000000000000000000000000000000000000000000000000000000008561425c565b5050565b5f610cd87f000000000000000000000000000000000000000000000000000000000000000083615089565b8051606090151580613e1a578484604051602001613e0692919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052613e41565b84843385604051602001613e3194939291906158a7565b6040516020818303038152906040525b9150935093915050565b6001600160a01b0383165f908152600b6020908152604080832085845290915281208054839290613e7d908490614e30565b92505081905550815f6001600160a01b0316846001600160a01b03165f805160206159318339815191523385604051613eb7929190614e56565b60405180910390a4505050565b5f80601183511015613ee957604051638d0242c960e01b815260040160405180910390fd5b600760ff16835f81518110613f0057613f006150af565b016020015160f81c14613f2657604051635b60892f60e01b815260040160405180910390fd5b613f318360016142b6565b9150613f3e8360096142b6565b9050816001600160401b03165f03613f685760405162320bd360e51b815260040160405180910390fd5b806001600160401b03165f03613f915760405163162908e360e11b815260040160405180910390fd5b64e8d4a51000816001600160401b03161115613fef5760405162461bcd60e51b815260206004820152601c60248201527f436f6d70656e736174696f6e2065786365656473206d6178696d756d000000006044820152606401610f4a565b915091565b5f610cd86001600160401b03831664e8d4a5100061505e565b60608484848460405160200161402694939291906158d3565b6040516020818303038152906040529050949350505050565b81545f90819081905b808210156140e4575f600261405d8484614e30565b6140679190615089565b6140719084614e43565b905085875f018281548110614088576140886150af565b905f5260205f200154036140a4579350600192506140eb915050565b85875f0182815481106140b9576140b96150af565b905f5260205f20015410156140da576140d3816001614e43565b92506140de565b8091505b50614048565b5091505f90505b9250929050565b80545f9081906001600160401b0381111561410f5761410f61498f565b604051908082528060200260200182016040528015614138578160200160208202803683370190505b5090505f5b83548110156141ba575f19845f01828154811061415c5761415c6150af565b905f5260205f200154146141b257835f01818154811061417e5761417e6150af565b905f5260205f200154828481518110614199576141996150af565b6020908102919091010152826141ae8161590d565b9350505b60010161413d565b50816001600160401b038111156141d3576141d361498f565b6040519080825280602002602001820160405280156141fc578160200160208202803683370190505b5080516142109185916020909101906145e0565b505f5b828110156127765781818151811061422d5761422d6150af565b6020026020010151845f018281548110614249576142496150af565b5f91825260209091200155600101614213565b61277684856001600160a01b03166323b872dd86868660405160240161428493929190614f34565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505061445b565b5f6142c2826008614e43565b835110156143125760405162461bcd60e51b815260206004820152601c60248201527f496e73756666696369656e74206461746120666f722075696e743634000000006044820152606401610f4a565b603883614320846007614e43565b81518110614330576143306150af565b016020015160f81c901b603084614348856006614e43565b81518110614358576143586150af565b016020015160f81c901b602885614370866005614e43565b81518110614380576143806150af565b0160209081015160f81c90911b908661439a876004614e43565b815181106143aa576143aa6150af565b016020015160f81c901b6018876143c2886003614e43565b815181106143d2576143d26150af565b016020015160f81c901b6010886143ea896002614e43565b815181106143fa576143fa6150af565b016020015160f81c901b6008896144128a6001614e43565b81518110614422576144226150af565b01602001518a5160f89190911c90911b908a908a908110614445576144456150af565b016020015160f81c171717171717179392505050565b5f61446f6001600160a01b038416836144b3565b905080515f141580156144935750808060200190518101906144919190615689565b155b156124535782604051635274afe760e01b8152600401610f4a9190614634565b6060610e1683835f845f80856001600160a01b031684866040516144d79190615925565b5f6040518083038185875af1925050503d805f8114614511576040519150601f19603f3d011682016040523d82523d5f602084013e614516565b606091505b50915091506114da8683836060826145365761453182614574565b610e16565b815115801561454d57506001600160a01b0384163b155b1561456d5783604051639996b31560e01b8152600401610f4a9190614634565b5080610e16565b8051156145845780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60405180606001604052805f80191681526020015f6001600160401b031681526020016145db60405180604001604052805f81526020015f81525090565b905290565b828054828255905f5260205f20908101928215614619579160200282015b828111156146195782518255916020019190600101906145fe565b50613cbf9291505b80821115613cbf575f8155600101614621565b6001600160a01b0391909116815260200190565b6001600160a01b03811681146122ac575f80fd5b5f806040838503121561466d575f80fd5b823561467881614648565b946020939093013593505050565b5f60208284031215614696575f80fd5b81356001600160e01b031981168114610e16575f80fd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610e1660208301846146ad565b5f805f606084860312156146ff575f80fd5b833561470a81614648565b95602085013595506040909401359392505050565b5f60e0828403121561472f575f80fd5b50919050565b5f60208284031215614745575f80fd5b81356001600160401b0381111561475a575f80fd5b611e658482850161471f565b83518152602080850151908201525f60a0820160a0604084015280855180835260c08501915060c08160051b8601019250602087015f5b828110156147e55760bf1987860301845281518051865260208101519050604060208701526147cf60408701826146ad565b955050602093840193919091019060010161479d565b5050855160608601525050602084015160808401529050611e65565b5f6060828403121561472f575f80fd5b5f8083601f840112614821575f80fd5b5081356001600160401b03811115614837575f80fd5b6020830191508360208285010111156140eb575f80fd5b5f805f805f805f60e0888a031215614864575f80fd5b61486e8989614801565b96506060880135955060808801356001600160401b0381111561488f575f80fd5b61489b8a828b01614811565b90965094505060a08801356148af81614648565b925060c08801356001600160401b038111156148c9575f80fd5b6148d58a828b01614811565b989b979a50959850939692959293505050565b5f805f606084860312156148fa575f80fd5b833561490581614648565b9250602084013561491581614648565b929592945050506040919091013590565b5f60208284031215614936575f80fd5b5035919050565b5f806040838503121561494e575f80fd5b823561495981614648565b9150602083013561496981614648565b809150509250929050565b5f60208284031215614984575f80fd5b8135610e1681614648565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b03811182821017156149c5576149c561498f565b60405290565b604051601f8201601f191681016001600160401b03811182821017156149f3576149f361498f565b604052919050565b5f6001600160401b03821115614a1357614a1361498f565b50601f01601f191660200190565b5f60208284031215614a31575f80fd5b81356001600160401b03811115614a46575f80fd5b8201601f81018413614a56575f80fd5b8035614a69614a64826149fb565b6149cb565b818152856020838501011115614a7d575f80fd5b816020840160208301375f91810160200191909152949350505050565b803563ffffffff8116811461141e575f80fd5b5f8060408385031215614abe575f80fd5b61467883614a9a565b80151581146122ac575f80fd5b5f8060408385031215614ae5575f80fd5b82356001600160401b03811115614afa575f80fd5b614b068582860161471f565b925050602083013561496981614ac7565b815181526020808301519082015260408101610cd8565b5f805f8060808587031215614b41575f80fd5b8435614b4c81614648565b9350602085013592506040850135614b6381614648565b9396929550929360600135925050565b803561ffff8116811461141e575f80fd5b5f8060408385031215614b95575f80fd5b614b9e83614a9a565b9150614bac60208401614b73565b90509250929050565b5f8060408385031215614bc6575f80fd5b8235614bd181614648565b9150602083013561496981614ac7565b5f8083601f840112614bf1575f80fd5b5081356001600160401b03811115614c07575f80fd5b6020830191508360208260051b85010111156140eb575f80fd5b5f8060208385031215614c32575f80fd5b82356001600160401b03811115614c47575f80fd5b614c5385828601614be1565b90969095509350505050565b5f60208284031215614c6f575f80fd5b610e1682614a9a565b5f805f8060608587031215614c8b575f80fd5b614c9485614a9a565b9350614ca260208601614b73565b925060408501356001600160401b03811115614cbc575f80fd5b614cc887828801614811565b95989497509550505050565b5f805f8385036080811215614ce7575f80fd5b84356001600160401b03811115614cfc575f80fd5b614d088782880161471f565b9450506040601f1982011215614d1c575f80fd5b506020840191506060840135614d3181614648565b809150509250925092565b5f60c082019050835182526001600160401b0360208501511660208301526040840151614d76604084018280518252602090810151910152565b5082516080830152602083015160a0830152610e16565b5f805f8060808587031215614da0575f80fd5b8435614dab81614648565b93506020850135614dbb81614648565b93969395505050506040820135916060013590565b5f60608284031215614de0575f80fd5b610e168383614801565b600181811c90821680614dfe57607f821691505b60208210810361472f57634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115610cd857610cd8614e1c565b80820180821115610cd857610cd8614e1c565b6001600160a01b03929092168252602082015260400190565b6020808252600390820152620d0c0d60ea1b604082015260600190565b60208082526003908201526234303360e81b604082015260600190565b5f60208284031215614eb9575f80fd5b5051919050565b5f60208284031215614ed0575f80fd5b81516001600160401b03811115614ee5575f80fd5b8201601f81018413614ef5575f80fd5b8051614f03614a64826149fb565b818152856020838501011115614f17575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b5f60208284031215614f68575f80fd5b815160ff81168114610e16575f80fd5b6001815b600184111561249357808504811115614f9757614f97614e1c565b6001841615614fa557908102905b60019390931c928002614f7c565b5f82614fc157506001610cd8565b81614fcd57505f610cd8565b8160018114614fe35760028114614fed57615009565b6001915050610cd8565b60ff841115614ffe57614ffe614e1c565b50506001821b610cd8565b5060208310610133831016604e8410600b841016171561502c575081810a610cd8565b6150385f198484614f78565b805f190482111561504b5761504b614e1c565b029392505050565b5f610e168383614fb3565b8082028115828204841417610cd857610cd8614e1c565b634e487b7160e01b5f52601260045260245ffd5b5f8261509757615097615075565b500490565b5f826150aa576150aa615075565b500690565b634e487b7160e01b5f52603260045260245ffd5b5f8235605e198336030181126150d7575f80fd5b9190910192915050565b5f808335601e198436030181126150f6575f80fd5b8301803591506001600160401b0382111561510f575f80fd5b6020019150368190038213156140eb575f80fd5b5f60208284031215615133575f80fd5b610e1682614b73565b601f82111561245357805f5260205f20601f840160051c810160208510156151615750805b601f840160051c820191505b81811015612161575f815560010161516d565b6001600160401b038311156151975761519761498f565b6151ab836151a58354614dea565b8361513c565b5f601f8411600181146151dc575f85156151c55750838201355b5f19600387901b1c1916600186901b178355612161565b5f83815260208120601f198716915b8281101561520b57868501358255602094850194600190920191016151eb565b5086821015615227575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602080825281018290525f6040600584901b830181019083018583605e1936839003015b8782101561533957868503603f1901845282358181126152a3575f80fd5b890163ffffffff6152b382614a9a565b16865261ffff6152c560208301614b73565b1660208701526040810135601e198236030181126152e1575f80fd5b016020810190356001600160401b038111156152fb575f80fd5b803603821315615309575f80fd5b6060604088015261531e606088018284615239565b96505050602083019250602084019350600182019150615285565b5092979650505050505050565b5f8085851115615354575f80fd5b83861115615360575f80fd5b5050820193919092039150565b5f81518060208401855e5f93019283525090919050565b5f61538f828661536d565b838582375f930192835250909392505050565b602081525f611e65602083018486615239565b5f823561013e198336030181126150d7575f80fd5b6001600160401b03811681146122ac575f80fd5b63ffffffff6153ec89614a9a565b168152602088810135908201525f6040890135615408816153ca565b6001600160401b03811660408401525087606083015260e0608083015261543360e083018789615239565b6001600160a01b03861660a084015282810360c0840152615455818587615239565b9a9950505050505050505050565b5f6040828403128015615474575f80fd5b5061547d6149a3565b823581526020928301359281019290925250919050565b6001600160a01b039390931683526020830191909152604082015260600190565b606080825284519082018190525f9060208601906080840190835b818110156154ee5783518352602093840193909201916001016154d0565b50506020840195909552505060400152919050565b8181035f83128015838313168383128216171561552257615522614e1c565b5092915050565b634e487b7160e01b5f52603160045260245ffd5b6040815263ffffffff8351166040820152602083015160608201525f604084015160a0608084015261557260e08401826146ad565b90506060850151603f198483030160a085015261558f82826146ad565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b5f604082840312156155c5575f80fd5b6155cd6149a3565b825181526020928301519281019290925250919050565b5f604082840312156155f4575f80fd5b610e1683836155b5565b5f608082840312801561560f575f80fd5b50604051606081016001600160401b03811182821017156156325761563261498f565b604052825181526020830151615647816153ca565b602082015261565984604085016155b5565b60408201529392505050565b604081525f61567760408301856146ad565b8281036020840152612e0c81856146ad565b5f60208284031215615699575f80fd5b8151610e1681614ac7565b80356001600160f01b03198116906002841015615522576001600160f01b031960029490940360031b84901b1690921692915050565b5f82601f8301126156e9575f80fd5b81516001600160401b038111156157025761570261498f565b8060051b615712602082016149cb565b9182526020818501810192908101908684111561572d575f80fd5b6020860192505b838310156114da578251825260209283019290910190615734565b5f8060408385031215615760575f80fd5b82516001600160401b03811115615775575f80fd5b615781858286016156da565b92505060208301516001600160401b0381111561579c575f80fd5b6157a8858286016156da565b9150509250929050565b80356001600160c01b03198116906008841015615522576001600160c01b031960089490940360031b84901b1690921692915050565b80356020831015610cd8575f19602084900360031b1b1692915050565b6001600160401b0384168152826020820152606060408201525f612e0c60608301846146ad565b5f6020828403121561583c575f80fd5b8135610e16816153ca565b60018060a01b038516815283602082015261ffff83166040820152608060608201525f6114da60808301846146ad565b5f8161588557615885614e1c565b505f190190565b5f6020828403121561589c575f80fd5b8151610e1681614648565b8481526001600160401b0360c01b8460c01b1660208201528260288201525f6114da604883018461536d565b60c085901b6001600160c01b031916815260e084901b6001600160e01b0319166008820152600c81018390525f6114da602c83018461536d565b5f6001820161591e5761591e614e1c565b5060010190565b5f610e16828461536d56fe1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac7288590000000000000000000000008e846fd5f828425a2f4df7eac2ac2a84524f2256000000000000000000000000692fe0104706d772d51236418c7d1a2264b15031

Deployed Bytecode

0x608060405260043610610386575f3560e01c80635e280f11116101d9578063b731ea0a11610100578063cd5d211811610099578063cd5d211814610bb0578063d045a0dc14610bcf578063d424388514610be2578063dd62ed3e14610c01578063e9bbb04014610c20578063f2fde38b14610c4b578063fc0c546a14610769578063fe99049a14610c6a578063ff7bd03d14610c89575f80fd5b8063b731ea0a14610ac0578063b92d0eff14610769578063b98bd07014610adf578063bb0b6a5314610afe578063bc70b35414610b29578063bd815db014610b48578063c7c7f5b314610b5b578063ca5eb5e114610b7c578063ccb3afea14610b9b575f80fd5b806388ba16ab1161017257806388ba16ab1461096c5780638da5cb5b1461099a57806395d89b41146109b6578063963efcaa146109ca5780639f68b964146109fd578063a9059cbb14610a0f578063b40d18fb14610a2e578063b54fa47414610a5c578063b6363cf214610a87575f80fd5b80635e280f11146108495780636100d69a1461087c578063696c5ded1461089b5780636fc1b31e146108ba57806370a08231146108d9578063715018a6146108f85780637d25a05e1461090c578063857749b014610945578063862a4d4714610958575f80fd5b806323b872dd116102bd5780633b6f743b116102565780633b6f743b146106ff5780633c173a4f1461072b578063426a84931461074a57806352ae2879146107695780635535d4611461077b578063558a72971461079a578063598af9e7146107b95780635a0dfe4d146107f55780635cb0fd151461082a575f80fd5b806323b872dd146105f05780632a11ced01461060f5780632d34ba791461062e5780632eb9921f1461064d5780632ff42ab614610678578063313ce5671461069757806331ba5c0f146106b8578063334041a6146106cb5780633400288b146106e0575f80fd5b80630d35b4151161032f5780630d35b415146104da578063111ecdad1461050857806313137d6514610527578063134d4f251461053c578063156a0d0f1461056357806316ce049b1461058957806317442b70146105a857806318160ddd146105c85780631f5e1334146105dc575f80fd5b8062971a301461038a578062fdd58e146103bf57806301ffc9a7146104035780630455a1a11461043257806306fdde031461045c578063095bcdb61461047d578063095ea7b31461049c57806309fd3f6e146104bb575b5f80fd5b348015610395575f80fd5b506013546103a9906001600160a01b031681565b6040516103b69190614634565b60405180910390f35b3480156103ca575f80fd5b506103f56103d936600461465c565b600b60209081525f928352604080842090915290825290205481565b6040519081526020016103b6565b34801561040e575f80fd5b5061042261041d366004614686565b610ca8565b60405190151581526020016103b6565b34801561043d575f80fd5b506104476175d881565b60405163ffffffff90911681526020016103b6565b348015610467575f80fd5b50610470610cde565b6040516103b691906146db565b348015610488575f80fd5b506104226104973660046146ed565b610d6e565b3480156104a7575f80fd5b506104226104b636600461465c565b610e1d565b3480156104c6575f80fd5b506014546103a9906001600160a01b031681565b3480156104e5575f80fd5b506104f96104f4366004614735565b610e34565b6040516103b693929190614766565b348015610513575f80fd5b506004546103a9906001600160a01b031681565b61053a61053536600461484e565b610eff565b005b348015610547575f80fd5b50610550600281565b60405161ffff90911681526020016103b6565b34801561056e575f80fd5b506040805162b9270b60e21b815260016020820152016103b6565b348015610594575f80fd5b506012546103a9906001600160a01b031681565b3480156105b3575f80fd5b506040805160018082526020820152016103b6565b3480156105d3575f80fd5b506007546103f5565b3480156105e7575f80fd5b50610550600181565b3480156105fb575f80fd5b5061042261060a3660046148e8565b610fc3565b34801561061a575f80fd5b506103a9610629366004614926565b610fe7565b348015610639575f80fd5b5061053a61064836600461493d565b61100f565b348015610658575f80fd5b506103f5610667366004614974565b601b6020525f908152604090205481565b348015610683575f80fd5b506103f561069236600461465c565b611062565b3480156106a2575f80fd5b5060125b60405160ff90911681526020016103b6565b6103f56106c6366004614a21565b611186565b3480156106d6575f80fd5b506103f560115481565b3480156106eb575f80fd5b5061053a6106fa366004614aad565b611423565b34801561070a575f80fd5b5061071e610719366004614ad4565b611480565b6040516103b69190614b17565b348015610736575f80fd5b506103f5610745366004614b2e565b6114e4565b348015610755575f80fd5b506104226107643660046146ed565b611889565b348015610774575f80fd5b50306103a9565b348015610786575f80fd5b50610470610795366004614b84565b6118ed565b3480156107a5575f80fd5b506104226107b4366004614bb5565b61198f565b3480156107c4575f80fd5b506103f56107d33660046148e8565b600c60209081525f938452604080852082529284528284209052825290205481565b348015610800575f80fd5b5061042261080f366004614aad565b63ffffffff919091165f908152600160205260409020541490565b348015610835575f80fd5b5061053a61084436600461465c565b6119fe565b348015610854575f80fd5b506103a97f0000000000000000000000001a44076050125825900e736c501f859c50fe728c81565b348015610887575f80fd5b5061053a61089636600461465c565b611a75565b3480156108a6575f80fd5b50600e546103a9906001600160a01b031681565b3480156108c5575f80fd5b5061053a6108d4366004614974565b611ac1565b3480156108e4575f80fd5b506103f56108f3366004614974565b611b1f565b348015610903575f80fd5b5061053a611b39565b348015610917575f80fd5b5061092d610926366004614aad565b5f92915050565b6040516001600160401b0390911681526020016103b6565b348015610950575f80fd5b5060066106a6565b348015610963575f80fd5b506103f5611b4c565b348015610977575f80fd5b50610422610986366004614926565b60166020525f908152604090205460ff1681565b3480156109a5575f80fd5b505f546001600160a01b03166103a9565b3480156109c1575f80fd5b50610470611b6e565b3480156109d5575f80fd5b506103f57f000000000000000000000000000000000000000000000000000000e8d4a5100081565b348015610a08575f80fd5b505f610422565b348015610a1a575f80fd5b50610422610a2936600461465c565b611b7d565b348015610a39575f80fd5b50610422610a48366004614926565b60156020525f908152604090205460ff1681565b348015610a67575f80fd5b506103f5610a76366004614926565b601a6020525f908152604090205481565b348015610a92575f80fd5b50610422610aa136600461493d565b600a60209081525f928352604080842090915290825290205460ff1681565b348015610acb575f80fd5b506002546103a9906001600160a01b031681565b348015610aea575f80fd5b5061053a610af9366004614c21565b611b9c565b348015610b09575f80fd5b506103f5610b18366004614c5f565b60016020525f908152604090205481565b348015610b34575f80fd5b50610470610b43366004614c78565b611cf8565b61053a610b56366004614c21565b611e6d565b610b6e610b69366004614cd4565b611fed565b6040516103b6929190614d3c565b348015610b87575f80fd5b5061053a610b96366004614974565b6120e4565b348015610ba6575f80fd5b506103f560175481565b348015610bbb575f80fd5b50610422610bca366004614974565b612168565b61053a610bdd36600461484e565b6121c6565b348015610bed575f80fd5b5061053a610bfc366004614974565b6121f5565b348015610c0c575f80fd5b506103f5610c1b36600461493d565b612248565b348015610c2b575f80fd5b506103f5610c3a366004614974565b601c6020525f908152604090205481565b348015610c56575f80fd5b5061053a610c65366004614974565b612272565b348015610c75575f80fd5b50610422610c84366004614d8d565b6122af565b348015610c94575f80fd5b50610422610ca3366004614dd0565b612412565b5f6301ffc9a760e01b6001600160e01b031983161480610cd85750630f632fb360e01b6001600160e01b03198316145b92915050565b606060088054610ced90614dea565b80601f0160208091040260200160405190810160405280929190818152602001828054610d1990614dea565b8015610d645780601f10610d3b57610100808354040283529160200191610d64565b820191905f5260205f20905b815481529060010190602001808311610d4757829003601f168201915b5050505050905090565b335f908152600b60209081526040808320858452909152812080548391908390610d99908490614e30565b90915550506001600160a01b0384165f908152600b6020908152604080832086845290915281208054849290610dd0908490614e43565b9250508190555082846001600160a01b0316336001600160a01b03165f805160206159318339815191523386604051610e0a929190614e56565b60405180910390a45060015b9392505050565b5f33610e2a818585612446565b5060019392505050565b604080518082019091525f80825260208201526060610e6460405180604001604052805f81526020015f81525090565b6040805180820182525f8082526001600160401b03602080840182905284518381529081019094529195509182610ebd565b604080518082019091525f815260606020820152815260200190600190039081610e965790505b5093505f80610ee1604089013560608a0135610edc60208c018c614c5f565b612458565b60408051808201909152918252602082015296989597505050505050565b7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b03163314610f5357336040516391ac5e4f60e01b8152600401610f4a9190614634565b60405180910390fd5b60208701803590610f6d90610f68908a614c5f565b61249b565b14610fab57610f7f6020880188614c5f565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610f4a565b610fba878787878787876124d6565b50505050505050565b5f33610fd085828561272c565b610fdb85858561277c565b50600195945050505050565b60188181548110610ff6575f80fd5b5f918252602090912001546001600160a01b0316905081565b61101833612168565b6110345760405162461bcd60e51b8152600401610f4a90614e6f565b601380546001600160a01b039384166001600160a01b03199182161790915560148054929093169116179055565b5f8061106d33612168565b15905080156110c1576001600160a01b038416331480156110a557506001600160a01b0384165f9081526019602052604090205460ff165b6110c15760405162461bcd60e51b8152600401610f4a90614e8c565b6014545f906001600160a01b031633146110db575f6110e8565b6014546001600160a01b03165b90506110f585828661277c565b9250811561117e57600e5460405163daf8629560e01b815233600482015260248101859052306044820152600160648201526001600160a01b039091169063daf86295906084016020604051808303815f875af1158015611158573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061117c9190614ea9565b505b505092915050565b5f61119033612168565b6111ac5760405162461bcd60e51b8152600401610f4a90614e6f565b5f8251116111ec5760405162461bcd60e51b815260206004820152600d60248201526c656d707479206d65737361676560981b6044820152606401610f4a565b7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b031661124d5760405162461bcd60e51b8152602060048201526008602482015267195b991c1bda5b9d60c21b6044820152606401610f4a565b5f61125783612cf5565b905060ff81166006146112965760405162461bcd60e51b81526020600482015260076024820152661a5b9d985b1a5960ca1b6044820152606401610f4a565b60405163815d56a960e01b815260ff821660048201526175d8905f9073e88bda54bccb0ac383bcc71843b079175cb480bb9063815d56a9906024015f60405180830381865af41580156112eb573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526113129190810190614ec0565b90505f6113218387845f612d37565b80519091503410156113685760405162461bcd60e51b815260206004820152601060248201526f496e73756666696369656e742066656560801b6044820152606401610f4a565b5f6113768488858533612e15565b80515f90815260166020908152604091829020805460ff191660011790558251825160ff8a16815242928101929092529293507fd23c04eb80d47f722670c30a453e7989a870fca6f0610d995f87b38b73473969910160405180910390a2815134111561141657815133906108fc906113ef9034614e30565b6040518115909202915f818181858888f19350505050158015611414573d5f803e3d5ffd5b505b519450505050505b919050565b61142b612f1b565b63ffffffff82165f81815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b91015b60405180910390a15050565b604080518082019091525f80825260208201525f6114ae60408501356060860135610edc6020880188614c5f565b9150505f806114bd8684612f47565b90925090506114da6114d26020880188614c5f565b838388612d37565b9695505050505050565b5f600d546001146115245760405162461bcd60e51b815260206004820152600a6024820152695245454e5452414e435960b01b6044820152606401610f4a565b6002600d555f611532611b4c565b61153d906001614e43565b6012549091506001600160a01b031633036115665761155d868287613066565b8491505061187c565b600e54604051638340f54960e01b81525f916001600160a01b031690638340f5499061159a908a9089908b90600401614f34565b6020604051808303815f875af11580156115b6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115da9190614ea9565b90505f856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611619573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061163d9190614f58565b60ff1690505f601282106116515782611671565b61165c826012614e30565b61166790600a615053565b611671908461505e565b90505f61167e878661321d565b9050600c61168c8683614e30565b11156117b8576002603c6116ab670de0b6b3a7640000622dc6c061505e565b6116b59190615089565b6116bf9084615089565b101580156116f55750603c6116df670de0b6b3a7640000622dc6c061505e565b6116e99190615089565b6116f3908361509c565b155b61172b5760405162461bcd60e51b81526020600482015260076024820152667472616e63686560c81b6044820152606401610f4a565b8160115f82825461173c9190614e43565b909155506117579050670de0b6b3a7640000622dc6c061505e565b60115410611763575f80fd5b601054611784906001600160a01b031661177e600188614e30565b84613066565b61178f60038361505e565b6001600160a01b038b165f908152601960205260409020805460ff19166001179055915061186a565b600e546040805163949b9c6960e01b815290515f926001600160a01b03169163949b9c699160048083019260209291908290030181865afa1580156117ff573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118239190614ea9565b905061185c611832828561505e565b61183a611b4c565b6118449085614e30565b611857670de0b6b3a7640000600c61505e565b613232565b6118669084614e43565b9250505b6118758a8284613066565b5093505050505b6001600d55949350505050565b335f818152600c602090815260408083206001600160a01b03881680855290835281842087855290925280832085905551919285927fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a790610e0a9087815260200190565b600360209081525f92835260408084209091529082529020805461191090614dea565b80601f016020809104026020016040519081016040528092919081815260200182805461193c90614dea565b80156119875780601f1061195e57610100808354040283529160200191611987565b820191905f5260205f20905b81548152906001019060200180831161196a57829003601f168201915b505050505081565b335f818152600a602090815260408083206001600160a01b038716808552908352818420805460ff191687151590811790915591519182529293917fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267910160405180910390a350600192915050565b611a0733612168565b611a235760405162461bcd60e51b8152600401610f4a90614e6f565b6001600160a01b0382165f908152601b6020526040902054611a4590826132ce565b6001600160a01b0383165f908152601b602052604081208054909190611a6c908490614e30565b90915550505050565b611a7e33612168565b611a9a5760405162461bcd60e51b8152600401610f4a90614e6f565b6001600160a01b0382165f908152601b602052604081208054839290611a6c908490614e43565b611ac9612f1b565b600480546001600160a01b0319166001600160a01b0383161790556040517ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d441419790611b14908390614634565b60405180910390a150565b6001600160a01b03165f9081526005602052604090205490565b611b41612f1b565b611b4a5f6132dc565b565b5f6224ed20600f5442611b5f9190614e30565b611b699190615089565b905090565b606060098054610ced90614dea565b5f611b8933848461277c565b8214611b93575f80fd5b50600192915050565b611ba4612f1b565b5f5b81811015611cc657611be8838383818110611bc357611bc36150af565b9050602002810190611bd591906150c3565b611be39060408101906150e1565b61332b565b828282818110611bfa57611bfa6150af565b9050602002810190611c0c91906150c3565b611c1a9060408101906150e1565b60035f868686818110611c2f57611c2f6150af565b9050602002810190611c4191906150c3565b611c4f906020810190614c5f565b63ffffffff1663ffffffff1681526020019081526020015f205f868686818110611c7b57611c7b6150af565b9050602002810190611c8d91906150c3565b611c9e906040810190602001615123565b61ffff16815260208101919091526040015f2091611cbd919083615180565b50600101611ba6565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b6748282604051611474929190615261565b63ffffffff84165f90815260036020908152604080832061ffff87168452909152812080546060929190611d2b90614dea565b80601f0160208091040260200160405190810160405280929190818152602001828054611d5790614dea565b8015611da25780601f10611d7957610100808354040283529160200191611da2565b820191905f5260205f20905b815481529060010190602001808311611d8557829003601f168201915b5050505050905080515f03611df05783838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250929450611e659350505050565b5f839003611dff579050611e65565b60028310611e4857611e11848461332b565b80611e1f8460028188615346565b604051602001611e3193929190615384565b604051602081830303815290604052915050611e65565b8383604051639a6d49cd60e01b8152600401610f4a9291906153a2565b949350505050565b5f5b81811015611f705736838383818110611e8a57611e8a6150af565b9050602002810190611e9c91906153b5565b9050611ece611eae6020830183614c5f565b602083013563ffffffff919091165f908152600160205260409020541490565b611ed85750611f68565b3063d045a0dc60c08301358360a0810135611ef76101008301836150e1565b611f08610100890160e08a01614974565b611f166101208a018a6150e1565b6040518963ffffffff1660e01b8152600401611f3897969594939291906153de565b5f604051808303818588803b158015611f4f575f80fd5b505af1158015611f61573d5f803e3d5ffd5b5050505050505b600101611e6f565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b81526004015f60405180830381865afa158015611fac573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052611fd39190810190614ec0565b604051638351eea760e01b8152600401610f4a91906146db565b611ff561459d565b604080518082019091525f80825260208201525f806120296040880135606089013561202460208b018b614c5f565b61336c565b915091505f806120398984612f47565b909250905061206561204e60208b018b614c5f565b838361205f368d90038d018d615463565b8b612e15565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a906120b3908d018d614c5f565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6120ec612f1b565b60405163ca5eb5e160e01b81526001600160a01b037f0000000000000000000000001a44076050125825900e736c501f859c50fe728c169063ca5eb5e190612138908490600401614634565b5f604051808303815f87803b15801561214f575f80fd5b505af1158015612161573d5f803e3d5ffd5b5050505050565b600e545f906001600160a01b038381169116148061219357506014546001600160a01b038381169116145b806121ab57506013546001600160a01b038381169116145b80610cd85750506012546001600160a01b0390811691161490565b3330146121e65760405163029a949d60e31b815260040160405180910390fd5b610fba87878787878787610fab565b6121fd612f1b565b600280546001600160a01b0319166001600160a01b0383161790556040517fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090611b14908390614634565b6001600160a01b039182165f90815260066020908152604080832093909416825291909152205490565b61227a612f1b565b6001600160a01b0381166122a3575f604051631e4fbdf760e01b8152600401610f4a9190614634565b6122ac816132dc565b50565b5f336001600160a01b038616148015906122ec57506001600160a01b0385165f908152600a6020908152604080832033845290915290205460ff16155b1561235c576001600160a01b0385165f908152600c6020908152604080832033845282528083208684529091529020545f19811461235a5761232e8382614e30565b6001600160a01b0387165f908152600c6020908152604080832033845282528083208884529091529020555b505b6001600160a01b0385165f908152600b602090815260408083208684529091528120805484929061238e908490614e30565b90915550506001600160a01b0384165f908152600b60209081526040808320868452909152812080548492906123c5908490614e43565b9250508190555082846001600160a01b0316866001600160a01b03165f8051602061593183398151915233866040516123ff929190614e56565b60405180910390a4506001949350505050565b5f6020820180359060019083906124299086614c5f565b63ffffffff16815260208101919091526040015f20541492915050565b61245383838360016134fc565b505050565b5f80612463856135ce565b915081905083811015612493576040516371c4efed60e01b81526004810182905260248101859052604401610f4a565b935093915050565b63ffffffff81165f9081526001602052604081205480610cd85760405163f6ff4fb760e01b815263ffffffff84166004820152602401610f4a565b6175d86124e66020890189614c5f565b63ffffffff161461251f5760405162461bcd60e51b81526020600482015260036024820152621cdbdb60ea1b6044820152606401610f4a565b60015f61252f60208a018a614c5f565b63ffffffff1663ffffffff1681526020019081526020015f205487602001351461256b5760405162461bcd60e51b8152600401610f4a90614e8c565b5f8681526015602052604090205460ff16156125af5760405162461bcd60e51b815260206004820152600360248201526234303960e81b6044820152606401610f4a565b5f868152601560205260408120805460ff191660011790556125d18686613604565b90505f6125dd82613626565b90505f6125ea888861365a565b90505f6125fd6125fa8a8a6136a4565b90565b90505f61260983612cf5565b905060041960ff8216016126e757601354604051620b465d60e71b81526001600160a01b03909116906305a32e80906126469086906004016146db565b5f604051808303815f87803b15801561265d575f80fd5b505af115801561266f573d5f803e3d5ffd5b5050505060135f9054906101000a90046001600160a01b03166001600160a01b03168b7fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c8e5f0160208101906126c59190614c5f565b6040805163ffffffff90921682525f60208301520160405180910390a361271e565b60061960ff82160161270f5761270a8b61270460208f018f614c5f565b856136bb565b61271e565b61271e8c8c8c8c87878a6137b9565b505050505050505050505050565b5f6127378484612248565b90505f198114612776578181101561276857828183604051637dc7a0d960e11b8152600401610f4a93929190615494565b61277684848484035f6134fc565b50505050565b6001600160a01b0383165f908152601b6020526040812054829061279f86611b1f565b6127a99190614e30565b10156127b3575f80fd5b6001600160a01b0384165f908152601d602052604081206127d39061392c565b90506001600160a01b038416155f81806127fa57506012546001600160a01b038781169116145b90505f8261281557600184516128109190614e30565b612891565b600f5460405163c6dbdf1d60e01b815273e88bda54bccb0ac383bcc71843b079175cb480bb9163c6dbdf1d916128529188914291906004016154b5565b602060405180830381865af415801561286d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128919190614ea9565b90505b5f861180156128a357505f8112155b15612a36575f8482815181106128bb576128bb6150af565b6020908102919091018101516001600160a01b038b165f908152600b8352604080822083835290935291909120549091508015612a22576128fc88826132ce565b6001600160a01b038b165f908152600b60209081526040808320868452909152812080549293508392909190612933908490614e30565b9091555084905061299c576001600160a01b0389165f908152601d6020526040902061295f9083613985565b6001600160a01b0389165f908152600b6020908152604080832085845290915281208054839290612991908490614e43565b909155506129bf9050565b5f828152601a6020526040812080548392906129b9908490614e30565b90915550505b6001600160a01b038a165f908152600b602090815260408083208584529091528120549003612a09576001600160a01b038a165f908152601d60205260409020612a099083613a76565b612a138189614e30565b9750612a1f8188614e43565b96505b612a2d600184615503565b92505050612894565b8415612cea57612a47888887613b89565b612a5088611b1f565b9550612a66670de0b6b3a764000061019061505e565b861080612a9d5750603c612a85670de0b6b3a7640000622dc6c061505e565b612a8f9190615089565b612a9a90600461505e565b86115b8015612abf57506001600160a01b0388165f908152601c602052604090205415155b15612be8576001600160a01b0388165f908152601c6020526040812054612ae890600190614e30565b6018549091505f90612afc90600190614e30565b9050808214612b96575f60188281548110612b1957612b196150af565b5f918252602090912001546001600160a01b03169050612b3a836001614e43565b6001600160a01b0382165f908152601c60205260409020556018805482919085908110612b6957612b696150af565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b03160217905550505b6001600160a01b038a165f908152601c60205260408120556018805480612bbf57612bbf615529565b5f8281526020902081015f1990810180546001600160a01b031916905501905550506018546017555b612bf187611b1f565b95506001600160a01b03871615801590612c2057506001600160a01b0387165f908152601c6020526040902054155b8015612c3e5750612c3b670de0b6b3a764000061019061505e565b86115b8015612c745750603c612c5c670de0b6b3a7640000622dc6c061505e565b612c669190615089565b612c7190600461505e565b86105b15612cea57601854612c87906001614e43565b6001600160a01b0388165f818152601c602052604081209290925560188054600181018255928190527fb13d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e90920180546001600160a01b0319169091179055546017555b505050509392505050565b5f81515f03612d1757604051638d0242c960e01b815260040160405180910390fd5b815f81518110612d2957612d296150af565b016020015160f81c92915050565b604080518082019091525f80825260208201527f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b031663ddc28c586040518060a001604052808863ffffffff168152602001612d998961249b565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b8152600401612dce92919061553d565b6040805180830381865afa158015612de8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e0c91906155e4565b95945050505050565b612e1d61459d565b5f612e2a845f0151613c9c565b602085015190915015612e4457612e448460200151613cc3565b7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b0316632637a450826040518060a001604052808b63ffffffff168152602001612e948c61249b565b81526020018a81526020018981526020015f8960200151111515815250866040518463ffffffff1660e01b8152600401612ecf92919061553d565b60806040518083038185885af1158015612eeb573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190612f1091906155fe565b979650505050505050565b5f546001600160a01b03163314611b4a573360405163118cdaa760e01b8152600401610f4a9190614634565b6060805f612fa28560200135612f5c86613da6565b612f6960a08901896150e1565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250613dd192505050565b90935090505f81612fb4576001612fb7565b60025b9050612fd7612fc96020880188614c5f565b82610b4360808a018a6150e1565b6004549093506001600160a01b03161561305d576004805460405163043a78eb60e01b81526001600160a01b039091169163043a78eb9161301c918891889101615665565b602060405180830381865afa158015613037573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061305b9190615689565b505b50509250929050565b5f828152601a602052604081208054839290613083908490614e43565b90915550506001600160a01b0383165f908152601d602052604090206130a99083613985565b6130b45f8483613b89565b6001600160a01b0383165f908152600b60209081526040808320858452909152812080548392906130e6908490614e43565b909155505f90506130f684611b1f565b6001600160a01b0385165f908152601c6020526040902054909150158015613130575061312d670de0b6b3a764000061019061505e565b81115b80156131665750603c61314e670de0b6b3a7640000622dc6c061505e565b6131589190615089565b61316390600461505e565b81105b156131dc57601854613179906001614e43565b6001600160a01b0385165f818152601c602052604081209290925560188054600181018255928190527fb13d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e90920180546001600160a01b0319169091179055546017555b82846001600160a01b03165f6001600160a01b03165f80516020615931833981519152338660405161320f929190614e56565b60405180910390a450505050565b5f81831161322b5781610e16565b5090919050565b5f838302815f1985870982811083820303915050808411613251575f80fd5b805f0361326357508290049050610e16565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f81831061322b5781610e16565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f6133396002828486615346565b613342916156a4565b60f01c905060038114612453578282604051639a6d49cd60e01b8152600401610f4a9291906153a2565b5f80613379858585612458565b6040516323a9655f60e21b815291935091505f9073e88bda54bccb0ac383bcc71843b079175cb480bb90638ea5957c906133b990849036906004016153a2565b5f60405180830381865af41580156133d3573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526133fa9190810190614ec0565b90505f81511161343a5760405162461bcd60e51b815260206004820152600b60248201526a4c5a3a206e6f206461746160a81b6044820152606401610f4a565b5f8082806020019051810190613450919061574f565b915091508051825114801561346557505f8251115b6134a05760405162461bcd60e51b815260206004820152600c60248201526b4c5a3a20626164206461746160a01b6044820152606401610f4a565b5f5b82518110156134f0576134e8338483815181106134c1576134c16150af565b60200260200101518484815181106134db576134db6150af565b6020026020010151613e4b565b6001016134a2565b50505050935093915050565b6001600160a01b038416613525575f60405163e602df0560e01b8152600401610f4a9190614634565b6001600160a01b03831661354e575f604051634a1406b160e11b8152600401610f4a9190614634565b6001600160a01b038085165f908152600660209081526040808320938716835292905220829055801561277657826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516135c091815260200190565b60405180910390a350505050565b5f7f000000000000000000000000000000000000000000000000000000e8d4a510006135fa8184615089565b610cd8919061505e565b5f613613602860208486615346565b61361c916157b2565b60c01c9392505050565b5f610cd87f000000000000000000000000000000000000000000000000000000e8d4a510006001600160401b03841661505e565b60606136698260288186615346565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250929695505050505050565b5f6136b26020828486615346565b610e16916157e8565b5f806136c683613ec4565b915091505f6136d482613ff4565b601454909150613700906001600160a01b03166136ef611b4c565b6136fa906001614e43565b83613066565b60145460405163d7c109a960e01b81526001600160a01b039091169063d7c109a99061373490869085908990600401615805565b5f604051808303815f87803b15801561374b575f80fd5b505af115801561375d573d5f803e3d5ffd5b50506014546040805163ffffffff8a168152602081018690526001600160a01b0390921693508992507fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c910160405180910390a3505050505050565b5f80848060200190518101906137cf919061574f565b91509150805182511480156137e457505f8251115b61381b5760405162461bcd60e51b81526020600482015260086024820152674c5a3a206461746160c01b6044820152606401610f4a565b5f5b825181101561386b576138638584838151811061383c5761383c6150af565b6020026020010151848481518110613856576138566150af565b6020026020010151613066565b60010161381d565b506028861115613921575f61389d61388960608c0160408d0161582c565b61389660208d018d614c5f565b868961400d565b604051633e5ac80960e11b81529091506001600160a01b037f0000000000000000000000001a44076050125825900e736c501f859c50fe728c1690637cb59012906138f29088908d905f908790600401615847565b5f604051808303815f87803b158015613909575f80fd5b505af115801561391b573d5f803e3d5ffd5b50505050505b505050505050505050565b6060815f0180548060200260200160405190810160405280929190818152602001828054801561397957602002820191905f5260205f20905b815481526020019060010190808311613965575b50505050509050919050565b5f81815260018301602052604090205460ff16156139a1575050565b5f818152600183810160205260408220805460ff191690911790556139c6838361403f565b508354600181810186555f86815260208120909201829055855492935090916139ef9190614e30565b90505b81811115613a505783613a06600183614e30565b81548110613a1657613a166150af565b905f5260205f200154845f018281548110613a3357613a336150af565b5f9182526020909120015580613a4881615877565b9150506139f2565b5081835f018281548110613a6657613a666150af565b5f91825260209091200155505050565b5f81815260018301602052604090205460ff16613acc5760405162461bcd60e51b815260206004820152601460248201527315985b1d5948191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610f4a565b5f613ad7838361403f565b50835490915081108015613b06575081835f018281548110613afb57613afb6150af565b905f5260205f200154145b613b445760405162461bcd60e51b815260206004820152600f60248201526e15985b1d59481b9bdd08199bdd5b99608a1b6044820152606401610f4a565b5f19835f018281548110613b5a57613b5a6150af565b5f9182526020808320909101929092558381526001850190915260409020805460ff19169055612453836140f2565b6001600160a01b038316613bb3578060075f828254613ba89190614e43565b90915550613c109050565b6001600160a01b0383165f9081526005602052604090205481811015613bf25783818360405163391434e360e21b8152600401610f4a93929190615494565b6001600160a01b0384165f9081526005602052604090209082900390555b6001600160a01b038216613c2c57600780548290039055613c4a565b6001600160a01b0382165f9081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051613c8f91815260200190565b60405180910390a3505050565b5f813414613cbf576040516304fb820960e51b8152346004820152602401610f4a565b5090565b5f7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa158015613d20573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613d44919061588c565b90506001600160a01b038116613d6d576040516329b99a9560e11b815260040160405180910390fd5b613da26001600160a01b038216337f0000000000000000000000001a44076050125825900e736c501f859c50fe728c8561425c565b5050565b5f610cd87f000000000000000000000000000000000000000000000000000000e8d4a5100083615089565b8051606090151580613e1a578484604051602001613e0692919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052613e41565b84843385604051602001613e3194939291906158a7565b6040516020818303038152906040525b9150935093915050565b6001600160a01b0383165f908152600b6020908152604080832085845290915281208054839290613e7d908490614e30565b92505081905550815f6001600160a01b0316846001600160a01b03165f805160206159318339815191523385604051613eb7929190614e56565b60405180910390a4505050565b5f80601183511015613ee957604051638d0242c960e01b815260040160405180910390fd5b600760ff16835f81518110613f0057613f006150af565b016020015160f81c14613f2657604051635b60892f60e01b815260040160405180910390fd5b613f318360016142b6565b9150613f3e8360096142b6565b9050816001600160401b03165f03613f685760405162320bd360e51b815260040160405180910390fd5b806001600160401b03165f03613f915760405163162908e360e11b815260040160405180910390fd5b64e8d4a51000816001600160401b03161115613fef5760405162461bcd60e51b815260206004820152601c60248201527f436f6d70656e736174696f6e2065786365656473206d6178696d756d000000006044820152606401610f4a565b915091565b5f610cd86001600160401b03831664e8d4a5100061505e565b60608484848460405160200161402694939291906158d3565b6040516020818303038152906040529050949350505050565b81545f90819081905b808210156140e4575f600261405d8484614e30565b6140679190615089565b6140719084614e43565b905085875f018281548110614088576140886150af565b905f5260205f200154036140a4579350600192506140eb915050565b85875f0182815481106140b9576140b96150af565b905f5260205f20015410156140da576140d3816001614e43565b92506140de565b8091505b50614048565b5091505f90505b9250929050565b80545f9081906001600160401b0381111561410f5761410f61498f565b604051908082528060200260200182016040528015614138578160200160208202803683370190505b5090505f5b83548110156141ba575f19845f01828154811061415c5761415c6150af565b905f5260205f200154146141b257835f01818154811061417e5761417e6150af565b905f5260205f200154828481518110614199576141996150af565b6020908102919091010152826141ae8161590d565b9350505b60010161413d565b50816001600160401b038111156141d3576141d361498f565b6040519080825280602002602001820160405280156141fc578160200160208202803683370190505b5080516142109185916020909101906145e0565b505f5b828110156127765781818151811061422d5761422d6150af565b6020026020010151845f018281548110614249576142496150af565b5f91825260209091200155600101614213565b61277684856001600160a01b03166323b872dd86868660405160240161428493929190614f34565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505061445b565b5f6142c2826008614e43565b835110156143125760405162461bcd60e51b815260206004820152601c60248201527f496e73756666696369656e74206461746120666f722075696e743634000000006044820152606401610f4a565b603883614320846007614e43565b81518110614330576143306150af565b016020015160f81c901b603084614348856006614e43565b81518110614358576143586150af565b016020015160f81c901b602885614370866005614e43565b81518110614380576143806150af565b0160209081015160f81c90911b908661439a876004614e43565b815181106143aa576143aa6150af565b016020015160f81c901b6018876143c2886003614e43565b815181106143d2576143d26150af565b016020015160f81c901b6010886143ea896002614e43565b815181106143fa576143fa6150af565b016020015160f81c901b6008896144128a6001614e43565b81518110614422576144226150af565b01602001518a5160f89190911c90911b908a908a908110614445576144456150af565b016020015160f81c171717171717179392505050565b5f61446f6001600160a01b038416836144b3565b905080515f141580156144935750808060200190518101906144919190615689565b155b156124535782604051635274afe760e01b8152600401610f4a9190614634565b6060610e1683835f845f80856001600160a01b031684866040516144d79190615925565b5f6040518083038185875af1925050503d805f8114614511576040519150601f19603f3d011682016040523d82523d5f602084013e614516565b606091505b50915091506114da8683836060826145365761453182614574565b610e16565b815115801561454d57506001600160a01b0384163b155b1561456d5783604051639996b31560e01b8152600401610f4a9190614634565b5080610e16565b8051156145845780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60405180606001604052805f80191681526020015f6001600160401b031681526020016145db60405180604001604052805f81526020015f81525090565b905290565b828054828255905f5260205f20908101928215614619579160200282015b828111156146195782518255916020019190600101906145fe565b50613cbf9291505b80821115613cbf575f8155600101614621565b6001600160a01b0391909116815260200190565b6001600160a01b03811681146122ac575f80fd5b5f806040838503121561466d575f80fd5b823561467881614648565b946020939093013593505050565b5f60208284031215614696575f80fd5b81356001600160e01b031981168114610e16575f80fd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610e1660208301846146ad565b5f805f606084860312156146ff575f80fd5b833561470a81614648565b95602085013595506040909401359392505050565b5f60e0828403121561472f575f80fd5b50919050565b5f60208284031215614745575f80fd5b81356001600160401b0381111561475a575f80fd5b611e658482850161471f565b83518152602080850151908201525f60a0820160a0604084015280855180835260c08501915060c08160051b8601019250602087015f5b828110156147e55760bf1987860301845281518051865260208101519050604060208701526147cf60408701826146ad565b955050602093840193919091019060010161479d565b5050855160608601525050602084015160808401529050611e65565b5f6060828403121561472f575f80fd5b5f8083601f840112614821575f80fd5b5081356001600160401b03811115614837575f80fd5b6020830191508360208285010111156140eb575f80fd5b5f805f805f805f60e0888a031215614864575f80fd5b61486e8989614801565b96506060880135955060808801356001600160401b0381111561488f575f80fd5b61489b8a828b01614811565b90965094505060a08801356148af81614648565b925060c08801356001600160401b038111156148c9575f80fd5b6148d58a828b01614811565b989b979a50959850939692959293505050565b5f805f606084860312156148fa575f80fd5b833561490581614648565b9250602084013561491581614648565b929592945050506040919091013590565b5f60208284031215614936575f80fd5b5035919050565b5f806040838503121561494e575f80fd5b823561495981614648565b9150602083013561496981614648565b809150509250929050565b5f60208284031215614984575f80fd5b8135610e1681614648565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b03811182821017156149c5576149c561498f565b60405290565b604051601f8201601f191681016001600160401b03811182821017156149f3576149f361498f565b604052919050565b5f6001600160401b03821115614a1357614a1361498f565b50601f01601f191660200190565b5f60208284031215614a31575f80fd5b81356001600160401b03811115614a46575f80fd5b8201601f81018413614a56575f80fd5b8035614a69614a64826149fb565b6149cb565b818152856020838501011115614a7d575f80fd5b816020840160208301375f91810160200191909152949350505050565b803563ffffffff8116811461141e575f80fd5b5f8060408385031215614abe575f80fd5b61467883614a9a565b80151581146122ac575f80fd5b5f8060408385031215614ae5575f80fd5b82356001600160401b03811115614afa575f80fd5b614b068582860161471f565b925050602083013561496981614ac7565b815181526020808301519082015260408101610cd8565b5f805f8060808587031215614b41575f80fd5b8435614b4c81614648565b9350602085013592506040850135614b6381614648565b9396929550929360600135925050565b803561ffff8116811461141e575f80fd5b5f8060408385031215614b95575f80fd5b614b9e83614a9a565b9150614bac60208401614b73565b90509250929050565b5f8060408385031215614bc6575f80fd5b8235614bd181614648565b9150602083013561496981614ac7565b5f8083601f840112614bf1575f80fd5b5081356001600160401b03811115614c07575f80fd5b6020830191508360208260051b85010111156140eb575f80fd5b5f8060208385031215614c32575f80fd5b82356001600160401b03811115614c47575f80fd5b614c5385828601614be1565b90969095509350505050565b5f60208284031215614c6f575f80fd5b610e1682614a9a565b5f805f8060608587031215614c8b575f80fd5b614c9485614a9a565b9350614ca260208601614b73565b925060408501356001600160401b03811115614cbc575f80fd5b614cc887828801614811565b95989497509550505050565b5f805f8385036080811215614ce7575f80fd5b84356001600160401b03811115614cfc575f80fd5b614d088782880161471f565b9450506040601f1982011215614d1c575f80fd5b506020840191506060840135614d3181614648565b809150509250925092565b5f60c082019050835182526001600160401b0360208501511660208301526040840151614d76604084018280518252602090810151910152565b5082516080830152602083015160a0830152610e16565b5f805f8060808587031215614da0575f80fd5b8435614dab81614648565b93506020850135614dbb81614648565b93969395505050506040820135916060013590565b5f60608284031215614de0575f80fd5b610e168383614801565b600181811c90821680614dfe57607f821691505b60208210810361472f57634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115610cd857610cd8614e1c565b80820180821115610cd857610cd8614e1c565b6001600160a01b03929092168252602082015260400190565b6020808252600390820152620d0c0d60ea1b604082015260600190565b60208082526003908201526234303360e81b604082015260600190565b5f60208284031215614eb9575f80fd5b5051919050565b5f60208284031215614ed0575f80fd5b81516001600160401b03811115614ee5575f80fd5b8201601f81018413614ef5575f80fd5b8051614f03614a64826149fb565b818152856020838501011115614f17575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b5f60208284031215614f68575f80fd5b815160ff81168114610e16575f80fd5b6001815b600184111561249357808504811115614f9757614f97614e1c565b6001841615614fa557908102905b60019390931c928002614f7c565b5f82614fc157506001610cd8565b81614fcd57505f610cd8565b8160018114614fe35760028114614fed57615009565b6001915050610cd8565b60ff841115614ffe57614ffe614e1c565b50506001821b610cd8565b5060208310610133831016604e8410600b841016171561502c575081810a610cd8565b6150385f198484614f78565b805f190482111561504b5761504b614e1c565b029392505050565b5f610e168383614fb3565b8082028115828204841417610cd857610cd8614e1c565b634e487b7160e01b5f52601260045260245ffd5b5f8261509757615097615075565b500490565b5f826150aa576150aa615075565b500690565b634e487b7160e01b5f52603260045260245ffd5b5f8235605e198336030181126150d7575f80fd5b9190910192915050565b5f808335601e198436030181126150f6575f80fd5b8301803591506001600160401b0382111561510f575f80fd5b6020019150368190038213156140eb575f80fd5b5f60208284031215615133575f80fd5b610e1682614b73565b601f82111561245357805f5260205f20601f840160051c810160208510156151615750805b601f840160051c820191505b81811015612161575f815560010161516d565b6001600160401b038311156151975761519761498f565b6151ab836151a58354614dea565b8361513c565b5f601f8411600181146151dc575f85156151c55750838201355b5f19600387901b1c1916600186901b178355612161565b5f83815260208120601f198716915b8281101561520b57868501358255602094850194600190920191016151eb565b5086821015615227575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602080825281018290525f6040600584901b830181019083018583605e1936839003015b8782101561533957868503603f1901845282358181126152a3575f80fd5b890163ffffffff6152b382614a9a565b16865261ffff6152c560208301614b73565b1660208701526040810135601e198236030181126152e1575f80fd5b016020810190356001600160401b038111156152fb575f80fd5b803603821315615309575f80fd5b6060604088015261531e606088018284615239565b96505050602083019250602084019350600182019150615285565b5092979650505050505050565b5f8085851115615354575f80fd5b83861115615360575f80fd5b5050820193919092039150565b5f81518060208401855e5f93019283525090919050565b5f61538f828661536d565b838582375f930192835250909392505050565b602081525f611e65602083018486615239565b5f823561013e198336030181126150d7575f80fd5b6001600160401b03811681146122ac575f80fd5b63ffffffff6153ec89614a9a565b168152602088810135908201525f6040890135615408816153ca565b6001600160401b03811660408401525087606083015260e0608083015261543360e083018789615239565b6001600160a01b03861660a084015282810360c0840152615455818587615239565b9a9950505050505050505050565b5f6040828403128015615474575f80fd5b5061547d6149a3565b823581526020928301359281019290925250919050565b6001600160a01b039390931683526020830191909152604082015260600190565b606080825284519082018190525f9060208601906080840190835b818110156154ee5783518352602093840193909201916001016154d0565b50506020840195909552505060400152919050565b8181035f83128015838313168383128216171561552257615522614e1c565b5092915050565b634e487b7160e01b5f52603160045260245ffd5b6040815263ffffffff8351166040820152602083015160608201525f604084015160a0608084015261557260e08401826146ad565b90506060850151603f198483030160a085015261558f82826146ad565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b5f604082840312156155c5575f80fd5b6155cd6149a3565b825181526020928301519281019290925250919050565b5f604082840312156155f4575f80fd5b610e1683836155b5565b5f608082840312801561560f575f80fd5b50604051606081016001600160401b03811182821017156156325761563261498f565b604052825181526020830151615647816153ca565b602082015261565984604085016155b5565b60408201529392505050565b604081525f61567760408301856146ad565b8281036020840152612e0c81856146ad565b5f60208284031215615699575f80fd5b8151610e1681614ac7565b80356001600160f01b03198116906002841015615522576001600160f01b031960029490940360031b84901b1690921692915050565b5f82601f8301126156e9575f80fd5b81516001600160401b038111156157025761570261498f565b8060051b615712602082016149cb565b9182526020818501810192908101908684111561572d575f80fd5b6020860192505b838310156114da578251825260209283019290910190615734565b5f8060408385031215615760575f80fd5b82516001600160401b03811115615775575f80fd5b615781858286016156da565b92505060208301516001600160401b0381111561579c575f80fd5b6157a8858286016156da565b9150509250929050565b80356001600160c01b03198116906008841015615522576001600160c01b031960089490940360031b84901b1690921692915050565b80356020831015610cd8575f19602084900360031b1b1692915050565b6001600160401b0384168152826020820152606060408201525f612e0c60608301846146ad565b5f6020828403121561583c575f80fd5b8135610e16816153ca565b60018060a01b038516815283602082015261ffff83166040820152608060608201525f6114da60808301846146ad565b5f8161588557615885614e1c565b505f190190565b5f6020828403121561589c575f80fd5b8151610e1681614648565b8481526001600160401b0360c01b8460c01b1660208201528260288201525f6114da604883018461536d565b60c085901b6001600160c01b031916815260e084901b6001600160e01b0319166008820152600c81018390525f6114da602c83018461536d565b5f6001820161591e5761591e614e1c565b5060010190565b5f610e16828461536d56fe1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859

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.