Treasury

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/Treasury.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/* ========== SHIB STRATEGY ========== *
 * URL : https://shibstrategy.io
 * X : https://x.com/Shib_Strategy
 * Built by : Shib Strategy Collective
 * Ver : v1.0.0
 * =================================== */
 
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IUniswapV4Router04} from "./interfaces/IUniswapV4Router04.sol";
import {Currency} from "@uniswap/v4-periphery/lib/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-periphery/lib/v4-core/src/types/PoolKey.sol";
import {IHooks} from "@uniswap/v4-periphery/lib/v4-core/src/interfaces/IHooks.sol";
import {IWETH9} from "./interfaces/IWETH9.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IStrategyToken is IERC20 {
    function burn(uint256 amount) external;
}

contract Treasury is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    uint256 public constant BPS = 10_000;
    uint256 public constant devSliceParts = 2;
    uint256 public constant stratSliceParts = 8;
    uint256 public constant TOTAL_PARTS = 10;
    uint256 public constant PROCESS_PROBE_BPS = 50;
    uint256 public constant MIN_PROBE_ABS = 1e15;

    IStrategyToken public immutable SHIBSTR;
    IERC20 public immutable WETH; 
    IWETH9 public immutable WETH9; 
    IERC20 public immutable SHIB; 
    address payable private constant DEV =
        payable(0x8406a7933bCaed5C1E982DD4E72F6697B7f2Bb63);
    address public immutable POOL_MANAGER; 

    uint256 public min_position_weth = 0.1 ether;
    uint256 public constant MAX_POSITION_WETH = 10 ether;
    uint256 public constant TARGET_PROFIT_OUT_BPS = 10_530;
    uint256 public minWethToOpen = 0.25 ether;
    uint256 public constant MAX_OPEN_POSITIONS = 20;
    uint256 public constant MAX_CONSOLIDATIONS = 3;
    uint256 public caller_bounty_bps = 50;
    uint256 public constant MAX_BOUNTY_WETH = 0.05 ether;
    uint256 public constant minProcessingSHIBSTR = 5_000_000e18;
    uint256 public constant processSlippageBps = 8_800;
    uint256 public constant swapDeadlineSeconds = 120;

    // lifetime stats
    uint256 public totalOpenedPositions;
    uint256 public totalClosedPositions;
    uint256 public cumulativeWethCost; // sum of costWeth for all opened positions
    uint256 public cumulativeWethProceeds; // sum of WETH received when closing
    uint256 public sumOpenCostWeth; // live total cost of open positions

    struct Position {
        uint256 amountSHIB;
        uint256 costWeth;
        bool open;
    }

    // V4 Uniswap
    struct V4Route {
        address router; // Uniswap v4 router
        address hook; // pool hook address
        uint24 fee; // lp fee (0-10000) - we use no fee on LP because of tax collected at hook
        int24 tickSpacing; // spacing - 60
        bool frozen;
    }

    mapping(address => V4Route) public v4Routes; //token -> route
    mapping(address => bool) public ethSenderAllowed;
    event EthSenderAllowed(address indexed a, bool allowed);

    Position[] public positions;
    uint256[] public openIds;
    mapping(uint256 => uint256) public idx;

    event Processed(
        uint256 SHIBSTRIn,
        uint256 WETHOut,
        uint256 devWethSent,
        address caller,
        uint256 bounty
    );
    event Opened(uint256 id, uint256 amountSHIB, uint256 costWeth);
    event Closed(
        uint256 id,
        uint256 proceedsWeth,
        uint256 burned,
        address caller,
        uint256 bounty
    );
    event Consolidated(
        uint256 idKept,
        uint256 idMerged,
        uint256 newSHIBAmount,
        uint256 newCost
    );
    event OpenAttempt(
        uint256 wethBalance,
        uint256 wethUsed,
        uint256 positionsOpened,
        address caller,
        uint256 bounty
    );

    event V4RouteSet(
        address indexed token,
        address router,
        address hook,
        uint24 fee,
        int24 spacing
    );
    event V4RouteFrozen(address indexed token);
    event V4RouteDeleted(address indexed token);
    event CallerBountyUpdated(uint256 bps);

    event MinWethToOpenUpdated(uint256 newValue);
    event MinPositionWethUpdated(uint256 newValue);

    error PMOnly();
    error NoEligiblePositions();

    constructor(
        address strategyToken,
        address weth,
        address shib,
        address poolManager
    ) Ownable(msg.sender) {
        require(strategyToken != address(0), "addr=0");
        require(weth != address(0), "weth=0");
        require(shib != address(0), "shib=0");
        require(poolManager != address(0), "pm=0");
        SHIBSTR = IStrategyToken(strategyToken);
        WETH = IERC20(weth);
        WETH9 = IWETH9(weth);
        SHIB = IERC20(shib);
        POOL_MANAGER = poolManager;
    }

    function setEthSenderAllowed(address a, bool allowed) external onlyOwner {
        ethSenderAllowed[a] = allowed;
        emit EthSenderAllowed(a, allowed);
    }

    function setBountyBps(uint256 newBps) external onlyOwner {
        require(newBps < BPS, "bps>BPS");
        require(newBps >= 50, "bps<50");
        caller_bounty_bps = newBps;
        emit CallerBountyUpdated(newBps);
    }

    // V4 Uniswap Router Admin functions
    function setV4Route(
        address token,
        address router_,
        address hook_,
        uint24 fee_,
        int24 spacing_
    ) external onlyOwner {
        require(token != address(0), "token=0");
        require(router_ != address(0), "v4 router=0");

        V4Route storage route = v4Routes[token];
        require(!route.frozen, "frozen");

        route.router = router_;
        route.hook = hook_;
        route.fee = fee_;
        route.tickSpacing = spacing_;
        route.frozen = false;

        ethSenderAllowed[router_] = true;
        if (hook_ != address(0)) {
            ethSenderAllowed[hook_] = true;
        }

        emit V4RouteSet(token, router_, hook_, fee_, spacing_);
    }

    function freezeV4Route(address token) external onlyOwner {
        V4Route storage route = v4Routes[token];
        require(!route.frozen, "already");
        route.frozen = true;
        emit V4RouteFrozen(token);
    }

    // admin failsafe to clear out a bad route so it can be recreated during deployment
    function deleteV4Route(address token) external onlyOwner {
        delete v4Routes[token];
        emit V4RouteDeleted(token);
    }

    function setMinWethToOpen(uint256 newMin) external onlyOwner {
        require(newMin >= 0.01 ether, "min too low");
        require(newMin <= 10 ether, "min too high");
        require(newMin >= min_position_weth, "min below position floor");
        minWethToOpen = newMin;
        emit MinWethToOpenUpdated(newMin);
    }

    function setMinPositionWeth(uint256 newMin) external onlyOwner {
        require(newMin <= MAX_POSITION_WETH, "min too high");
        require(newMin >= 0.01 ether, "min too low");
        min_position_weth = newMin;
        emit MinPositionWethUpdated(newMin);
    }

    // auto-wrap all ETH received from the hook into WETH
    receive() external payable {
        address s = msg.sender;
        if (s != POOL_MANAGER && s != address(WETH) && !ethSenderAllowed[s])
            revert PMOnly();
        // If it's coming from WETH.withdraw, do not re-wrap.
        if (s != address(WETH) && msg.value > 0) {
            WETH9.deposit{value: msg.value}();
        }
    }

    // salvage raw ETH forced into the account by selfdestruct
    function wrapStuckETH() external {
        _autoWrap();
    }

    function _autoWrap() internal {
        uint256 bal = address(this).balance;
        if (bal > 0) {
            WETH9.deposit{value: bal}();
        }
    }

    // Getter for all managed positions
    function getPosition(
        uint256 id
    )
        external
        view
        returns (uint256 amountSHIB, uint256 costWeth, bool open)
    {
        Position storage p = positions[id];
        return (p.amountSHIB, p.costWeth, p.open);
    }

    // Getter for all open managed positions
    function getOpenPositions()
        external
        view
        returns (
            uint256[] memory ids,
            uint256[] memory amountSHIB,
            uint256[] memory costWeth
        )
    {
        uint256 n = openIds.length;
        ids = new uint256[](n);
        amountSHIB = new uint256[](n);
        costWeth = new uint256[](n);
        for (uint256 i; i < n; ++i) {
            uint256 id = openIds[i];
            ids[i] = id;
            Position storage p = positions[id];
            amountSHIB[i] = p.amountSHIB;
            costWeth[i] = p.costWeth;
        }
    }

    // Core Flows
    /// Anyone can process accrued fees if the treasury has accrued enough
    function processFees() external nonReentrant {
        uint256 amt = SHIBSTR.balanceOf(address(this));
        require(amt >= minProcessingSHIBSTR, "amt too low");

        uint256 gotW = _v4SellFeesWithProbe(address(SHIBSTR), amt); // returns WETH
        require(gotW > 0, "no WETH");

        // reward bounty to caller
        uint256 bounty = _calcBounty(gotW);
        if (bounty > 0) {
            WETH.safeTransfer(msg.sender, bounty);
            gotW -= bounty;
        }

        // split WETH between dev and SHIB
        uint256 devWeth = Math.mulDiv(gotW, devSliceParts, TOTAL_PARTS);

        if (devWeth > 0) {
            WETH.safeTransfer(DEV, devWeth);
        }
        emit Processed(amt, gotW + bounty, devWeth, msg.sender, bounty);
    }

    /// Anyone can open positions if the treasury has accumulated enough WETH - chunked open - enforces cap
    function openIfThreshold() external nonReentrant {
        _autoWrap();

        uint256 wethBal = WETH.balanceOf(address(this));
        require(wethBal >= minWethToOpen, "below threshold");

        // pay bounty up front
        uint256 bounty = _calcBounty(wethBal);
        uint256 planned = wethBal - bounty;

        require(planned >= min_position_weth, "insufficient for min position");

        if (bounty > 0) {
            WETH.safeTransfer(msg.sender, bounty);
        }

        // open positions
        (uint256 used, uint256 opened) = _openWithWeth(planned);
        emit OpenAttempt(wethBal, used, opened, msg.sender, bounty);
    }

    /// @notice Close first eligible position found - this will need to be called repeatedly if multiple positions are eligible to be closed at the same time
    function closeFirstEligible() external nonReentrant {
        uint256 n = openIds.length;
        if (n == 0) revert NoEligiblePositions();

        uint256[] memory candidates = _selectCheapestOpenIndices();
        uint256 m = candidates.length;
        if (m == 0) revert NoEligiblePositions();

        for (uint256 i = 0; i < m; i++) {
            uint256 openIndex = candidates[i];
            uint256 id = openIds[openIndex];
            Position storage p = positions[id];
            if (!p.open || p.amountSHIB == 0) continue;

            // profit threshold: min proceeds required to close
            // required WETH >= costWeth * targetProfitBps / BPS
            uint256 minOutForTarget = Math.mulDiv(
                p.costWeth,
                TARGET_PROFIT_OUT_BPS,
                BPS
            );

            // best-effort sell: router will revert if threshold cannot be met
            (bool ok, uint256 gotW) = _v4SellForWeth(
                address(SHIB),
                p.amountSHIB,
                minOutForTarget
            );
            if (!ok) {
                // position could not be closed
                continue;
            }

            // success: finalize close
            p.open = false;
            _removeOpenIdByIndex(openIndex);

            totalClosedPositions += 1;
            cumulativeWethProceeds += gotW;
            sumOpenCostWeth -= p.costWeth;

            // reward bounty
            uint256 bounty = _calcBounty(gotW);
            if (bounty > 0) {
                WETH.safeTransfer(msg.sender, bounty);
                gotW -= bounty;
            }

            // buy and burn SHIBSTR
            uint256 bought = _v4BuyWithWeth(address(SHIBSTR), gotW, 0);
            require(bought > 0, "no SHIBSTR");

            // burn everything we bought
            SHIBSTR.burn(bought);
            emit Closed(id, gotW + bounty, bought, msg.sender, bounty);
            return; // close only one position per function call
        }
        revert NoEligiblePositions();
    }

    // open position with consolidation helper
    function _openWithWeth(
        uint256 wethAmount
    ) internal returns (uint256 used, uint256 opened) {
        uint256 remaining = wethAmount;
        uint256 consolidations = 0;

        while (remaining >= min_position_weth) {
            // check capacity and free slot if possible
            if (openIds.length >= MAX_OPEN_POSITIONS) {
                // safety - bound the function to avoid gas exhaustion
                if (consolidations >= MAX_CONSOLIDATIONS) break;
                _consolidatePositions();
                consolidations++;
                // if consolidation failed, stop
                if (openIds.length >= MAX_OPEN_POSITIONS) break;
            }

            // size chunk appropriately
            uint256 chunk = remaining > MAX_POSITION_WETH
                ? MAX_POSITION_WETH
                : remaining;

            // price agnostic swap WETH -> SHIB (v4)
            uint256 got = _v4BuyWithWeth(address(SHIB), chunk, 0);

            // record position
            uint256 id = positions.length;
            positions.push(
                Position({amountSHIB: got, costWeth: chunk, open: true})
            );
            idx[id] = openIds.length;
            openIds.push(id);

            totalOpenedPositions += 1;
            cumulativeWethCost += chunk;
            sumOpenCostWeth += chunk;

            emit Opened(id, got, chunk);

            opened += 1;
            used += chunk;
            remaining -= chunk;
        }
    }

    /// @dev Merge the worst cost basis with best cost basis to narrow dispersion.
    function _consolidatePositions() internal {
        if (openIds.length < 2) return;

        bool haveMin = false;
        bool haveMax = false;

        uint256 minPrice;
        uint256 maxPrice;
        uint256 minId;
        uint256 maxId;

        for (uint256 i = 0; i < openIds.length; i++) {
            uint256 id = openIds[i];
            Position storage p = positions[id];
            if (!p.open || p.amountSHIB == 0) continue;

            uint256 up = Math.mulDiv(p.costWeth, 1e18, p.amountSHIB);
            if (!haveMin || up < minPrice) {
                haveMin = true;
                minPrice = up;
                minId = id;
            }
            if (!haveMax || up > maxPrice) {
                haveMax = true;
                maxPrice = up;
                maxId = id;
            }
        }

        if (!haveMin || !haveMax || minId == maxId) return;

        // merge higher price into lower
        Position storage a = positions[minId];
        Position storage b = positions[maxId];

        a.amountSHIB += b.amountSHIB;
        a.costWeth += b.costWeth;

        b.open = false;
        _removeOpenId(maxId);

        emit Consolidated(minId, maxId, a.amountSHIB, a.costWeth);
    }

    // internal helpers

    function _removeOpenId(uint256 id) internal {
        uint256 i = idx[id];
        _removeOpenIdByIndex(i);
    }

    function _removeOpenIdByIndex(uint256 i) internal {
        uint256 last = openIds[openIds.length - 1];
        uint256 id = openIds[i];
        openIds[i] = last;
        idx[last] = i;
        openIds.pop();
        delete idx[id];
    }

    // allowances and utility functions

    // catches wrong address passed early and reverts
    function _requireContract(address a, string memory what) private view {
        require(a.code.length > 0, what);
    }

    function _approveExact(
        address token,
        address spender,
        uint256 amount
    ) internal {
        _requireContract(token, "approve: token !contract");
        _requireContract(spender, "approve: spender !contract");
        IERC20 t = IERC20(token);
        uint256 curr = t.allowance(address(this), spender);
        if (curr < amount) {
            // zero-first pattern for USDT-like tokens
            t.forceApprove(spender, 0);
            t.forceApprove(spender, amount);
        }
    }

    function _calcBounty(uint256 baseWETH) internal view returns (uint256) {
        uint256 bounty = Math.mulDiv(baseWETH, caller_bounty_bps, BPS);
        if (bounty > MAX_BOUNTY_WETH) return MAX_BOUNTY_WETH;
        if (bounty > baseWETH) return baseWETH;
        return bounty;
    }

    function positionsCount() external view returns (uint256) {
        return positions.length;
    }

    function openCount() external view returns (uint256) {
        return openIds.length;
    }

    // V4 Uniswap Helpers
    function _v4Key(address token) internal view returns (PoolKey memory key) {
        V4Route storage route = v4Routes[token];
        require(route.frozen, "route not frozen");
        require(
            route.router != address(0) &&
                route.tickSpacing != 0,
            "v4 incomplete"
        );
        key = PoolKey({
            currency0: Currency.wrap(address(0)), // ETH
            currency1: Currency.wrap(token), // ERC20 side
            fee: route.fee,
            tickSpacing: route.tickSpacing,
            hooks: IHooks(route.hook)
        });
        // extra sanity: ensure we set ETH first
        require(Currency.unwrap(key.currency0) == address(0), "bad key order");
    }

    function _v4BuyWithWeth(
        address token, // ERC20 being bought
        uint256 wethIn,
        uint256 minOutToken
    ) internal returns (uint256 gotToken) {
        V4Route storage route = v4Routes[token];
        require(
            route.router != address(0) &&
                route.tickSpacing != 0,
            "v4 incomplete"
        );

        // unwrap WETH to native ETH
        WETH9.withdraw(wethIn);

        PoolKey memory key = _v4Key(token);
        int256 out = IUniswapV4Router04(route.router).swapExactTokensForTokens{
            value: wethIn
        }(
            wethIn,
            minOutToken,
            true, // exact input
            key,
            "",
            address(this),
            block.timestamp + swapDeadlineSeconds
        );
        require(out > 0, "v4: amountOut<=0");
        gotToken = uint256(out);
    }

    function _v4SellForWeth(
        address token, // ERC20 being sold
        uint256 tokenIn,
        uint256 minOutWeth
    ) internal returns (bool ok, uint256 gotWeth) {
        V4Route storage route = v4Routes[token];
        require(
            route.router != address(0) &&
                route.tickSpacing != 0,
            "v4 incomplete"
        );

        PoolKey memory key = _v4Key(token);

        // best effort to support price target without Oracle lookup - if router reverts, catch and return (false, 0)
        _approveExact(token, route.router, tokenIn);
        try
            IUniswapV4Router04(route.router).swapExactTokensForTokens(
                tokenIn,
                minOutWeth,
                true, // exact input
                key,
                "",
                address(this),
                block.timestamp + swapDeadlineSeconds
            )
        returns (int256 out) {
            if (out <= 0) {
                return (false, 0);
            }
            uint256 ethOut = uint256(out);

            return (true, ethOut);
        } catch {
            return (false, 0);
        }
    }

    // SHIBSTR -> WETH fee processing without an oracle
    // sell a tiny probe chunk with minOut = 0 to learn an effective rate.
    // apply processSlippageBps to that rate and sell the remainder with amountOutMin
    // fees are generally taken in ETH so this may never run
    function _v4SellFeesWithProbe(
        address token,
        uint256 amt
    ) internal returns (uint256 gotWeth) {
        // route must be configured
        V4Route storage route = v4Routes[token];
        require(
            route.router != address(0) &&
                route.tickSpacing != 0,
            "v4 incomplete"
        );

        // if whole amount is small, just swap once
        if (amt <= MIN_PROBE_ABS) {
            (bool okSmall, uint256 gotSmall) = _v4SellForWeth(token, amt, 0);
            require(okSmall, "probe-only swap failed");
            return gotSmall;
        }

        uint256 probeIn = Math.mulDiv(amt, PROCESS_PROBE_BPS, BPS);
        if (probeIn == 0) probeIn = MIN_PROBE_ABS;

        if (probeIn >= amt) {
            (bool okSingle, uint256 gotSingle) = _v4SellForWeth(token, amt, 0);
            require(okSingle, "single swap failed");
            return gotSingle;
        }

        (bool okProbe, uint256 outProbe) = _v4SellForWeth(token, probeIn, 0);
        require(okProbe && outProbe > 0, "probe=0");

        uint256 rate = Math.mulDiv(outProbe, 1e18, probeIn);

        uint256 restIn = amt - probeIn;
        uint256 expectedRest = Math.mulDiv(restIn, rate, 1e18);
        uint256 minRest = Math.mulDiv(expectedRest, processSlippageBps, BPS);

        (bool okRest, uint256 outRest) = _v4SellForWeth(token, restIn, minRest);
        require(okRest, "rest swap slippage");

        gotWeth = outProbe + outRest;
    }

    /// @dev Return up to the 3 cheapest open positions by unit cost (WETH per SHIB).
    ///      The values returned are indices into `openIds` (not position ids).
    function _selectCheapestOpenIndices()
        internal
        view
        returns (uint256[] memory chosen)
    {
        uint256 INF = type(uint256).max;

        // Track the 3 cheapest (price, openId Indices). INF = unset.
        uint256 p1 = INF;
        uint256 i1 = INF;
        uint256 p2 = INF;
        uint256 i2 = INF;
        uint256 p3 = INF;
        uint256 i3 = INF;

        uint256 n = openIds.length;
        for (uint256 i = 0; i < n; i++) {
            uint256 id = openIds[i];
            Position storage p = positions[id];
            if (!p.open || p.amountSHIB == 0) continue;

            // unit cost = WETH per SHIB (scaled 1e18)
            uint256 up = Math.mulDiv(p.costWeth, 1e18, p.amountSHIB);

            // Insert into top-3 (ascending). Single-pass.
            if (up < p1) {
                // shift down
                p3 = p2;
                i3 = i2;
                p2 = p1;
                i2 = i1;
                p1 = up;
                i1 = i;
            } else if (up < p2) {
                p3 = p2;
                i3 = i2;
                p2 = up;
                i2 = i;
            } else if (up < p3) {
                p3 = up;
                i3 = i;
            }
        }

        // Count how many valid picks we actually have (<= k)
        uint256 m;
        if (i1 != INF) m++;
        if (i2 != INF) m++;
        if (i3 != INF) m++;
        if (m == 0) return new uint256[](0);

        chosen = new uint256[](m);
        uint256 j;
        if (i1 != INF) chosen[j++] = i1;
        if (i2 != INF) chosen[j++] = i2;
        if (i3 != INF) chosen[j++] = i3;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

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

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

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

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

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

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

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

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

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

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

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

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        if (!_safeTransfer(token, to, value, true)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

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

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _safeTransfer(token, to, value, false);
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _safeTransferFrom(token, from, to, value, false);
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        if (!_safeApprove(token, spender, value, false)) {
            if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));
            if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
     * return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param to The recipient of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
        bytes4 selector = IERC20.transfer.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(to, shr(96, not(0))))
            mstore(0x24, value)
            success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
        }
    }

    /**
     * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
     * value: the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param from The sender of the tokens
     * @param to The recipient of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value,
        bool bubble
    ) private returns (bool success) {
        bytes4 selector = IERC20.transferFrom.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(from, shr(96, not(0))))
            mstore(0x24, and(to, shr(96, not(0))))
            mstore(0x44, value)
            success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
            mstore(0x60, 0)
        }
    }

    /**
     * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
     * the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param spender The spender of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
        bytes4 selector = IERC20.approve.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(spender, shr(96, not(0))))
            mstore(0x24, value)
            success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
        }
    }
}
"
    },
    "src/interfaces/IUniswapV4Router04.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {PoolKey} from "@uniswap/v4-periphery/lib/v4-core/src/types/PoolKey.sol";
/**
 * Minimal interface for the Router04 *shape* used by SHIBSTR.
 * Matches the call you saw in their contract:
 *   swapExactTokensForTokens{value: amountIn}(...)
 */
