ETH Price: $2,232.99 (-8.62%)
 

Overview

ETH Balance

Scroll LogoScroll LogoScroll Logo0 ETH

ETH Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Burn Range48298752024-04-14 12:34:52658 days ago1713098092IN
0x50456024...bAd35582a
0 ETH0.000273970.96
Burn Limit35446902024-02-22 6:36:42710 days ago1708583802IN
0x50456024...bAd35582a
0 ETH0.000261290.46
Burn Limit33183962024-02-14 1:56:49719 days ago1707875809IN
0x50456024...bAd35582a
0 ETH0.000201680.45
Burn Range32595182024-02-12 0:15:37721 days ago1707696937IN
0x50456024...bAd35582a
0 ETH0.000500341.39
Burn Range32594822024-02-12 0:13:49721 days ago1707696829IN
0x50456024...bAd35582a
0 ETH0.000351491.39
Burn Range32594772024-02-12 0:13:34721 days ago1707696814IN
0x50456024...bAd35582a
0 ETH0.000328381.39
Burn Range32593622024-02-12 0:07:49721 days ago1707696469IN
0x50456024...bAd35582a
0 ETH0.000450091.39
Burn Range32592762024-02-12 0:03:31721 days ago1707696211IN
0x50456024...bAd35582a
0 ETH0.000469661.39
Burn Range32592652024-02-12 0:02:58721 days ago1707696178IN
0x50456024...bAd35582a
0 ETH0.000491161.39
Burn Range32592322024-02-12 0:01:19721 days ago1707696079IN
0x50456024...bAd35582a
0 ETH0.000305621.39

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
26333922024-01-20 22:18:59743 days ago1705789139  Contract Creation0 ETH
Cross-Chain Transactions
Loading...
Loading

Minimal Proxy Contract for 0xc1e9a857e96aeefe28f8340cf82de7801bd95a77

Contract Name:
LimitPool

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity Standard Json-Input format)

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import './interfaces/IPool.sol';
import './interfaces/range/IRangePool.sol';
import './interfaces/limit/ILimitPool.sol';
import './interfaces/limit/ILimitPoolView.sol';
import './interfaces/limit/ILimitPoolManager.sol';
import './base/storage/LimitPoolStorage.sol';
import './base/storage/LimitPoolImmutables.sol';
import './libraries/pool/SwapCall.sol';
import './libraries/pool/QuoteCall.sol';
import './libraries/pool/FeesCall.sol';
import './libraries/pool/SampleCall.sol';
import './libraries/range/pool/MintRangeCall.sol';
import './libraries/range/pool/BurnRangeCall.sol';
import './libraries/range/pool/SnapshotRangeCall.sol';
import './libraries/limit/pool/MintLimitCall.sol';
import './libraries/limit/pool/BurnLimitCall.sol';
import './libraries/limit/pool/SnapshotLimitCall.sol';
import './libraries/math/ConstantProduct.sol';
import './external/solady/LibClone.sol';
import './external/openzeppelin/security/LimitReentrancyGuard.sol';

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%#%@@@@%@@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%@@@@@@@@%%%%%%@@%==========+++**#@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@%%@@@@@@@@@@@@@@@%%##%%%@@%%%@@%%*-=====+++**#**+*#*#*#@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@=+===========+#@@@@@%#@%%%%%%*#%%%%%##%@@@@@@@@#****++#%#**#%=%@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@+-==+++***++++=++@@%%%%%%%%*****#%#*#@%@@@@@@@@@@@@@%#+#%#*##*=#@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@+=+*******+++%%##%%####++++++**@@%%@@@@@@@@@@@@@@@###*#%#+==@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@%=**##**+#%###########+====*@%@@%%@@@@@@@@@@@@@%###%@+%=+=*@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@#%%####*##*##*#**#**+=+=-=*%%%###%@@@@@@@@@@@####@#=====+*@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@#%#***#***#*****=*+=+==--=@@%#**###@@@@@@@@@@@###@@%=+++=++#@@@@*@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@##*********+***##++*+#%@*%@@@@@####@@@@#=%@@@@@@@@@@@+==++=+=%%#+*@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@##*****+**+*********++=%%%@@@@@@@@@*@@@%===@@@@@@@@@*===++=+++**#%#@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@#**++++*+++++===+***+==#%%#@@@@@@@@*##@*===+%@@@@%+==+=+++++++*%@%%@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@**++++++++====----+++====+###@@@@@@@%#%%*==++*+#+===+++++++++++*%%@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@%###*++++++====---=*@@@@+===-%@**%@@@@@@@#%%%===*+****+++*++++*+*+*#@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@*+++*+++++===--=-*@@@@@@@@=--=@@@#%@@@@@@@@#%%*++++**#+++*++**++***#*@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@%===+++++=+++===*@@@@@@@@@@=-=%@@@@@%#@@@@@@@*===*++=++*++++****+**#%+@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@*=+@@#==+==+=-=@@@%%@@@@@@@@@@@@#***%%@@@@%+===+++*+*******+**++*#%*#@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@======-==#***%#@@@@@@@@@@@##%%%%%%@#====****+*****#**###*+++#*@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@%-===*=++**###@@@@@@@@@@@@@+#%%%%@*======+*********###+++++++++%@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@%-=+#%%+*###%@@@@@@@@@@@@@@@#%#@%++=+++===**#*#****#%=====+++++++%@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@-=#****####@@@@@@@@@@@@@@@@#%@*++*#++++=+#*#####*#%%==============*@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@*=#**+*###@@@@@@@@@@@@@@@@@#%+**###**+++######%%#+-==--=-=------======#@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@++#++++*##%@@@@@@@@@@@@@@@%+*#%%######*##%#+=-=#@@@@@*----------------*@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@%+#*+++==+***#@@@@@@@@@@@%#%%####%%#*+#*++%@@@@@@@@@@@@@%+=======*@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@*+**+========------=-=%@#%%%%%%%%%%%%#%#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@#=====-==-------=#@@@@########%%%%%%##++#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%##%%@@@@@@@@@@@@%#######%%#*+=++@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%#*####++++=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%##**+=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@***%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

/**
 * @title Limit Pool - Constant Product
 * @author Poolshark
 * @author @alphak3y
 */
contract LimitPool is
    ILimitPool,
    ILimitPoolView,
    LimitPoolStorage,
    LimitPoolImmutables,
    LimitReentrancyGuard
{
    /// @notice This modifier only allows `owner` to call a function
    modifier ownerOnly() {
        _onlyOwner();
        _;
    }

    /// @notice This modifier only allows `factory` to call a function
    modifier factoryOnly() {
        _onlyFactory();
        _;
    }

    /// @notice This modifier checks for canoncial limit pools
    modifier canonicalOnly() {
        _onlyCanoncialClones();
        _;
    }

    /// @notice The original address of the deployed contract
    address public immutable original;
    /// @notice The factory address for the `initialize()` call
    address public immutable factory;

    constructor(address factory_) {
        original = address(this);
        factory = factory_;
    }

    /// @notice Initializes the LimitPool contract storage
    /// @param startPrice the Q64.96 sqrt price to start the pool from
    function initialize(uint160 startPrice)
        external
        nonReentrant(globalState)
        factoryOnly
        canonicalOnly
    {
        // initialize state
        globalState = Ticks.initialize(
            rangeTickMap,
            limitTickMap,
            samples,
            globalState,
            immutables(),
            startPrice
        );
    }

    /// @notice Adds bidirectional liquidity (RangePosition)
    /// @param params the params for minting the position
    /// @dev See PoolsharkStructs.sol for struct data
    function mintRange(MintRangeParams memory params)
        external
        nonReentrant(globalState)
        canonicalOnly
        returns (int256, int256)
    {
        MintRangeCache memory cache;
        cache.constants = immutables();
        return
            MintRangeCall.perform(
                positions,
                ticks,
                rangeTickMap,
                samples,
                globalState,
                cache,
                params
            );
    }

    /// @notice Removes bidirectional liquidity (RangePosition)
    /// @param params the params for burning the position
    /// @dev See PoolsharkStructs.sol for struct data
    function burnRange(BurnRangeParams memory params)
        external
        nonReentrant(globalState)
        canonicalOnly
        returns (int256, int256)
    {
        BurnRangeCache memory cache;
        cache.constants = immutables();
        return
            BurnRangeCall.perform(
                positions,
                ticks,
                rangeTickMap,
                samples,
                globalState,
                cache,
                params
            );
    }

    /// @notice Adds directional liquidity (LimitPosition)
    /// @param params the params for minting the position
    /// @dev See PoolsharkStructs.sol for struct data
    function mintLimit(MintLimitParams memory params)
        external
        nonReentrant(globalState)
        canonicalOnly
        returns (int256, int256)
    {
        MintLimitCache memory cache;
        cache.constants = immutables();
        return
            MintLimitCall.perform(
                params.zeroForOne ? positions0 : positions1,
                ticks,
                samples,
                rangeTickMap,
                limitTickMap,
                globalState,
                params,
                cache
            );
    }

    /// @notice Removes directional liquidity (LimitPosition)
    /// @param params the params for burning the position
    /// @dev See PoolsharkStructs.sol for struct data
    function burnLimit(BurnLimitParams memory params)
        external
        nonReentrant(globalState)
        canonicalOnly
        returns (int256, int256)
    {
        BurnLimitCache memory cache;
        cache.constants = immutables();
        return
            BurnLimitCall.perform(
                params.zeroForOne ? positions0 : positions1,
                ticks,
                limitTickMap,
                globalState,
                params,
                cache
            );
    }

    /// @notice Swaps tokens with the liquidity pool
    /// @param params the params for executing the swap
    /// @dev See PoolsharkStructs.sol for struct data
    function swap(SwapParams memory params)
        external
        nonReentrant(globalState)
        canonicalOnly
        returns (int256, int256)
    {
        SwapCache memory cache;
        cache.constants = immutables();
        return
            SwapCall.perform(
                ticks,
                samples,
                rangeTickMap,
                limitTickMap,
                globalState,
                params,
                cache
            );
    }

    /// @notice Increase the max sample count for oracle data
    /// @param newSampleCountMax the new max sample count
    function increaseSampleCount(uint16 newSampleCountMax)
        external
        nonReentrant(globalState)
        canonicalOnly
    {
        Samples.expand(samples, globalState.pool, newSampleCountMax);
    }

    /// @notice Modify or collect protocol fees
    /// @param params the params for modifying fees
    /// @dev See PoolsharkStructs.sol for struct data
    function fees(FeesParams memory params)
        external
        ownerOnly
        nonReentrant(globalState)
        canonicalOnly
        returns (uint128 token0Fees, uint128 token1Fees)
    {
        return FeesCall.perform(globalState, params, immutables());
    }

    /// @notice Receive a swap quote for tokenIn and tokenOut amounts
    /// @param params the params for executing the quote
    /// @dev See PoolsharkStructs.sol for struct data
    function quote(QuoteParams memory params)
        external
        view
        returns (
            uint256,
            uint256,
            uint160
        )
    {
        SwapCache memory cache;
        cache.constants = immutables();
        return
            QuoteCall.perform(
                ticks,
                rangeTickMap,
                limitTickMap,
                globalState,
                params,
                cache
            );
    }

    /// @notice Receive oracle samples
    /// @param secondsAgo an array of seconds in the past to receive samples for
    function sample(uint32[] memory secondsAgo)
        external
        view
        override
        returns (
            int56[] memory tickSecondsAccum,
            uint160[] memory secondsPerLiquidityAccum,
            uint160 averagePrice,
            uint128 averageLiquidity,
            int24 averageTick
        )
    {
        return SampleCall.perform(globalState, immutables(), secondsAgo);
    }

    /// @notice Snapshot token amounts for a RangePosition
    /// @param positionId the position id to snapshot values for
    function snapshotRange(uint32 positionId)
        external
        view
        returns (
            int56 tickSecondsAccum,
            uint160 secondsPerLiquidityAccum,
            uint128 feesOwed0,
            uint128 feesOwed1
        )
    {
        return
            SnapshotRangeCall.perform(
                positions,
                ticks,
                globalState,
                immutables(),
                positionId
            );
    }

    /// @notice Snapshot token amounts for a LimitPosition
    /// @param params the params to snapshot values for
    /// @dev See PoolsharkStructs.sol for struct data
    function snapshotLimit(SnapshotLimitParams memory params)
        external
        view
        returns (uint128, uint128)
    {
        return
            SnapshotLimitCall.perform(
                params.zeroForOne ? positions0 : positions1,
                ticks,
                limitTickMap,
                globalState,
                immutables(),
                params
            );
    }

    /// @notice Immutable values embedded directly in the contract bytecode
    /// @dev See PoolsharkStructs.sol for struct data
    function immutables() public view returns (LimitImmutables memory) {
        return
            LimitImmutables(
                owner(),
                original,
                factory,
                PriceBounds(minPrice(), maxPrice()),
                token0(),
                token1(),
                poolToken(),
                genesisTime(),
                tickSpacing(),
                swapFee()
            );
    }

    /// @notice The price bounds for the given curve math
    function priceBounds(int16 tickSpacing)
        external
        pure
        returns (uint160, uint160)
    {
        return ConstantProduct.priceBounds(tickSpacing);
    }

    function _onlyOwner() private view {
        if (msg.sender != owner()) require(false, 'OwnerOnly()');
    }

    function _onlyCanoncialClones() private view {
        // compute pool key
        bytes32 key = keccak256(
            abi.encode(original, token0(), token1(), swapFee())
        );

        // compute canonical pool address
        address predictedAddress = LibClone.predictDeterministicAddress(
            original,
            abi.encodePacked(
                owner(),
                token0(),
                token1(),
                poolToken(),
                minPrice(),
                maxPrice(),
                genesisTime(),
                tickSpacing(),
                swapFee()
            ),
            key,
            factory
        );
        // only allow delegateCall from canonical clones
        if (address(this) != predictedAddress)
            require(false, 'NoDelegateCall()');
    }

    function _onlyFactory() private view {
        if (msg.sender != factory) require(false, 'FactoryOnly()');
    }
}

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

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.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}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * 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.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

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

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * 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 override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override 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 value {ERC20} uses, unless this function is
     * 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 override returns (uint8) {
        return 18;
    }

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

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override 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 `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

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

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` 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 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        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 `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `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.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` 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.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

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

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
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 v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 7 of 57 : LimitPoolEvents.sol
// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

/// @notice Events emitted by the LimitPool contract(s)
interface LimitPoolEvents {

    /////////////////////////////////////////////////////////////
    /////////////////////// Custom Events ///////////////////////
    /////////////////////////////////////////////////////////////

    /// @notice Custom event emitted when pool is initialized by the factory
    event InitializeLimit(
        int24 minTick,
        int24 maxTick,
        uint160 startPrice,
        int24 startTick
    );

    /// @notice Custom event emitted when a swap is successful
    event SwapLimit(
        address indexed recipient,
        uint256 amountIn,
        uint256 amountOut,
        uint200 feeGrowthGlobal0,
        uint200 feeGrowthGlobal1,
        uint160 price,
        uint128 liquidity,
        uint128 feeAmount,
        int24 tickAtPrice,
        bool indexed zeroForOne,
        bool indexed exactIn
    );

    /// @notice Event emitted when liquidity added to RangePosition
    event MintRange(
        address indexed recipient,
        int24 lower,
        int24 upper,
        uint32 indexed positionId,
        uint128 liquidityMinted,
        int128 amount0Delta,
        int128 amount1Delta
    );

    /// @notice Event emitted when liquidity removed from RangePosition
    event BurnRange(
        address indexed recipient,
        uint256 indexed positionId,
        uint128 liquidityBurned,
        int128 amount0,
        int128 amount1
    );

    /// @notice Event emitted when liquidity is added as a result of calling `burnRange`
    event CompoundRange(uint32 indexed positionId, uint128 liquidityCompounded);

    /// @notice Event emitted when a RangeTick is updated
    event SyncRangeTick(
        uint200 feeGrowthOutside0,
        uint200 feeGrowthOutside1,
        int24 tick
    );

    /// @notice Event emitted when liquidity is added to a LimitPosition
    event MintLimit(
        address indexed to,
        int24 lower,
        int24 upper,
        bool zeroForOne,
        uint32 positionId,
        uint32 epochLast,
        uint128 amountIn,
        uint128 liquidityMinted
    );

    /// @notice Event emitted when liquidity is removed from a LimitPosition
    event BurnLimit(
        address indexed to,
        uint32 positionId,
        int24 lower,
        int24 upper,
        int24 oldClaim,
        int24 newClaim,
        bool zeroForOne,
        uint128 liquidityBurned,
        uint128 tokenInClaimed,
        uint128 tokenOutBurned
    );

    /// @notice Event emitted when a LimitPosition undercuts pool0 or pool1
    event SyncLimitPool(
        uint160 price,
        uint128 liquidity,
        uint32 epoch,
        int24 tickAtPrice,
        bool isPool0
    );

    /// @notice Event emitted when a LimitTick is created via undercutting
    event SyncLimitLiquidity(
        uint128 liquidityAdded,
        int24 tick,
        bool zeroForOne
    );

    /// @notice Event emitted when a LimitTick is crossed or initialized
    event SyncLimitTick(uint32 epoch, int24 tick, bool zeroForOne);

    /// @notice Event emitted when an oracle sample is recorded
    event SampleRecorded(
        int56 tickSecondsAccum,
        uint160 secondsPerLiquidityAccum
    );

    /// @notice Event emitted when max sample count is increased
    event SampleCountIncreased(uint16 newSampleCountMax);
    
    /////////////////////////////////////////////////////////////
    ////////////////////// Standard Events //////////////////////
    /////////////////////////////////////////////////////////////

    /// @notice Emitted when pool is initialized by the factory
    event Initialize(uint160 price, int24 tick);

    /// @notice Emitted when liquidity is added
    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted when liquidity is removed
    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted when liquidity is swapped
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 price,
        uint128 liquidity,
        int24 tickAtPrice
    );

    /// @notice Emitted when fees are collected by the owner of a position
    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );
}

File 8 of 57 : LimitPoolFactoryStorage.sol
// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

abstract contract LimitPoolFactoryStorage {
    mapping(bytes32 => address) public pools; ///@dev - map for limit pool lookup by key
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import {Clone} from '../../external/solady/Clone.sol';

contract LimitPoolImmutables is Clone {
    function owner() public pure returns (address) {
        return _getArgAddress(0);
    }

    function token0() public pure returns (address) {
        return _getArgAddress(20);
    }

    function token1() public pure returns (address) {
        return _getArgAddress(40);
    }

    function poolToken() public pure returns (address) {
        return _getArgAddress(60);
    }

    function minPrice() public pure returns (uint160) {
        return _getArgUint160(80);
    }

    function maxPrice() public pure returns (uint160) {
        return _getArgUint160(100);
    }

    function genesisTime() public pure returns (uint32) {
        return _getArgUint32(120);
    }

    function tickSpacing() public pure returns (int16) {
        return int16(_getArgUint16(124));
    }

    function swapFee() public pure returns (uint16) {
        return _getArgUint16(126);
    }
}

File 10 of 57 : LimitPoolStorage.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/RangePoolStructs.sol';
import '../../interfaces/structs/LimitPoolStructs.sol';
import '../../interfaces/limit/ILimitPoolFactory.sol';
import '../../interfaces/limit/ILimitPoolStorageView.sol';

abstract contract LimitPoolStorage is ILimitPoolStorageView, RangePoolStructs {
    GlobalState public globalState; ///@dev - holds pool state and other contract storage
    TickMap public rangeTickMap; ///@dev - tick bitmap for range ticks
    TickMap public limitTickMap; ///@dev - tick bitmap for limit ticks
    Sample[65535] public samples; ///@dev - oracle TWAP samples
    mapping(int24 => Tick) public ticks; ///@dev - range and limit tick data
    mapping(uint256 => RangePosition) public positions; ///@dev - range positions token0 <> token1
    mapping(uint256 => LimitPosition) public positions0; ///@dev - limit positions token0 -> token1
    mapping(uint256 => LimitPosition) public positions1; ///@dev - limit positions token0 <- token1
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity 0.8.18;

import '../../../interfaces/structs/PoolsharkStructs.sol';

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract LimitReentrancyGuard is PoolsharkStructs {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint8 private constant _NOT_ENTERED = 1;
    uint8 private constant _ENTERED = 2;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    /**
     * @dev Unauthorized read-only reentrant call.
     */
    error ReentrancyGuardReadOnlyReentrantCall();

    /**
     * @dev Reentrant state invalid.
     */
    error ReentrancyGuardInvalidState();

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant(GlobalState storage state) {
        _nonReentrantBefore(state);
        _;
        _nonReentrantAfter(state);
    }

    function _nonReentrantBefore(GlobalState storage state) private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        if (state.unlocked == _ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        state.unlocked = _ENTERED;
    }

    function _nonReentrantAfter(GlobalState storage state) private {
        if (state.unlocked != _ENTERED) revert ReentrancyGuardInvalidState();
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        state.unlocked = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered(GlobalState storage state)
        internal
        view
        returns (bool)
    {
        return state.unlocked == _ENTERED;
    }
}

File 12 of 57 : Clone.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

/// @notice Class with helper read functions for clone with immutable args.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Clone.sol)
/// @author Adapted from clones with immutable args by zefram.eth, Saw-mon & Natalie
/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args)
abstract contract Clone {
    /// @dev Reads an immutable arg with type bytes.
    function _getArgBytes(uint256 argOffset, uint256 length)
        internal
        pure
        returns (bytes memory arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := mload(0x40)
            mstore(arg, length) // Store the length.
            calldatacopy(add(arg, 0x20), add(offset, argOffset), length)
            let o := add(add(arg, 0x20), length)
            mstore(o, 0) // Zeroize the slot after the bytes.
            mstore(0x40, add(o, 0x20)) // Allocate the memory.
        }
    }

    /// @dev Reads an immutable arg with type address.
    function _getArgAddress(uint256 argOffset)
        internal
        pure
        returns (address arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(96, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads a uint256 array stored in the immutable args.
    function _getArgUint256Array(uint256 argOffset, uint256 length)
        internal
        pure
        returns (uint256[] memory arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := mload(0x40)
            mstore(arg, length) // Store the length.
            calldatacopy(add(arg, 0x20), add(offset, argOffset), shl(5, length))
            mstore(0x40, add(add(arg, 0x20), shl(5, length))) // Allocate the memory.
        }
    }

    /// @dev Reads a bytes32 array stored in the immutable args.
    function _getArgBytes32Array(uint256 argOffset, uint256 length)
        internal
        pure
        returns (bytes32[] memory arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := mload(0x40)
            mstore(arg, length) // Store the length.
            calldatacopy(add(arg, 0x20), add(offset, argOffset), shl(5, length))
            mstore(0x40, add(add(arg, 0x20), shl(5, length))) // Allocate the memory.
        }
    }

    /// @dev Reads an immutable arg with type bytes32.
    function _getArgBytes32(uint256 argOffset)
        internal
        pure
        returns (bytes32 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := calldataload(add(offset, argOffset))
        }
    }

    /// @dev Reads an immutable arg with type uint256.
    function _getArgUint256(uint256 argOffset)
        internal
        pure
        returns (uint256 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := calldataload(add(offset, argOffset))
        }
    }

    /// @dev Reads an immutable arg with type uint248.
    function _getArgUint248(uint256 argOffset)
        internal
        pure
        returns (uint248 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(8, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint240.
    function _getArgUint240(uint256 argOffset)
        internal
        pure
        returns (uint240 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(16, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint232.
    function _getArgUint232(uint256 argOffset)
        internal
        pure
        returns (uint232 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(24, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint224.
    function _getArgUint224(uint256 argOffset)
        internal
        pure
        returns (uint224 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(0x20, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint216.
    function _getArgUint216(uint256 argOffset)
        internal
        pure
        returns (uint216 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(40, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint208.
    function _getArgUint208(uint256 argOffset)
        internal
        pure
        returns (uint208 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(48, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint200.
    function _getArgUint200(uint256 argOffset)
        internal
        pure
        returns (uint200 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(56, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint192.
    function _getArgUint192(uint256 argOffset)
        internal
        pure
        returns (uint192 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(64, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint184.
    function _getArgUint184(uint256 argOffset)
        internal
        pure
        returns (uint184 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(72, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint176.
    function _getArgUint176(uint256 argOffset)
        internal
        pure
        returns (uint176 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(80, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint168.
    function _getArgUint168(uint256 argOffset)
        internal
        pure
        returns (uint168 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(88, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint160.
    function _getArgUint160(uint256 argOffset)
        internal
        pure
        returns (uint160 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(96, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint152.
    function _getArgUint152(uint256 argOffset)
        internal
        pure
        returns (uint152 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(104, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint144.
    function _getArgUint144(uint256 argOffset)
        internal
        pure
        returns (uint144 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(112, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint136.
    function _getArgUint136(uint256 argOffset)
        internal
        pure
        returns (uint136 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(120, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint128.
    function _getArgUint128(uint256 argOffset)
        internal
        pure
        returns (uint128 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(128, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint120.
    function _getArgUint120(uint256 argOffset)
        internal
        pure
        returns (uint120 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(136, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint112.
    function _getArgUint112(uint256 argOffset)
        internal
        pure
        returns (uint112 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(144, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint104.
    function _getArgUint104(uint256 argOffset)
        internal
        pure
        returns (uint104 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(152, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint96.
    function _getArgUint96(uint256 argOffset)
        internal
        pure
        returns (uint96 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(160, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint88.
    function _getArgUint88(uint256 argOffset)
        internal
        pure
        returns (uint88 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(168, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint80.
    function _getArgUint80(uint256 argOffset)
        internal
        pure
        returns (uint80 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(176, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint72.
    function _getArgUint72(uint256 argOffset)
        internal
        pure
        returns (uint72 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(184, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint64.
    function _getArgUint64(uint256 argOffset)
        internal
        pure
        returns (uint64 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(192, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint56.
    function _getArgUint56(uint256 argOffset)
        internal
        pure
        returns (uint56 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(200, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint48.
    function _getArgUint48(uint256 argOffset)
        internal
        pure
        returns (uint48 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(208, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint40.
    function _getArgUint40(uint256 argOffset)
        internal
        pure
        returns (uint40 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(216, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint32.
    function _getArgUint32(uint256 argOffset)
        internal
        pure
        returns (uint32 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(224, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint24.
    function _getArgUint24(uint256 argOffset)
        internal
        pure
        returns (uint24 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(232, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint16.
    function _getArgUint16(uint256 argOffset)
        internal
        pure
        returns (uint16 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(240, calldataload(add(offset, argOffset)))
        }
    }

    /// @dev Reads an immutable arg with type uint8.
    function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) {
        uint256 offset = _getImmutableArgsOffset();
        /// @solidity memory-safe-assembly
        assembly {
            arg := shr(248, calldataload(add(offset, argOffset)))
        }
    }

    /// @return offset The offset of the packed immutable args in calldata.
    function _getImmutableArgsOffset() internal pure returns (uint256 offset) {
        /// @solidity memory-safe-assembly
        assembly {
            offset := sub(
                calldatasize(),
                shr(240, calldataload(sub(calldatasize(), 2)))
            )
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

/// @notice Minimal proxy library.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol)
/// @author Minimal proxy by 0age (https://github.com/0age)
/// @author Clones with immutable args by wighawag, zefram.eth, Saw-mon & Natalie
/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args)
///
/// @dev Minimal proxy:
/// Although the sw0nt pattern saves 5 gas over the erc-1167 pattern during runtime,
/// it is not supported out-of-the-box on Etherscan. Hence, we choose to use the 0age pattern,
/// which saves 4 gas over the erc-1167 pattern during runtime, and has the smallest bytecode.
///
/// @dev Clones with immutable args (CWIA):
/// The implementation of CWIA here implements a `receive()` method that emits the
/// `ReceiveETH(uint256)` event. This skips the `DELEGATECALL` when there is no calldata,
/// enabling us to accept hard gas-capped `sends` & `transfers` for maximum backwards
/// composability. The minimal proxy implementation does not offer this feature.
library LibClone {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Unable to deploy the clone.
    error DeploymentFailed();

    /// @dev The salt must start with either the zero address or the caller.
    error SaltDoesNotStartWithCaller();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  MINIMAL PROXY OPERATIONS                  */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Deploys a deterministic clone of `implementation`,
    /// using immutable  arguments encoded in `data`, with `salt`.
    function cloneDeterministic(
        address implementation,
        bytes memory data,
        bytes32 salt
    ) internal returns (address instance) {
        assembly {
            // Compute the boundaries of the data and cache the memory slots around it.
            let mBefore3 := mload(sub(data, 0x60))
            let mBefore2 := mload(sub(data, 0x40))
            let mBefore1 := mload(sub(data, 0x20))
            let dataLength := mload(data)
            let dataEnd := add(add(data, 0x20), dataLength)
            let mAfter1 := mload(dataEnd)

            // +2 bytes for telling how much data there is appended to the call.
            let extraLength := add(dataLength, 2)

            // Write the bytecode before the data.
            mstore(data, 0x5af43d3d93803e606057fd5bf3)
            // Write the address of the implementation.
            mstore(sub(data, 0x0d), implementation)
            // Write the rest of the bytecode.
            mstore(
                sub(data, 0x21),
                or(
                    shl(0x48, extraLength),
                    0x593da1005b363d3d373d3d3d3d610000806062363936013d73
                )
            )
            // `keccak256("ReceiveETH(uint256)")`
            mstore(
                sub(data, 0x3a),
                0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff
            )
            mstore(
                sub(data, 0x5a),
                or(
                    shl(0x78, add(extraLength, 0x62)),
                    0x6100003d81600a3d39f336602c57343d527f
                )
            )
            mstore(dataEnd, shl(0xf0, extraLength))

            // Create the instance.
            instance := create2(
                0,
                sub(data, 0x4c),
                add(extraLength, 0x6c),
                salt
            )

            // If `instance` is zero, revert.
            if iszero(instance) {
                // Store the function selector of `DeploymentFailed()`.
                mstore(0x00, 0x30116425)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Restore the overwritten memory surrounding `data`.
            mstore(dataEnd, mAfter1)
            mstore(data, dataLength)
            mstore(sub(data, 0x20), mBefore1)
            mstore(sub(data, 0x40), mBefore2)
            mstore(sub(data, 0x60), mBefore3)
        }
    }

    /// @dev Returns the initialization code hash of the clone of `implementation`
    /// using immutable arguments encoded in `data`.
    /// Used for mining vanity addresses with create2crunch.
    function initCodeHash(address implementation, bytes memory data)
        internal
        pure
        returns (bytes32 hash)
    {
        assembly {
            // Compute the boundaries of the data and cache the memory slots around it.
            let mBefore3 := mload(sub(data, 0x60))
            let mBefore2 := mload(sub(data, 0x40))
            let mBefore1 := mload(sub(data, 0x20))
            let dataLength := mload(data)
            let dataEnd := add(add(data, 0x20), dataLength)
            let mAfter1 := mload(dataEnd)

            // +2 bytes for telling how much data there is appended to the call.
            let extraLength := add(dataLength, 2)

            // Write the bytecode before the data.
            mstore(data, 0x5af43d3d93803e606057fd5bf3)
            // Write the address of the implementation.
            mstore(sub(data, 0x0d), implementation)
            // Write the rest of the bytecode.
            mstore(
                sub(data, 0x21),
                or(
                    shl(0x48, extraLength),
                    0x593da1005b363d3d373d3d3d3d610000806062363936013d73
                )
            )
            // `keccak256("ReceiveETH(uint256)")`
            mstore(
                sub(data, 0x3a),
                0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff
            )
            mstore(
                sub(data, 0x5a),
                or(
                    shl(0x78, add(extraLength, 0x62)),
                    0x6100003d81600a3d39f336602c57343d527f
                )
            )
            mstore(dataEnd, shl(0xf0, extraLength))

            // Compute and store the bytecode hash.
            hash := keccak256(sub(data, 0x4c), add(extraLength, 0x6c))

            // Restore the overwritten memory surrounding `data`.
            mstore(dataEnd, mAfter1)
            mstore(data, dataLength)
            mstore(sub(data, 0x20), mBefore1)
            mstore(sub(data, 0x40), mBefore2)
            mstore(sub(data, 0x60), mBefore3)
        }
    }

    /// @dev Returns the address of the deterministic clone of
    /// `implementation` using immutable arguments encoded in `data`, with `salt`, by `deployer`.
    function predictDeterministicAddress(
        address implementation,
        bytes memory data,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes32 hash = initCodeHash(implementation, data);
        predicted = predictDeterministicAddress(hash, salt, deployer);
    }

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

    /// @dev Returns the address when a contract with initialization code hash,
    /// `hash`, is deployed with `salt`, by `deployer`.
    function predictDeterministicAddress(
        bytes32 hash,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and store the bytecode hash.
            mstore8(0x00, 0xff) // Write the prefix.
            mstore(0x35, hash)
            mstore(0x01, shl(96, deployer))
            mstore(0x15, salt)
            predicted := keccak256(0x00, 0x55)
            // Restore the part of the free memory pointer that has been overwritten.
            mstore(0x35, 0)
        }
    }

    /// @dev Reverts if `salt` does not start with either the zero address or the caller.
    function checkStartsWithCaller(bytes32 salt) internal view {
        /// @solidity memory-safe-assembly
        assembly {
            // If the salt does not start with the zero address or the caller.
            if iszero(or(iszero(shr(96, salt)), eq(caller(), shr(96, salt)))) {
                // Store the function selector of `SaltDoesNotStartWithCaller()`.
                mstore(0x00, 0x2f634836)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

/// @title Callback for range mints
/// @notice Any contract that calls the `mintRange` function must implement this interface.
interface ILimitPoolMintRangeCallback {
    /// @notice Called to `msg.sender` after executing a mint.
    /// @param amount0Delta The amount of token0 either received by (positive) or sent from (negative) the user.
    /// @param amount1Delta The amount of token1 either received by (positive) or sent from (negative) the user.
    function limitPoolMintRangeCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

/// @title Callback for limit mints
/// @notice Any contract that calls the `mintLimit` function must implement this interface.
interface ILimitPoolMintLimitCallback {
    /// @notice Called to `msg.sender` after executing a mint.
    /// @param amount0Delta The amount of token0 either received by (positive) or sent from (negative) the user.
    /// @param amount1Delta The amount of token1 either received by (positive) or sent from (negative) the user.
    function limitPoolMintLimitCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

/// @title Callback for swaps
/// @notice Any contract that calls the `swap` function must implement this interface.
interface ILimitPoolSwapCallback {
    /// @notice Called to `msg.sender` after executing a swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 either received by (positive) or sent from (negative) the user.
    /// @param amount1Delta The amount of token1 either received by (positive) or sent from (negative) the user.
    function limitPoolSwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity ^0.8.18;

import '../structs/PoolsharkStructs.sol';

interface ITwapSource {
    function initialize(PoolsharkStructs.CoverImmutables memory constants)
        external
        returns (uint8 initializable, int24 startingTick);

    function calculateAverageTick(
        PoolsharkStructs.CoverImmutables memory constants,
        int24 latestTick
    ) external view returns (int24 averageTick);

    function getPool(
        address tokenA,
        address tokenB,
        uint16 feeTier
    ) external view returns (address pool);

    function feeTierTickSpacing(uint16 feeTier)
        external
        view
        returns (int24 tickSpacing);

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

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

interface IERC20Minimal {
    /// @notice Returns the balance of a token
    /// @param account The address for which to look up the balance for
    /// @return amount of tokens held by the account
    function balanceOf(address account) external view returns (uint256);
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../interfaces/structs/PoolsharkStructs.sol';

interface IPool is PoolsharkStructs {
    function immutables() external view returns (LimitImmutables memory);

    function swap(SwapParams memory params)
        external
        returns (int256 amount0, int256 amount1);

    function quote(QuoteParams memory params)
        external
        view
        returns (
            int256 inAmount,
            int256 outAmount,
            uint160 priceAfter
        );

    function fees(FeesParams memory params)
        external
        returns (uint128 token0Fees, uint128 token1Fees);

    function sample(uint32[] memory secondsAgo)
        external
        view
        returns (
            int56[] memory tickSecondsAccum,
            uint160[] memory secondsPerLiquidityAccum,
            uint160 averagePrice,
            uint128 averageLiquidity,
            int24 averageTick
        );

    function snapshotRange(uint32 positionId)
        external
        view
        returns (
            int56 tickSecondsAccum,
            uint160 secondsPerLiquidityAccum,
            uint128 feesOwed0,
            uint128 feesOwed1
        );

    function snapshotLimit(SnapshotLimitParams memory params)
        external
        view
        returns (uint128 amountIn, uint128 amountOut);

    function poolToken() external view returns (address poolToken);

    function token0() external view returns (address token0);

    function token1() external view returns (address token1);

    function globalState() external view returns (
        RangePoolState memory pool,
        LimitPoolState memory pool0,
        LimitPoolState memory pool1,
        uint128 liquidityGlobal,
        uint32 positionIdNext,
        uint32 epoch,
        uint8 unlocked
    );
}

// SPDX-License-Identifier: SSPL-1.0

pragma solidity 0.8.18;

import '../interfaces/structs/PoolsharkStructs.sol';
import '@openzeppelin/contracts/utils/introspection/IERC165.sol';

interface IPositionERC1155 is IERC165, PoolsharkStructs {
    event TransferSingle(
        address indexed sender,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );

    event TransferBatch(
        address indexed sender,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );

    event ApprovalForAll(
        address indexed account,
        address indexed sender,
        bool approve
    );

    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function balanceOf(address account, uint256 id)
        external
        view
        returns (uint256);

    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory batchBalances);

    function totalSupply() external view returns (uint256);

    function isApprovedForAll(address owner, address spender)
        external
        view
        returns (bool);

    function setApprovalForAll(address sender, bool approved) external;

    function mint(
        address account,
        uint256 id,
        uint256 amount,
        PoolsharkStructs.LimitImmutables memory constants
    ) external;

    function burn(
        address account,
        uint256 id,
        uint256 amount,
        PoolsharkStructs.LimitImmutables memory constants
    ) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount
    ) external;

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata id,
        uint256[] calldata amount
    ) external;

    function withdrawEth(
        address recipient,
        PoolsharkStructs.LimitImmutables memory constants
    ) external;
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../structs/LimitPoolStructs.sol';

interface ILimitPool is LimitPoolStructs {
    function initialize(uint160 startPrice) external;

    function mintLimit(MintLimitParams memory params)
        external
        returns (int256, int256);

    function burnLimit(BurnLimitParams memory params)
        external
        returns (int256, int256);

    function fees(FeesParams memory params)
        external
        returns (uint128 token0Fees, uint128 token1Fees);
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../structs/PoolsharkStructs.sol';
import '../../base/storage/LimitPoolFactoryStorage.sol';

abstract contract ILimitPoolFactory is
    LimitPoolFactoryStorage,
    PoolsharkStructs
{
    function createLimitPool(LimitPoolParams memory params)
        external
        virtual
        returns (address pool, address poolToken);

    function getLimitPool(
        address tokenIn,
        address tokenOut,
        uint16 swapFee,
        uint16 poolTypeId
    ) external view virtual returns (address pool, address poolToken);
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

/// @notice LimitPoolManager interface
interface ILimitPoolManager {
    function owner() external view returns (address);

    function feeTo() external view returns (address);

    function feeDeltaConsts(address pool) external view returns (uint16);

    function poolTypes(uint16 poolType)
        external
        view
        returns (address poolImpl, address tokenImpl);

    function feeTiers(uint16 swapFee) external view returns (int16 tickSpacing);
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../structs/LimitPoolStructs.sol';

interface ILimitPoolStorageView is LimitPoolStructs {
    function globalState()
        external
        view
        returns (
            RangePoolState memory pool,
            LimitPoolState memory pool0,
            LimitPoolState memory pool1,
            uint128 liquidityGlobal,
            uint32 positionIdNext,
            uint32 epoch,
            uint8 unlocked
        );
    
    function ticks(int24 tickIndex)
        external
        view
        returns (
            RangeTick memory,
            LimitTick memory
        );
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../structs/LimitPoolStructs.sol';

interface ILimitPoolView is LimitPoolStructs {
    function snapshotLimit(SnapshotLimitParams memory params)
        external
        view
        returns (uint128, uint128);

    function immutables() external view returns (LimitImmutables memory);

    function priceBounds(int16 tickSpacing)
        external
        pure
        returns (uint160 minPrice, uint160 maxPrice);

    function sample(uint32[] memory secondsAgo)
        external
        view
        returns (
            int56[] memory tickSecondsAccum,
            uint160[] memory secondsPerLiquidityAccum,
            uint160 averagePrice,
            uint128 averageLiquidity,
            int24 averageTick
        );
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../structs/RangePoolStructs.sol';
import './IRangePoolManager.sol';

interface IRangePool is RangePoolStructs {
    function mintRange(MintRangeParams memory mintParams)
        external
        returns (int256, int256);

    function burnRange(BurnRangeParams memory burnParams)
        external
        returns (int256, int256);

    function swap(SwapParams memory params)
        external
        returns (int256 amount0, int256 amount1);

    function quote(QuoteParams memory params)
        external
        view
        returns (
            uint256 inAmount,
            uint256 outAmount,
            uint160 priceAfter
        );

    function snapshotRange(uint32 positionId)
        external
        view
        returns (
            int56 tickSecondsAccum,
            uint160 secondsPerLiquidityAccum,
            uint128 feesOwed0,
            uint128 feesOwed1
        );

    function sample(uint32[] memory secondsAgo)
        external
        view
        returns (
            int56[] memory tickSecondsAccum,
            uint160[] memory secondsPerLiquidityAccum,
            uint160 averagePrice,
            uint128 averageLiquidity,
            int24 averageTick
        );

    function positions(uint256 positionId)
        external
        view
        returns (
            uint256 feeGrowthInside0Last,
            uint256 feeGrowthInside1Last,
            uint128 liquidity,
            int24 lower,
            int24 upper
        );

    function increaseSampleCount(uint16 newSampleCountMax) external;

    function ticks(int24)
        external
        view
        returns (RangeTick memory, LimitTick memory);

    function samples(uint256)
        external
        view
        returns (
            uint32,
            int56,
            uint160
        );
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

interface IRangePoolFactory {
    function createRangePool(
        address fromToken,
        address destToken,
        uint16 fee,
        uint160 startPrice
    ) external returns (address book);

    function getRangePool(
        address fromToken,
        address destToken,
        uint256 fee
    ) external view returns (address);

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

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../structs/RangePoolStructs.sol';

interface IRangePoolManager {
    function owner() external view returns (address);

    function feeTo() external view returns (address);

    function protocolFees(address pool) external view returns (uint16);

    function feeTiers(uint16 swapFee) external view returns (int24);
}

File 27 of 57 : LimitPoolStructs.sol
// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import './PoolsharkStructs.sol';

interface LimitPoolStructs is PoolsharkStructs {
    struct LimitPosition {
        uint128 liquidity; // expected amount to be used not actual
        uint32 epochLast; // epoch when this position was created at
        int24 lower; // lower price tick of position range
        int24 upper; // upper price tick of position range
        bool crossedInto; // whether the position was crossed into already
    }

    struct MintLimitCache {
        GlobalState state;
        LimitPosition position;
        LimitImmutables constants;
        LimitPoolState pool;
        SwapCache swapCache;
        uint256 liquidityMinted;
        uint256 mintSize;
        uint256 priceLimit;
        int256 amountIn;
        uint256 amountOut;
        uint256 priceLower;
        uint256 priceUpper;
        int24 tickLimit;
    }

    struct BurnLimitCache {
        GlobalState state;
        LimitPoolState pool;
        LimitTick claimTick;
        LimitPosition position;
        PoolsharkStructs.LimitImmutables constants;
        uint160 priceLower;
        uint160 priceClaim;
        uint160 priceUpper;
        uint128 liquidityBurned;
        uint128 amountIn;
        uint128 amountOut;
        int24 claim;
        bool removeLower;
        bool removeUpper;
        bool search;
    }

    struct InsertSingleLocals {
        int24 previousFullTick;
        int24 nextFullTick;
        uint256 priceNext;
        uint256 pricePrevious;
        uint256 amountInExact;
        uint256 amountOutExact;
        uint256 amountToCross;
    }

    struct GetDeltasLocals {
        int24 previousFullTick;
        uint256 pricePrevious;
        uint256 priceNext;
    }

    struct SearchLocals {
        int24[] ticksFound;
        int24 searchTick;
        int24 searchTickAhead;
        uint16 searchIdx;
        uint16 startIdx;
        uint16 endIdx;
        uint16 ticksIncluded;
        uint32 claimTickEpoch;
        uint32 claimTickAheadEpoch;
    }

    struct TickMapLocals {
        uint256 word;
        uint256 tickIndex;
        uint256 wordIndex;
        uint256 blockIndex;
    }
}

File 28 of 57 : PoolsharkStructs.sol
// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../cover/ITwapSource.sol';

interface PoolsharkStructs {
    /**
     * @custom:struct LimitPoolParams
     */
    struct LimitPoolParams {
        /**
         * @custom:field tokenIn
         * @notice Address for the first token in the pair
         */
        address tokenIn;
        /**
         * @custom:field tokenOut
         * @notice Address for the second token in the pair
         */
        address tokenOut;
        /**
         * @custom:field startPrice
         * @notice Q64.96 formatted sqrt price to start the pool at
         */
        uint160 startPrice;
        /**
         * @custom:field swapFee
         * @notice The base swap fee for the pool; 1000 = 0.1% fee
         */
        uint16 swapFee;
        /**
         * @custom:field poolTypeId
         * @notice The pool type id for which to clone the implementation for
         */
        uint16 poolTypeId;
    }

    /**
     * @custom:struct MintRangeParams
     */
    struct MintRangeParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the minted position
         */
        address to;
        /**
         * @custom:field lower
         * @notice The lower price tick for the position range
         */
        int24 lower;
        /**
         * @custom:field upper
         * @notice The upper price tick for the position range
         */
        int24 upper;
        /**
         * @custom:field positionId
         * @notice 0 if creating a new position; id of previous if adding liquidity
         */
        uint32 positionId;
        /**
         * @custom:field amount0
         * @notice token0 amount to be deposited into the minted position
         */
        uint128 amount0;
        /**
         * @custom:field amount1
         * @notice token1 amount to be deposited into the minted position
         */
        uint128 amount1;
        /**
         * @custom:field callbackData
         * @notice callback data which gets passed back to msg.sender at the end of a `mint` call
         */
        bytes callbackData;
    }

    struct BurnRangeParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the burned liquidity
         */
        address to;
        /**
         * @custom:field positionId
         * @notice id of previous position minted
         */
        uint32 positionId;
        /**
         * @custom:field burnPercent
         * @notice Percent of the remaining liquidity to be removed
         * @notice 1e38 represents 100%
         * @notice 5e37 represents 50%
         * @notice 1e37 represents 10%
         */
        uint128 burnPercent;
    }

    /**
     * @custom:struct MintLimitParams
     */
    struct MintLimitParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the minted position
         */
        address to;
        /**
         * @custom:field amount
         * @notice Token amount to be deposited into the minted position
         */
        uint128 amount;
        /**
         * @custom:field mintPercent
         * @notice The percent of `amount` below which a LimitPosition will not be minted
         * @notice 1e26 = 1%
         * @notice 5e25 = 0.5%
         */
        uint96 mintPercent;
        /**
         * @custom:field positionId
         * @notice 0 if creating a new position; id of previous if adding liquidity
         */
        uint32 positionId;
        /**
         * @custom:field lower
         * @notice The lower price tick for the position range
         */
        int24 lower;
        /**
         * @custom:field upper
         * @notice The upper price tick for the position range
         */
        int24 upper;
        /**
         * @custom:field zeroForOne
         * @notice True if depositing token0, the first token address in lexographical order
         * @notice False if depositing token1, the second token address in lexographical order
         */
        bool zeroForOne;
        /**
         * @custom:field callbackData
         * @notice callback data which gets passed back to msg.sender at the end of a `mint` call
         */
        bytes callbackData;
    }

    /**
     * @custom:struct BurnLimitParams
     */
    struct BurnLimitParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the collected position amounts
         */
        address to;
        /**
         * @custom:field burnPercent
         * @notice Percent of the remaining liquidity to be removed
         * @notice 1e38 represents 100%
         * @notice 5e37 represents 50%
         * @notice 1e37 represents 10%
         */
        uint128 burnPercent;
        /**
         * @custom:field positionId
         * @notice 0 if creating a new position; id of previous if adding liquidity
         */
        uint32 positionId;
        /**
         * @custom:field claim
         * @notice The most recent tick crossed in this range
         * @notice if `zeroForOne` is true, claim tick progresses from lower => upper
         * @notice if `zeroForOne` is false, claim tick progresses from upper => lower
         */
        int24 claim;
        /**
         * @custom:field zeroForOne
         * @notice True if deposited token0, the first token address in lexographical order
         * @notice False if deposited token1, the second token address in lexographical order
         */
        bool zeroForOne;
    }

    struct SwapParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the swap token output
         */
        address to;
        /**
         * @custom:field priceLimit
         * @notice The Q64.96 formatted sqrt price to stop swapping at
         * @notice zeroForOne (i.e. token0 => token1 swap) moves price lower
         * @notice !zeroForOne (i.e. token1 => token0 swap) moves price higher
         */
        uint160 priceLimit;
        /**
         * @custom:field amount
         * @notice The maximum tokenIn to be spent (exactIn)
         * @notice OR tokenOut amount to be received (!exactIn)
         */
        uint128 amount;
        /**
         * @custom:field exactIn
         * @notice True if `amount` is in tokenIn; False if `amount` is in tokenOut
         */
        bool exactIn;
        /**
         * @custom:field zeroForOne
         * @notice True if swapping token0 => token1
         * @notice False if swapping token1 => token0
         */
        bool zeroForOne;
        /**
         * @custom:field callbackData
         * @notice callback data which gets passed back to msg.sender at the end of a `mint` call
         */
        bytes callbackData;
    }

    struct QuoteParams {
        /**
         * @custom:field priceLimit
         * @notice The Q64.96 formatted sqrt price to stop swapping at
         * @notice zeroForOne (i.e. token0 => token1 swap) moves price lower
         * @notice !zeroForOne (i.e. token1 => token0 swap) moves price higher
         */
        uint160 priceLimit;
        /**
         * @custom:field amount
         * @notice The maximum tokenIn to be spent (exactIn)
         * @notice OR tokenOut amount to be received (!exactIn)
         */
        uint128 amount;
        /**
         * @custom:field exactIn
         * @notice True if `amount` is in tokenIn; False if `amount` is in tokenOut
         */
        bool exactIn;
        /**
         * @custom:field zeroForOne
         * @notice True if swapping token0 => token1
         * @notice False if swapping token1 => token0
         */
        bool zeroForOne;
    }

    struct SnapshotLimitParams {
        /**
         * @custom:field owner
         * @notice The owner address of the Limit Position
         */
        address owner;
        /**
         * @custom:field burnPercent
         * @notice The % of liquidity to burn
         * @notice 1e38 = 100%
         */
        uint128 burnPercent;
        /**
         * @custom:field positionId
         * @notice The position id for the LimitPosition
         */
        uint32 positionId;
        /**
         * @custom:field claim
         * @notice The most recent tick crossed in this range
         * @notice if `zeroForOne` is true, claim tick progresses from lower => upper
         * @notice if `zeroForOne` is false, claim tick progresses from upper => lower
         */
        int24 claim;
        /**
         * @custom:field zeroForOne
         * @notice True if swapping token0 => token1
         * @notice False if swapping token1 => token0
         */
        bool zeroForOne;
    }

    struct FeesParams {
        /**
         * @custom:field protocolSwapFee0
         * @notice The protocol fee taken on all token0 fees
         * @notice 1e4 = 100%
         */
        uint16 protocolSwapFee0;
        /**
         * @custom:field protocolSwapFee1
         * @notice The protocol fee taken on all token1 fees
         * @notice 1e4 = 100%
         */
        uint16 protocolSwapFee1;
        /**
         * @custom:field protocolFillFee0
         * @notice The protocol fee taken on all token0 LimitPosition fills
         * @notice 1e2 = 1%
         */
        uint16 protocolFillFee0;
        /**
         * @custom:field protocolFillFee1
         * @notice The protocol fee taken on all token1 LimitPosition fills
         * @notice 1e2 = 1%
         */
        uint16 protocolFillFee1;
        /**
         * @custom:field setFeesFlags
         * @notice The flags for which protocol fees will be set
         * @notice - PROTOCOL_SWAP_FEE_0 = 2**0;
         * @notice - PROTOCOL_SWAP_FEE_1 = 2**1;
         * @notice - PROTOCOL_FILL_FEE_0 = 2**2;
         * @notice - PROTOCOL_FILL_FEE_1 = 2**3;
         */
        uint8 setFeesFlags;
    }

    struct GlobalState {
        RangePoolState pool;
        LimitPoolState pool0;
        LimitPoolState pool1;
        uint128 liquidityGlobal;
        uint32 positionIdNext;
        uint32 epoch;
        uint8 unlocked;
    }

    struct LimitPoolState {
        uint160 price; /// @dev Starting price current
        uint128 liquidity; /// @dev Liquidity currently active
        uint128 protocolFees;
        uint16 protocolFillFee;
        int24 tickAtPrice;
    }

    struct RangePoolState {
        SampleState samples;
        uint200 feeGrowthGlobal0;
        uint200 feeGrowthGlobal1;
        uint160 secondsPerLiquidityAccum;
        uint160 price; /// @dev Starting price current
        uint128 liquidity; /// @dev Liquidity currently active
        int56 tickSecondsAccum;
        int24 tickAtPrice;
        uint16 protocolSwapFee0;
        uint16 protocolSwapFee1;
    }

    struct Tick {
        RangeTick range;
        LimitTick limit;
    }

    struct LimitTick {
        uint160 priceAt;
        int128 liquidityDelta;
        uint128 liquidityAbsolute;
    }

    struct RangeTick {
        uint200 feeGrowthOutside0;
        uint200 feeGrowthOutside1;
        uint160 secondsPerLiquidityAccumOutside;
        int56 tickSecondsAccumOutside;
        int128 liquidityDelta;
        uint128 liquidityAbsolute;
    }

    struct Sample {
        uint32 blockTimestamp;
        int56 tickSecondsAccum;
        uint160 secondsPerLiquidityAccum;
    }

    struct SampleState {
        uint16 index;
        uint16 count;
        uint16 countMax;
    }

    struct StakeRangeParams {
        address to;
        address pool;
        uint32 positionId;
    }

    struct UnstakeRangeParams {
        address to;
        address pool;
        uint32 positionId;
    }

    struct StakeFinParams {
        address to;
        uint128 amount;
    }

    struct QuoteResults {
        address pool;
        int256 amountIn;
        int256 amountOut;
        uint160 priceAfter;
    }

    struct LimitImmutables {
        address owner;
        address poolImpl;
        address factory;
        PriceBounds bounds;
        address token0;
        address token1;
        address poolToken;
        uint32 genesisTime;
        int16 tickSpacing;
        uint16 swapFee;
    }

    struct CoverImmutables {
        ITwapSource source;
        PriceBounds bounds;
        address owner;
        address token0;
        address token1;
        address poolImpl;
        address poolToken;
        address inputPool;
        uint128 minAmountPerAuction;
        uint32 genesisTime;
        int16 minPositionWidth;
        int16 tickSpread;
        uint16 twapLength;
        uint16 auctionLength;
        uint16 sampleInterval;
        uint8 token0Decimals;
        uint8 token1Decimals;
        bool minAmountLowerPriced;
    }

    struct PriceBounds {
        uint160 min;
        uint160 max;
    }

    struct TickMap {
        uint256 blocks; /// @dev - sets of words
        mapping(uint256 => uint256) words; /// @dev - blocks to words
        mapping(uint256 => uint256) ticks; /// @dev - words to ticks
        mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) epochs0; /// @dev - ticks to epochs
        mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) epochs1; /// @dev - ticks to epochs
    }

    struct SwapCache {
        GlobalState state;
        LimitImmutables constants;
        uint256 price;
        uint256 liquidity;
        uint256 amountLeft;
        uint256 input;
        uint256 output;
        uint160 crossPrice;
        uint160 averagePrice;
        uint160 secondsPerLiquidityAccum;
        uint128 feeAmount;
        int56 tickSecondsAccum;
        int56 tickSecondsAccumBase;
        int24 crossTick;
        uint8 crossStatus;
        bool limitActive;
        bool exactIn;
        bool cross;
    }

    enum CrossStatus {
        RANGE,
        LIMIT,
        BOTH
    }

    /**
     * @custom:struct MintCoverParams
     */
    struct MintCoverParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the minted position
         */
        address to;
        /**
         * @custom:field amount
         * @notice Token amount to be deposited into the minted position
         */
        uint128 amount;
        /**
         * @custom:field positionId
         * @notice 0 if creating a new position; id of previous if adding liquidity
         */
        uint32 positionId;
        /**
         * @custom:field lower
         * @notice The lower price tick for the position range
         */
        int24 lower;
        /**
         * @custom:field upper
         * @notice The upper price tick for the position range
         */
        int24 upper;
        /**
         * @custom:field zeroForOne
         * @notice True if depositing token0, the first token address in lexographical order
         * @notice False if depositing token1, the second token address in lexographical order
         */
        bool zeroForOne;
        /**
         * @custom:field callbackData
         * @notice callback data which gets passed back to msg.sender at the end of a `mint` call
         */
        bytes callbackData;
    }

    /**
     * @custom:struct BurnCoverParams
     */
    struct BurnCoverParams {
        /**
         * @custom:field to
         * @notice Address for the receiver of the collected position amounts
         */
        address to;
        /**
         * @custom:field burnPercent
         * @notice Percent of the remaining liquidity to be removed
         * @notice 1e38 represents 100%
         * @notice 5e37 represents 50%
         * @notice 1e37 represents 10%
         */
        uint128 burnPercent;
        /**
         * @custom:field positionId
         * @notice 0 if creating a new position; id of previous if adding liquidity
         */
        uint32 positionId;
        /**
         * @custom:field claim
         * @notice The most recent tick crossed in this range
         * @notice if `zeroForOne` is true, claim tick progresses from upper => lower
         * @notice if `zeroForOne` is false, claim tick progresses from lower => upper
         */
        int24 claim;
        /**
         * @custom:field zeroForOne
         * @notice True if deposited token0, the first token address in lexographical order
         * @notice False if deposited token1, the second token address in lexographical order
         */
        bool zeroForOne;
        /**
         * @custom:field sync
         * @notice True will sync the pool latestTick
         * @notice False will skip syncing latestTick
         */
        bool sync;
    }

    /**
     * @custom:struct SnapshotCoverParams
     */
    struct SnapshotCoverParams {
        /**
         * @custom:field to
         * @notice Address of the position owner
         */
        address owner;
        /**
         * @custom:field positionId
         * @notice id of position
         */
        uint32 positionId;
        /**
         * @custom:field burnPercent
         * @notice Percent of the remaining liquidity to be removed
         * @notice 1e38 represents 100%
         * @notice 5e37 represents 50%
         * @notice 1e37 represents 10%
         */
        uint128 burnPercent;
        /**
         * @custom:field claim
         * @notice The most recent tick crossed in this range
         * @notice if `zeroForOne` is true, claim tick progresses from upper => lower
         * @notice if `zeroForOne` is false, claim tick progresses from lower => upper
         */
        int24 claim;
        /**
         * @custom:field zeroForOne
         * @notice True if deposited token0, the first token address in lexographical order
         * @notice False if deposited token1, the second token address in lexographical order
         */
        bool zeroForOne;
    }
}

File 29 of 57 : RangePoolStructs.sol
// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import './PoolsharkStructs.sol';

interface RangePoolStructs is PoolsharkStructs {
    struct RangePosition {
        uint256 feeGrowthInside0Last;
        uint256 feeGrowthInside1Last;
        uint128 liquidity;
        int24 lower;
        int24 upper;
    }

    struct CompoundRangeParams {
        uint160 priceLower;
        uint160 priceUpper;
        uint128 amount0;
        uint128 amount1;
        uint32 positionId;
    }

    struct SampleParams {
        uint16 sampleIndex;
        uint16 sampleLength;
        uint32 time;
        uint32[] secondsAgo;
        int24 tick;
        uint128 liquidity;
        PoolsharkStructs.LimitImmutables constants;
    }

    struct UpdateParams {
        int24 lower;
        int24 upper;
        uint32 positionId;
        uint128 burnPercent;
    }

    struct MintRangeCache {
        GlobalState state;
        RangePosition position;
        PoolsharkStructs.LimitImmutables constants;
        address owner;
        uint256 liquidityMinted;
        uint160 priceLower;
        uint160 priceUpper;
        int128 amount0;
        int128 amount1;
        int128 feesAccrued0;
        int128 feesAccrued1;
    }

    struct BurnRangeCache {
        GlobalState state;
        RangePosition position;
        PoolsharkStructs.LimitImmutables constants;
        uint256 liquidityBurned;
        uint160 priceLower;
        uint160 priceUpper;
        int128 amount0;
        int128 amount1;
    }

    struct RangePositionCache {
        uint256 liquidityAmount;
        uint256 rangeFeeGrowth0;
        uint256 rangeFeeGrowth1;
        uint128 amountFees0;
        uint128 amountFees1;
        uint128 feesBurned0;
        uint128 feesBurned1;
    }

    struct SnapshotRangeCache {
        RangePosition position;
        SampleState samples;
        PoolsharkStructs.LimitImmutables constants;
        uint160 price;
        uint160 secondsPerLiquidityAccum;
        uint160 secondsPerLiquidityAccumLower;
        uint160 secondsPerLiquidityAccumUpper;
        uint128 liquidity;
        uint128 amount0;
        uint128 amount1;
        int56 tickSecondsAccum;
        int56 tickSecondsAccumLower;
        int56 tickSecondsAccumUpper;
        uint32 secondsOutsideLower;
        uint32 secondsOutsideUpper;
        uint32 blockTimestamp;
        int24 tick;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/LimitPoolStructs.sol';
import './EpochMap.sol';
import '../TickMap.sol';
import '../utils/String.sol';
import '../utils/SafeCast.sol';

library Claims {
    using SafeCast for uint256;

    // if claim tick searched, look max 512 spacings ahead
    uint256 public constant maxWordsSearched = 4;

    function validate(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.BurnLimitParams memory params,
        LimitPoolStructs.BurnLimitCache memory cache
    )
        internal
        view
        returns (
            PoolsharkStructs.BurnLimitParams memory,
            LimitPoolStructs.BurnLimitCache memory
        )
    {
        if (
            params.claim < cache.position.lower ||
            params.claim > cache.position.upper
        ) require(false, 'ClaimTick::OutsidePositionBounds()');

        if (params.claim % (cache.constants.tickSpacing / 2) != 0)
            require(false, 'ClaimTick::NotHalfTickOrFullTick()');

        uint32 claimTickEpoch = EpochMap.get(
            params.claim,
            params.zeroForOne,
            tickMap,
            cache.constants
        );

        if (params.zeroForOne) {
            if (cache.pool.price >= cache.priceClaim) {
                if (cache.pool.price <= cache.priceUpper) {
                    cache.priceClaim = cache.pool.price;
                    params.claim = TickMap.roundBack(
                        cache.pool.tickAtPrice,
                        cache.constants,
                        params.zeroForOne,
                        cache.priceClaim
                    );
                } else {
                    cache.priceClaim = cache.priceUpper;
                    params.claim = cache.position.upper;
                    cache.claimTick = ticks[cache.position.upper].limit;
                }
                claimTickEpoch = cache.state.epoch;
            } else if (params.claim % cache.constants.tickSpacing != 0) {
                if (cache.claimTick.priceAt == 0) {
                    // if tick untouched since position creation revert
                    if (claimTickEpoch <= cache.position.epochLast)
                        require(false, 'ClaimTick::HalfTickClaimInvalid()');
                        // search ahead for the correct claim tick
                    else cache.search = true;
                }
                cache.priceClaim = cache.claimTick.priceAt;
            }
        } else {
            if (cache.pool.price <= cache.priceClaim) {
                if (cache.pool.price >= cache.priceLower) {
                    cache.priceClaim = cache.pool.price;
                    params.claim = TickMap.roundBack(
                        cache.pool.tickAtPrice,
                        cache.constants,
                        params.zeroForOne,
                        cache.priceClaim
                    );
                } else {
                    cache.priceClaim = cache.priceLower;
                    params.claim = cache.position.lower;
                    cache.claimTick = ticks[cache.position.upper].limit;
                }
                claimTickEpoch = cache.state.epoch;
            } else if (params.claim % cache.constants.tickSpacing != 0) {
                if (cache.claimTick.priceAt == 0) {
                    if (claimTickEpoch <= cache.position.epochLast)
                        require(false, 'ClaimTick::HalfTickClaimInvalid()');
                        // search ahead for the correct claim tick
                    else cache.search = true;
                }
                cache.priceClaim = cache.claimTick.priceAt;
            }
        }

        if (
            params.claim ==
            (params.zeroForOne ? cache.position.upper : cache.position.lower)
        ) {
            // check if final tick crossed
            cache.liquidityBurned = 0;
            if (claimTickEpoch <= cache.position.epochLast)
                // nothing to search
                require(false, 'ClaimTick::FinalTickNotCrossedYet()');
        } else if (cache.liquidityBurned > 0) {
            /// @dev - partway claim is valid as long as liquidity is not being removed
            if (params.zeroForOne) {
                // check final tick first
                uint32 endTickEpoch = EpochMap.get(
                    cache.position.upper,
                    params.zeroForOne,
                    tickMap,
                    cache.constants
                );
                if (endTickEpoch > cache.position.epochLast) {
                    // final tick crossed
                    params.claim = cache.position.upper;
                    cache.priceClaim = cache.priceUpper;
                    cache.claimTick = ticks[cache.position.upper].limit;
                    cache.liquidityBurned = 0;
                } else {
                    // check claim tick passed is valid
                    int24 claimTickNext = TickMap.next(
                        tickMap,
                        params.claim,
                        cache.constants.tickSpacing,
                        false
                    );
                    uint32 claimTickNextEpoch = EpochMap.get(
                        claimTickNext,
                        params.zeroForOne,
                        tickMap,
                        cache.constants
                    );
                    if (claimTickNextEpoch > cache.position.epochLast) {
                        ///@dev - next tick in range should not have been crossed
                        // require (false, 'ClaimTick::NextTickAlreadyCrossed()');
                        cache.search = true;
                    }
                }
            } else {
                // check final tick first
                uint32 endTickEpoch = EpochMap.get(
                    cache.position.lower,
                    params.zeroForOne,
                    tickMap,
                    cache.constants
                );
                if (endTickEpoch > cache.position.epochLast) {
                    // final tick crossed
                    params.claim = cache.position.lower;
                    cache.priceClaim = cache.priceLower;
                    cache.claimTick = ticks[cache.position.lower].limit;
                    cache.liquidityBurned = 0;
                } else {
                    // check claim tick passed is valid
                    int24 claimTickNext = TickMap.previous(
                        tickMap,
                        params.claim,
                        cache.constants.tickSpacing,
                        false
                    );
                    uint32 claimTickNextEpoch = EpochMap.get(
                        claimTickNext,
                        params.zeroForOne,
                        tickMap,
                        cache.constants
                    );
                    if (claimTickNextEpoch > cache.position.epochLast) {
                        ///@dev - next tick in range should not have been crossed
                        // require (false, 'ClaimTick::NextTickAlreadyCrossed()');
                        cache.search = true;
                    }
                }
            }
        }

        if (cache.search) {
            (params, cache, claimTickEpoch) = search(
                ticks,
                tickMap,
                params,
                cache
            );
        }

        /// @dev - start tick does not overwrite position and final tick clears position
        if (
            params.claim != cache.position.upper &&
            params.claim != cache.position.lower
        ) {
            // check epochLast on claim tick
            if (claimTickEpoch <= cache.position.epochLast)
                require(false, 'ClaimTick::TickNotCrossed()');
        }

        return (params, cache);
    }

    function getDeltas(
        PoolsharkStructs.BurnLimitParams memory params,
        LimitPoolStructs.BurnLimitCache memory cache,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal pure returns (LimitPoolStructs.BurnLimitCache memory) {
        // if half tick priceAt > 0 add amountOut to amountOutClaimed
        // set claimPriceLast if zero
        if (!cache.position.crossedInto) {
            cache.position.crossedInto = true;
        }
        LimitPoolStructs.GetDeltasLocals memory locals;

        if (params.claim % constants.tickSpacing != 0)
            // this should pass price at the claim tick
            locals.previousFullTick = TickMap.roundBack(
                params.claim,
                constants,
                params.zeroForOne,
                ConstantProduct.getPriceAtTick(params.claim, constants)
            );
        else locals.previousFullTick = params.claim;
        locals.pricePrevious = ConstantProduct.getPriceAtTick(
            locals.previousFullTick,
            constants
        );
        if (
            params.zeroForOne
                ? locals.previousFullTick > cache.position.lower
                : locals.previousFullTick < cache.position.upper
        ) {
            // claim amounts up to latest full tick crossed
            cache.amountIn += uint128(
                params.zeroForOne
                    ? ConstantProduct.getDy(
                        cache.position.liquidity,
                        cache.priceLower,
                        locals.pricePrevious,
                        false
                    )
                    : ConstantProduct.getDx(
                        cache.position.liquidity,
                        locals.pricePrevious,
                        cache.priceUpper,
                        false
                    )
            );
        }
        if (cache.liquidityBurned > 0) {
            // if tick hasn't been set back calculate amountIn
            if (
                params.zeroForOne
                    ? cache.priceClaim > locals.pricePrevious
                    : cache.priceClaim < locals.pricePrevious
            ) {
                // allow partial tick claim if removing liquidity
                cache.amountIn += uint128(
                    params.zeroForOne
                        ? ConstantProduct.getDy(
                            cache.liquidityBurned,
                            locals.pricePrevious,
                            cache.priceClaim,
                            false
                        )
                        : ConstantProduct.getDx(
                            cache.liquidityBurned,
                            cache.priceClaim,
                            locals.pricePrevious,
                            false
                        )
                );
            }
            // use priceClaim if tick hasn't been set back
            // else use claimPriceLast to calculate amountOut
            if (
                params.claim !=
                (
                    params.zeroForOne
                        ? cache.position.upper
                        : cache.position.lower
                )
            ) {
                cache.amountOut += uint128(
                    params.zeroForOne
                        ? ConstantProduct.getDx(
                            cache.liquidityBurned,
                            cache.priceClaim,
                            cache.priceUpper,
                            false
                        )
                        : ConstantProduct.getDy(
                            cache.liquidityBurned,
                            cache.priceLower,
                            cache.priceClaim,
                            false
                        )
                );
            }
        }
        // take protocol fee if needed
        if (cache.pool.protocolFillFee > 0 && cache.amountIn > 0) {
            uint128 protocolFeeAmount = OverflowMath
                .mulDiv(cache.amountIn, cache.pool.protocolFillFee, 1e4)
                .toUint128();
            cache.amountIn -= protocolFeeAmount;
            cache.pool.protocolFees += protocolFeeAmount;
        }
        return cache;
    }

    function search(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.BurnLimitParams memory params,
        LimitPoolStructs.BurnLimitCache memory cache
    )
        internal
        view
        returns (
            PoolsharkStructs.BurnLimitParams memory,
            LimitPoolStructs.BurnLimitCache memory,
            uint32 claimTickEpoch
        )
    {
        LimitPoolStructs.SearchLocals memory locals;

        locals.ticksFound = new int24[](256);
        locals.searchTick = params.claim;
        if (params.zeroForOne) {
            for (uint256 i = 0; i < maxWordsSearched; ) {
                (
                    locals.ticksFound,
                    locals.ticksIncluded,
                    locals.searchTick
                ) = TickMap.nextTicksWithinWord(
                    tickMap,
                    locals.searchTick,
                    cache.constants.tickSpacing,
                    cache.position.upper,
                    locals.ticksFound,
                    locals.ticksIncluded
                );
                // add start of next word if tick exists and is within range
                if (
                    locals.searchTick < cache.position.upper &&
                    TickMap.get(
                        tickMap,
                        locals.searchTick,
                        cache.constants.tickSpacing
                    )
                ) {
                    locals.ticksFound[locals.ticksIncluded] = locals.searchTick;
                    unchecked {
                        ++locals.ticksIncluded;
                    }
                }
                // if we reached the final tick break the loop
                if (
                    locals.ticksIncluded > 0 &&
                    locals.searchTick >= cache.position.upper
                ) {
                    break;
                }
                unchecked {
                    ++i;
                }
            }
        } else {
            for (int256 i = 0; i < 2; ) {
                (
                    locals.ticksFound,
                    locals.ticksIncluded,
                    locals.searchTick
                ) = TickMap.previousTicksWithinWord(
                    tickMap,
                    locals.searchTick,
                    cache.constants.tickSpacing,
                    cache.position.lower,
                    locals.ticksFound,
                    locals.ticksIncluded
                );
                // add start of next word if tick exists and is within range
                if (
                    locals.searchTick > cache.position.lower &&
                    TickMap.get(
                        tickMap,
                        locals.searchTick,
                        cache.constants.tickSpacing
                    )
                ) {
                    locals.ticksFound[locals.ticksIncluded] = locals.searchTick;
                    unchecked {
                        ++locals.ticksIncluded;
                    }
                }
                // if we reached the final tick break the loop
                if (
                    locals.ticksIncluded > 0 &&
                    locals.searchTick <= cache.position.lower
                ) {
                    break;
                }
                unchecked {
                    ++i;
                }
            }
        }

        // set initial endIdx
        if (locals.ticksIncluded > 0) {
            locals.endIdx = locals.ticksIncluded - 1;
        } else {
            require(false, 'ClaimTick::NoTicksFoundViaSearch()');
        }

        while (locals.startIdx <= locals.endIdx) {
            // set idx at middle of start & end
            locals.searchIdx =
                (locals.endIdx - locals.startIdx) /
                2 +
                locals.startIdx;

            // set ticks
            locals.searchTick = locals.ticksFound[locals.searchIdx];
            if (locals.searchIdx + 1 < locals.ticksIncluded) {
                // tick ahead in array
                locals.searchTickAhead = locals.ticksFound[
                    locals.searchIdx + 1
                ];
            } else {
                // tick ahead in storage
                locals.searchTickAhead = params.zeroForOne
                    ? TickMap.next(
                        tickMap,
                        locals.searchTick,
                        cache.constants.tickSpacing,
                        false
                    )
                    : TickMap.previous(
                        tickMap,
                        locals.searchTick,
                        cache.constants.tickSpacing,
                        false
                    );
            }

            // set epochs
            locals.claimTickEpoch = EpochMap.get(
                locals.searchTick,
                params.zeroForOne,
                tickMap,
                cache.constants
            );
            locals.claimTickAheadEpoch = EpochMap.get(
                locals.searchTickAhead,
                params.zeroForOne,
                tickMap,
                cache.constants
            );

            // check epochs
            if (locals.claimTickEpoch > cache.position.epochLast) {
                if (locals.claimTickAheadEpoch <= cache.position.epochLast) {
                    // correct claim tick
                    break;
                } else {
                    // search higher
                    locals.startIdx = locals.searchIdx + 1;
                }
            } else if (locals.searchIdx > 0) {
                // search lower
                locals.endIdx = locals.searchIdx - 1;
            } else {
                // 0 index hit; end of search
                break;
            }
        }

        // final check on valid claim tick
        if (
            locals.claimTickEpoch <= cache.position.epochLast ||
            locals.claimTickAheadEpoch > cache.position.epochLast
        ) {
            require(false, 'ClaimTick::NotFoundViaSearch()');
        }

        cache.claimTick = ticks[locals.searchTick].limit;
        if ((locals.searchTick % cache.constants.tickSpacing) == 0)
            cache.priceClaim = ConstantProduct.getPriceAtTick(
                locals.searchTick,
                cache.constants
            );
        else {
            cache.priceClaim = cache.claimTick.priceAt;
        }
        if (cache.liquidityBurned == 0)
            params.claim = TickMap.roundBack(
                locals.searchTick,
                cache.constants,
                params.zeroForOne,
                cache.priceClaim
            );
        else params.claim = locals.searchTick;

        return (params, cache, locals.claimTickEpoch);
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../math/ConstantProduct.sol';
import '../../interfaces/structs/LimitPoolStructs.sol';

library EpochMap {
    event SyncLimitTick(uint32 epoch, int24 tick, bool zeroForOne);

    function set(
        int24 tick,
        bool zeroForOne,
        uint256 epoch,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal {
        (
            uint256 tickIndex,
            uint256 wordIndex,
            uint256 blockIndex,
            uint256 volumeIndex
        ) = getIndices(tick, constants);
        // assert epoch isn't bigger than max uint32
        uint256 epochValue = zeroForOne
            ? tickMap.epochs0[volumeIndex][blockIndex][wordIndex]
            : tickMap.epochs1[volumeIndex][blockIndex][wordIndex];
        // clear previous value
        epochValue &= ~(((1 << 9) - 1) << ((tickIndex & 0x7) * 32));
        // add new value to word
        epochValue |= epoch << ((tickIndex & 0x7) * 32);
        // store word in map
        zeroForOne
            ? tickMap.epochs0[volumeIndex][blockIndex][wordIndex] = epochValue
            : tickMap.epochs1[volumeIndex][blockIndex][wordIndex] = epochValue;

        emit SyncLimitTick(uint32(epoch), tick, zeroForOne);
    }

    function get(
        int24 tick,
        bool zeroForOne,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal view returns (uint32 epoch) {
        (
            uint256 tickIndex,
            uint256 wordIndex,
            uint256 blockIndex,
            uint256 volumeIndex
        ) = getIndices(tick, constants);

        uint256 epochValue = zeroForOne
            ? tickMap.epochs0[volumeIndex][blockIndex][wordIndex]
            : tickMap.epochs1[volumeIndex][blockIndex][wordIndex];
        // right shift so first 8 bits are epoch value
        epochValue >>= ((tickIndex & 0x7) * 32);
        // clear other bits
        epochValue &= ((1 << 32) - 1);
        return uint32(epochValue);
    }

    function getIndices(
        int24 tick,
        PoolsharkStructs.LimitImmutables memory constants
    )
        internal
        pure
        returns (
            uint256 tickIndex,
            uint256 wordIndex,
            uint256 blockIndex,
            uint256 volumeIndex
        )
    {
        unchecked {
            if (tick > ConstantProduct.maxTick(constants.tickSpacing))
                require(false, 'TickIndexOverflow()');
            if (tick < ConstantProduct.minTick(constants.tickSpacing))
                require(false, 'TickIndexUnderflow()');
            if (tick % (constants.tickSpacing / 2) != 0) {
                require(false, 'TickIndexInvalid()');
            }
            tickIndex = uint256(
                int256(
                    (_round(tick, constants.tickSpacing / 2) -
                        _round(
                            ConstantProduct.MIN_TICK,
                            constants.tickSpacing / 2
                        )) / (constants.tickSpacing / 2)
                )
            );
            wordIndex = tickIndex >> 3; // 2^3 epochs per word
            blockIndex = tickIndex >> 11; // 2^8 words per block
            volumeIndex = tickIndex >> 19; // 2^8 blocks per volume
            if (blockIndex > 2046) require(false, 'BlockIndexOverflow()');
        }
    }

    function _round(int24 tick, int24 tickSpacing)
        internal
        pure
        returns (int24 roundedTick)
    {
        return (tick / tickSpacing) * tickSpacing;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import './LimitTicks.sol';
import '../../interfaces/IPositionERC1155.sol';
import '../../interfaces/structs/RangePoolStructs.sol';
import '../../interfaces/structs/LimitPoolStructs.sol';
import '../math/OverflowMath.sol';
import './Claims.sol';
import './EpochMap.sol';
import '../utils/SafeCast.sol';
import '../Ticks.sol';

/// @notice Position management library for limit liquidity.
library LimitPositions {
    using SafeCast for uint256;

    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    event BurnLimit(
        address indexed to,
        uint32 positionId,
        int24 lower,
        int24 upper,
        int24 oldClaim,
        int24 newClaim,
        bool zeroForOne,
        uint128 liquidityBurned,
        uint128 tokenInClaimed,
        uint128 tokenOutBurned
    );

    function resize(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.MintLimitParams memory params,
        LimitPoolStructs.MintLimitCache memory cache
    )
        external
        returns (
            PoolsharkStructs.MintLimitParams memory,
            LimitPoolStructs.MintLimitCache memory
        )
    {
        cache.priceLower = ConstantProduct.getPriceAtTick(
            params.lower,
            cache.constants
        );
        cache.priceUpper = ConstantProduct.getPriceAtTick(
            params.upper,
            cache.constants
        );
        cache.mintSize =
            (uint256(params.mintPercent) * uint256(params.amount)) /
            1e28;

        // calculate L constant
        cache.liquidityMinted = ConstantProduct.getLiquidityForAmounts(
            cache.priceLower,
            cache.priceUpper,
            params.zeroForOne ? cache.priceLower : cache.priceUpper,
            params.zeroForOne ? 0 : uint256(params.amount),
            params.zeroForOne ? uint256(params.amount) : 0
        );

        if (cache.liquidityMinted == 0)
            require(false, 'NoLiquidityBeingAdded()');
        // calculate price limit by using half of input
        {
            cache.priceLimit = params.zeroForOne
                ? ConstantProduct.getNewPrice(
                    cache.priceUpper,
                    cache.liquidityMinted,
                    params.amount / 2,
                    true,
                    true
                )
                : ConstantProduct.getNewPrice(
                    cache.priceLower,
                    cache.liquidityMinted,
                    params.amount / 2,
                    false,
                    true
                );
            if (cache.priceLimit == 0) require(false, 'PriceLimitZero()');
            // get tick at price
            cache.tickLimit = ConstantProduct.getTickAtPrice(
                cache.priceLimit.toUint160(),
                cache.constants
            );
            // round to nearest tick spacing
            cache.priceLimit = ConstantProduct.getPriceAtTick(
                cache.tickLimit,
                cache.constants
            );
        }

        PoolsharkStructs.SwapCache memory swapCache;
        swapCache.state = cache.state;
        swapCache.constants = cache.constants;
        swapCache.price = cache.state.pool.price;

        // swap zero if no liquidity near market price
        if (
            cache.state.pool.liquidity == 0 &&
            (
                params.zeroForOne
                    ? swapCache.price > cache.priceLower
                    : swapCache.price < cache.priceUpper
            )
        ) {
            swapCache = Ticks.swap(
                ticks,
                samples,
                rangeTickMap,
                limitTickMap,
                PoolsharkStructs.SwapParams({
                    to: params.to,
                    priceLimit: (
                        params.zeroForOne ? cache.priceLower : cache.priceUpper
                    ).toUint160(),
                    amount: 0,
                    exactIn: true,
                    zeroForOne: params.zeroForOne,
                    callbackData: abi.encodePacked(bytes1(0x0))
                }),
                swapCache
            );
        }

        // only swap if priceLimit is beyond current pool price
        if (
            params.zeroForOne
                ? cache.priceLimit < swapCache.price
                : cache.priceLimit > swapCache.price
        ) {
            // swap and save the pool state
            swapCache = Ticks.swap(
                ticks,
                samples,
                rangeTickMap,
                limitTickMap,
                PoolsharkStructs.SwapParams({
                    to: params.to,
                    priceLimit: cache.priceLimit.toUint160(),
                    amount: params.amount,
                    exactIn: true,
                    zeroForOne: params.zeroForOne,
                    callbackData: abi.encodePacked(bytes1(0x0))
                }),
                swapCache
            );
            // subtract from remaining input amount
            params.amount -= uint128(swapCache.input);
        }
        // save to cache
        cache.swapCache = swapCache;
        cache.state = swapCache.state;

        if (params.amount < cache.mintSize) params.amount = 0;
        // move start tick based on amount filled in swap
        if (
            (params.amount > 0 && swapCache.input > 0) ||
            (
                params.zeroForOne
                    ? cache.priceLower < swapCache.price
                    : cache.priceUpper > swapCache.price
            )
        ) {
            // move the tick limit based on pool.tickAtPrice
            if (
                params.zeroForOne
                    ? cache.priceLower < swapCache.price
                    : cache.priceUpper > swapCache.price
            ) {
                cache.tickLimit = swapCache.state.pool.tickAtPrice;
            }
            // round ahead tickLimit to avoid crossing epochs
            cache.tickLimit = TickMap.roundAhead(
                cache.tickLimit,
                cache.constants,
                params.zeroForOne,
                swapCache.price
            );
            if (params.zeroForOne) {
                if (cache.priceLower < swapCache.price) {
                    // if rounding goes past limit trim position
                    /// @dev - if swap didn't go to limit user would be 100% filled
                    params.lower = cache.tickLimit;
                    cache.priceLower = ConstantProduct.getPriceAtTick(
                        params.lower,
                        cache.constants
                    );
                }
                if (
                    params.lower >= params.upper &&
                    params.lower <
                    ConstantProduct.maxTick(cache.constants.tickSpacing) -
                        cache.constants.tickSpacing
                ) {
                    params.upper = params.lower + cache.constants.tickSpacing;
                }
                cache.priceUpper = ConstantProduct.getPriceAtTick(
                    params.upper,
                    cache.constants
                );
            } else {
                if (cache.priceUpper > swapCache.price) {
                    // if rounding goes past limit trim position
                    params.upper = cache.tickLimit;
                    cache.priceUpper = ConstantProduct.getPriceAtTick(
                        params.upper,
                        cache.constants
                    );
                }
                if (
                    params.upper <= params.lower &&
                    params.lower >
                    ConstantProduct.minTick(cache.constants.tickSpacing) +
                        cache.constants.tickSpacing
                ) {
                    params.lower = params.upper - cache.constants.tickSpacing;
                }
                cache.priceLower = ConstantProduct.getPriceAtTick(
                    params.lower,
                    cache.constants
                );
            }
            if (params.amount > 0 && params.lower < params.upper) {
                cache.liquidityMinted = ConstantProduct.getLiquidityForAmounts(
                    cache.priceLower,
                    cache.priceUpper,
                    params.zeroForOne ? cache.priceLower : cache.priceUpper,
                    params.zeroForOne ? 0 : uint256(params.amount),
                    params.zeroForOne ? uint256(params.amount) : 0
                );
                if (cache.liquidityMinted == 0) {
                    // skip minting
                    params.amount = 0;
                }
            } else {
                // skip minting
                params.amount = 0;
                cache.liquidityMinted = 0;
            }
            cache.state.epoch += 1;
        }

        if (params.lower >= params.upper) {
            // zero out amount transferred in
            params.amount = 0;
        }

        // liquidity overflow check
        if (
            cache.state.liquidityGlobal + cache.liquidityMinted >
            uint128(type(int128).max)
        ) require(false, 'LiquidityOverflow()');

        return (params, cache);
    }

    function add(
        LimitPoolStructs.MintLimitCache memory cache,
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.MintLimitParams memory params
    )
        internal
        returns (
            PoolsharkStructs.LimitPoolState memory,
            LimitPoolStructs.LimitPosition memory
        )
    {
        if (cache.liquidityMinted == 0) return (cache.pool, cache.position);

        if (cache.position.liquidity == 0) {
            cache.position.epochLast = cache.state.epoch;
            cache.state.epoch += 1; // increment for future swaps
            IPositionERC1155(cache.constants.poolToken).mint(
                params.to,
                params.positionId,
                1,
                cache.constants
            );
        } else {
            // safety check in case we somehow get here
            if (
                params.zeroForOne
                    ? EpochMap.get(
                        params.lower,
                        params.zeroForOne,
                        tickMap,
                        cache.constants
                    ) > cache.position.epochLast
                    : EpochMap.get(
                        params.upper,
                        params.zeroForOne,
                        tickMap,
                        cache.constants
                    ) > cache.position.epochLast
            ) {
                require(false, 'PositionAlreadyEntered()');
            }
            /// @auditor maybe this shouldn't be a revert but rather just not mint the position?
        }

        // add liquidity to ticks
        LimitTicks.insert(ticks, tickMap, cache, params);

        // update liquidity global
        cache.state.liquidityGlobal += uint128(cache.liquidityMinted);

        cache.position.liquidity += uint128(cache.liquidityMinted);

        return (cache.pool, cache.position);
    }

    function update(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        LimitPoolStructs.BurnLimitCache memory cache,
        PoolsharkStructs.BurnLimitParams memory params
    )
        internal
        returns (
            PoolsharkStructs.BurnLimitParams memory,
            LimitPoolStructs.BurnLimitCache memory
        )
    {
        (params, cache) = _deltas(ticks, tickMap, params, cache);

        // update pool liquidity
        if (cache.priceClaim == cache.pool.price && cache.liquidityBurned > 0) {
            // handle pool.price at edge of range
            if (
                params.zeroForOne
                    ? cache.priceClaim < cache.priceUpper
                    : cache.priceClaim > cache.priceLower
            ) cache.pool.liquidity -= cache.liquidityBurned;
        }

        if (cache.liquidityBurned > 0) {
            if (
                params.claim ==
                (
                    params.zeroForOne
                        ? cache.position.upper
                        : cache.position.lower
                )
            ) {
                // if claim is final tick no liquidity to remove
                cache.removeLower = false;
                cache.removeUpper = false;
            } else {
                // else remove liquidity from final tick
                params.zeroForOne
                    ? cache.removeUpper = true
                    : cache.removeLower = true;
                if (params.zeroForOne) {
                    if (
                        params.claim == cache.position.lower &&
                        cache.pool.price < cache.priceLower
                    ) {
                        // full tick price was touched
                        cache.removeLower = true;
                    } else if (
                        params.claim % cache.constants.tickSpacing != 0 &&
                        cache.pool.price < cache.priceClaim
                    )
                        // half tick was created
                        cache.removeLower = true;
                } else {
                    if (
                        params.claim == cache.position.upper &&
                        cache.pool.price > cache.priceUpper
                    )
                        // full tick price was touched
                        cache.removeUpper = true;
                    else if (
                        params.claim % cache.constants.tickSpacing != 0 &&
                        cache.pool.price > cache.priceClaim
                    )
                        // half tick was created
                        cache.removeUpper = true;
                }
            }
            LimitTicks.remove(ticks, tickMap, params, cache, cache.constants);
            // update position liquidity
            cache.position.liquidity -= uint128(cache.liquidityBurned);
            // update global liquidity
            cache.state.liquidityGlobal -= cache.liquidityBurned;
        }

        // round back claim tick for storage
        if (params.claim % cache.constants.tickSpacing != 0) {
            cache.claim = params.claim;
            params.claim = TickMap.roundBack(
                params.claim,
                cache.constants,
                params.zeroForOne,
                cache.priceClaim
            );
        }

        // emit custom event
        emit BurnLimit(
            params.to,
            params.positionId,
            cache.position.lower,
            cache.position.upper,
            cache.claim,
            params.claim,
            params.zeroForOne,
            cache.liquidityBurned,
            cache.amountIn,
            cache.amountOut
        );

        // clear filled position
        if (
            params.zeroForOne
                ? params.claim == cache.position.upper
                : params.claim == cache.position.lower
        ) {
            cache.liquidityBurned = cache.position.liquidity;
            cache.state.liquidityGlobal -= cache.position.liquidity;
            cache.position.liquidity = 0;
        }

        // clear position if empty
        if (cache.position.liquidity == 0) {
            cache.position.epochLast = 0;
            cache.position.crossedInto = false;
        }

        // emit standard event
        emit Burn(
            msg.sender,
            cache.position.lower,
            cache.position.upper,
            uint128(cache.liquidityBurned),
            params.zeroForOne ? cache.amountOut
                              : cache.amountIn,
            params.zeroForOne ? cache.amountIn
                              : cache.amountOut
        );

        // save pool to state in memory
        if (params.zeroForOne) cache.state.pool0 = cache.pool;
        else cache.state.pool1 = cache.pool;

        return (params, cache);
    }

    function snapshot(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        LimitPoolStructs.BurnLimitCache memory cache,
        PoolsharkStructs.BurnLimitParams memory params
    ) internal view returns (uint128 amountIn, uint128 amountOut) {
        (params, cache) = _deltas(ticks, tickMap, params, cache);

        return (cache.amountIn, cache.amountOut);
    }

    function _deltas(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.BurnLimitParams memory params,
        LimitPoolStructs.BurnLimitCache memory cache
    )
        internal
        view
        returns (
            PoolsharkStructs.BurnLimitParams memory,
            LimitPoolStructs.BurnLimitCache memory
        )
    {
        cache = LimitPoolStructs.BurnLimitCache({
            state: cache.state,
            pool: params.zeroForOne ? cache.state.pool0 : cache.state.pool1,
            claimTick: ticks[params.claim].limit,
            position: cache.position,
            constants: cache.constants,
            priceLower: ConstantProduct.getPriceAtTick(
                cache.position.lower,
                cache.constants
            ),
            priceClaim: ticks[params.claim].limit.priceAt == 0
                ? ConstantProduct.getPriceAtTick(params.claim, cache.constants)
                : ticks[params.claim].limit.priceAt,
            priceUpper: ConstantProduct.getPriceAtTick(
                cache.position.upper,
                cache.constants
            ),
            liquidityBurned: _convert(
                cache.position.liquidity,
                params.burnPercent
            ),
            amountIn: 0,
            amountOut: 0,
            claim: params.claim,
            removeLower: false,
            removeUpper: false,
            search: false
        });

        // check claim is valid
        (params, cache) = Claims.validate(ticks, tickMap, params, cache);

        // calculate position deltas
        cache = Claims.getDeltas(params, cache, cache.constants);

        return (params, cache);
    }

    function _convert(uint128 liquidity, uint128 percent)
        internal
        pure
        returns (uint128)
    {
        // convert percentage to liquidity amount
        if (percent > 1e38) percent = 1e38;
        if (liquidity == 0 && percent > 0) require(false, 'PositionNotFound()');
        return uint128((uint256(liquidity) * uint256(percent)) / 1e38);
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/LimitPoolStructs.sol';
import '../../interfaces/limit/ILimitPoolFactory.sol';
import '../../interfaces/limit/ILimitPool.sol';
import '../math/ConstantProduct.sol';
import './LimitPositions.sol';
import '../math/OverflowMath.sol';
import '../TickMap.sol';
import './EpochMap.sol';
import '../Samples.sol';
import '../utils/SafeCast.sol';

/// @notice Tick management library for limit pools
library LimitTicks {
    using SafeCast for uint256;

    uint256 internal constant Q96 = 0x1000000000000000000000000;

    event SyncLimitLiquidity(
        uint128 liquidityAdded,
        int24 tick,
        bool zeroForOne
    );

    function validate(
        int24 lower,
        int24 upper,
        int24 tickSpacing
    ) internal pure {
        if (lower % tickSpacing != 0) require(false, 'InvalidLowerTick()');
        if (lower <= ConstantProduct.MIN_TICK)
            require(false, 'InvalidLowerTick()');
        if (upper % tickSpacing != 0) require(false, 'InvalidUpperTick()');
        if (upper >= ConstantProduct.MAX_TICK)
            require(false, 'InvalidUpperTick()');
        if (lower >= upper) require(false, 'InvalidPositionBounds()');
    }

    function insert(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        LimitPoolStructs.MintLimitCache memory cache,
        PoolsharkStructs.MintLimitParams memory params
    ) internal {
        int256 liquidityMinted = int256(cache.liquidityMinted);

        // check if adding liquidity necessary
        if (!params.zeroForOne || cache.priceLower > cache.pool.price) {
            // sets bit in map
            if (
                !TickMap.set(tickMap, params.lower, cache.constants.tickSpacing)
            ) {
                // inherit epoch
                int24 tickAhead;
                if (params.zeroForOne) {
                    tickAhead = TickMap.next(
                        tickMap,
                        params.lower,
                        cache.constants.tickSpacing,
                        false
                    );
                } else {
                    tickAhead = TickMap.previous(
                        tickMap,
                        params.lower,
                        cache.constants.tickSpacing,
                        false
                    );
                }
                uint32 epochAhead = EpochMap.get(
                    tickAhead,
                    params.zeroForOne,
                    tickMap,
                    cache.constants
                );
                EpochMap.set(
                    params.lower,
                    params.zeroForOne,
                    epochAhead,
                    tickMap,
                    cache.constants
                );
            }
            PoolsharkStructs.LimitTick memory tickLower = ticks[params.lower]
                .limit;
            if (params.zeroForOne) {
                tickLower.liquidityDelta += int128(liquidityMinted);
            } else {
                tickLower.liquidityDelta -= int128(liquidityMinted);
            }
            tickLower.liquidityAbsolute += cache.liquidityMinted.toUint128();
            ticks[params.lower].limit = tickLower;
        } else {
            /// @dev - i.e. if zeroForOne && cache.priceLower <= cache.pool.price
            cache.state.epoch += 1;
            // mark epoch on undercut tick
            EpochMap.set(
                params.lower,
                params.zeroForOne,
                cache.state.epoch,
                tickMap,
                cache.constants
            );
        }

        if (params.zeroForOne || cache.priceUpper < cache.pool.price) {
            if (
                !TickMap.set(tickMap, params.upper, cache.constants.tickSpacing)
            ) {
                int24 tickAhead;
                if (params.zeroForOne) {
                    tickAhead = TickMap.next(
                        tickMap,
                        params.upper,
                        cache.constants.tickSpacing,
                        false
                    );
                } else {
                    tickAhead = TickMap.previous(
                        tickMap,
                        params.upper,
                        cache.constants.tickSpacing,
                        false
                    );
                }
                uint32 epochAhead = EpochMap.get(
                    tickAhead,
                    params.zeroForOne,
                    tickMap,
                    cache.constants
                );
                EpochMap.set(
                    params.upper,
                    params.zeroForOne,
                    epochAhead,
                    tickMap,
                    cache.constants
                );
            }
            PoolsharkStructs.LimitTick memory tickUpper = ticks[params.upper]
                .limit;
            if (params.zeroForOne) {
                tickUpper.liquidityDelta -= int128(liquidityMinted);
            } else {
                tickUpper.liquidityDelta += int128(liquidityMinted);
            }
            tickUpper.liquidityAbsolute += cache.liquidityMinted.toUint128();
            ticks[params.upper].limit = tickUpper;
        } else {
            /// @dev - i.e. if !zeroForOne && cache.priceUpper >= cache.pool.price
            cache.state.epoch += 1;
            // mark epoch on undercut tick
            EpochMap.set(
                params.upper,
                params.zeroForOne,
                cache.state.epoch,
                tickMap,
                cache.constants
            );
        }
    }

    function insertSingle(
        PoolsharkStructs.MintLimitParams memory params,
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        LimitPoolStructs.MintLimitCache memory cache,
        PoolsharkStructs.LimitPoolState memory pool,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal returns (PoolsharkStructs.LimitPoolState memory) {
        /// @auditor - would be smart to protect against the case of epochs crossing
        (int24 tickToSave, uint160 roundedPrice) = TickMap.roundHalf(
            pool.tickAtPrice,
            constants,
            pool.price
        );
        // update tick to save
        LimitPoolStructs.LimitTick memory tick = ticks[tickToSave].limit;
        /// @auditor - tick.priceAt will be zero for tick % tickSpacing == 0
        if (tick.priceAt == 0) {
            if (
                pool.price !=
                (params.zeroForOne ? cache.priceLower : cache.priceUpper)
            ) {
                TickMap.set(tickMap, tickToSave, constants.tickSpacing);
            }
            EpochMap.set(
                tickToSave,
                params.zeroForOne,
                cache.state.epoch,
                tickMap,
                constants
            );
        }
        // skip if we are at the nearest full tick
        if (pool.price != roundedPrice) {
            // if empty just save the pool price
            if (tick.priceAt == 0) {
                tick.priceAt = pool.price;
            } else {
                // we need to blend the two partial fills into a single tick
                LimitPoolStructs.InsertSingleLocals memory locals;
                if (params.zeroForOne) {
                    // price moves up so previousFullTick is lesser
                    locals.previousFullTick =
                        tickToSave -
                        constants.tickSpacing /
                        2;
                    locals.pricePrevious = ConstantProduct.getPriceAtTick(
                        locals.previousFullTick,
                        constants
                    );
                    // calculate amountOut filled across both partial fills
                    locals.amountOutExact = ConstantProduct.getDy(
                        pool.liquidity,
                        locals.pricePrevious,
                        pool.price,
                        false
                    );
                    locals.amountOutExact += ConstantProduct.getDy(
                        uint128(tick.liquidityDelta),
                        locals.pricePrevious,
                        tick.priceAt,
                        false
                    );
                    // add current pool liquidity to partial tick
                    uint128 combinedLiquidity = pool.liquidity +
                        uint128(tick.liquidityDelta);
                    // advance price based on combined fill
                    tick.priceAt = ConstantProduct
                        .getNewPrice(
                            uint256(locals.pricePrevious),
                            combinedLiquidity,
                            locals.amountOutExact,
                            false,
                            true
                        )
                        .toUint160();
                    // dx to the next tick is less than before the tick blend
                    EpochMap.set(
                        tickToSave,
                        params.zeroForOne,
                        cache.state.epoch,
                        tickMap,
                        constants
                    );
                } else {
                    // price moves down so previousFullTick is greater
                    locals.previousFullTick =
                        tickToSave +
                        constants.tickSpacing /
                        2;
                    locals.pricePrevious = ConstantProduct.getPriceAtTick(
                        locals.previousFullTick,
                        constants
                    );
                    // calculate amountOut filled across both partial fills
                    locals.amountOutExact = ConstantProduct.getDx(
                        pool.liquidity,
                        pool.price,
                        locals.pricePrevious,
                        false
                    );
                    locals.amountOutExact += ConstantProduct.getDx(
                        uint128(tick.liquidityDelta),
                        tick.priceAt,
                        locals.pricePrevious,
                        false
                    );
                    // add current pool liquidity to partial tick
                    uint128 combinedLiquidity = pool.liquidity +
                        uint128(tick.liquidityDelta);
                    // advance price based on combined fill
                    tick.priceAt = ConstantProduct
                        .getNewPrice(
                            uint256(locals.pricePrevious),
                            combinedLiquidity,
                            locals.amountOutExact,
                            true,
                            true
                        )
                        .toUint160();
                    // mark epoch for second partial fill positions
                    EpochMap.set(
                        tickToSave,
                        params.zeroForOne,
                        cache.state.epoch,
                        tickMap,
                        constants
                    );
                }
            }
        }
        // invariant => if we save liquidity to tick clear pool liquidity
        if ((tickToSave != (params.zeroForOne ? params.lower : params.upper))) {
            tick.liquidityDelta += int128(pool.liquidity);
            tick.liquidityAbsolute += pool.liquidity;
            emit SyncLimitLiquidity(
                pool.liquidity,
                tickToSave,
                params.zeroForOne
            );
            pool.liquidity = 0;
        }
        ticks[tickToSave].limit = tick;
        return pool;
    }

    function remove(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.BurnLimitParams memory params,
        LimitPoolStructs.BurnLimitCache memory cache,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal {
        // set ticks based on claim and zeroForOne
        int24 lower = params.zeroForOne ? params.claim : cache.position.lower;
        int24 upper = params.zeroForOne ? cache.position.upper : params.claim;
        {
            PoolsharkStructs.LimitTick memory tickLower = ticks[lower].limit;

            if (cache.removeLower) {
                if (params.zeroForOne) {
                    tickLower.liquidityDelta -= int128(cache.liquidityBurned);
                } else {
                    tickLower.liquidityDelta += int128(cache.liquidityBurned);
                }
                tickLower.liquidityAbsolute -= cache.liquidityBurned;
                ticks[lower].limit = tickLower;
                clear(ticks, constants, tickMap, lower);
            }
        }
        {
            PoolsharkStructs.LimitTick memory tickUpper = ticks[upper].limit;
            if (cache.removeUpper) {
                if (params.zeroForOne) {
                    tickUpper.liquidityDelta += int128(cache.liquidityBurned);
                } else {
                    tickUpper.liquidityDelta -= int128(cache.liquidityBurned);
                }
                tickUpper.liquidityAbsolute -= cache.liquidityBurned;
                ticks[upper].limit = tickUpper;
                clear(ticks, constants, tickMap, upper);
            }
        }
    }

    function unlock(
        LimitPoolStructs.MintLimitCache memory cache,
        PoolsharkStructs.LimitPoolState memory pool,
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        bool zeroForOne
    )
        internal
        returns (
            LimitPoolStructs.MintLimitCache memory,
            PoolsharkStructs.LimitPoolState memory
        )
    {
        if (pool.liquidity > 0) return (cache, pool);

        (int24 startTick, ) = TickMap.roundHalf(
            pool.tickAtPrice,
            cache.constants,
            pool.price
        );

        if (zeroForOne) {
            pool.tickAtPrice = TickMap.next(
                tickMap,
                startTick,
                cache.constants.tickSpacing,
                true
            );
            if (
                pool.tickAtPrice <
                ConstantProduct.maxTick(cache.constants.tickSpacing)
            ) {
                EpochMap.set(
                    pool.tickAtPrice,
                    zeroForOne,
                    cache.state.epoch,
                    tickMap,
                    cache.constants
                );
            }
        } else {
            /// @dev - roundedUp true since liquidity could be equal to the current pool tickAtPrice
            pool.tickAtPrice = TickMap.previous(
                tickMap,
                startTick,
                cache.constants.tickSpacing,
                true
            );
            if (
                pool.tickAtPrice >
                ConstantProduct.minTick(cache.constants.tickSpacing)
            ) {
                EpochMap.set(
                    pool.tickAtPrice,
                    zeroForOne,
                    cache.state.epoch,
                    tickMap,
                    cache.constants
                );
            }
        }

        // increment pool liquidity
        pool.liquidity += uint128(ticks[pool.tickAtPrice].limit.liquidityDelta);
        int24 tickToClear = pool.tickAtPrice;
        uint160 tickPriceAt = ticks[pool.tickAtPrice].limit.priceAt;

        if (tickPriceAt == 0) {
            // if full tick crossed
            pool.price = ConstantProduct.getPriceAtTick(
                pool.tickAtPrice,
                cache.constants
            );
        } else {
            // if half tick crossed
            pool.price = tickPriceAt;
            pool.tickAtPrice = ConstantProduct.getTickAtPrice(
                tickPriceAt,
                cache.constants
            );
        }

        // zero out tick
        ticks[tickToClear].limit = PoolsharkStructs.LimitTick(0, 0, 0);
        clear(ticks, cache.constants, tickMap, tickToClear);

        return (cache, pool);
    }

    function clear(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.LimitImmutables memory constants,
        PoolsharkStructs.TickMap storage tickMap,
        int24 tickToClear
    ) internal {
        if (_empty(ticks[tickToClear])) {
            if (
                tickToClear != ConstantProduct.maxTick(constants.tickSpacing) &&
                tickToClear != ConstantProduct.minTick(constants.tickSpacing)
            ) {
                ticks[tickToClear].limit = PoolsharkStructs.LimitTick(0, 0, 0);
                TickMap.unset(tickMap, tickToClear, constants.tickSpacing);
            }
        }
    }

    function _empty(LimitPoolStructs.Tick memory tick)
        internal
        pure
        returns (bool)
    {
        return tick.limit.liquidityAbsolute == 0;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../../interfaces/structs/LimitPoolStructs.sol';
import '../../../interfaces/IPositionERC1155.sol';
import '../LimitPositions.sol';
import '../../utils/Collect.sol';
import '../../utils/PositionTokens.sol';

library BurnLimitCall {
    using SafeCast for uint128;

    event BurnLimit(
        address indexed to,
        uint32 positionId,
        int24 lower,
        int24 upper,
        int24 oldClaim,
        int24 newClaim,
        bool zeroForOne,
        uint128 liquidityBurned,
        uint128 tokenInClaimed,
        uint128 tokenOutBurned
    );

    struct BurnLimitLocals {
        int128 amount0Delta;
        int128 amount1Delta;
    }

    function perform(
        mapping(uint256 => LimitPoolStructs.LimitPosition) storage positions,
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.GlobalState storage globalState,
        PoolsharkStructs.BurnLimitParams memory params,
        LimitPoolStructs.BurnLimitCache memory cache
    )
        external
        returns (
            int256, // amount0Delta
            int256 // amount1Delta
        )
    {
        // check for invalid receiver
        if (params.to == address(0)) require(false, 'CollectToZeroAddress()');

        // initialize cache
        cache.state = globalState;
        cache.position = positions[params.positionId];

        if (cache.position.liquidity == 0) require(false, 'PositionNotFound()');
        if (
            PositionTokens.balanceOf(
                cache.constants,
                msg.sender,
                params.positionId
            ) == 0
        ) require(false, 'PositionOwnerMismatch()');

        // update position
        (params, cache) = LimitPositions.update(ticks, tickMap, cache, params);

        // save position before transfer
        if (
            (
                params.zeroForOne
                    ? params.claim != cache.position.upper
                    : params.claim != cache.position.lower
            )
        ) {
            if (cache.position.liquidity > 0) {
                if (params.zeroForOne) {
                    cache.position.lower = params.claim;
                } else {
                    cache.position.upper = params.claim;
                }
                positions[params.positionId] = cache.position;
            } else {
                IPositionERC1155(cache.constants.poolToken).burn(
                    msg.sender,
                    params.positionId,
                    1,
                    cache.constants
                );
                delete positions[params.positionId];
            }
        } else {
            IPositionERC1155(cache.constants.poolToken).burn(
                msg.sender,
                params.positionId,
                1,
                cache.constants
            );
            delete positions[params.positionId];
        }

        // save state before transfer call
        save(cache, globalState, params.zeroForOne);

        BurnLimitLocals memory locals;
        (cache, locals.amount0Delta, locals.amount1Delta) = CollectLib.burnLimit(
            cache,
            params
        );

        return (locals.amount0Delta, locals.amount1Delta);
    }

    function save(
        LimitPoolStructs.BurnLimitCache memory cache,
        PoolsharkStructs.GlobalState storage globalState,
        bool zeroForOne
    ) internal {
        globalState.epoch = cache.state.epoch;
        globalState.liquidityGlobal = cache.state.liquidityGlobal;
        if (zeroForOne) {
            globalState.pool = cache.state.pool;
            globalState.pool0 = cache.state.pool0;
        } else {
            globalState.pool = cache.state.pool;
            globalState.pool1 = cache.state.pool1;
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../../interfaces/structs/LimitPoolStructs.sol';
import '../../../interfaces/callbacks/ILimitPoolCallback.sol';
import '../../../interfaces/IERC20Minimal.sol';
import '../LimitPositions.sol';
import '../../utils/Collect.sol';
import '../../utils/PositionTokens.sol';

library MintLimitCall {

    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    event MintLimit(
        address indexed to,
        int24 lower,
        int24 upper,
        bool zeroForOne,
        uint32 positionId,
        uint32 epochLast,
        uint128 amountIn,
        uint128 liquidityMinted
    );

    event SyncLimitPool(
        uint160 price,
        uint128 liquidity,
        uint32 epoch,
        int24 tickAtPrice,
        bool isPool0
    );

    function perform(
        mapping(uint256 => LimitPoolStructs.LimitPosition) storage positions,
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.GlobalState storage globalState,
        PoolsharkStructs.MintLimitParams memory params,
        LimitPoolStructs.MintLimitCache memory cache
    )
        external
        returns (
            int256, // amount0Delta
            int256 // amount1Delta
        )
    {
        // check for invalid receiver
        if (params.to == address(0)) require(false, 'CollectToZeroAddress()');

        cache.state = globalState;

        // validate position ticks
        ConstantProduct.checkTicks(
            params.lower,
            params.upper,
            cache.constants.tickSpacing
        );

        if (params.positionId > 0) {
            cache.position = positions[params.positionId];
            if (cache.position.liquidity == 0) {
                // position doesn't exist
                require(false, 'PositionNotFound()');
            }
            if (
                PositionTokens.balanceOf(
                    cache.constants,
                    params.to,
                    params.positionId
                ) == 0
            ) require(false, 'PositionOwnerMismatch()');
        }

        // resize position if necessary
        (params, cache) = LimitPositions.resize(
            ticks,
            samples,
            rangeTickMap,
            limitTickMap,
            params,
            cache
        );

        // save state for reentrancy safety
        save(cache, globalState, !params.zeroForOne);

        // transfer out if swap output
        if (cache.swapCache.output > 0)
            SafeTransfers.transferOut(
                params.to,
                params.zeroForOne
                    ? cache.constants.token1
                    : cache.constants.token0,
                cache.swapCache.output
            );
        // mint position if amount is left
        if (params.amount > 0 && params.lower < params.upper) {
            // check if new position created
            if (
                params.positionId == 0 || // new position
                params.lower != cache.position.lower || // lower mismatch
                params.upper != cache.position.upper
            ) {
                // upper mismatch
                LimitPoolStructs.LimitPosition memory newPosition;
                newPosition.lower = params.lower;
                newPosition.upper = params.upper;
                // use new position in cache
                cache.position = newPosition;
                params.positionId = cache.state.positionIdNext;
                cache.state.positionIdNext += 1;
            }
            cache.pool = params.zeroForOne
                ? cache.state.pool0
                : cache.state.pool1;
            // bump to the next tick if there is no liquidity
            if (cache.pool.liquidity == 0) {
                /// @dev - this makes sure to have liquidity unlocked if undercutting
                (cache, cache.pool) = LimitTicks.unlock(
                    cache,
                    cache.pool,
                    ticks,
                    limitTickMap,
                    params.zeroForOne
                );
            }

            if (params.zeroForOne) {
                uint160 priceLower = ConstantProduct.getPriceAtTick(
                    params.lower,
                    cache.constants
                );
                if (priceLower <= cache.pool.price) {
                    // save liquidity if active
                    if (cache.pool.liquidity > 0) {
                        cache.pool = LimitTicks.insertSingle(
                            params,
                            ticks,
                            limitTickMap,
                            cache,
                            cache.pool,
                            cache.constants
                        );
                    }
                    cache.pool.price = priceLower;
                    cache.pool.tickAtPrice = params.lower;
                    /// @auditor - double check liquidity is set correctly for this in insertSingle
                    cache.pool.liquidity += uint128(cache.liquidityMinted);
                    cache.position.crossedInto = true;
                    // set epoch on start tick to signify position being crossed into
                    /// @auditor - this is safe assuming we have swapped at least this far on the other side
                    emit SyncLimitPool(
                        cache.pool.price,
                        cache.pool.liquidity,
                        cache.state.epoch,
                        cache.pool.tickAtPrice,
                        params.zeroForOne
                    );
                }
            } else {
                uint160 priceUpper = ConstantProduct.getPriceAtTick(
                    params.upper,
                    cache.constants
                );
                if (priceUpper >= cache.pool.price) {
                    if (cache.pool.liquidity > 0) {
                        cache.pool = LimitTicks.insertSingle(
                            params,
                            ticks,
                            limitTickMap,
                            cache,
                            cache.pool,
                            cache.constants
                        );
                    }
                    cache.pool.price = priceUpper;
                    cache.pool.tickAtPrice = params.upper;
                    cache.pool.liquidity += uint128(cache.liquidityMinted);
                    cache.position.crossedInto = true;
                    // set epoch on start tick to signify position being crossed into
                    /// @auditor - this is safe assuming we have swapped at least this far on the other side
                    emit SyncLimitPool(
                        cache.pool.price,
                        cache.pool.liquidity,
                        cache.state.epoch,
                        cache.pool.tickAtPrice,
                        params.zeroForOne
                    );
                }
            }
            (cache.pool, cache.position) = LimitPositions.add(
                cache,
                ticks,
                limitTickMap,
                params
            );

            // save position to storage
            positions[params.positionId] = cache.position;

            params.zeroForOne
                ? cache.state.pool0 = cache.pool
                : cache.state.pool1 = cache.pool;

            emit Mint(
                msg.sender,
                params.to,
                cache.position.lower,
                cache.position.upper,
                uint128(cache.liquidityMinted),
                uint128(params.zeroForOne ? params.amount : 0),
                uint128(params.zeroForOne ? 0 : params.amount)
            );

            emit MintLimit(
                params.to,
                params.lower,
                params.upper,
                params.zeroForOne,
                params.positionId,
                cache.position.epochLast,
                uint128(params.amount),
                uint128(cache.liquidityMinted)
            );
        }
        // save lp side for safe reentrancy
        save(cache, globalState, params.zeroForOne);

        // check balance and execute callback
        uint256 balanceStart = balance(params, cache);
        ILimitPoolMintLimitCallback(msg.sender).limitPoolMintLimitCallback(
            params.zeroForOne
                ? -int256(params.amount + cache.swapCache.input)
                : int256(cache.swapCache.output),
            params.zeroForOne
                ? int256(cache.swapCache.output)
                : -int256(params.amount + cache.swapCache.input),
            params.callbackData
        );

        // check balance requirements after callback
        if (
            balance(params, cache) <
            balanceStart + params.amount + cache.swapCache.input
        ) require(false, 'MintInputAmountTooLow()');

        return (
            params.zeroForOne
                ? -int256(params.amount + cache.swapCache.input)
                : int256(cache.swapCache.output),
            params.zeroForOne
                ? int256(cache.swapCache.output)
                : -int256(params.amount + cache.swapCache.input)
        );
    }

    function save(
        LimitPoolStructs.MintLimitCache memory cache,
        PoolsharkStructs.GlobalState storage globalState,
        bool zeroForOne
    ) internal {
        globalState.epoch = cache.state.epoch;
        globalState.liquidityGlobal = cache.state.liquidityGlobal;
        globalState.positionIdNext = cache.state.positionIdNext;
        if (zeroForOne) {
            globalState.pool = cache.state.pool;
            globalState.pool0 = cache.state.pool0;
        } else {
            globalState.pool = cache.state.pool;
            globalState.pool1 = cache.state.pool1;
        }
    }

    function balance(
        PoolsharkStructs.MintLimitParams memory params,
        LimitPoolStructs.MintLimitCache memory cache
    ) private view returns (uint256) {
        (bool success, bytes memory data) = (
            params.zeroForOne ? cache.constants.token0 : cache.constants.token1
        ).staticcall(
                abi.encodeWithSelector(
                    IERC20Minimal.balanceOf.selector,
                    address(this)
                )
            );
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }
}

File 36 of 57 : SnapshotLimitCall.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../../interfaces/structs/LimitPoolStructs.sol';
import '../LimitPositions.sol';
import '../../utils/Collect.sol';

library SnapshotLimitCall {
    uint8 private constant _ENTERED = 2;

    event BurnLimit(
        address indexed to,
        uint32 positionId,
        int24 lower,
        int24 upper,
        int24 oldClaim,
        int24 newClaim,
        bool zeroForOne,
        uint128 liquidityBurned,
        uint128 tokenInClaimed,
        uint128 tokenOutBurned
    );

    function perform(
        mapping(uint256 => LimitPoolStructs.LimitPosition) storage positions,
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        LimitPoolStructs.SnapshotLimitParams memory params
    ) external view returns (uint128, uint128) {
        if (state.unlocked == _ENTERED)
            require(false, 'ReentrancyGuardReadOnlyReentrantCall()');
        LimitPoolStructs.BurnLimitCache memory cache;
        cache.state = state;
        cache.constants = constants;
        cache.position = positions[params.positionId];
        PoolsharkStructs.BurnLimitParams memory burnParams = PoolsharkStructs
            .BurnLimitParams({
                to: params.owner,
                burnPercent: params.burnPercent,
                positionId: params.positionId,
                claim: params.claim,
                zeroForOne: params.zeroForOne
            });
        if (cache.position.epochLast == 0) require(false, 'PositionNotFound()');
        return LimitPositions.snapshot(ticks, tickMap, cache, burnParams);
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import './OverflowMath.sol';
import '../../interfaces/structs/LimitPoolStructs.sol';
import '../../interfaces/structs/PoolsharkStructs.sol';

/// @notice Math library that facilitates ranged liquidity calculations.
library ConstantProduct {
    uint256 internal constant Q96 = 0x1000000000000000000000000;

    struct PriceBounds {
        uint160 min;
        uint160 max;
    }

    /////////////////////////////////////////////////////////////
    ///////////////////////// DYDX MATH /////////////////////////
    /////////////////////////////////////////////////////////////

    function getDy(
        uint256 liquidity,
        uint256 priceLower,
        uint256 priceUpper,
        bool roundUp
    ) internal pure returns (uint256 dy) {
        unchecked {
            if (liquidity == 0) return 0;
            if (roundUp) {
                dy = OverflowMath.mulDivRoundingUp(
                    liquidity,
                    priceUpper - priceLower,
                    Q96
                );
            } else {
                dy = OverflowMath.mulDiv(
                    liquidity,
                    priceUpper - priceLower,
                    Q96
                );
            }
        }
    }

    function getDx(
        uint256 liquidity,
        uint256 priceLower,
        uint256 priceUpper,
        bool roundUp
    ) internal pure returns (uint256 dx) {
        unchecked {
            if (liquidity == 0) return 0;
            if (roundUp) {
                dx = OverflowMath.divRoundingUp(
                    OverflowMath.mulDivRoundingUp(
                        liquidity << 96,
                        priceUpper - priceLower,
                        priceUpper
                    ),
                    priceLower
                );
            } else {
                dx =
                    OverflowMath.mulDiv(
                        liquidity << 96,
                        priceUpper - priceLower,
                        priceUpper
                    ) /
                    priceLower;
            }
        }
    }

    function getLiquidityForAmounts(
        uint256 priceLower,
        uint256 priceUpper,
        uint256 currentPrice,
        uint256 dy,
        uint256 dx
    ) internal pure returns (uint256 liquidity) {
        unchecked {
            if (priceUpper <= currentPrice) {
                liquidity = OverflowMath.mulDiv(
                    dy,
                    Q96,
                    priceUpper - priceLower
                );
            } else if (currentPrice <= priceLower) {
                liquidity = OverflowMath.mulDiv(
                    dx,
                    OverflowMath.mulDiv(priceLower, priceUpper, Q96),
                    priceUpper - priceLower
                );
            } else {
                uint256 liquidity0 = OverflowMath.mulDiv(
                    dx,
                    OverflowMath.mulDiv(priceUpper, currentPrice, Q96),
                    priceUpper - currentPrice
                );
                uint256 liquidity1 = OverflowMath.mulDiv(
                    dy,
                    Q96,
                    currentPrice - priceLower
                );
                liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
            }
        }
    }

    function getAmountsForLiquidity(
        uint256 priceLower,
        uint256 priceUpper,
        uint256 currentPrice,
        uint256 liquidityAmount,
        bool roundUp
    ) internal pure returns (uint128 token0amount, uint128 token1amount) {
        if (priceUpper <= currentPrice) {
            token1amount = uint128(
                getDy(liquidityAmount, priceLower, priceUpper, roundUp)
            );
        } else if (currentPrice <= priceLower) {
            token0amount = uint128(
                getDx(liquidityAmount, priceLower, priceUpper, roundUp)
            );
        } else {
            token0amount = uint128(
                getDx(liquidityAmount, currentPrice, priceUpper, roundUp)
            );
            token1amount = uint128(
                getDy(liquidityAmount, priceLower, currentPrice, roundUp)
            );
        }
        if (token0amount > uint128(type(int128).max))
            require(false, 'AmountsOutOfBounds()');
        if (token1amount > uint128(type(int128).max))
            require(false, 'AmountsOutOfBounds()');
    }

    function getNewPrice(
        uint256 price,
        uint256 liquidity,
        uint256 amount,
        bool zeroForOne,
        bool exactIn
    ) internal pure returns (uint256 newPrice) {
        if (exactIn) {
            if (zeroForOne) {
                uint256 liquidityPadded = liquidity << 96;
                newPrice = OverflowMath.mulDivRoundingUp(
                    liquidityPadded,
                    price,
                    liquidityPadded + price * amount
                );
            } else {
                newPrice = price + (amount << 96) / liquidity;
            }
        } else {
            if (zeroForOne) {
                newPrice =
                    price -
                    OverflowMath.divRoundingUp(amount << 96, liquidity);
            } else {
                uint256 liquidityPadded = uint256(liquidity) << 96;
                newPrice = OverflowMath.mulDivRoundingUp(
                    liquidityPadded,
                    price,
                    liquidityPadded - uint256(price) * amount
                );
            }
        }
    }

    function getPrice(uint256 sqrtPrice) internal pure returns (uint256 price) {
        if (sqrtPrice >= 2**48)
            price = OverflowMath.mulDiv(sqrtPrice, sqrtPrice, 2**96);
        else price = sqrtPrice;
    }

    /////////////////////////////////////////////////////////////
    ///////////////////////// TICK MATH /////////////////////////
    /////////////////////////////////////////////////////////////

    int24 internal constant MIN_TICK = -887272; /// @dev - tick for price of 2^-128
    int24 internal constant MAX_TICK = -MIN_TICK; /// @dev - tick for price of 2^128

    function minTick(int16 tickSpacing) internal pure returns (int24 tick) {
        return (MIN_TICK / tickSpacing) * tickSpacing;
    }

    function maxTick(int16 tickSpacing) internal pure returns (int24 tick) {
        return (MAX_TICK / tickSpacing) * tickSpacing;
    }

    function priceBounds(int16 tickSpacing)
        internal
        pure
        returns (uint160, uint160)
    {
        return (minPrice(tickSpacing), maxPrice(tickSpacing));
    }

    function minPrice(int16 tickSpacing) internal pure returns (uint160 price) {
        PoolsharkStructs.LimitImmutables memory constants;
        constants.tickSpacing = tickSpacing;
        return getPriceAtTick(minTick(tickSpacing), constants);
    }

    function maxPrice(int16 tickSpacing) internal pure returns (uint160 price) {
        PoolsharkStructs.LimitImmutables memory constants;
        constants.tickSpacing = tickSpacing;
        return getPriceAtTick(maxTick(tickSpacing), constants);
    }

    function checkTicks(
        int24 lower,
        int24 upper,
        int16 tickSpacing
    ) internal pure {
        if (lower < minTick(tickSpacing))
            require(false, 'LowerTickOutOfBounds()');
        if (upper > maxTick(tickSpacing))
            require(false, 'UpperTickOutOfBounds()');
        if (lower % tickSpacing != 0)
            require(false, 'LowerTickOutsideTickSpacing()');
        if (upper % tickSpacing != 0)
            require(false, 'UpperTickOutsideTickSpacing()');
        if (lower >= upper) require(false, 'LowerUpperTickOrderInvalid()');
    }

    function checkPrice(uint160 price, PriceBounds memory bounds)
        internal
        pure
    {
        if (price < bounds.min || price >= bounds.max)
            require(false, 'PriceOutOfBounds()');
    }

    /// @notice Calculates sqrt(1.0001^tick) * 2^96.
    /// @dev Throws if |tick| > max tick.
    /// @param tick The input tick for the above formula.
    /// @return price Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick.
    function getPriceAtTick(
        int24 tick,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal pure returns (uint160 price) {
        uint256 absTick = tick < 0
            ? uint256(-int256(tick))
            : uint256(int256(tick));
        if (absTick > uint256(uint24(maxTick(constants.tickSpacing))))
            require(false, 'TickOutOfBounds()');
        unchecked {
            uint256 ratio = absTick & 0x1 != 0
                ? 0xfffcb933bd6fad37aa2d162d1a594001
                : 0x100000000000000000000000000000000;
            if (absTick & 0x2 != 0)
                ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0)
                ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0)
                ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0)
                ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0)
                ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0)
                ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0)
                ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0)
                ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0)
                ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0)
                ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0)
                ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0)
                ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0)
                ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0)
                ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0)
                ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0)
                ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0)
                ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0)
                ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0)
                ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

            if (tick > 0) ratio = type(uint256).max / ratio;
            // 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 getTickAtPrice of the output price is always consistent.
            price = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
        }
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio.
    /// @param price The sqrt ratio for which to compute the tick as a Q64.96.
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio.
    function getTickAtPrice(
        uint160 price,
        PoolsharkStructs.LimitImmutables memory constants
    ) internal pure returns (int24 tick) {
        // Second inequality must be < because the price can never reach the price at the max tick.
        if (price < constants.bounds.min || price > constants.bounds.max)
            require(false, 'PriceOutOfBounds()');
        uint256 ratio = uint256(price) << 32;

        uint256 r = ratio;
        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 = ratio >> (msb - 127);
        else r = ratio << (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
            : getPriceAtTick(tickHi, constants) <= price
            ? tickHi
            : tickLow;
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

/// @notice Math library that facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision.
library OverflowMath {
    // @dev no underflow or overflow checks
    function divRoundingUp(uint256 x, uint256 y)
        internal
        pure
        returns (uint256 z)
    {
        assembly {
            z := add(div(x, y), gt(mod(x, y), 0))
        }
    }

    /// @notice Calculates floor(a×b÷denominator) with full precision - throws if result overflows an 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; // Least significant 256 bits of the product.
            uint256 prod1; // Most significant 256 bits of the product.
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }
            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                require(denominator > 0);
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }
            // Make sure the result is less than 2**256 -
            // also prevents denominator == 0.
            require(denominator > prod1);
            ///////////////////////////////////////////////
            // 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 = uint256(-int256(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
            // 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 precoditions 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 an 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) {
        result = mulDiv(a, b, denominator);
        unchecked {
            if (mulmod(a, b, denominator) != 0) {
                if (result >= type(uint256).max)
                    require(false, 'MaxUintExceeded()');
                result++;
            }
        }
    }
}

File 39 of 57 : FeesCall.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/IPositionERC1155.sol';
import '../../interfaces/structs/PoolsharkStructs.sol';
import '../../interfaces/limit/ILimitPoolManager.sol';
import '../utils/SafeTransfers.sol';

library FeesCall {
    // protocol fee ceilings
    uint16 public constant MAX_PROTOCOL_SWAP_FEE = 1e4; // max protocol swap fee of 100%
    uint16 public constant MAX_PROTOCOL_FILL_FEE = 1e2; // max protocol fill fee of 1%

    // protocol fee flags
    uint8 internal constant PROTOCOL_SWAP_FEE_0 = 2**0;
    uint8 internal constant PROTOCOL_SWAP_FEE_1 = 2**1;
    uint8 internal constant PROTOCOL_FILL_FEE_0 = 2**2;
    uint8 internal constant PROTOCOL_FILL_FEE_1 = 2**3;

    // eth address for safe withdrawal
    address public constant ethAddress = address(0);

    function perform(
        PoolsharkStructs.GlobalState storage globalState,
        PoolsharkStructs.FeesParams memory params,
        PoolsharkStructs.LimitImmutables memory constants
    ) external returns (uint128 token0Fees, uint128 token1Fees) {
        // swap fee token0
        if ((params.setFeesFlags & PROTOCOL_SWAP_FEE_0) > 0) {
            if (params.protocolSwapFee0 > MAX_PROTOCOL_SWAP_FEE)
                require(false, 'ProtocolSwapFeeCeilingExceeded()');
            globalState.pool.protocolSwapFee0 = params.protocolSwapFee0;
        }
        // swap fee token1
        if ((params.setFeesFlags & PROTOCOL_SWAP_FEE_1) > 0) {
            if (params.protocolSwapFee1 > MAX_PROTOCOL_SWAP_FEE)
                require(false, 'ProtocolSwapFeeCeilingExceeded()');
            globalState.pool.protocolSwapFee1 = params.protocolSwapFee1;
        }
        // fill fee token0
        if ((params.setFeesFlags & PROTOCOL_FILL_FEE_0) > 0) {
            if (params.protocolFillFee0 > MAX_PROTOCOL_FILL_FEE)
                require(false, 'ProtocolFillFeeCeilingExceeded()');
            globalState.pool1.protocolFillFee = params.protocolFillFee0;
        }
        // fill fee token1
        if ((params.setFeesFlags & PROTOCOL_FILL_FEE_1) > 0) {
            if (params.protocolFillFee1 > MAX_PROTOCOL_FILL_FEE)
                require(false, 'ProtocolFillFeeCeilingExceeded()');
            globalState.pool0.protocolFillFee = params.protocolFillFee1;
        }
        address feeTo = ILimitPoolManager(constants.owner).feeTo();

        // token0 fees stored on pool1 for swaps and fills
        token0Fees = globalState.pool1.protocolFees;
        // token1 fees stored on pool0 for swaps and fills
        token1Fees = globalState.pool0.protocolFees;
        globalState.pool0.protocolFees = 0;
        globalState.pool1.protocolFees = 0;

        if (token0Fees > 0)
            SafeTransfers.transferOut(feeTo, constants.token0, token0Fees);
        if (token1Fees > 0)
            SafeTransfers.transferOut(feeTo, constants.token1, token1Fees);

        // withdraw errantly received ETH from pool
        if (address(this).balance > 0) {
            // send eth balance to feeTo
            SafeTransfers.transferOut(feeTo, ethAddress, address(this).balance);
        }

        // withdraw errantly received ETH from pool token
        if (address(constants.poolToken).balance > 0) {
            // send eth balance to feeTo
            IPositionERC1155(constants.poolToken).withdrawEth(feeTo, constants);
        }

        return (token0Fees, token1Fees);
    }
}

File 40 of 57 : QuoteCall.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/LimitPoolStructs.sol';
import '../Ticks.sol';

library QuoteCall {
    uint8 private constant _ENTERED = 2;

    event Event();

    function perform(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.GlobalState storage globalState,
        PoolsharkStructs.QuoteParams memory params,
        PoolsharkStructs.SwapCache memory cache
    )
        external
        view
        returns (
            uint256,
            uint256,
            uint160
        )
    {
        if (cache.state.unlocked == _ENTERED)
            require(false, 'ReentrancyGuardReadOnlyReentrantCall()');
        cache.state = globalState;
        return Ticks.quote(ticks, rangeTickMap, limitTickMap, params, cache);
    }
}

File 41 of 57 : SampleCall.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/RangePoolStructs.sol';
import '../Samples.sol';

library SampleCall {
    uint8 private constant _ENTERED = 2;

    event Event();

    function perform(
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        uint32[] memory secondsAgo
    )
        external
        view
        returns (
            int56[] memory tickSecondsAccum,
            uint160[] memory secondsPerLiquidityAccum,
            uint160 averagePrice,
            uint128 averageLiquidity,
            int24 averageTick
        )
    {
        if (state.unlocked == _ENTERED)
            require(false, 'ReentrancyGuardReadOnlyReentrantCall()');
        return
            Samples.get(
                address(this),
                RangePoolStructs.SampleParams(
                    state.pool.samples.index,
                    state.pool.samples.count,
                    uint32(block.timestamp),
                    secondsAgo,
                    state.pool.tickAtPrice,
                    state.pool.liquidity,
                    constants
                )
            );
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/LimitPoolStructs.sol';
import '../../interfaces/callbacks/ILimitPoolCallback.sol';
import '../../interfaces/IERC20Minimal.sol';
import '../Ticks.sol';
import '../utils/Collect.sol';
import '../utils/SafeTransfers.sol';

library SwapCall {

    event Event();

    function perform(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.GlobalState storage globalState,
        PoolsharkStructs.SwapParams memory params,
        PoolsharkStructs.SwapCache memory cache
    ) external returns (int256, int256) {
        // check for invalid receiver
        if (params.to == address(0)) require(false, 'CollectToZeroAddress()');

        // initialize state
        cache.state = globalState;

        // execute swap
        cache = Ticks.swap(
            ticks,
            samples,
            rangeTickMap,
            limitTickMap,
            params,
            cache
        );

        // save state for reentrancy protection
        save(cache, globalState, params.zeroForOne);

        // transfer output amount
        SafeTransfers.transferOut(
            params.to,
            params.zeroForOne ? cache.constants.token1 : cache.constants.token0,
            cache.output
        );

        // check balance and execute callback
        uint256 balanceStart = balance(params, cache);
        ILimitPoolSwapCallback(msg.sender).limitPoolSwapCallback(
            params.zeroForOne ? -int256(cache.input) : int256(cache.output),
            params.zeroForOne ? int256(cache.output) : -int256(cache.input),
            params.callbackData
        );

        // check balance requirements after callback
        if (balance(params, cache) < balanceStart + cache.input) {
            require(false, 'SwapInputAmountTooLow()');
        }

        return (
            params.zeroForOne
                ? (-int256(cache.input), int256(cache.output))
                : (int256(cache.output), -int256(cache.input))
        );
    }

    function save(
        PoolsharkStructs.SwapCache memory cache,
        PoolsharkStructs.GlobalState storage globalState,
        bool zeroForOne
    ) internal {
        globalState.epoch = cache.state.epoch;
        globalState.pool = cache.state.pool;
        if (zeroForOne) globalState.pool1 = cache.state.pool1;
        else globalState.pool0 = cache.state.pool0;
    }

    function balance(
        PoolsharkStructs.SwapParams memory params,
        PoolsharkStructs.SwapCache memory cache
    ) private view returns (uint256) {
        (bool success, bytes memory data) = (
            params.zeroForOne ? cache.constants.token0 : cache.constants.token1
        ).staticcall(
                abi.encodeWithSelector(
                    IERC20Minimal.balanceOf.selector,
                    address(this)
                )
            );
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }
}

File 43 of 57 : FeeMath.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../Samples.sol';
import '../../utils/SafeCast.sol';
import '../../math/OverflowMath.sol';
import '../../../interfaces/limit/ILimitPoolManager.sol';
import '../../../interfaces/structs/PoolsharkStructs.sol';
import '../../../interfaces/structs/RangePoolStructs.sol';

/// @notice Math library that facilitates fee handling.
library FeeMath {
    using SafeCast for uint256;

    uint256 internal constant Q128 = 0x100000000000000000000000000000000;

    struct CalculateLocals {
        uint256 price;
        uint256 minPrice;
        uint256 lastPrice;
        uint256 swapFee;
        uint256 feeAmount;
        uint256 protocolFee;
        uint256 protocolFeesAccrued;
        uint256 amountRange;
        bool feeDirection;
    }

    function calculate(
        PoolsharkStructs.SwapCache memory cache,
        uint256 amountIn,
        uint256 amountOut,
        bool zeroForOne
    ) internal view returns (PoolsharkStructs.SwapCache memory) {
        CalculateLocals memory locals;
        if (cache.state.pool.liquidity != 0) {
            // calculate dynamic fee
            {
                locals.minPrice = ConstantProduct.getPrice(
                    cache.constants.bounds.min
                );
                // square prices to take delta
                locals.price = ConstantProduct.getPrice(cache.price);
                locals.lastPrice = ConstantProduct.getPrice(cache.averagePrice);
                if (locals.price < locals.minPrice)
                    locals.price = locals.minPrice;
                if (locals.lastPrice < locals.minPrice)
                    locals.lastPrice = locals.minPrice;
                // delta is % modifier on the swapFee
                uint256 delta = OverflowMath.mulDiv(
                    ILimitPoolManager(cache.constants.owner).feeDeltaConsts(address(this)) / // higher feeDeltaConst means
                        uint16(cache.constants.tickSpacing), // more aggressive dynamic fee
                    (
                        locals.price > locals.lastPrice
                            ? locals.price - locals.lastPrice
                            : locals.lastPrice - locals.price
                    ) * 1_000_000,
                    locals.lastPrice
                );
                // max fee increase at 5x
                if (delta > 4_000_000) delta = 4_000_000;
                // true means increased fee for zeroForOne = true
                locals.feeDirection = locals.price < locals.lastPrice;
                // adjust fee based on direction
                if (zeroForOne == locals.feeDirection) {
                    // if swapping away from twap price, increase fee
                    locals.swapFee =
                        cache.constants.swapFee +
                        OverflowMath.mulDiv(
                            delta,
                            cache.constants.swapFee,
                            1e6
                        );
                } else if (delta < 1e6) {
                    // if swapping towards twap price, decrease fee
                    locals.swapFee =
                        cache.constants.swapFee -
                        OverflowMath.mulDiv(
                            delta,
                            cache.constants.swapFee,
                            1e6
                        );
                } else {
                    // if swapping towards twap price and delta > 100%, set fee to zero
                    locals.swapFee = 0;
                }
            }
            if (cache.exactIn) {
                // calculate output from range liquidity
                locals.amountRange = OverflowMath.mulDiv(
                    amountOut,
                    cache.state.pool.liquidity,
                    cache.liquidity
                );
                // take enough fees to cover fee growth
                locals.feeAmount = OverflowMath.mulDivRoundingUp(
                    locals.amountRange,
                    locals.swapFee,
                    1e6
                );
                amountOut -= locals.feeAmount;
            } else {
                // calculate input from range liquidity
                locals.amountRange = OverflowMath.mulDiv(
                    amountIn,
                    cache.state.pool.liquidity,
                    cache.liquidity
                );
                // take enough fees to cover fee growth
                locals.feeAmount = OverflowMath.mulDivRoundingUp(
                    locals.amountRange,
                    locals.swapFee,
                    1e6
                );
                amountIn += locals.feeAmount;
            }
            // add to total fees paid for swap
            cache.feeAmount += locals.feeAmount.toUint128();
            // load protocol fee from cache
            // zeroForOne && !exactIn  = fee on token0
            // zeroForOne && exactIn   = fee on token1
            locals.protocolFee = (zeroForOne == cache.exactIn)
                ? cache.state.pool.protocolSwapFee1
                : cache.state.pool.protocolSwapFee0;
            // calculate fee
            locals.protocolFeesAccrued = OverflowMath.mulDiv(
                locals.feeAmount,
                locals.protocolFee,
                1e4
            );
            // fees for this swap step
            locals.feeAmount -= locals.protocolFeesAccrued;
            // save fee growth and protocol fees
            if (zeroForOne == cache.exactIn) {
                cache.state.pool0.protocolFees += uint128(
                    locals.protocolFeesAccrued
                );
                cache.state.pool.feeGrowthGlobal1 += uint200(
                    OverflowMath.mulDiv(
                        locals.feeAmount,
                        Q128,
                        cache.state.pool.liquidity
                    )
                );
            } else {
                cache.state.pool1.protocolFees += uint128(
                    locals.protocolFeesAccrued
                );
                cache.state.pool.feeGrowthGlobal0 += uint200(
                    OverflowMath.mulDiv(
                        locals.feeAmount,
                        Q128,
                        cache.state.pool.liquidity
                    )
                );
            }
        }
        cache.input += amountIn;
        cache.output += amountOut;

        return cache;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../../interfaces/structs/RangePoolStructs.sol';
import '../../utils/Collect.sol';
import '../../utils/PositionTokens.sol';
import '../RangePositions.sol';

library BurnRangeCall {
    using SafeCast for int128;

    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    event BurnRange(
        address indexed recipient,
        int24 lower,
        int24 upper,
        uint256 indexed tokenId,
        uint128 liquidityBurned,
        uint128 amount0,
        uint128 amount1
    );

    function perform(
        mapping(uint256 => RangePoolStructs.RangePosition) storage positions,
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.GlobalState storage globalState,
        RangePoolStructs.BurnRangeCache memory cache,
        RangePoolStructs.BurnRangeParams memory params
    )
        external
        returns (
            int256, // amount0Delta
            int256 // amount1Delta
        )
    {
        // check for invalid receiver
        if (params.to == address(0)) require(false, 'CollectToZeroAddress()');

        // initialize cache
        cache.state = globalState;
        cache.position = positions[params.positionId];

        if (cache.position.liquidity == 0) require(false, 'PositionNotFound()');
        if (
            PositionTokens.balanceOf(
                cache.constants,
                msg.sender,
                params.positionId
            ) == 0
        ) require(false, 'PositionOwnerMismatch()');

        (cache.position, cache.amount0, cache.amount1) = RangePositions.update(
            ticks,
            cache.position,
            cache.state,
            cache.constants,
            RangePoolStructs.UpdateParams(
                cache.position.lower,
                cache.position.upper,
                params.positionId,
                params.burnPercent
            )
        );
        cache = RangePositions.remove(ticks, samples, tickMap, params, cache);
        // only compound if burnPercent is zero
        if (params.burnPercent == 0)
            if (cache.amount0 > 0 || cache.amount1 > 0) {
                (
                    cache.position,
                    cache.state,
                    cache.amount0,
                    cache.amount1
                ) = RangePositions.compound(
                    ticks,
                    tickMap,
                    samples,
                    cache.state,
                    cache.constants,
                    cache.position,
                    RangePoolStructs.CompoundRangeParams(
                        cache.priceLower,
                        cache.priceUpper,
                        cache.amount0.toUint128(),
                        cache.amount1.toUint128(),
                        params.positionId
                    )
                );
            }
        // save changes to storage
        save(positions, globalState, cache, params.positionId);

        // transfer amounts to user
        if (cache.amount0 > 0 || cache.amount1 > 0)
            CollectLib.range(
                cache.position,
                cache.constants,
                msg.sender,
                params.to,
                cache.amount0,
                cache.amount1
            );

        // return amount deltas
        return (cache.amount0, cache.amount1);
    }

    function save(
        mapping(uint256 => RangePoolStructs.RangePosition) storage positions,
        PoolsharkStructs.GlobalState storage globalState,
        RangePoolStructs.BurnRangeCache memory cache,
        uint32 positionId
    ) internal {
        positions[positionId] = cache.position;
        globalState.pool = cache.state.pool;
        globalState.liquidityGlobal = cache.state.liquidityGlobal;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../../interfaces/structs/RangePoolStructs.sol';
import '../../../interfaces/callbacks/ILimitPoolCallback.sol';
import '../../../interfaces/IERC20Minimal.sol';
import '../../utils/SafeTransfers.sol';
import '../../utils/Collect.sol';
import '../../utils/PositionTokens.sol';
import '../RangePositions.sol';

library MintRangeCall {
    using SafeCast for uint256;
    using SafeCast for int128;
    using SafeCast for uint128;

    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    event MintRange(
        address indexed recipient,
        int24 lower,
        int24 upper,
        uint32 indexed positionId,
        uint128 liquidityMinted,
        int128 amount0Delta,
        int128 amount1Delta
    );

    struct Balances {
        uint256 amount0;
        uint256 amount1;
    }

    function perform(
        mapping(uint256 => RangePoolStructs.RangePosition) storage positions,
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.GlobalState storage globalState,
        RangePoolStructs.MintRangeCache memory cache,
        RangePoolStructs.MintRangeParams memory params
    )
        external
        returns (
            int256, // amount0Delta
            int256 // amount1Delta
        )
    {
        // check for invalid receiver
        if (params.to == address(0)) require(false, 'CollectToZeroAddress()');

        // validate position ticks
        ConstantProduct.checkTicks(
            params.lower,
            params.upper,
            cache.constants.tickSpacing
        );

        cache.state = globalState;

        // id of 0 passed to create new position
        if (params.positionId > 0) {
            // require balance held
            cache.position = positions[params.positionId];
            if (cache.position.liquidity == 0)
                require(false, 'PositionNotFound()');
            if (
                PositionTokens.balanceOf(
                    cache.constants,
                    params.to,
                    params.positionId
                ) == 0
            ) require(false, 'PositionOwnerMismatch()');
            // existing position
            cache.owner = params.to;
            // set bounds as defined by position
            params.lower = cache.position.lower;
            params.upper = cache.position.upper;
            // update existing position
            (
                cache.position,
                cache.feesAccrued0,
                cache.feesAccrued1
            ) = RangePositions.update(
                ticks,
                cache.position,
                cache.state,
                cache.constants,
                RangePoolStructs.UpdateParams(
                    params.lower,
                    params.upper,
                    params.positionId,
                    0
                )
            );
        } else {
            // create a new position
            params.positionId = cache.state.positionIdNext;
            // increment for next position
            cache.state.positionIdNext += 1;
            // set tick bounds on position
            cache.position.lower = params.lower;
            cache.position.upper = params.upper;
            cache.owner = params.to;
        }
        // set cache based on bounds
        cache.priceLower = ConstantProduct.getPriceAtTick(
            cache.position.lower,
            cache.constants
        );
        cache.priceUpper = ConstantProduct.getPriceAtTick(
            cache.position.upper,
            cache.constants
        );

        // validate input amounts
        (params, cache) = RangePositions.validate(params, cache);

        // save changes to storage before transfer in
        save(positions, globalState, cache, params.positionId);
        cache.amount0 -= params.amount0.toInt128();
        cache.amount1 -= params.amount1.toInt128();

        emit Mint(
            msg.sender,
            cache.owner,
            cache.position.lower,
            cache.position.upper,
            cache.liquidityMinted.toUint128(),
            params.amount0,
            params.amount1
        );

        emit MintRange(
            cache.owner,
            cache.position.lower,
            cache.position.upper,
            params.positionId,
            cache.liquidityMinted.toUint128(),
            -(cache.amount0 + cache.feesAccrued0), /// @dev - emit token0 balance delta
            -(cache.amount1 + cache.feesAccrued1) /// @dev - emit token1 balance delta
        );

        // update position with latest fees accrued
        cache = RangePositions.add(ticks, samples, tickMap, cache, params);

        // save changes to storage before transfer out
        save(positions, globalState, cache, params.positionId);

        // transfer positive amounts back to user
        if (cache.feesAccrued0 > 0 || cache.feesAccrued1 > 0)
            CollectLib.range(
                cache.position,
                cache.constants,
                cache.owner,
                cache.owner,
                cache.feesAccrued0,
                cache.feesAccrued1
            );

        // check starting balances
        Balances memory startBalance;
        if (cache.amount0 < 0) startBalance.amount0 = balance0(cache);
        if (cache.amount1 < 0) startBalance.amount1 = balance1(cache);

        // execute mint range callback
        ILimitPoolMintRangeCallback(msg.sender).limitPoolMintRangeCallback(
            cache.amount0,
            cache.amount1,
            params.callbackData
        );

        // check balance after callback
        if (cache.amount0 < 0)
            if (
                balance0(cache) <
                startBalance.amount0 + (-cache.amount0).toUint128()
            ) require(false, 'MintInputAmount0TooLow()');
        if (cache.amount1 < 0)
            if (
                balance1(cache) <
                startBalance.amount1 + (-cache.amount1).toUint128()
            ) require(false, 'MintInputAmount1TooLow()');

        return (
            cache.amount0 + cache.feesAccrued0,
            cache.amount1 + cache.feesAccrued1
        );
    }

    function save(
        mapping(uint256 => RangePoolStructs.RangePosition) storage positions,
        PoolsharkStructs.GlobalState storage globalState,
        RangePoolStructs.MintRangeCache memory cache,
        uint32 positionId
    ) internal {
        positions[positionId] = cache.position;
        globalState.pool = cache.state.pool;
        globalState.liquidityGlobal = cache.state.liquidityGlobal;
        globalState.positionIdNext = cache.state.positionIdNext;
    }

    function balance0(RangePoolStructs.MintRangeCache memory cache)
        private
        view
        returns (uint256)
    {
        (bool success, bytes memory data) = (cache.constants.token0).staticcall(
            abi.encodeWithSelector(
                IERC20Minimal.balanceOf.selector,
                address(this)
            )
        );
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }

    function balance1(RangePoolStructs.MintRangeCache memory cache)
        private
        view
        returns (uint256)
    {
        (bool success, bytes memory data) = (cache.constants.token1).staticcall(
            abi.encodeWithSelector(
                IERC20Minimal.balanceOf.selector,
                address(this)
            )
        );
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }
}

File 46 of 57 : SnapshotRangeCall.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../../interfaces/structs/LimitPoolStructs.sol';
import '../RangePositions.sol';
import '../../utils/Collect.sol';

library SnapshotRangeCall {
    uint8 private constant _ENTERED = 2;

    event Burn(
        address indexed recipient,
        int24 lower,
        int24 upper,
        uint256 indexed tokenId,
        uint128 liquidityBurned,
        uint128 amount0,
        uint128 amount1
    );

    function perform(
        mapping(uint256 => RangePoolStructs.RangePosition) storage positions,
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        uint32 positionId
    )
        external
        view
        returns (
            int56,
            uint160,
            uint128,
            uint128
        )
    {
        if (state.unlocked == _ENTERED)
            require(false, 'ReentrancyGuardReadOnlyReentrantCall()');
        return
            RangePositions.snapshot(
                positions,
                ticks,
                state,
                constants,
                positionId
            );
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/IPool.sol';
import '../../interfaces/IPositionERC1155.sol';
import '../../interfaces/structs/RangePoolStructs.sol';
import '../math/ConstantProduct.sol';
import './math/FeeMath.sol';
import '../math/OverflowMath.sol';
import '../utils/SafeCast.sol';
import './RangeTicks.sol';
import '../Samples.sol';

/// @notice Position management library for ranged liquidity.
library RangePositions {
    using SafeCast for uint256;
    using SafeCast for uint128;
    using SafeCast for int256;
    using SafeCast for int128;

    uint256 internal constant Q96 = 0x1000000000000000000000000;
    uint256 internal constant Q128 = 0x100000000000000000000000000000000;

    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    event BurnRange(
        address indexed recipient,
        uint256 indexed positionId,
        uint128 liquidityBurned,
        int128 amount0,
        int128 amount1
    );

    event CompoundRange(uint32 indexed positionId, uint128 liquidityCompounded);

    function validate(
        RangePoolStructs.MintRangeParams memory params,
        RangePoolStructs.MintRangeCache memory cache
    )
        internal
        pure
        returns (
            RangePoolStructs.MintRangeParams memory,
            RangePoolStructs.MintRangeCache memory
        )
    {
        cache.liquidityMinted = ConstantProduct.getLiquidityForAmounts(
            cache.priceLower,
            cache.priceUpper,
            cache.state.pool.price,
            params.amount1,
            params.amount0
        );
        if (cache.liquidityMinted == 0)
            require(false, 'NoLiquidityBeingAdded()');
        (params.amount0, params.amount1) = ConstantProduct
            .getAmountsForLiquidity(
                cache.priceLower,
                cache.priceUpper,
                cache.state.pool.price,
                cache.liquidityMinted,
                true
            );
        if (
            cache.state.liquidityGlobal + cache.liquidityMinted >
            uint128(type(int128).max)
        ) require(false, 'LiquidityOverflow()');

        return (params, cache);
    }

    function add(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage tickMap,
        RangePoolStructs.MintRangeCache memory cache,
        RangePoolStructs.MintRangeParams memory params
    ) internal returns (RangePoolStructs.MintRangeCache memory) {
        if (params.amount0 == 0 && params.amount1 == 0) return cache;

        cache.state = RangeTicks.insert(
            ticks,
            samples,
            tickMap,
            cache.state,
            cache.constants,
            cache.position.lower,
            cache.position.upper,
            cache.liquidityMinted.toUint128()
        );
        (
            cache.position.feeGrowthInside0Last,
            cache.position.feeGrowthInside1Last
        ) = rangeFeeGrowth(
            ticks[cache.position.lower].range,
            ticks[cache.position.upper].range,
            cache.state,
            cache.position.lower,
            cache.position.upper
        );
        if (cache.position.liquidity == 0) {
            IPositionERC1155(cache.constants.poolToken).mint(
                params.to,
                params.positionId,
                1,
                cache.constants
            );
        }
        cache.position.liquidity += uint128(cache.liquidityMinted);
        return cache;
    }

    function remove(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage tickMap,
        RangePoolStructs.BurnRangeParams memory params,
        RangePoolStructs.BurnRangeCache memory cache
    ) internal returns (RangePoolStructs.BurnRangeCache memory) {
        cache.priceLower = ConstantProduct.getPriceAtTick(
            cache.position.lower,
            cache.constants
        );
        cache.priceUpper = ConstantProduct.getPriceAtTick(
            cache.position.upper,
            cache.constants
        );
        cache.liquidityBurned = _convert(
            cache.position.liquidity,
            params.burnPercent
        );
        if (cache.liquidityBurned == 0) {
            return cache;
        }
        if (cache.liquidityBurned > cache.position.liquidity)
            require(false, 'NotEnoughPositionLiquidity()');
        {
            uint128 amount0Removed;
            uint128 amount1Removed;
            (amount0Removed, amount1Removed) = ConstantProduct
                .getAmountsForLiquidity(
                    cache.priceLower,
                    cache.priceUpper,
                    cache.state.pool.price,
                    cache.liquidityBurned,
                    false
                );
            cache.amount0 += amount0Removed.toInt128();
            cache.amount1 += amount1Removed.toInt128();
            cache.position.liquidity -= cache.liquidityBurned.toUint128();
        }
        cache.state = RangeTicks.remove(
            ticks,
            samples,
            tickMap,
            cache.state,
            cache.constants,
            cache.position.lower,
            cache.position.upper,
            uint128(cache.liquidityBurned)
        );

        // emit standard event
        emit Burn(
            msg.sender,
            cache.position.lower,
            cache.position.upper,
            uint128(cache.liquidityBurned),
            cache.amount0.toUint128(),
            cache.amount1.toUint128()
        );

        // emit custom event
        emit BurnRange(
            params.to,
            params.positionId,
            uint128(cache.liquidityBurned),
            cache.amount0,
            cache.amount1
        );

        // clear position bounds
        if (cache.position.liquidity == 0) {
            cache.position.lower = 0;
            cache.position.upper = 0;
        }

        return cache;
    }

    function compound(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage tickMap,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        RangePoolStructs.RangePosition memory position,
        RangePoolStructs.CompoundRangeParams memory params
    )
        internal
        returns (
            RangePoolStructs.RangePosition memory,
            PoolsharkStructs.GlobalState memory,
            int128,
            int128
        )
    {
        // price tells you the ratio so you need to swap into the correct ratio and add liquidity
        uint256 liquidityAmount = ConstantProduct.getLiquidityForAmounts(
            params.priceLower,
            params.priceUpper,
            state.pool.price,
            params.amount1,
            params.amount0
        );
        if (liquidityAmount > 0) {
            state = RangeTicks.insert(
                ticks,
                samples,
                tickMap,
                state,
                constants,
                position.lower,
                position.upper,
                uint128(liquidityAmount)
            );
            uint256 amount0;
            uint256 amount1;
            (amount0, amount1) = ConstantProduct.getAmountsForLiquidity(
                params.priceLower,
                params.priceUpper,
                state.pool.price,
                liquidityAmount,
                true
            );
            params.amount0 -= (amount0 <= params.amount0)
                ? uint128(amount0)
                : params.amount0;
            params.amount1 -= (amount1 <= params.amount1)
                ? uint128(amount1)
                : params.amount1;
            position.liquidity += uint128(liquidityAmount);
        }
        emit Mint(
            msg.sender,
            msg.sender,
            position.lower,
            position.upper,
            liquidityAmount.toUint128(),
            0,
            0
        );
        emit CompoundRange(params.positionId, uint128(liquidityAmount));
        return (
            position,
            state,
            params.amount0.toInt128(),
            params.amount1.toInt128()
        );
    }

    function update(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        RangePoolStructs.RangePosition memory position,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        RangePoolStructs.UpdateParams memory params
    )
        internal
        returns (
            RangePoolStructs.RangePosition memory,
            int128,
            int128
        )
    {
        RangePoolStructs.RangePositionCache memory cache;
        /// @dev - only true if burn call
        if (params.burnPercent > 0) {
            cache.liquidityAmount = _convert(
                position.liquidity,
                params.burnPercent
            );
            if (position.liquidity == cache.liquidityAmount)
                IPositionERC1155(constants.poolToken).burn(
                    msg.sender,
                    params.positionId,
                    1,
                    constants
                );
        }

        (uint256 rangeFeeGrowth0, uint256 rangeFeeGrowth1) = rangeFeeGrowth(
            ticks[position.lower].range,
            ticks[position.upper].range,
            state,
            position.lower,
            position.upper
        );

        int128 amount0Fees = OverflowMath
            .mulDiv(
                rangeFeeGrowth0 - position.feeGrowthInside0Last,
                uint256(position.liquidity),
                Q128
            )
            .toInt256()
            .toInt128();

        int128 amount1Fees = OverflowMath
            .mulDiv(
                rangeFeeGrowth1 - position.feeGrowthInside1Last,
                position.liquidity,
                Q128
            )
            .toInt256()
            .toInt128();

        position.feeGrowthInside0Last = rangeFeeGrowth0;
        position.feeGrowthInside1Last = rangeFeeGrowth1;

        return (position, amount0Fees, amount1Fees);
    }

    function rangeFeeGrowth(
        PoolsharkStructs.RangeTick memory lowerTick,
        PoolsharkStructs.RangeTick memory upperTick,
        PoolsharkStructs.GlobalState memory state,
        int24 lower,
        int24 upper
    )
        internal
        pure
        returns (uint256 feeGrowthInside0, uint256 feeGrowthInside1)
    {
        uint256 feeGrowthGlobal0 = state.pool.feeGrowthGlobal0;
        uint256 feeGrowthGlobal1 = state.pool.feeGrowthGlobal1;

        uint256 feeGrowthBelow0;
        uint256 feeGrowthBelow1;
        if (state.pool.tickAtPrice >= lower) {
            feeGrowthBelow0 = lowerTick.feeGrowthOutside0;
            feeGrowthBelow1 = lowerTick.feeGrowthOutside1;
        } else {
            feeGrowthBelow0 = feeGrowthGlobal0 - lowerTick.feeGrowthOutside0;
            feeGrowthBelow1 = feeGrowthGlobal1 - lowerTick.feeGrowthOutside1;
        }

        uint256 feeGrowthAbove0;
        uint256 feeGrowthAbove1;
        if (state.pool.tickAtPrice < upper) {
            feeGrowthAbove0 = upperTick.feeGrowthOutside0;
            feeGrowthAbove1 = upperTick.feeGrowthOutside1;
        } else {
            feeGrowthAbove0 = feeGrowthGlobal0 - upperTick.feeGrowthOutside0;
            feeGrowthAbove1 = feeGrowthGlobal1 - upperTick.feeGrowthOutside1;
        }
        feeGrowthInside0 = feeGrowthGlobal0 - feeGrowthBelow0 - feeGrowthAbove0;
        feeGrowthInside1 = feeGrowthGlobal1 - feeGrowthBelow1 - feeGrowthAbove1;
    }

    function snapshot(
        mapping(uint256 => RangePoolStructs.RangePosition) storage positions,
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        uint32 positionId
    )
        internal
        view
        returns (
            int56 tickSecondsAccum,
            uint160 secondsPerLiquidityAccum,
            uint128 feesOwed0,
            uint128 feesOwed1
        )
    {
        RangePoolStructs.SnapshotRangeCache memory cache;
        cache.position = positions[positionId];

        // early return if position empty
        if (cache.position.liquidity == 0) return (0, 0, 0, 0);

        cache.price = state.pool.price;
        cache.liquidity = state.pool.liquidity;
        cache.samples = state.pool.samples;

        // grab lower tick
        PoolsharkStructs.RangeTick memory tickLower = ticks[
            cache.position.lower
        ].range;

        // grab upper tick
        PoolsharkStructs.RangeTick memory tickUpper = ticks[
            cache.position.upper
        ].range;

        cache.tickSecondsAccumLower = tickLower.tickSecondsAccumOutside;
        cache.secondsPerLiquidityAccumLower = tickLower
            .secondsPerLiquidityAccumOutside;

        // if both have never been crossed into return 0
        cache.tickSecondsAccumUpper = tickUpper.tickSecondsAccumOutside;
        cache.secondsPerLiquidityAccumUpper = tickUpper
            .secondsPerLiquidityAccumOutside;
        cache.constants = constants;

        (uint256 rangeFeeGrowth0, uint256 rangeFeeGrowth1) = rangeFeeGrowth(
            tickLower,
            tickUpper,
            state,
            cache.position.lower,
            cache.position.upper
        );

        // calcuate fees earned
        cache.amount0 += uint128(
            OverflowMath.mulDiv(
                rangeFeeGrowth0 - cache.position.feeGrowthInside0Last,
                cache.position.liquidity,
                Q128
            )
        );
        cache.amount1 += uint128(
            OverflowMath.mulDiv(
                rangeFeeGrowth1 - cache.position.feeGrowthInside1Last,
                cache.position.liquidity,
                Q128
            )
        );

        cache.tick = state.pool.tickAtPrice;

        if (cache.tick < cache.position.lower) {
            // lower accum values are greater
            return (
                cache.tickSecondsAccumLower - cache.tickSecondsAccumUpper,
                cache.secondsPerLiquidityAccumLower -
                    cache.secondsPerLiquidityAccumUpper,
                cache.amount0,
                cache.amount1
            );
        } else if (cache.position.upper >= cache.tick) {
            // grab current sample
            cache.blockTimestamp = uint32(block.timestamp);
            (cache.tickSecondsAccum, cache.secondsPerLiquidityAccum) = Samples
                .getSingle(
                    IRangePool(address(this)),
                    RangePoolStructs.SampleParams(
                        cache.samples.index,
                        cache.samples.count,
                        uint32(block.timestamp),
                        new uint32[](2),
                        cache.tick,
                        cache.liquidity,
                        cache.constants
                    ),
                    0
                );
            return (
                cache.tickSecondsAccum -
                    cache.tickSecondsAccumLower -
                    cache.tickSecondsAccumUpper,
                cache.secondsPerLiquidityAccum -
                    cache.secondsPerLiquidityAccumLower -
                    cache.secondsPerLiquidityAccumUpper,
                cache.amount0,
                cache.amount1
            );
        } else {
            // upper accum values are greater
            return (
                cache.tickSecondsAccumUpper - cache.tickSecondsAccumLower,
                cache.secondsPerLiquidityAccumUpper -
                    cache.secondsPerLiquidityAccumLower,
                cache.amount0,
                cache.amount1
            );
        }
    }

    function _convert(uint128 liquidity, uint128 percent)
        internal
        pure
        returns (uint128)
    {
        // convert percentage to liquidity amount
        if (percent > 1e38) percent = 1e38;
        if (liquidity == 0 && percent > 0) require(false, 'PositionNotFound()');
        return uint128((uint256(liquidity) * uint256(percent)) / 1e38);
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../../interfaces/structs/PoolsharkStructs.sol';
import '../../interfaces/structs/RangePoolStructs.sol';
import '../../interfaces/range/IRangePoolFactory.sol';
import '../../interfaces/range/IRangePool.sol';
import './math/FeeMath.sol';
import './RangePositions.sol';
import '../math/OverflowMath.sol';
import '../math/ConstantProduct.sol';
import '../TickMap.sol';
import '../Samples.sol';

/// @notice Tick management library for range pools
library RangeTicks {
    event SyncRangeTick(
        uint200 feeGrowthOutside0,
        uint200 feeGrowthOutside1,
        int24 tick
    );

    uint256 internal constant Q96 = 0x1000000000000000000000000;
    uint256 internal constant Q128 = 0x100000000000000000000000000000000;

    function validate(
        int24 lower,
        int24 upper,
        int16 tickSpacing
    ) internal pure {
        if (lower % tickSpacing != 0) require(false, 'InvalidLowerTick()');
        if (lower < ConstantProduct.minTick(tickSpacing))
            require(false, 'InvalidLowerTick()');
        if (upper % tickSpacing != 0) require(false, 'InvalidUpperTick()');
        if (upper > ConstantProduct.maxTick(tickSpacing))
            require(false, 'InvalidUpperTick()');
        if (lower >= upper) require(false, 'InvalidPositionBounds()');
    }

    function insert(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        int24 lower,
        int24 upper,
        uint128 amount
    ) internal returns (PoolsharkStructs.GlobalState memory) {
        // get tick at price
        int24 tickAtPrice = state.pool.tickAtPrice;

        if (TickMap.set(tickMap, lower, constants.tickSpacing)) {
            ticks[lower].range.liquidityDelta += int128(amount);
            ticks[lower].range.liquidityAbsolute += amount;
        } else {
            if (lower <= tickAtPrice) {
                (
                    int56 tickSecondsAccum,
                    uint160 secondsPerLiquidityAccum
                ) = Samples.getSingle(
                        IRangePool(address(this)),
                        RangePoolStructs.SampleParams(
                            state.pool.samples.index,
                            state.pool.samples.count,
                            uint32(block.timestamp),
                            new uint32[](2),
                            state.pool.tickAtPrice,
                            state.pool.liquidity,
                            constants
                        ),
                        0
                    );
                ticks[lower].range = PoolsharkStructs.RangeTick(
                    state.pool.feeGrowthGlobal0,
                    state.pool.feeGrowthGlobal1,
                    secondsPerLiquidityAccum,
                    tickSecondsAccum,
                    int128(amount), // liquidityDelta
                    amount // liquidityAbsolute
                );
                emit SyncRangeTick(
                    state.pool.feeGrowthGlobal0,
                    state.pool.feeGrowthGlobal1,
                    lower
                );
            } else {
                ticks[lower].range.liquidityDelta = int128(amount);
                ticks[lower].range.liquidityAbsolute += amount;
            }
        }
        if (TickMap.set(tickMap, upper, constants.tickSpacing)) {
            ticks[upper].range.liquidityDelta -= int128(amount);
            ticks[upper].range.liquidityAbsolute += amount;
        } else {
            if (upper <= tickAtPrice) {
                (
                    int56 tickSecondsAccum,
                    uint160 secondsPerLiquidityAccum
                ) = Samples.getSingle(
                        IRangePool(address(this)),
                        RangePoolStructs.SampleParams(
                            state.pool.samples.index,
                            state.pool.samples.count,
                            uint32(block.timestamp),
                            new uint32[](2),
                            state.pool.tickAtPrice,
                            state.pool.liquidity,
                            constants
                        ),
                        0
                    );
                ticks[upper].range = PoolsharkStructs.RangeTick(
                    state.pool.feeGrowthGlobal0,
                    state.pool.feeGrowthGlobal1,
                    secondsPerLiquidityAccum,
                    tickSecondsAccum,
                    -int128(amount),
                    amount
                );
                emit SyncRangeTick(
                    state.pool.feeGrowthGlobal0,
                    state.pool.feeGrowthGlobal1,
                    upper
                );
            } else {
                ticks[upper].range.liquidityDelta = -int128(amount);
                ticks[upper].range.liquidityAbsolute = amount;
            }
        }
        if (tickAtPrice >= lower && tickAtPrice < upper) {
            // write an oracle entry
            (state.pool.samples.index, state.pool.samples.count) = Samples.save(
                samples,
                state.pool.samples,
                state.pool.liquidity,
                state.pool.tickAtPrice
            );
            // update pool liquidity
            state.pool.liquidity += amount;
        }
        // update global liquidity
        state.liquidityGlobal += amount;

        return state;
    }

    function remove(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage tickMap,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        int24 lower,
        int24 upper,
        uint128 amount
    ) internal returns (PoolsharkStructs.GlobalState memory) {
        validate(lower, upper, constants.tickSpacing);
        //check for amount to overflow liquidity delta & global
        if (amount == 0) return state;
        if (amount > uint128(type(int128).max))
            require(false, 'LiquidityUnderflow()');
        if (amount > state.liquidityGlobal)
            require(false, 'LiquidityUnderflow()');

        // get pool tick at price
        int24 tickAtPrice = state.pool.tickAtPrice;

        // update lower liquidity values
        PoolsharkStructs.RangeTick memory tickLower = ticks[lower].range;
        unchecked {
            tickLower.liquidityDelta -= int128(amount);
            tickLower.liquidityAbsolute -= amount;
        }
        ticks[lower].range = tickLower;
        // try to clear tick if possible
        clear(ticks, constants, tickMap, lower);

        // update upper liquidity values
        PoolsharkStructs.RangeTick memory tickUpper = ticks[upper].range;
        unchecked {
            tickUpper.liquidityDelta += int128(amount);
            tickUpper.liquidityAbsolute -= amount;
        }
        ticks[upper].range = tickUpper;
        // try to clear tick if possible
        clear(ticks, constants, tickMap, upper);

        if (tickAtPrice >= lower && tickAtPrice < upper) {
            // write an oracle entry
            (state.pool.samples.index, state.pool.samples.count) = Samples.save(
                samples,
                state.pool.samples,
                state.pool.liquidity,
                tickAtPrice
            );
            state.pool.liquidity -= amount;
        }
        state.liquidityGlobal -= amount;

        return state;
    }

    function clear(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.LimitImmutables memory constants,
        PoolsharkStructs.TickMap storage tickMap,
        int24 tickToClear
    ) internal {
        if (_empty(ticks[tickToClear])) {
            if (
                tickToClear != ConstantProduct.maxTick(constants.tickSpacing) &&
                tickToClear != ConstantProduct.minTick(constants.tickSpacing)
            ) {
                ticks[tickToClear].range = PoolsharkStructs.RangeTick(
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                );
                TickMap.unset(tickMap, tickToClear, constants.tickSpacing);
            }
        }
    }

    function _empty(LimitPoolStructs.Tick memory tick)
        internal
        pure
        returns (bool)
    {
        return tick.range.liquidityAbsolute == 0;
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import './math/ConstantProduct.sol';
import './utils/SafeCast.sol';
import '../interfaces/range/IRangePool.sol';
import '../interfaces/structs/RangePoolStructs.sol';

library Samples {
    using SafeCast for uint256;

    uint8 internal constant TIME_DELTA_MAX = 6;

    event SampleRecorded(
        int56 tickSecondsAccum,
        uint160 secondsPerLiquidityAccum
    );

    event SampleCountIncreased(uint16 newSampleCountMax);

    function initialize(
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.RangePoolState memory state
    ) internal returns (PoolsharkStructs.RangePoolState memory) {
        samples[0] = PoolsharkStructs.Sample({
            blockTimestamp: uint32(block.timestamp),
            tickSecondsAccum: 0,
            secondsPerLiquidityAccum: 0
        });

        state.samples.count = 1;
        state.samples.countMax = 5;

        return state;
        /// @dev - TWAP length of 5 is safer for oracle manipulation
    }

    function save(
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.SampleState memory sampleState,
        uint128 startLiquidity, /// @dev - liquidity from start of block
        int24 tick
    ) internal returns (uint16 sampleIndexNew, uint16 sampleLengthNew) {
        // grab the latest sample
        RangePoolStructs.Sample memory newSample = samples[sampleState.index];

        // early return if timestamp has not advanced 2 seconds
        if (newSample.blockTimestamp + 2 > uint32(block.timestamp))
            return (sampleState.index, sampleState.count);

        if (
            sampleState.countMax > sampleState.count &&
            sampleState.index == (sampleState.count - 1)
        ) {
            // increase sampleLengthNew if old size exceeded
            sampleLengthNew = sampleState.count + 1;
        } else {
            sampleLengthNew = sampleState.count;
        }
        sampleIndexNew = (sampleState.index + 1) % sampleLengthNew;
        samples[sampleIndexNew] = _build(
            newSample,
            uint32(block.timestamp),
            tick,
            startLiquidity
        );

        emit SampleRecorded(
            samples[sampleIndexNew].tickSecondsAccum,
            samples[sampleIndexNew].secondsPerLiquidityAccum
        );
    }

    function expand(
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.RangePoolState storage pool,
        uint16 newSampleCountMax
    ) internal {
        if (newSampleCountMax <= pool.samples.countMax) return;
        for (uint16 i = pool.samples.countMax; i < newSampleCountMax; i++) {
            samples[i].blockTimestamp = 1;
        }
        pool.samples.countMax = newSampleCountMax;
        emit SampleCountIncreased(newSampleCountMax);
    }

    function get(address pool, RangePoolStructs.SampleParams memory params)
        internal
        view
        returns (
            int56[] memory tickSecondsAccum,
            uint160[] memory secondsPerLiquidityAccum,
            uint160 averagePrice,
            uint128 averageLiquidity,
            int24 averageTick
        )
    {
        if (params.sampleLength == 0) require(false, 'InvalidSampleLength()');
        if (params.secondsAgo.length == 0)
            require(false, 'SecondsAgoArrayEmpty()');
        uint256 size = params.secondsAgo.length > 1
            ? params.secondsAgo.length
            : 2;
        uint32[] memory secondsAgo = new uint32[](size);
        if (params.secondsAgo.length == 1) {
            secondsAgo = new uint32[](2);
            secondsAgo[0] = params.secondsAgo[0];
            secondsAgo[1] = params.secondsAgo[0] + 2;
        } else secondsAgo = params.secondsAgo;

        if (secondsAgo[0] == secondsAgo[secondsAgo.length - 1])
            require(false, 'SecondsAgoArrayValuesEqual()');

        tickSecondsAccum = new int56[](secondsAgo.length);
        secondsPerLiquidityAccum = new uint160[](secondsAgo.length);

        for (uint256 i = 0; i < secondsAgo.length; i++) {
            (tickSecondsAccum[i], secondsPerLiquidityAccum[i]) = getSingle(
                IRangePool(pool),
                params,
                secondsAgo[i]
            );
        }
        if (secondsAgo[secondsAgo.length - 1] > secondsAgo[0]) {
            averageTick = int24(
                (tickSecondsAccum[0] -
                    tickSecondsAccum[secondsAgo.length - 1]) /
                    int32(secondsAgo[secondsAgo.length - 1] - secondsAgo[0])
            );
            averagePrice = ConstantProduct.getPriceAtTick(
                averageTick,
                params.constants
            );
            averageLiquidity = uint128(
                (secondsPerLiquidityAccum[0] -
                    secondsPerLiquidityAccum[secondsAgo.length - 1]) *
                    (secondsAgo[secondsAgo.length - 1] - secondsAgo[0])
            );
        } else {
            averageTick = int24(
                (tickSecondsAccum[secondsAgo.length - 1] -
                    tickSecondsAccum[0]) /
                    int32(secondsAgo[0] - secondsAgo[secondsAgo.length - 1])
            );
            averagePrice = ConstantProduct.getPriceAtTick(
                averageTick,
                params.constants
            );
            averageLiquidity = uint128(
                (secondsPerLiquidityAccum[secondsAgo.length - 1] -
                    secondsPerLiquidityAccum[0]) *
                    (secondsAgo[0] - secondsAgo[secondsAgo.length - 1])
            );
        }
    }

    function _poolSample(IRangePool pool, uint256 sampleIndex)
        internal
        view
        returns (RangePoolStructs.Sample memory)
    {
        (
            uint32 blockTimestamp,
            int56 tickSecondsAccum,
            uint160 liquidityPerSecondsAccum
        ) = IRangePool(pool).samples(sampleIndex);

        return
            PoolsharkStructs.Sample(
                blockTimestamp,
                tickSecondsAccum,
                liquidityPerSecondsAccum
            );
    }

    function getSingle(
        IRangePool pool,
        RangePoolStructs.SampleParams memory params,
        uint32 secondsAgo
    )
        internal
        view
        returns (int56 tickSecondsAccum, uint160 secondsPerLiquidityAccum)
    {
        RangePoolStructs.Sample memory latest = _poolSample(
            pool,
            params.sampleIndex
        );

        if (secondsAgo == 0) {
            // if 2 seconds have elapsed build new sample
            if (latest.blockTimestamp + 2 <= uint32(block.timestamp)) {
                latest = _build(
                    latest,
                    uint32(block.timestamp),
                    params.tick,
                    params.liquidity
                );
            }
            return (latest.tickSecondsAccum, latest.secondsPerLiquidityAccum);
        }

        uint32 targetTime = uint32(block.timestamp) - secondsAgo;

        // should be getting samples
        (
            RangePoolStructs.Sample memory firstSample,
            RangePoolStructs.Sample memory secondSample
        ) = _getAdjacentSamples(pool, latest, params, targetTime);

        if (targetTime == firstSample.blockTimestamp) {
            // first sample
            return (
                firstSample.tickSecondsAccum,
                firstSample.secondsPerLiquidityAccum
            );
        } else if (targetTime == secondSample.blockTimestamp) {
            // second sample
            return (
                secondSample.tickSecondsAccum,
                secondSample.secondsPerLiquidityAccum
            );
        } else {
            // average two samples
            int32 sampleTimeDelta = int32(
                secondSample.blockTimestamp - firstSample.blockTimestamp
            );
            int56 targetDelta = int56(
                int32(targetTime - firstSample.blockTimestamp)
            );
            return (
                firstSample.tickSecondsAccum +
                    ((secondSample.tickSecondsAccum -
                        firstSample.tickSecondsAccum) / sampleTimeDelta) *
                    targetDelta,
                firstSample.secondsPerLiquidityAccum +
                    uint160(
                        (uint256(
                            secondSample.secondsPerLiquidityAccum -
                                firstSample.secondsPerLiquidityAccum
                        ) * uint256(uint56(targetDelta))) /
                            uint32(sampleTimeDelta)
                    )
            );
        }
    }

    function getLatest(
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        uint256 liquidity
    )
        internal
        view
        returns (
            uint160 latestPrice,
            uint160 secondsPerLiquidityAccum,
            int56 tickSecondsAccum
        )
    {
        uint32 timeDelta = timeElapsed(constants);
        (tickSecondsAccum, secondsPerLiquidityAccum) = getSingle(
            IRangePool(address(this)),
            RangePoolStructs.SampleParams(
                state.pool.samples.index,
                state.pool.samples.count,
                uint32(block.timestamp),
                new uint32[](2),
                state.pool.tickAtPrice,
                liquidity.toUint128(),
                constants
            ),
            0
        );
        // grab older sample for dynamic fee calculation
        (int56 tickSecondsAccumBase, ) = Samples.getSingle(
            IRangePool(address(this)),
            RangePoolStructs.SampleParams(
                state.pool.samples.index,
                state.pool.samples.count,
                uint32(block.timestamp),
                new uint32[](2),
                state.pool.tickAtPrice,
                liquidity.toUint128(),
                constants
            ),
            timeDelta
        );

        latestPrice = calculateLatestPrice(
            tickSecondsAccum,
            tickSecondsAccumBase,
            timeDelta,
            TIME_DELTA_MAX,
            constants
        );
    }

    function calculateLatestPrice(
        int56 tickSecondsAccum,
        int56 tickSecondsAccumBase,
        uint32 timeDelta,
        uint32 timeDeltaMax,
        PoolsharkStructs.LimitImmutables memory constants
    ) private pure returns (uint160 averagePrice) {
        int56 tickSecondsAccumDiff = tickSecondsAccum - tickSecondsAccumBase;
        int24 averageTick;
        if (timeDelta == timeDeltaMax) {
            averageTick = int24(tickSecondsAccumDiff / int32(timeDelta));
        } else {
            averageTick = int24(tickSecondsAccum / int32(timeDelta));
        }
        averagePrice = ConstantProduct.getPriceAtTick(averageTick, constants);
    }

    function timeElapsed(PoolsharkStructs.LimitImmutables memory constants)
        private
        view
        returns (uint32)
    {
        return
            uint32(block.timestamp) - constants.genesisTime >= TIME_DELTA_MAX
                ? TIME_DELTA_MAX
                : uint32(block.timestamp - constants.genesisTime);
    }

    function _lte(uint32 timeA, uint32 timeB) private view returns (bool) {
        uint32 currentTime = uint32(block.timestamp);
        if (timeA <= currentTime && timeB <= currentTime) return timeA <= timeB;

        uint256 timeAOverflow = timeA;
        uint256 timeBOverflow = timeB;

        if (timeA <= currentTime) {
            timeAOverflow = timeA + 2**32;
        }
        if (timeB <= currentTime) {
            timeBOverflow = timeB + 2**32;
        }

        return timeAOverflow <= timeBOverflow;
    }

    function _build(
        RangePoolStructs.Sample memory newSample,
        uint32 blockTimestamp,
        int24 tick,
        uint128 liquidity
    ) internal pure returns (RangePoolStructs.Sample memory) {
        int56 timeDelta = int56(
            uint56(blockTimestamp - newSample.blockTimestamp)
        );

        return
            PoolsharkStructs.Sample({
                blockTimestamp: blockTimestamp,
                tickSecondsAccum: newSample.tickSecondsAccum +
                    int56(tick) *
                    int32(timeDelta),
                secondsPerLiquidityAccum: newSample.secondsPerLiquidityAccum +
                    ((uint160(uint56(timeDelta)) << 128) /
                        (liquidity > 0 ? liquidity : 1))
            });
    }

    function _binarySearch(
        IRangePool pool,
        uint32 targetTime,
        uint16 sampleIndex,
        uint16 sampleLength
    )
        private
        view
        returns (
            RangePoolStructs.Sample memory firstSample,
            RangePoolStructs.Sample memory secondSample
        )
    {
        uint256 oldIndex = (sampleIndex + 1) % sampleLength;
        uint256 newIndex = oldIndex + sampleLength - 1;
        uint256 index;
        while (true) {
            // start in the middle
            index = (oldIndex + newIndex) / 2;

            // get the first sample
            firstSample = _poolSample(pool, index % sampleLength);

            // if sample is uninitialized
            if (firstSample.blockTimestamp == 0) {
                // skip this index and continue
                oldIndex = index + 1;
                continue;
            }
            // else grab second sample
            secondSample = _poolSample(pool, (index + 1) % sampleLength);

            // check if target time within first and second sample
            bool targetAfterFirst = _lte(
                firstSample.blockTimestamp,
                targetTime
            );
            bool targetBeforeSecond = _lte(
                targetTime,
                secondSample.blockTimestamp
            );
            if (targetAfterFirst && targetBeforeSecond) break;
            if (!targetAfterFirst) newIndex = index - 1;
            else oldIndex = index + 1;
        }
    }

    function _getAdjacentSamples(
        IRangePool pool,
        RangePoolStructs.Sample memory firstSample,
        RangePoolStructs.SampleParams memory params,
        uint32 targetTime
    )
        private
        view
        returns (RangePoolStructs.Sample memory, RangePoolStructs.Sample memory)
    {
        if (_lte(firstSample.blockTimestamp, targetTime)) {
            if (firstSample.blockTimestamp == targetTime) {
                return (firstSample, PoolsharkStructs.Sample(0, 0, 0));
            } else {
                return (
                    firstSample,
                    _build(
                        firstSample,
                        targetTime,
                        params.tick,
                        params.liquidity
                    )
                );
            }
        }
        firstSample = _poolSample(
            pool,
            (params.sampleIndex + 1) % params.sampleLength
        );
        if (firstSample.blockTimestamp == 0) {
            firstSample = _poolSample(pool, 0);
        }
        if (!_lte(firstSample.blockTimestamp, targetTime))
            require(false, 'SampleLengthNotAvailable()');

        return
            _binarySearch(
                pool,
                targetTime,
                params.sampleIndex,
                params.sampleLength
            );
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import './math/ConstantProduct.sol';
import '../interfaces/structs/PoolsharkStructs.sol';

library TickMap {
    function get(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int24 tickSpacing
    ) internal view returns (bool exists) {
        (uint256 tickIndex, uint256 wordIndex, ) = getIndices(
            tick,
            tickSpacing
        );

        // check if bit is already set
        uint256 word = tickMap.ticks[wordIndex] | (1 << (tickIndex & 0xFF));
        if (word == tickMap.ticks[wordIndex]) {
            return true;
        }
        return false;
    }

    function set(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int24 tickSpacing
    ) internal returns (bool exists) {
        (uint256 tickIndex, uint256 wordIndex, uint256 blockIndex) = getIndices(
            tick,
            tickSpacing
        );

        // check if bit is already set
        uint256 word = tickMap.ticks[wordIndex] | (1 << (tickIndex & 0xFF));
        if (word == tickMap.ticks[wordIndex]) {
            return true;
        }

        tickMap.ticks[wordIndex] = word;
        tickMap.words[blockIndex] |= 1 << (wordIndex & 0xFF); // same as modulus 255
        tickMap.blocks |= 1 << blockIndex;
        return false;
    }

    function unset(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int16 tickSpacing
    ) internal {
        (uint256 tickIndex, uint256 wordIndex, uint256 blockIndex) = getIndices(
            tick,
            tickSpacing
        );

        tickMap.ticks[wordIndex] &= ~(1 << (tickIndex & 0xFF));
        if (tickMap.ticks[wordIndex] == 0) {
            tickMap.words[blockIndex] &= ~(1 << (wordIndex & 0xFF));
            if (tickMap.words[blockIndex] == 0) {
                tickMap.blocks &= ~(1 << blockIndex);
            }
        }
    }

    function previous(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int16 tickSpacing,
        bool inclusive
    ) internal view returns (int24 previousTick) {
        unchecked {
            // rounds up to ensure relative position
            if (tick % (tickSpacing / 2) != 0 || inclusive) {
                if (
                    tick <
                    (ConstantProduct.maxTick(tickSpacing) - tickSpacing / 2)
                ) {
                    /// @dev - ensures we cross when tick >= 0
                    if (tick >= 0) {
                        tick += tickSpacing / 2;
                    } else if (inclusive && tick % (tickSpacing / 2) == 0) {
                        /// @dev - ensures we cross when tick == tickAtPrice
                        tick += tickSpacing / 2;
                    }
                }
            }
            (
                uint256 tickIndex,
                uint256 wordIndex,
                uint256 blockIndex
            ) = getIndices(tick, tickSpacing);

            uint256 word = tickMap.ticks[wordIndex] &
                ((1 << (tickIndex & 0xFF)) - 1);
            if (word == 0) {
                uint256 block_ = tickMap.words[blockIndex] &
                    ((1 << (wordIndex & 0xFF)) - 1);
                if (block_ == 0) {
                    uint256 blockMap = tickMap.blocks & ((1 << blockIndex) - 1);
                    if (blockMap == 0) return tick;

                    blockIndex = _msb(blockMap);
                    block_ = tickMap.words[blockIndex];
                }
                wordIndex = (blockIndex << 8) | _msb(block_);
                word = tickMap.ticks[wordIndex];
            }
            previousTick = _tick((wordIndex << 8) | _msb(word), tickSpacing);
        }
    }

    function next(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int16 tickSpacing,
        bool inclusive
    ) internal view returns (int24 nextTick) {
        unchecked {
            /// @dev - handles tickAtPrice being past tickSpacing / 2
            if (inclusive && tick % tickSpacing != 0) {
                // e.g. tick is 5 we subtract 1 to look ahead at 5
                if (tick > 0 && (tick % tickSpacing <= (tickSpacing / 2)))
                    tick -= 1;
                    // e.g. tick is -5 we subtract 1 to look ahead at -5
                else if (tick < 0 && (tick % tickSpacing <= -(tickSpacing / 2)))
                    tick -= 1;
                    // e.g. tick = 7 and tickSpacing = 10 we sub 5 to look ahead at 5
                    // e.g. tick = -2 and tickSpacing = 10 we sub 5 to look ahead at -5
                else tick -= tickSpacing / 2;
            }
            /// @dev - handles negative ticks rounding up
            if (tick % (tickSpacing / 2) != 0) {
                if (tick < 0)
                    if (
                        tick >
                        (ConstantProduct.minTick(tickSpacing) + tickSpacing / 2)
                    ) tick -= tickSpacing / 2;
            }
            (
                uint256 tickIndex,
                uint256 wordIndex,
                uint256 blockIndex
            ) = getIndices(tick, tickSpacing);
            uint256 word;
            if ((tickIndex & 0xFF) != 255) {
                word =
                    tickMap.ticks[wordIndex] &
                    ~((1 << ((tickIndex & 0xFF) + 1)) - 1);
            }
            if (word == 0) {
                uint256 block_;
                if ((blockIndex & 0xFF) != 255) {
                    block_ =
                        tickMap.words[blockIndex] &
                        ~((1 << ((wordIndex & 0xFF) + 1)) - 1);
                }
                if (block_ == 0) {
                    uint256 blockMap = tickMap.blocks &
                        ~((1 << (blockIndex + 1)) - 1);
                    if (blockMap == 0) return tick;
                    blockIndex = _lsb(blockMap);
                    block_ = tickMap.words[blockIndex];
                }
                wordIndex = (blockIndex << 8) | _lsb(block_);
                word = tickMap.ticks[wordIndex];
            }
            nextTick = _tick((wordIndex << 8) | _lsb(word), tickSpacing);
        }
    }

    function previousTicksWithinWord(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int16 tickSpacing,
        int24 stopTick,
        int24[] memory previousTicks,
        uint16 ticksIncluded
    )
        internal
        view
        returns (
            int24[] memory,
            uint16,
            int24
        )
    {
        // rounds up to ensure relative position
        if (tick % (tickSpacing / 2) != 0) {
            if (
                tick < (ConstantProduct.maxTick(tickSpacing) - tickSpacing / 2)
            ) {
                /// @dev - ensures we cross when tick >= 0
                if (tick >= 0) {
                    tick += tickSpacing / 2;
                }
            }
        }
        LimitPoolStructs.TickMapLocals memory locals;
        (locals.tickIndex, locals.wordIndex, locals.blockIndex) = getIndices(
            tick,
            tickSpacing
        );
        locals.word =
            tickMap.ticks[locals.wordIndex] &
            ((1 << (locals.tickIndex & 0xFF)) - 1);
        while (locals.word != 0 && tick > stopTick) {
            // ticks left within word
            tick = _tick(
                (locals.wordIndex << 8) | _msb(locals.word),
                tickSpacing
            );
            previousTicks[ticksIncluded] = tick;
            unchecked {
                ++ticksIncluded;
            }
            (locals.tickIndex, , ) = getIndices(tick, tickSpacing);
            locals.word = locals.word & ((1 << (locals.tickIndex & 0xFF)) - 1);
        }
        // no ticks left within word
        // int24 firstTickNextWord =
        return (
            previousTicks,
            ticksIncluded,
            locals.wordIndex > 0
                ? _tick(
                    ((locals.wordIndex - 1) << 8) | _msb(1 << 255),
                    tickSpacing
                )
                : ConstantProduct.minTick(tickSpacing)
        );
    }

    function nextTicksWithinWord(
        PoolsharkStructs.TickMap storage tickMap,
        int24 tick,
        int16 tickSpacing,
        int24 stopTick,
        int24[] memory nextTicks,
        uint16 ticksIncluded
    )
        internal
        view
        returns (
            int24[] memory,
            uint16,
            int24
        )
    {
        /// @dev - handles negative ticks rounding up
        if (tick % (tickSpacing / 2) != 0) {
            if (tick < 0)
                if (
                    tick >
                    (ConstantProduct.minTick(tickSpacing) + tickSpacing / 2)
                ) tick -= tickSpacing / 2;
        }
        LimitPoolStructs.TickMapLocals memory locals;
        (locals.tickIndex, locals.wordIndex, locals.blockIndex) = getIndices(
            tick,
            tickSpacing
        );
        if ((locals.tickIndex & 0xFF) != 255) {
            locals.word =
                tickMap.ticks[locals.wordIndex] &
                ~((1 << ((locals.tickIndex & 0xFF) + 1)) - 1);
        }
        while (locals.word != 0 && tick < stopTick) {
            // ticks left within word
            tick = _tick(
                (locals.wordIndex << 8) | _lsb(locals.word),
                tickSpacing
            );
            nextTicks[ticksIncluded] = tick;
            unchecked {
                ++ticksIncluded;
            }
            (locals.tickIndex, , ) = getIndices(tick, tickSpacing);
            if ((locals.tickIndex & 0xFF) != 255) {
                locals.word =
                    locals.word &
                    ~((1 << ((locals.tickIndex & 0xFF) + 1)) - 1);
            }
        }
        // no ticks left within word
        return (
            nextTicks,
            ticksIncluded,
            _tick(((locals.wordIndex + 1) << 8) | _lsb(1), tickSpacing)
        );
    }

    function getIndices(int24 tick, int24 tickSpacing)
        public
        pure
        returns (
            uint256 tickIndex,
            uint256 wordIndex,
            uint256 blockIndex
        )
    {
        unchecked {
            if (tick > ConstantProduct.MAX_TICK)
                require(false, 'TickIndexOverflow()');
            if (tick < ConstantProduct.MIN_TICK)
                require(false, 'TickIndexUnderflow()');
            if (tick % (tickSpacing / 2) != 0)
                tick = round(tick, tickSpacing / 2);
            tickIndex = uint256(
                int256(
                    (round(tick, tickSpacing / 2) -
                        round(ConstantProduct.MIN_TICK, tickSpacing / 2)) /
                        (tickSpacing / 2)
                )
            );
            wordIndex = tickIndex >> 8; // 2^8 ticks per word
            blockIndex = tickIndex >> 16; // 2^8 words per block
            if (blockIndex > 255) require(false, 'BlockIndexOverflow()');
        }
    }

    function _tick(uint256 tickIndex, int24 tickSpacing)
        internal
        pure
        returns (int24 tick)
    {
        unchecked {
            if (
                tickIndex >
                uint24(round(ConstantProduct.MAX_TICK, tickSpacing) * 2) * 2
            ) require(false, 'TickIndexOverflow()');
            tick = int24(
                int256(tickIndex) *
                    (tickSpacing / 2) +
                    round(ConstantProduct.MIN_TICK, tickSpacing / 2)
            );
        }
    }

    function _msb(uint256 x) internal pure returns (uint8 r) {
        unchecked {
            assert(x > 0);
            if (x >= 0x100000000000000000000000000000000) {
                x >>= 128;
                r += 128;
            }
            if (x >= 0x10000000000000000) {
                x >>= 64;
                r += 64;
            }
            if (x >= 0x100000000) {
                x >>= 32;
                r += 32;
            }
            if (x >= 0x10000) {
                x >>= 16;
                r += 16;
            }
            if (x >= 0x100) {
                x >>= 8;
                r += 8;
            }
            if (x >= 0x10) {
                x >>= 4;
                r += 4;
            }
            if (x >= 0x4) {
                x >>= 2;
                r += 2;
            }
            if (x >= 0x2) r += 1;
        }
    }

    function _lsb(uint256 x) internal pure returns (uint8 r) {
        unchecked {
            assert(x > 0); // if x is 0 return 0
            r = 255;
            if (x & type(uint128).max > 0) {
                r -= 128;
            } else {
                x >>= 128;
            }
            if (x & type(uint64).max > 0) {
                r -= 64;
            } else {
                x >>= 64;
            }
            if (x & type(uint32).max > 0) {
                r -= 32;
            } else {
                x >>= 32;
            }
            if (x & type(uint16).max > 0) {
                r -= 16;
            } else {
                x >>= 16;
            }
            if (x & type(uint8).max > 0) {
                r -= 8;
            } else {
                x >>= 8;
            }
            if (x & 0xf > 0) {
                r -= 4;
            } else {
                x >>= 4;
            }
            if (x & 0x3 > 0) {
                r -= 2;
            } else {
                x >>= 2;
            }
            if (x & 0x1 > 0) r -= 1;
        }
    }

    function round(int24 tick, int24 tickSpacing)
        internal
        pure
        returns (int24 roundedTick)
    {
        return (tick / tickSpacing) * tickSpacing;
    }

    function roundHalf(
        int24 tick,
        PoolsharkStructs.LimitImmutables memory constants,
        uint256 price
    ) internal pure returns (int24 roundedTick, uint160 roundedTickPrice) {
        //pool.tickAtPrice -99.5
        //pool.tickAtPrice -100
        //-105
        //-95
        roundedTick = (tick / constants.tickSpacing) * constants.tickSpacing;
        roundedTickPrice = ConstantProduct.getPriceAtTick(
            roundedTick,
            constants
        );
        if (price == roundedTickPrice) return (roundedTick, roundedTickPrice);
        if (roundedTick > 0) {
            roundedTick += constants.tickSpacing / 2;
        } else if (roundedTick < 0) {
            if (roundedTickPrice < price)
                roundedTick += constants.tickSpacing / 2;
            else roundedTick -= constants.tickSpacing / 2;
        } else {
            if (price > roundedTickPrice) {
                roundedTick += constants.tickSpacing / 2;
            } else if (price < roundedTickPrice) {
                roundedTick -= constants.tickSpacing / 2;
            }
        }
    }

    function roundAhead(
        int24 tick,
        PoolsharkStructs.LimitImmutables memory constants,
        bool zeroForOne,
        uint256 price
    ) internal pure returns (int24 roundedTick) {
        roundedTick = (tick / constants.tickSpacing) * constants.tickSpacing;
        uint160 roundedTickPrice = ConstantProduct.getPriceAtTick(
            roundedTick,
            constants
        );
        if (price == roundedTickPrice) return roundedTick;
        if (zeroForOne) {
            // round up if positive
            if (roundedTick > 0 || (roundedTick == 0 && tick >= 0))
                roundedTick += constants.tickSpacing;
            else if (tick % constants.tickSpacing == 0) {
                // handle price at -99.5 and tickAtPrice == -100
                if (tick < 0 && roundedTickPrice < price) {
                    roundedTick += constants.tickSpacing;
                }
            }
        } else {
            // round down if negative
            if (roundedTick < 0 || (roundedTick == 0 && tick < 0))
                /// @dev - strictly less due to TickMath always rounding to lesser values
                roundedTick -= constants.tickSpacing;
        }
    }

    function roundBack(
        int24 tick,
        PoolsharkStructs.LimitImmutables memory constants,
        bool zeroForOne,
        uint256 price
    ) internal pure returns (int24 roundedTick) {
        roundedTick = (tick / constants.tickSpacing) * constants.tickSpacing;
        uint160 roundedTickPrice = ConstantProduct.getPriceAtTick(
            roundedTick,
            constants
        );
        if (price == roundedTickPrice) return roundedTick;
        if (zeroForOne) {
            // round down if negative
            if (roundedTick < 0 || (roundedTick == 0 && tick < 0))
                roundedTick -= constants.tickSpacing;
        } else {
            // round up if positive
            if (roundedTick > 0 || (roundedTick == 0 && tick >= 0))
                roundedTick += constants.tickSpacing;
            else if (tick % constants.tickSpacing == 0) {
                // handle price at -99.5 and tickAtPrice == -100
                if (tick < 0 && roundedTickPrice < price) {
                    roundedTick += constants.tickSpacing;
                }
            }
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import '../interfaces/structs/PoolsharkStructs.sol';
import '../base/events/LimitPoolEvents.sol';
import './range/math/FeeMath.sol';
import './math/OverflowMath.sol';
import './math/ConstantProduct.sol';
import './TickMap.sol';
import './utils/SafeCast.sol';
import './range/math/FeeMath.sol';
import './Samples.sol';
import './limit/EpochMap.sol';
import './limit/LimitTicks.sol';

library Ticks {
    using SafeCast for uint256;

    // cross flags
    uint8 internal constant RANGE_TICK = 2**0;
    uint8 internal constant LIMIT_TICK = 2**1;
    uint8 internal constant LIMIT_POOL = 2**2;

    // for Q64.96 numbers
    uint256 internal constant Q96 = 0x1000000000000000000000000;

    event Initialize(uint160 price, int24 tick);
    event InitializeLimit(
        int24 minTick,
        int24 maxTick,
        uint160 startPrice,
        int24 startTick
    );

    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 price,
        uint128 liquidity,
        int24 tickAtPrice
    );
    event SwapLimit(
        address indexed recipient,
        uint256 amountIn,
        uint256 amountOut,
        uint200 feeGrowthGlobal0,
        uint200 feeGrowthGlobal1,
        uint160 price,
        uint128 liquidity,
        uint128 feeAmount,
        int24 tickAtPrice,
        bool indexed zeroForOne,
        bool indexed exactIn
    );

    event SyncRangeTick(
        uint200 feeGrowthOutside0,
        uint200 feeGrowthOutside1,
        int24 tick
    );

    function initialize(
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.GlobalState memory state,
        PoolsharkStructs.LimitImmutables memory constants,
        uint160 startPrice
    ) external returns (PoolsharkStructs.GlobalState memory) {
        // state should only be initialized once
        if (state.pool0.price > 0) require(false, 'PoolAlreadyInitialized()');

        // initialize state
        state.epoch = 1;
        state.positionIdNext = 1;

        // check price bounds
        if (
            startPrice < constants.bounds.min ||
            startPrice >= constants.bounds.max
        ) require(false, 'StartPriceInvalid()');

        // initialize range ticks
        TickMap.set(
            rangeTickMap,
            ConstantProduct.minTick(constants.tickSpacing),
            constants.tickSpacing
        );
        TickMap.set(
            rangeTickMap,
            ConstantProduct.maxTick(constants.tickSpacing),
            constants.tickSpacing
        );

        // initialize limit ticks
        TickMap.set(
            limitTickMap,
            ConstantProduct.minTick(constants.tickSpacing),
            constants.tickSpacing
        );
        TickMap.set(
            limitTickMap,
            ConstantProduct.maxTick(constants.tickSpacing),
            constants.tickSpacing
        );

        // initialize price
        state.pool.price = startPrice;
        state.pool0.price = startPrice;
        state.pool1.price = startPrice;

        int24 startTick = ConstantProduct.getTickAtPrice(startPrice, constants);
        state.pool.tickAtPrice = startTick;
        state.pool0.tickAtPrice = startTick;
        state.pool1.tickAtPrice = startTick;

        // intialize samples
        state.pool = Samples.initialize(samples, state.pool);

        // emit standard event
        emit Initialize(
            startPrice,
            startTick
        );

        // emit custom event
        emit InitializeLimit(
            ConstantProduct.minTick(constants.tickSpacing),
            ConstantProduct.maxTick(constants.tickSpacing),
            startPrice,
            startTick
        );

        return state;
    }

    function swap(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        RangePoolStructs.Sample[65535] storage samples,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.SwapParams memory params,
        PoolsharkStructs.SwapCache memory cache
    ) external returns (PoolsharkStructs.SwapCache memory) {
        cache.price = cache.state.pool.price;
        cache.crossTick = cache.state.pool.tickAtPrice;

        // set initial cross state
        cache = _iterate(
            ticks,
            rangeTickMap,
            limitTickMap,
            cache,
            params.zeroForOne,
            true
        );

        uint128 startLiquidity = cache.state.pool.liquidity;

        // grab sample for accumulators
        cache = PoolsharkStructs.SwapCache({
            state: cache.state,
            constants: cache.constants,
            price: cache.price,
            liquidity: cache.liquidity,
            amountLeft: params.amount,
            input: 0,
            output: 0,
            crossPrice: cache.crossPrice,
            secondsPerLiquidityAccum: 0,
            feeAmount: 0,
            tickSecondsAccum: 0,
            tickSecondsAccumBase: 0,
            crossTick: cache.crossTick,
            crossStatus: cache.crossStatus,
            limitActive: cache.limitActive,
            exactIn: params.exactIn,
            cross: true,
            averagePrice: 0
        });
        // grab latest price and sample
        (
            cache.averagePrice,
            cache.secondsPerLiquidityAccum,
            cache.tickSecondsAccum
        ) = Samples.getLatest(
            cache.state,
            cache.constants,
            cache.state.pool.liquidity
        );

        // grab latest sample and store in cache for _cross
        while (cache.cross) {
            // handle price being at cross tick
            cache = _quoteSingle(cache, params.priceLimit, params.zeroForOne);
            if (cache.cross) {
                cache = _cross(
                    ticks,
                    rangeTickMap,
                    limitTickMap,
                    cache,
                    params
                );
            }
        }
    
        /// @dev - write oracle entry after start of block
        (
            cache.state.pool.samples.index,
            cache.state.pool.samples.count
        ) = Samples.save(
            samples,
            cache.state.pool.samples,
            startLiquidity,
            cache.state.pool.tickAtPrice
        );

        // pool liquidity should be updated along the way
        cache.state.pool.price = cache.price.toUint160();

        if (cache.price != cache.crossPrice) {
            cache.state.pool.tickAtPrice = ConstantProduct.getTickAtPrice(
                cache.price.toUint160(),
                cache.constants
            );
        } else {
            cache.state.pool.tickAtPrice = cache.crossTick;
        }
        if (cache.limitActive) {
            if (params.zeroForOne) {
                cache.state.pool1.price = cache.state.pool.price;
                cache.state.pool1.tickAtPrice = cache.state.pool.tickAtPrice;
            } else {
                cache.state.pool0.price = cache.state.pool.price;
                cache.state.pool0.tickAtPrice = cache.state.pool.tickAtPrice;
            }
        }

        // emit standard event
        emit Swap(
            msg.sender,
            params.to,
            params.zeroForOne ? int256(cache.input) : -int256(cache.output),
            params.zeroForOne ? -int256(cache.output) : int256(cache.input),
            cache.state.pool.price,
            cache.liquidity.toUint128(),
            cache.state.pool.tickAtPrice
        );

        // emit custom event
        emit SwapLimit(
            params.to,
            cache.input,
            cache.output,
            cache.state.pool.feeGrowthGlobal0,
            cache.state.pool.feeGrowthGlobal1,
            cache.price.toUint160(),
            cache.liquidity.toUint128(),
            cache.feeAmount,
            cache.state.pool.tickAtPrice,
            params.zeroForOne,
            params.exactIn
        );

        return cache;
    }

    function quote(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.QuoteParams memory params,
        PoolsharkStructs.SwapCache memory cache
    )
        internal
        view
        returns (
            uint256,
            uint256,
            uint160
        )
    {
        // start with range price
        cache.price = cache.state.pool.price;
        cache.crossTick = cache.state.pool.tickAtPrice;

        cache = _iterate(
            ticks,
            rangeTickMap,
            limitTickMap,
            cache,
            params.zeroForOne,
            true
        );

        // set crossTick/crossPrice based on the best between limit and range
        // grab sample for accumulators
        cache = PoolsharkStructs.SwapCache({
            state: cache.state,
            constants: cache.constants,
            price: cache.price,
            liquidity: cache.liquidity,
            amountLeft: params.amount,
            input: 0,
            output: 0,
            crossPrice: cache.crossPrice,
            secondsPerLiquidityAccum: 0,
            feeAmount: 0,
            tickSecondsAccum: 0,
            tickSecondsAccumBase: 0,
            crossTick: cache.crossTick,
            crossStatus: cache.crossStatus,
            limitActive: cache.limitActive,
            exactIn: params.exactIn,
            cross: true,
            averagePrice: 0
        });
        
        // grab latest price and sample
        (
            cache.averagePrice,
            cache.secondsPerLiquidityAccum,
            cache.tickSecondsAccum
        ) = Samples.getLatest(
            cache.state,
            cache.constants,
            cache.state.pool.liquidity
        );

        while (cache.cross) {
            cache = _quoteSingle(cache, params.priceLimit, params.zeroForOne);
            if (cache.cross) {
                cache = _pass(ticks, rangeTickMap, limitTickMap, cache, params);
            }
        }

        return (cache.input, cache.output, cache.price.toUint160());
    }

    function _quoteSingle(
        PoolsharkStructs.SwapCache memory cache,
        uint160 priceLimit,
        bool zeroForOne
    ) internal view returns (PoolsharkStructs.SwapCache memory) {
        if (
            (
                zeroForOne
                    ? priceLimit >= cache.price
                    : priceLimit <= cache.price
            ) ||
            (zeroForOne && cache.price == cache.constants.bounds.min) ||
            (!zeroForOne && cache.price == cache.constants.bounds.max) ||
            (cache.amountLeft == 0 && cache.liquidity > 0)
        ) {
            cache.cross = false;
            return cache;
        }
        uint256 nextPrice = cache.crossPrice;
        uint256 amountIn;
        uint256 amountOut;
        if (zeroForOne) {
            // Trading token 0 (x) for token 1 (y).
            // price  is decreasing.
            if (nextPrice < priceLimit) {
                nextPrice = priceLimit;
            }
            uint256 amountMax = cache.exactIn
                ? ConstantProduct.getDx(
                    cache.liquidity,
                    nextPrice,
                    cache.price,
                    true
                )
                : ConstantProduct.getDy(
                    cache.liquidity,
                    nextPrice,
                    cache.price,
                    false
                );
            if (cache.amountLeft < amountMax) {
                // calculate price after swap
                uint256 newPrice = ConstantProduct.getNewPrice(
                    cache.price,
                    cache.liquidity,
                    cache.amountLeft,
                    zeroForOne,
                    cache.exactIn
                );
                if (newPrice < nextPrice)
                    require(false, 'NextPriceExceeded()');
                if (cache.exactIn) {
                    amountIn = cache.amountLeft;
                    amountOut = ConstantProduct.getDy(
                        cache.liquidity,
                        newPrice,
                        cache.price,
                        false
                    );
                } else {
                    amountIn = ConstantProduct.getDx(
                        cache.liquidity,
                        newPrice,
                        cache.price,
                        true
                    );
                    amountOut = cache.amountLeft;
                }
                cache.amountLeft = 0;
                cache.cross = false;
                cache.price = uint160(newPrice);
            } else {
                if (cache.exactIn) {
                    amountIn = amountMax;
                    amountOut = ConstantProduct.getDy(
                        cache.liquidity,
                        nextPrice,
                        cache.price,
                        false
                    );
                } else {
                    amountIn = ConstantProduct.getDx(
                        cache.liquidity,
                        nextPrice,
                        cache.price,
                        true
                    );
                    amountOut = amountMax;
                }
                cache.amountLeft -= amountMax;
                if (nextPrice == cache.crossPrice) cache.cross = true;
                else cache.cross = false;
                cache.price = uint160(nextPrice);
            }
        } else {
            // Price is increasing.
            if (nextPrice > priceLimit) {
                nextPrice = priceLimit;
            }
            uint256 amountMax = cache.exactIn
                ? ConstantProduct.getDy(
                    cache.liquidity,
                    cache.price,
                    nextPrice,
                    true
                )
                : ConstantProduct.getDx(
                    cache.liquidity,
                    cache.price,
                    nextPrice,
                    false
                );
            if (cache.amountLeft < amountMax) {
                uint256 newPrice = ConstantProduct.getNewPrice(
                    cache.price,
                    cache.liquidity,
                    cache.amountLeft,
                    zeroForOne,
                    cache.exactIn
                );
                if (newPrice > nextPrice)
                    require(false, 'NextPriceExceeded()');
                if (cache.exactIn) {
                    amountIn = cache.amountLeft;
                    amountOut = ConstantProduct.getDx(
                        cache.liquidity,
                        cache.price,
                        newPrice,
                        false
                    );
                } else {
                    amountIn = ConstantProduct.getDy(
                        cache.liquidity,
                        cache.price,
                        newPrice,
                        true
                    );
                    amountOut = cache.amountLeft;
                }
                cache.amountLeft = 0;
                cache.cross = false;
                cache.price = uint160(newPrice);
            } else {
                if (cache.exactIn) {
                    amountIn = amountMax;
                    amountOut = ConstantProduct.getDx(
                        cache.liquidity,
                        cache.price,
                        nextPrice,
                        false
                    );
                } else {
                    amountIn = ConstantProduct.getDy(
                        cache.liquidity,
                        cache.price,
                        nextPrice,
                        true
                    );
                    amountOut = amountMax;
                }
                cache.amountLeft -= amountMax;
                if (nextPrice == cache.crossPrice) cache.cross = true;
                else cache.cross = false;
                cache.price = uint160(nextPrice);
            }
        }
        cache = FeeMath.calculate(cache, amountIn, amountOut, zeroForOne);
        return cache;
    }

    function _cross(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.SwapCache memory cache,
        PoolsharkStructs.SwapParams memory params
    ) internal returns (PoolsharkStructs.SwapCache memory) {
        // crossing range ticks
        if ((cache.crossStatus & RANGE_TICK) > 0) {
            // skip if crossing down and stopping at crossPrice
            if (
                !params.zeroForOne ||
                (cache.amountLeft > 0 && params.priceLimit < cache.crossPrice)
            ) {
                PoolsharkStructs.RangeTick memory crossTick = ticks[
                    cache.crossTick
                ].range;
                crossTick.feeGrowthOutside0 =
                    cache.state.pool.feeGrowthGlobal0 -
                    crossTick.feeGrowthOutside0;
                crossTick.feeGrowthOutside1 =
                    cache.state.pool.feeGrowthGlobal1 -
                    crossTick.feeGrowthOutside1;
                crossTick.tickSecondsAccumOutside =
                    cache.tickSecondsAccum -
                    crossTick.tickSecondsAccumOutside;
                crossTick.secondsPerLiquidityAccumOutside =
                    cache.secondsPerLiquidityAccum -
                    crossTick.secondsPerLiquidityAccumOutside;
                ticks[cache.crossTick].range = crossTick;
                int128 liquidityDelta = crossTick.liquidityDelta;
                // emit custom event
                emit SyncRangeTick(
                    crossTick.feeGrowthOutside0,
                    crossTick.feeGrowthOutside1,
                    cache.crossTick
                );
                if (params.zeroForOne) {
                    unchecked {
                        if (liquidityDelta >= 0) {
                            cache.liquidity -= uint128(liquidityDelta);
                            cache.state.pool.liquidity -= uint128(
                                liquidityDelta
                            );
                        } else {
                            cache.liquidity += uint128(-liquidityDelta);
                            cache.state.pool.liquidity += uint128(
                                -liquidityDelta
                            );
                        }
                    }
                } else {
                    unchecked {
                        if (liquidityDelta >= 0) {
                            cache.liquidity += uint128(liquidityDelta);
                            cache.state.pool.liquidity += uint128(
                                liquidityDelta
                            );
                        } else {
                            cache.liquidity -= uint128(-liquidityDelta);
                            cache.state.pool.liquidity -= uint128(
                                -liquidityDelta
                            );
                        }
                    }
                }
            } else {
                // skip crossing the tick
                cache.cross = false;
            }
        }
        // crossing limit tick
        if ((cache.crossStatus & LIMIT_TICK) > 0) {
            // cross limit tick
            EpochMap.set(
                cache.crossTick,
                !params.zeroForOne,
                cache.state.epoch,
                limitTickMap,
                cache.constants
            );
            int128 liquidityDelta = ticks[cache.crossTick].limit.liquidityDelta;

            if (liquidityDelta >= 0) {
                cache.liquidity += uint128(liquidityDelta);
                if (params.zeroForOne)
                    cache.state.pool1.liquidity += uint128(liquidityDelta);
                else cache.state.pool0.liquidity += uint128(liquidityDelta);
            } else {
                cache.liquidity -= uint128(-liquidityDelta);
                if (params.zeroForOne)
                    cache.state.pool1.liquidity -= uint128(-liquidityDelta);
                else cache.state.pool0.liquidity -= uint128(-liquidityDelta);
            }
            // zero out liquidityDelta and priceAt
            ticks[cache.crossTick].limit = PoolsharkStructs.LimitTick(0, 0, 0);
            LimitTicks.clear(
                ticks,
                cache.constants,
                limitTickMap,
                cache.crossTick
            );
            /// @dev - price and tickAtPrice updated at end of loop
        }
        if ((cache.crossStatus & LIMIT_POOL) > 0) {
            // add one-way liquidity
            uint128 liquidityDelta = params.zeroForOne
                ? cache.state.pool1.liquidity
                : cache.state.pool0.liquidity;
            if (liquidityDelta > 0) cache.liquidity += liquidityDelta;
        }
        if (cache.cross)
            cache = _iterate(
                ticks,
                rangeTickMap,
                limitTickMap,
                cache,
                params.zeroForOne,
                false
            );

        return cache;
    }

    function _pass(
        mapping(int24 => LimitPoolStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.SwapCache memory cache,
        PoolsharkStructs.QuoteParams memory params
    ) internal view returns (PoolsharkStructs.SwapCache memory) {
        if ((cache.crossStatus & RANGE_TICK) > 0) {
            if (
                !params.zeroForOne ||
                (cache.amountLeft > 0 && params.priceLimit < cache.crossPrice)
            ) {
                int128 liquidityDelta = ticks[cache.crossTick]
                    .range
                    .liquidityDelta;
                if (params.zeroForOne) {
                    unchecked {
                        if (liquidityDelta >= 0) {
                            cache.state.pool.liquidity -= uint128(
                                liquidityDelta
                            );
                        } else {
                            cache.state.pool.liquidity += uint128(
                                -liquidityDelta
                            );
                        }
                    }
                } else {
                    unchecked {
                        if (liquidityDelta >= 0) {
                            cache.state.pool.liquidity += uint128(
                                liquidityDelta
                            );
                        } else {
                            cache.state.pool.liquidity -= uint128(
                                -liquidityDelta
                            );
                        }
                    }
                }
            } else {
                cache.cross = false;
            }
        }
        if ((cache.crossStatus & LIMIT_TICK) > 0) {
            // cross limit tick
            int128 liquidityDelta = ticks[cache.crossTick].limit.liquidityDelta;

            if (liquidityDelta > 0) {
                cache.liquidity += uint128(liquidityDelta);
                if (params.zeroForOne)
                    cache.state.pool1.liquidity += uint128(liquidityDelta);
                else cache.state.pool0.liquidity += uint128(liquidityDelta);
            } else {
                cache.liquidity -= uint128(-liquidityDelta);
                if (params.zeroForOne) {
                    cache.state.pool1.liquidity -= uint128(-liquidityDelta);
                } else {
                    cache.state.pool0.liquidity -= uint128(-liquidityDelta);
                }
            }
        }
        if ((cache.crossStatus & LIMIT_POOL) > 0) {
            // add limit pool
            uint128 liquidityDelta = params.zeroForOne
                ? cache.state.pool1.liquidity
                : cache.state.pool0.liquidity;

            if (liquidityDelta > 0) {
                cache.liquidity += liquidityDelta;
            }
        }

        if (cache.cross)
            cache = _iterate(
                ticks,
                rangeTickMap,
                limitTickMap,
                cache,
                params.zeroForOne,
                false
            );

        return cache;
    }

    function _iterate(
        mapping(int24 => PoolsharkStructs.Tick) storage ticks,
        PoolsharkStructs.TickMap storage rangeTickMap,
        PoolsharkStructs.TickMap storage limitTickMap,
        PoolsharkStructs.SwapCache memory cache,
        bool zeroForOne,
        bool inclusive
    ) internal view returns (PoolsharkStructs.SwapCache memory) {
        if (zeroForOne) {
            if (cache.price > cache.state.pool1.price) {
                // load range pool
                cache.limitActive = false;
                cache.liquidity = cache.state.pool.liquidity;
                (cache.crossTick, ) = TickMap.roundHalf(
                    cache.crossTick,
                    cache.constants,
                    cache.price
                );
                // next range tick vs. limit pool price
                cache.crossTick = TickMap.previous(
                    rangeTickMap,
                    cache.crossTick,
                    cache.constants.tickSpacing,
                    inclusive
                );
                cache.crossPrice = ConstantProduct.getPriceAtTick(
                    cache.crossTick,
                    cache.constants
                );
                if (cache.state.pool1.price >= cache.crossPrice) {
                    // cross into limit pool
                    cache.crossStatus = LIMIT_POOL;
                    if (cache.state.pool1.price == cache.crossPrice)
                        // also cross range tick
                        cache.crossStatus |= RANGE_TICK;
                    else {
                        cache.crossTick = cache.state.pool1.tickAtPrice;
                        cache.crossPrice = cache.state.pool1.price;
                    }
                } else {
                    // cross only range tick
                    cache.crossStatus = RANGE_TICK;
                }
            } else {
                // load range and limit pools
                cache.limitActive = true;
                cache.liquidity =
                    cache.state.pool.liquidity +
                    cache.state.pool1.liquidity;
                (cache.crossTick, ) = TickMap.roundHalf(
                    cache.crossTick,
                    cache.constants,
                    cache.price
                );
                int24 rangeTickAhead;
                int24 limitTickAhead;
                if (
                    cache.crossStatus == LIMIT_POOL &&
                    cache.crossTick % cache.constants.tickSpacing != 0 &&
                    TickMap.get(
                        limitTickMap,
                        cache.crossTick,
                        cache.constants.tickSpacing
                    )
                ) {
                    limitTickAhead = cache.crossTick;
                    rangeTickAhead =
                        cache.crossTick -
                        cache.constants.tickSpacing /
                        2;
                } else {
                    rangeTickAhead = TickMap.previous(
                        rangeTickMap,
                        cache.crossTick,
                        cache.constants.tickSpacing,
                        inclusive
                    );
                    limitTickAhead = TickMap.previous(
                        limitTickMap,
                        cache.crossTick,
                        cache.constants.tickSpacing,
                        inclusive
                    );
                }
                // next range tick vs. next limit tick

                if (rangeTickAhead >= limitTickAhead) {
                    cache.crossTick = rangeTickAhead;
                    // cross range tick
                    cache.crossStatus = RANGE_TICK;
                    if (rangeTickAhead == limitTickAhead)
                        // also cross limit tick
                        cache.crossStatus |= LIMIT_TICK;
                    cache.crossPrice = ConstantProduct.getPriceAtTick(
                        cache.crossTick,
                        cache.constants
                    );
                } else {
                    // only cross limit tick
                    cache.crossTick = limitTickAhead;
                    cache.crossStatus = LIMIT_TICK;
                    cache.crossPrice = ticks[cache.crossTick].limit.priceAt == 0
                        ? ConstantProduct.getPriceAtTick(
                            cache.crossTick,
                            cache.constants
                        )
                        : ticks[cache.crossTick].limit.priceAt;
                }
            }
        } else {
            if (cache.price < cache.state.pool0.price) {
                // load range pool
                cache.limitActive = false;
                cache.liquidity = cache.state.pool.liquidity;
                (cache.crossTick, ) = TickMap.roundHalf(
                    cache.crossTick,
                    cache.constants,
                    cache.price
                );
                // next range tick vs. limit pool price
                cache.crossTick = TickMap.next(
                    rangeTickMap,
                    cache.crossTick,
                    cache.constants.tickSpacing,
                    inclusive
                );
                cache.crossPrice = ConstantProduct.getPriceAtTick(
                    cache.crossTick,
                    cache.constants
                );
                if (cache.state.pool0.price <= cache.crossPrice) {
                    // cross into limit pool
                    cache.crossStatus = LIMIT_POOL;
                    if (cache.state.pool0.price == cache.crossPrice)
                        // also cross range tick
                        cache.crossStatus |= RANGE_TICK;
                    else {
                        cache.crossTick = cache.state.pool0.tickAtPrice;
                        cache.crossPrice = cache.state.pool0.price;
                    }
                } else {
                    // cross only range tick
                    cache.crossStatus = RANGE_TICK;
                }
            } else {
                // load range and limit pools
                cache.limitActive = true;
                cache.liquidity =
                    cache.state.pool.liquidity +
                    cache.state.pool0.liquidity;
                (cache.crossTick, ) = TickMap.roundHalf(
                    cache.crossTick,
                    cache.constants,
                    cache.price
                );
                // next range tick vs. next limit tick
                int24 rangeTickAhead;
                int24 limitTickAhead;
                if (
                    cache.crossStatus == LIMIT_POOL &&
                    cache.crossTick % cache.constants.tickSpacing != 0 &&
                    TickMap.get(
                        limitTickMap,
                        cache.crossTick,
                        cache.constants.tickSpacing
                    )
                ) {
                    limitTickAhead = cache.crossTick;
                    rangeTickAhead =
                        cache.crossTick +
                        cache.constants.tickSpacing /
                        2;
                } else {
                    rangeTickAhead = TickMap.next(
                        rangeTickMap,
                        cache.crossTick,
                        cache.constants.tickSpacing,
                        inclusive
                    );
                    limitTickAhead = TickMap.next(
                        limitTickMap,
                        cache.crossTick,
                        cache.constants.tickSpacing,
                        inclusive
                    );
                }
                if (rangeTickAhead <= limitTickAhead) {
                    cache.crossTick = rangeTickAhead;
                    // cross range tick
                    cache.crossStatus |= RANGE_TICK;
                    if (rangeTickAhead == limitTickAhead)
                        // also cross limit tick
                        cache.crossStatus |= LIMIT_TICK;
                    cache.crossPrice = ConstantProduct.getPriceAtTick(
                        cache.crossTick,
                        cache.constants
                    );
                } else {
                    // only cross limit tick
                    cache.crossTick = limitTickAhead;
                    cache.crossStatus |= LIMIT_TICK;
                    cache.crossPrice = ticks[cache.crossTick].limit.priceAt == 0
                        ? ConstantProduct.getPriceAtTick(
                            cache.crossTick,
                            cache.constants
                        )
                        : ticks[cache.crossTick].limit.priceAt;
                }
            }
        }
        return cache;
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

library Bytes {
    bytes16 private constant alphabet = '0123456789abcdef';

    function from(string memory source) internal pure returns (bytes32 result) {
        bytes memory tempEmptyStringTest = bytes(source);
        if (tempEmptyStringTest.length == 0) {
            return 0x0;
        }

        assembly {
            result := mload(add(source, 32))
        }
    }

    function bytes32ToString(bytes32 _bytes32)
        internal
        pure
        returns (string memory)
    {
        uint8 i = 0;
        while (i < 32 && _bytes32[i] != 0) {
            i++;
        }
        bytes memory bytesArray = new bytes(i);
        for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return string(bytesArray);
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '../../interfaces/structs/LimitPoolStructs.sol';
import '../limit/LimitPositions.sol';
import '../utils/SafeTransfers.sol';

library CollectLib {
    using SafeCast for int128;
    using SafeCast for uint128;

    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );

    function range(
        RangePoolStructs.RangePosition memory position,
        PoolsharkStructs.LimitImmutables memory constants,
        address owner,
        address recipient,
        int128 amount0,
        int128 amount1
    ) internal {
        /// @dev - negative balances will revert
        if (amount0 > 0) {
            /// @dev - cast to ensure user doesn't owe the pool balance
            SafeTransfers.transferOut(
                recipient,
                constants.token0,
                amount0.toUint128()
            );
        }
        if (amount1 > 0) {
            /// @dev - cast to ensure user doesn't owe the pool balance
            SafeTransfers.transferOut(
                recipient,
                constants.token1,
                amount1.toUint128()
            );
        }
        emit Collect(
            owner,
            recipient,
            position.lower,
            position.upper,
            amount0 > 0 ? amount0.toUint128() : 0,
            amount1 > 0 ? amount1.toUint128() : 0
        );
    }

    function burnLimit(
        LimitPoolStructs.BurnLimitCache memory cache,
        PoolsharkStructs.BurnLimitParams memory params
    )
        internal
        returns (
            LimitPoolStructs.BurnLimitCache memory,
            int128 amount0Delta,
            int128 amount1Delta
        )
    {
        uint128 amount0 = params.zeroForOne ? cache.amountOut : cache.amountIn;
        uint128 amount1 = params.zeroForOne ? cache.amountIn : cache.amountOut;

        /// zero out balances and transfer out
        if (amount0 > 0) {
            cache.amountIn = 0;
            SafeTransfers.transferOut(
                params.to,
                cache.constants.token0,
                amount0
            );
        }
        if (amount1 > 0) {
            cache.amountOut = 0;
            SafeTransfers.transferOut(
                params.to,
                cache.constants.token1,
                amount1
            );
        }
        return (cache, amount0.toInt128(), amount1.toInt128());
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import './Bytes.sol';
import './String.sol';
import '../math/OverflowMath.sol';
import '../../interfaces/IPositionERC1155.sol';
import '../../interfaces/range/IRangePoolFactory.sol';
import '../../interfaces/structs/RangePoolStructs.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

/// @notice Token library for ERC-1155 calls.
library PositionTokens {
    uint256 internal constant Q128 = 0x100000000000000000000000000000000;

    function balanceOf(
        PoolsharkStructs.LimitImmutables memory constants,
        address owner,
        uint32 positionId
    ) internal view returns (uint256) {
        return
            IPositionERC1155(constants.poolToken).balanceOf(owner, positionId);
    }

    function name(address token0, address token1)
        internal
        view
        returns (bytes32 result)
    {
        string memory nameString = string.concat(
            'Poolshark ',
            ERC20(token0).symbol(),
            '-',
            ERC20(token1).symbol()
        );

        result = Bytes.from(nameString);
    }

    function symbol(address token0, address token1)
        internal
        view
        returns (bytes32 result)
    {
        string memory symbolString = string.concat(
            'PSHARK-',
            ERC20(token0).symbol(),
            '-',
            ERC20(token1).symbol()
        );

        result = Bytes.from(symbolString);
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
    /// @notice Cast a uint256 to a uint128, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint128
    function toUint128(uint256 y) internal pure returns (uint128 z) {
        if ((z = uint128(y)) != y)
            require(false, 'Uint256ToUint128:Overflow()');
    }

    /// @notice Cast a uint256 to a uint128, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint128
    function toUint128(int128 y) internal pure returns (uint128 z) {
        if (y < 0) require(false, 'Int128ToUint128:Underflow()');
        z = uint128(y);
    }

    /// @notice Cast a uint256 to a uint160, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint160
    function toUint160(uint256 y) internal pure returns (uint160 z) {
        if ((z = uint160(y)) != y)
            require(false, 'Uint256ToUint160:Overflow()');
    }

    /// @notice Cast a uint256 to a uint160, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint160
    function toUint32(uint256 y) internal pure returns (uint32 z) {
        if ((z = uint32(y)) != y) require(false, 'Uint256ToUint32:Overflow()');
    }

    /// @notice Cast a int256 to a int128, revert on overflow or underflow
    /// @param y The int256 to be downcasted
    /// @return z The downcasted integer, now type int128
    function toInt128(int256 y) internal pure returns (int128 z) {
        if ((z = int128(y)) != y) require(false, 'Int256ToInt128:Overflow()');
    }

    /// @notice Cast a int256 to a int128, revert on overflow or underflow
    /// @param y The int256 to be downcasted
    /// @return z The downcasted integer, now type int128
    function toInt128(uint128 y) internal pure returns (int128 z) {
        if (y > uint128(type(int128).max))
            require(false, 'Uint128ToInt128:Overflow()');
        z = int128(y);
    }

    /// @notice Cast a uint256 to a int256, revert on overflow
    /// @param y The uint256 to be casted
    /// @return z The casted integer, now type int256
    function toInt256(uint256 y) internal pure returns (int256 z) {
        if (y > uint256(type(int256).max))
            require(false, 'Uint256ToInt256:Overflow()');
        z = int256(y);
    }

    /// @notice Cast a uint256 to a uint128, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint128
    function toUint256(int256 y) internal pure returns (uint256 z) {
        if (y < 0) require(false, 'Int256ToUint256:Underflow()');
        z = uint256(y);
    }

    /// @notice Cast a uint256 to a uint16, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint128
    function toUint16(uint256 y) internal pure returns (uint16 z) {
        if ((z = uint16(y)) != y) require(false, 'Uint256ToUint16:Overflow()');
    }
}

//SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

library SafeTransfers {
    /**
     * @dev Similar to EIP20 transfer, except it handles a False success from `transfer` and returns an explanatory
     *      error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to
     *      insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified
     *      it is >= amount, this should not revert in normal conditions.
     *
     *      Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value.
     *            See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
     */
    // slither-disable-next-line assembly
    function transferOut(
        address to,
        address token,
        uint256 amount
    ) internal {
        bool success;
        if (amount == 0) return;
        if (token == address(0)) {
            (success, ) = to.call{value: amount}('');
            if (!success) require(false, 'SafeTransfers::EthTransferFailed()');
            return;
        }
        IERC20 erc20Token = IERC20(token);
        // ? We are checking the transfer, but since we are doing so in an assembly block
        // ? Slither does not pick up on that and results in a hit
        // slither-disable-next-line unchecked-transfer
        erc20Token.transfer(to, amount);

        success = false;
        assembly {
            switch returndatasize()
            case 0 {
                // This is a non-standard ERC-20
                success := 1 // set success to true
            }
            case 32 {
                // This is a complaint ERC-20
                returndatacopy(0, 0, 32)
                success := mload(0) // Set `success = returndata` of external call
            }
            default {
                // This is an excessively non-compliant ERC-20, revert.
                success := 0
            }
        }
        if (!success)
            require(false, 'TransferFailed(address(this), msg.sender');
    }

    /**
     * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and reverts in that case.
     *      This will revert due to insufficient balance or insufficient allowance.
     *      This function returns the actual amount received,
     *      which may be less than `amount` if there is a fee attached to the transfer.
     *
     *      Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value.
     *            See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
     */
    // slither-disable-next-line assembly
    function transferInto(
        address token,
        address sender,
        uint256 amount
    ) internal {
        if (token == address(0)) {
            require(false, 'SafeTransfers::CannotTransferInEth()');
        }
        IERC20 erc20Token = IERC20(token);

        /// @dev - msg.sender here is the pool
        erc20Token.transferFrom(sender, msg.sender, amount);

        bool success;
        assembly {
            switch returndatasize()
            case 0 {
                // This is a non-standard ERC-20
                success := 1 // set success to true
            }
            case 32 {
                // This is a compliant ERC-20
                returndatacopy(0, 0, 32)
                success := mload(0) // Set `success = returndata` of external call
            }
            default {
                // This is an excessively non-compliant ERC-20, revert.
                success := 0
            }
        }
        if (!success)
            require(false, 'TransferFailed(msg.sender, address(this)');
    }
}

// SPDX-License-Identifier: SSPL-1.0
pragma solidity 0.8.18;

library String {
    bytes16 private constant alphabet = '0123456789abcdef';

    function from(bytes32 value) internal pure returns (string memory) {
        return toString(abi.encodePacked(value));
    }

    function from(address account) internal pure returns (string memory) {
        return toString(abi.encodePacked(account));
    }

    function from(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), alphabet))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    function from(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? '-' : '', from(abs(value))));
    }

    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }

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

    function toString(bytes memory data) internal pure returns (string memory) {
        bytes memory str = new bytes(2 + data.length * 2);
        str[0] = '0';
        str[1] = 'x';
        for (uint256 i = 0; i < data.length; ) {
            str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))];
            str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))];
            unchecked {
                ++i;
            }
        }
        return string(str);
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/libraries/Ticks.sol": {
      "Ticks": "0x85023692e5fff6531004130c0f01046aab816c53"
    },
    "contracts/libraries/limit/pool/BurnLimitCall.sol": {
      "BurnLimitCall": "0x26323c7e4f000227932d3ed640ea79d6027701de"
    },
    "contracts/libraries/limit/pool/MintLimitCall.sol": {
      "MintLimitCall": "0xe7623a3a8d94296f9fe2b0c1fdfbcd36cc9c8d32"
    },
    "contracts/libraries/limit/pool/SnapshotLimitCall.sol": {
      "SnapshotLimitCall": "0x912c3e7d140e4ec85eb8e3910447acda8654d523"
    },
    "contracts/libraries/pool/FeesCall.sol": {
      "FeesCall": "0xa826f06c47597549faa74d8916ef4da8f417f16c"
    },
    "contracts/libraries/pool/QuoteCall.sol": {
      "QuoteCall": "0x7024879eda80577cbc0cb039ad3c7081f38abb41"
    },
    "contracts/libraries/pool/SampleCall.sol": {
      "SampleCall": "0x92192101f6c5138160c093882b0fa502dee889cd"
    },
    "contracts/libraries/pool/SwapCall.sol": {
      "SwapCall": "0xd50b04a5693f2d026d589bf239609bf5b8346adc"
    },
    "contracts/libraries/range/pool/BurnRangeCall.sol": {
      "BurnRangeCall": "0xd58628f15d4391a036c578638105e67f857b6770"
    },
    "contracts/libraries/range/pool/MintRangeCall.sol": {
      "MintRangeCall": "0x9f479560cd8a531e6c0fe04521cb246264fe6b71"
    },
    "contracts/libraries/range/pool/SnapshotRangeCall.sol": {
      "SnapshotRangeCall": "0x4dc4f1a2a3303e40a07c3aac4919a97f33ab01eb"
    }
  }
}

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"factory_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ReentrancyGuardInvalidState","type":"error"},{"inputs":[],"name":"ReentrancyGuardReadOnlyReentrantCall","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint128","name":"burnPercent","type":"uint128"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"int24","name":"claim","type":"int24"},{"internalType":"bool","name":"zeroForOne","type":"bool"}],"internalType":"struct PoolsharkStructs.BurnLimitParams","name":"params","type":"tuple"}],"name":"burnLimit","outputs":[{"internalType":"int256","name":"","type":"int256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"uint128","name":"burnPercent","type":"uint128"}],"internalType":"struct PoolsharkStructs.BurnRangeParams","name":"params","type":"tuple"}],"name":"burnRange","outputs":[{"internalType":"int256","name":"","type":"int256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint16","name":"protocolSwapFee0","type":"uint16"},{"internalType":"uint16","name":"protocolSwapFee1","type":"uint16"},{"internalType":"uint16","name":"protocolFillFee0","type":"uint16"},{"internalType":"uint16","name":"protocolFillFee1","type":"uint16"},{"internalType":"uint8","name":"setFeesFlags","type":"uint8"}],"internalType":"struct PoolsharkStructs.FeesParams","name":"params","type":"tuple"}],"name":"fees","outputs":[{"internalType":"uint128","name":"token0Fees","type":"uint128"},{"internalType":"uint128","name":"token1Fees","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"genesisTime","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"globalState","outputs":[{"components":[{"components":[{"internalType":"uint16","name":"index","type":"uint16"},{"internalType":"uint16","name":"count","type":"uint16"},{"internalType":"uint16","name":"countMax","type":"uint16"}],"internalType":"struct PoolsharkStructs.SampleState","name":"samples","type":"tuple"},{"internalType":"uint200","name":"feeGrowthGlobal0","type":"uint200"},{"internalType":"uint200","name":"feeGrowthGlobal1","type":"uint200"},{"internalType":"uint160","name":"secondsPerLiquidityAccum","type":"uint160"},{"internalType":"uint160","name":"price","type":"uint160"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"int56","name":"tickSecondsAccum","type":"int56"},{"internalType":"int24","name":"tickAtPrice","type":"int24"},{"internalType":"uint16","name":"protocolSwapFee0","type":"uint16"},{"internalType":"uint16","name":"protocolSwapFee1","type":"uint16"}],"internalType":"struct PoolsharkStructs.RangePoolState","name":"pool","type":"tuple"},{"components":[{"internalType":"uint160","name":"price","type":"uint160"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint128","name":"protocolFees","type":"uint128"},{"internalType":"uint16","name":"protocolFillFee","type":"uint16"},{"internalType":"int24","name":"tickAtPrice","type":"int24"}],"internalType":"struct PoolsharkStructs.LimitPoolState","name":"pool0","type":"tuple"},{"components":[{"internalType":"uint160","name":"price","type":"uint160"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint128","name":"protocolFees","type":"uint128"},{"internalType":"uint16","name":"protocolFillFee","type":"uint16"},{"internalType":"int24","name":"tickAtPrice","type":"int24"}],"internalType":"struct PoolsharkStructs.LimitPoolState","name":"pool1","type":"tuple"},{"internalType":"uint128","name":"liquidityGlobal","type":"uint128"},{"internalType":"uint32","name":"positionIdNext","type":"uint32"},{"internalType":"uint32","name":"epoch","type":"uint32"},{"internalType":"uint8","name":"unlocked","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"immutables","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"poolImpl","type":"address"},{"internalType":"address","name":"factory","type":"address"},{"components":[{"internalType":"uint160","name":"min","type":"uint160"},{"internalType":"uint160","name":"max","type":"uint160"}],"internalType":"struct PoolsharkStructs.PriceBounds","name":"bounds","type":"tuple"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"address","name":"poolToken","type":"address"},{"internalType":"uint32","name":"genesisTime","type":"uint32"},{"internalType":"int16","name":"tickSpacing","type":"int16"},{"internalType":"uint16","name":"swapFee","type":"uint16"}],"internalType":"struct PoolsharkStructs.LimitImmutables","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"newSampleCountMax","type":"uint16"}],"name":"increaseSampleCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint160","name":"startPrice","type":"uint160"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"limitTickMap","outputs":[{"internalType":"uint256","name":"blocks","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxPrice","outputs":[{"internalType":"uint160","name":"","type":"uint160"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"minPrice","outputs":[{"internalType":"uint160","name":"","type":"uint160"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint96","name":"mintPercent","type":"uint96"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"int24","name":"lower","type":"int24"},{"internalType":"int24","name":"upper","type":"int24"},{"internalType":"bool","name":"zeroForOne","type":"bool"},{"internalType":"bytes","name":"callbackData","type":"bytes"}],"internalType":"struct PoolsharkStructs.MintLimitParams","name":"params","type":"tuple"}],"name":"mintLimit","outputs":[{"internalType":"int256","name":"","type":"int256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"int24","name":"lower","type":"int24"},{"internalType":"int24","name":"upper","type":"int24"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"},{"internalType":"bytes","name":"callbackData","type":"bytes"}],"internalType":"struct PoolsharkStructs.MintRangeParams","name":"params","type":"tuple"}],"name":"mintRange","outputs":[{"internalType":"int256","name":"","type":"int256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"original","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"poolToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"positions","outputs":[{"internalType":"uint256","name":"feeGrowthInside0Last","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1Last","type":"uint256"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"int24","name":"lower","type":"int24"},{"internalType":"int24","name":"upper","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"positions0","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint32","name":"epochLast","type":"uint32"},{"internalType":"int24","name":"lower","type":"int24"},{"internalType":"int24","name":"upper","type":"int24"},{"internalType":"bool","name":"crossedInto","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"positions1","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint32","name":"epochLast","type":"uint32"},{"internalType":"int24","name":"lower","type":"int24"},{"internalType":"int24","name":"upper","type":"int24"},{"internalType":"bool","name":"crossedInto","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int16","name":"tickSpacing","type":"int16"}],"name":"priceBounds","outputs":[{"internalType":"uint160","name":"","type":"uint160"},{"internalType":"uint160","name":"","type":"uint160"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"uint160","name":"priceLimit","type":"uint160"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bool","name":"exactIn","type":"bool"},{"internalType":"bool","name":"zeroForOne","type":"bool"}],"internalType":"struct PoolsharkStructs.QuoteParams","name":"params","type":"tuple"}],"name":"quote","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint160","name":"","type":"uint160"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rangeTickMap","outputs":[{"internalType":"uint256","name":"blocks","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32[]","name":"secondsAgo","type":"uint32[]"}],"name":"sample","outputs":[{"internalType":"int56[]","name":"tickSecondsAccum","type":"int56[]"},{"internalType":"uint160[]","name":"secondsPerLiquidityAccum","type":"uint160[]"},{"internalType":"uint160","name":"averagePrice","type":"uint160"},{"internalType":"uint128","name":"averageLiquidity","type":"uint128"},{"internalType":"int24","name":"averageTick","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"samples","outputs":[{"internalType":"uint32","name":"blockTimestamp","type":"uint32"},{"internalType":"int56","name":"tickSecondsAccum","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityAccum","type":"uint160"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint128","name":"burnPercent","type":"uint128"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"int24","name":"claim","type":"int24"},{"internalType":"bool","name":"zeroForOne","type":"bool"}],"internalType":"struct PoolsharkStructs.SnapshotLimitParams","name":"params","type":"tuple"}],"name":"snapshotLimit","outputs":[{"internalType":"uint128","name":"","type":"uint128"},{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"positionId","type":"uint32"}],"name":"snapshotRange","outputs":[{"internalType":"int56","name":"tickSecondsAccum","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityAccum","type":"uint160"},{"internalType":"uint128","name":"feesOwed0","type":"uint128"},{"internalType":"uint128","name":"feesOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint160","name":"priceLimit","type":"uint160"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bool","name":"exactIn","type":"bool"},{"internalType":"bool","name":"zeroForOne","type":"bool"},{"internalType":"bytes","name":"callbackData","type":"bytes"}],"internalType":"struct PoolsharkStructs.SwapParams","name":"params","type":"tuple"}],"name":"swap","outputs":[{"internalType":"int256","name":"","type":"int256"},{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int16","name":"","type":"int16"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int24","name":"","type":"int24"}],"name":"ticks","outputs":[{"components":[{"internalType":"uint200","name":"feeGrowthOutside0","type":"uint200"},{"internalType":"uint200","name":"feeGrowthOutside1","type":"uint200"},{"internalType":"uint160","name":"secondsPerLiquidityAccumOutside","type":"uint160"},{"internalType":"int56","name":"tickSecondsAccumOutside","type":"int56"},{"internalType":"int128","name":"liquidityDelta","type":"int128"},{"internalType":"uint128","name":"liquidityAbsolute","type":"uint128"}],"internalType":"struct PoolsharkStructs.RangeTick","name":"range","type":"tuple"},{"components":[{"internalType":"uint160","name":"priceAt","type":"uint160"},{"internalType":"int128","name":"liquidityDelta","type":"int128"},{"internalType":"uint128","name":"liquidityAbsolute","type":"uint128"}],"internalType":"struct PoolsharkStructs.LimitTick","name":"limit","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"}]

Block Transaction Gas Used Reward
view all blocks sequenced

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

Validator Index Block Amount
View All Withdrawals

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

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