interface IUniswapV4Router04 {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        bool exactInput,
        PoolKey calldata key,
        bytes calldata hookData,
        address recipient,
        uint256 deadline
    ) external payable returns (int256 amountOut);
}
"
    },
    "lib/v4-periphery/lib/v4-core/src/types/Currency.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

type Currency is address;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // If the upper 12 bytes are non-zero, they will be zero-ed out
    // Therefore, fromId() and toId() are not inverses of each other
    function fromId(uint256 id) internal pure returns (Currency) {
        return Currency.wrap(address(uint160(id)));
    }
}
"
    },
    "lib/v4-periphery/lib/v4-core/src/types/PoolKey.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

using PoolIdLibrary for PoolKey global;

/// @notice Returns the key for identifying a pool
struct PoolKey {
    /// @notice The lower currency of the pool, sorted numerically
    Currency currency0;
    /// @notice The higher currency of the pool, sorted numerically
    Currency currency1;
    /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
    uint24 fee;
    /// @notice Ticks that involve positions must be a multiple of tick spacing
    int24 tickSpacing;
    /// @notice The hooks of the pool
    IHooks hooks;
}
"
    },
    "lib/v4-periphery/lib/v4-core/src/interfaces/IHooks.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";

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

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

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

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

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

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

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

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

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

    /// @notice The hook called after donate
    /// @param sender The initial msg.sender for the donate call
    /// @param key The key for the pool
    /// @param amount0 The amount of token0 being donated
    /// @param amount1 The amount of token1 being donated
    /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function afterDonate(
        address sender,
        PoolKey calldata key,
        uint256 amount0,
        uint256 amount1,
        bytes calldata hookData
    ) external returns (bytes4);
}
"
    },
    "src/interfaces/IWETH9.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IWETH9 {
    function deposit() external payable;

    function withdraw(uint256) external;
}"
    },
    "lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Return the 512-bit addition of two uint256.
     *
     * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
     */
    function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        assembly ("memory-safe") {
            low := add(a, b)
            high := lt(low, a)
        }
    }

    /**
     * @dev Return the 512-bit multiplication of two uint256.
     *
     * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
     */
    function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
        // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
        // variables such that product = high * 2²⁵⁶ + low.
        assembly ("memory-safe") {
            let mm := mulmod(a, b, not(0))
            low := mul(a, b)
            high := sub(sub(mm, low), lt(mm, low))
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a + b;
            success = c >= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a - b;
            success = c <= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a * b;
            assembly ("memory-safe") {
                // Only true when the multiplication doesn't overflow
                // (c / a == b) || (a == 0)
                success := or(eq(div(c, a), b), iszero(a))
            }
            // equivalent to: success ? c : 0
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `DIV` opcode returns zero when the denominator is 0.
                result := div(a, b)
            }
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `MOD` opcode returns zero when the denominator is 0.
                result := mod(a, b)
            }
        }
    }

    /**
     * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryAdd(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
     */
    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
        (, uint256 result) = trySub(a, b);
        return result;
    }

    /**
     * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryMul(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Branchless ternary evaluation for `condition ? a : b`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `condition ? a : b`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * SafeCast.toUint(condition));
        }
    }

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

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

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

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

        // The following calculation ensures accurate ceiling division without overflow.
        // Since a is non-zero, (a - 1) / b will not overflow.
        // The largest possible result occurs when (a - 1) / b is type(uint256).max,
        // but the largest value we can obtain is type(uint256).max - 1, which happens
        // when a = type(uint256).max and b = 1.
        unchecked {
            return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
        }
    }

    /**
     * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     *
     * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);

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

            // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
            if (denominator <= high) {
                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVER

Tags:
ERC20, ERC165, Multisig, Burnable, Swap, Liquidity, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x9999ecd93dce2c31d8736dd176805037ed6c1359|verified:true|block:23548967|tx:0xdc423d82dbb666b95344316061e653c3c4f43000d442a1fd15f39714883e89e9|first_check:1760119388

Submitted on: 2025-10-10 20:03:10

Comments

Log in to comment.

No comments yet.