MemeStrategy

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/MemeStrategy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/*
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&-MEMS2025-&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&                         &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%    %%%%%%%%#    %%    &&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%    %%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%    %%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%            ,%%%%%%    &&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%            ,%%%%%%%%%%    &&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    &&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%    %%%%%%%%    &&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&    %%%%%%%%%%%%%%%%%%%%%    %%%%%%%%    &&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&                     &&&&        &&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

      ╔═══════════════════════════════════════════════════════════════╗
      ║               MEMESTRATEGY | MEMS - by Yokai Works            ║
      ║            The Perpetual Meme Token Flipping Machine          ║
      ╚═══════════════════════════════════════════════════════════════╝
*/

import {Ownable} from "solady/auth/Ownable.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {IMemeStrategyHook} from "./interfaces/IMemeStrategyHook.sol";
import {IUniswapV2Router} from "./interfaces/IUniswapV2Router.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";

interface IERC20 {
    function balanceOf(address) external view returns (uint256);
    function transfer(address, uint256) external;
    function approve(address, uint256) external;
    function decimals() external view returns (uint8);
}

interface IUnlockCallback {
    function unlockCallback(
        bytes calldata data
    ) external returns (bytes memory);
}

/// @title MemeStrategy - The Perpetual Meme Token Flipping Machine
/// @author YokaiWorks (https://yokai.works/)
/// @notice MEMS is an innovative DeFi protocol that combines automated meme token trading
///         through a queue-based system, dynamic fee collection via a Uniswap V4 hook with
///         a 3-phase fee system, a buyback and burn mechanism that automatically uses profits
///         to repurchase and burn MEMS tokens, and treasury operations that allocate 80% of
///         fees to the strategy while 20% goes to the treasury as rake.
/// @dev Uses Uniswap V2 Router for meme token swaps (with fee-on-transfer support),
///      V4 unlock pattern for MEMS buyback & burn
contract MemeStrategy is ERC20, Ownable, ReentrancyGuard, IUnlockCallback {
    using CurrencyLibrary for Currency;

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                   STRUCTS                                    */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    struct MemeToken {
        address v2Router;
        bool isWhitelisted;
    }

    struct QueuedPurchase {
        uint256 queueId;
        address targetAddress;
        uint256 ethAmount;
        uint256 targetMultiplier;
        uint256 maxSlippage;
        uint256 targetEntryPrice;
        uint256 queuedAt;
        bool executed;
        bool exists;
        bool executeToTreasury; // If true, use executeSaleToTreasury(); if false, use executeSale()
    }

    struct Purchase {
        uint256 purchaseId;
        address tokenAddress;      // Token address
        uint256 tokenAmount;       // Amount of tokens received
        uint256 buyPricePerUnit;   // ETH per token
        uint256 targetPrice;       // Calculated from targetMultiplier
        uint256 ethSpent;          // Native ETH spent
        uint256 timestamp;
        bool sold;
        uint256 soldPrice;
        uint256 soldTimestamp;
        bool isProfit;
        uint256 profitOrLoss;
        uint256 slippageUsed;
        bool executeToTreasury; // If true, must use executeSaleToTreasury(); if false, must use executeSale()
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                  CONSTANTS                                   */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    IPoolManager private immutable POOL_MANAGER;
    IPositionManager private immutable POSM;
    IAllowanceTransfer private immutable PERMIT2;
    IWETH private immutable WETH;

    uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
    uint256 public constant BPS_BASE = 10_000;
    uint256 public constant MIN_MULTIPLIER = 200;      // 2% min
    uint256 public constant MAX_MULTIPLIER = 990_000;  // 9900% max (100x total return)
    uint256 public constant MAX_SLIPPAGE = 5_000;      // 50% max
    uint256 public constant MAX_BATCH_SIZE = 50;

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                STATE VARIABLES                               */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    // Trading Control (Anti-Snipe)
    bool public tradingEnabled;

    // Access Control (Operators)
    mapping(address => bool) public operators;

    // Fee Management
    uint256 public executorReward;

    // Slippage Protection
    uint256 public maxSaleSlippage;  // Max slippage when selling meme tokens
    uint256 public maxBuybackSlippage; // Max slippage when buying back MEMS tokens (default: 500 = 5%)
    uint256 public minForceSellBPS; // Min return % of original cost in forceSell (default: 5000 = 50%)

    // Pool Configuration
    address public hookAddress;
    bool public poolInitialized;     // Track if pool has been initialized

    // Purchase Tracking
    uint256 public totalPurchases;
    uint256 public totalQueues;
    mapping(uint256 => Purchase) public purchases;
    mapping(address => MemeToken) public whitelistedTokens;

    // Queue System (Backend-Managed FIFO)
    // Backend listens to events and maintains queue order off-chain
    // Contract only stores by ID for gas efficiency
    mapping(uint256 => QueuedPurchase) public queuedPurchases;
    uint256 public nextQueueId = 1;     // Auto-incrementing ID counter
    uint256 public activeQueueCount;    // Number of items currently in queue

    // Statistics
    uint256 public totalProfit;
    uint256 public totalLoss;
    uint256 public totalTokensBurned;
    uint256 public successfulFlips;  // Used for phase transition (successfulFlips == 0)
    uint256 public failedFlips;

    // Pause State
    bool public paused;

    // Risk Management
    uint256 public maxSinglePurchase;
    uint256 public minSpikeThreshold; // Min ETH to trigger fee spikes
    uint256 public maxQueueSize;      // Max queue items (0 = unlimited, for spam/risk management)
    mapping(address => bool) public approvedRouters;

    // Module System (inline, replaces external ModuleManager)
    mapping(address => bool) public whitelistedModules;

    // Active Purchases Tracking (Backend-Managed)
    // Backend listens to TokenPurchaseExecuted and TokenSaleExecuted events
    // to maintain list of active purchases off-chain for gas efficiency
    uint256 public activePurchaseCount;

    // Callback Protection
    bool private _unlockInProgress;

    // Force Operation Protection (prevents cross-function reentrancy)
    bool private _forceOperationInProgress;

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                    EVENTS                                    */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    // Access Control Events
    event TradingEnabled(uint256 timestamp);
    event OperatorAdded(address indexed operator);
    event OperatorRemoved(address indexed operator);

    // Whitelist Events
    event MemeTokenWhitelisted(address indexed token, address indexed router);
    event MemeTokenDelisted(address indexed token);

    // Queue Events
    event TokenPurchaseQueued(
        uint256 indexed queueId,
        address indexed tokenAddress,
        address router,
        uint256 ethAmount,
        uint256 targetMultiplier,
        uint256 maxSlippage,
        uint256 targetEntryPrice,
        address indexed queuedBy,
        uint256 treasuryBalance,
        uint256 timestamp
    );
    event QueueCleared(uint256 indexed queueId);
    event QueueRemoved(uint256 indexed queueId);

    // Execution Events
    event TokenPurchaseExecuted(
        uint256 indexed purchaseId,
        uint256 indexed queueId,
        address indexed tokenAddress,
        address executor,
        uint256 ethSpent,
        uint256 tokenAmount,
        uint8 tokenDecimals,
        uint256 buyPricePerToken,
        uint256 targetPrice,
        uint256 targetMultiplier,
        uint256 slippageUsed,
        bool executeToTreasury,
        uint256 timestamp
    );

    event TokenSaleExecuted(
        uint256 indexed purchaseId,
        address indexed tokenAddress,
        address indexed executor,
        uint256 tokenAmount,
        uint256 ethReceived,
        uint256 ethSpent,
        bool isProfit,
        uint256 profitOrLossAmount,
        uint256 memsBurned,
        uint256 executorReward,
        uint256 buyPrice,
        uint256 sellPrice,
        uint256 holdingDuration,
        bool isFirstSale,
        bool toTreasury,
        uint256 timestamp
    );

    event ForceSold(
        uint256 indexed purchaseId,
        uint256 ethReceived,
        bool isProfit,
        uint256 amount,
        uint256 tokensBurned,
        bool toTreasury
    );
    event ForceBurn(
        address indexed initiator,
        uint256 ethUsed,
        uint256 tokensBurned,
        uint256 timestamp
    );

    // System Events
    event FeesAdded(uint256 amount, uint256 newTotal, address indexed from);
    event TokensBurned(uint256 ethSpent, uint256 amount, uint256 totalBurned);
    event EmergencyETHWithdraw(uint256 amount, address indexed to);
    event EmergencyWithdraw(address indexed token, uint256 amount, address to);
    event ModuleAdded(address indexed module);
    event ModuleRemoved(address indexed module);

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                   ERRORS                                     */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    error OnlyOwnerOrOperator();
    error NotAuthorized();
    error InvalidAmount();
    error TokenNotWhitelisted();
    error NoQueueExists();
    error PurchaseNotFound();
    error AlreadyProcessed();
    error WrongExecutionFunction();
    error TargetNotReached();
    error ContractPaused();
    error InvalidAddress();
    error CannotWithdrawStrategyToken();
    error RouterNotApproved();
    error PoolLiquidityTooLow();
    error InvalidCurrency();
    error RouterWETHMismatch();
    error NoV2Liquidity();
    error ModuleNotWhitelisted();
    error PriceCalculationOverflow();
    error ForceOperationInProgress();
    error QueueFull();

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                  MODIFIERS                                   */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    modifier whenNotPaused() {
        if (paused) revert ContractPaused();
        _;
    }

    modifier validSlippage(uint256 slippage) {
        if (slippage > MAX_SLIPPAGE) revert InvalidAmount();
        _;
    }

    modifier onlyOwnerOrOperator() {
        if (msg.sender != owner() && !operators[msg.sender])
            revert OnlyOwnerOrOperator();
        _;
    }

    modifier noForceOperationReentrancy() {
        if (_forceOperationInProgress) revert ForceOperationInProgress();
        _forceOperationInProgress = true;
        _;
        _forceOperationInProgress = false;
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                                 CONSTRUCTOR                                  */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    constructor(
        address _owner,
        address _posm,
        address _permit2,
        address _weth
    ) {
        if (_weth == address(0)) revert InvalidAddress();
        
        POSM = IPositionManager(_posm);
        POOL_MANAGER = POSM.poolManager();
        PERMIT2 = IAllowanceTransfer(_permit2);
        WETH = IWETH(_weth);

        _initializeOwner(_owner);

        // Set defaults
        executorReward = 0.01 ether;
        maxSaleSlippage = 500; // 5%
        maxBuybackSlippage = 500; // 5% - slippage protection for MEMS buybacks
        maxSinglePurchase = 50 ether; // Global max per purchase
        minSpikeThreshold = 2 ether; // Min ETH to trigger fee spikes
        minForceSellBPS = 5000; // 50% - force sell must return at least 50% of original cost
        maxQueueSize = 0; // Unlimited by default (set to limit for spam/risk management)
    }

    /// @notice Returns the name of the token
    function name() public pure override returns (string memory) {
        return "MemeStrategy";
    }

    /// @notice Returns the symbol of the token
    function symbol() public pure override returns (string memory) {
        return "MEMS";
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                          SAFE MATH HELPERS                                   */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Safely calculate price per token with overflow protection
    /// @dev Uses checked arithmetic to prevent overflow in (amount * precision) / tokens
    /// @param ethAmount Amount of ETH spent (in wei)
    /// @param tokenAmount Amount of tokens received (in token's base units)
    /// @param tokenAddress Address of the token to get decimals
    /// @return pricePerToken Price in wei per full token (e.g., for 9 decimals: wei per 1e9 base units)
    function _safePriceCalculation(
        uint256 ethAmount,
        uint256 tokenAmount,
        address tokenAddress
    ) internal view returns (uint256 pricePerToken) {
        if (tokenAmount == 0) revert InvalidAmount();

        // Get token decimals
        uint8 decimals = IERC20(tokenAddress).decimals();
        uint256 decimalMultiplier = 10 ** decimals;

        // Check if multiplication would overflow
        if (ethAmount > type(uint256).max / decimalMultiplier) revert PriceCalculationOverflow();

        // Calculate price per full token: (ethAmount * 10^decimals) / tokenAmount
        // This gives us wei per full token
        pricePerToken = (ethAmount * decimalMultiplier) / tokenAmount;

        return pricePerToken;
    }

    /// @notice Safely calculate target price with multiplier
    /// @dev Prevents overflow in (price * multiplier) calculation
    /// @param basePrice Base price per token
    /// @param multiplierBPS Target multiplier in basis points (10000 = 100%)
    /// @return targetPrice Target price including multiplier
    function _safeTargetPriceCalculation(
        uint256 basePrice,
        uint256 multiplierBPS
    ) internal pure returns (uint256 targetPrice) {
        // Calculate total multiplier (base + target)
        uint256 totalMultiplier = BPS_BASE + multiplierBPS;

        // Check if multiplication would overflow
        if (basePrice > type(uint256).max / totalMultiplier) revert PriceCalculationOverflow();

        // Safe to multiply now
        targetPrice = (basePrice * totalMultiplier) / BPS_BASE;

        return targetPrice;
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                          ADMIN - ACCESS CONTROL                              */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Initialize V4 pool and add initial liquidity (one-time, owner only)
    /// @dev Atomically initializes pool and adds liquidity. LP NFT sent to owner.
    ///      Uses ONLY msg.value for ETH (preserves any treasury ETH already accrued)
    /// @param _hook Hook address for the pool
    /// @param liquidity Amount of liquidity to add
    /// @param sqrtPriceX96 Starting price (79228162514264337593543950336 for 1:1)
    /// @param tickLower Lower tick for position (e.g., -887220)
    /// @param tickUpper Upper tick for position (e.g., 887220)
    function initializePoolAndAddLiquidity(
        address _hook,
        uint128 liquidity,
        uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper
    ) external payable onlyOwner nonReentrant {
        if (poolInitialized) revert AlreadyProcessed();
        if (_hook == address(0)) revert InvalidAddress();
        if (liquidity == 0) revert InvalidAmount();
        if (msg.value == 0) revert InvalidAmount();

        uint256 amount0Max = msg.value;
        uint256 amount1Max = MAX_SUPPLY;

        _mint(address(this), MAX_SUPPLY);
        hookAddress = _hook;
        PoolKey memory strategyPoolKey = PoolKey({
            currency0: Currency.wrap(address(0)),       // Native ETH (v1.1)
            currency1: Currency.wrap(address(this)),    // MEMS token
            fee: 0,                                     // 0% pool fee (hook collects via deltas)
            tickSpacing: 60,
            hooks: IHooks(_hook)
        });

        // Validate PoolKey configuration
        if (Currency.unwrap(strategyPoolKey.currency0) != address(0))
            revert InvalidCurrency();
        if (Currency.unwrap(strategyPoolKey.currency1) != address(this))
            revert InvalidCurrency();

        // 1. Initialize the pool
        POSM.initializePool(strategyPoolKey, sqrtPriceX96);

        // 2. Approve MEMS to Permit2 and PositionManager (temporary)
        approve(address(PERMIT2), type(uint256).max);
        PERMIT2.approve(address(this), address(POSM), type(uint160).max, uint48(block.timestamp + 300));

        bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));

        bytes[] memory mintParams = new bytes[](2);
        mintParams[0] = abi.encode(strategyPoolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner(), "");
        mintParams[1] = abi.encode(strategyPoolKey.currency0, strategyPoolKey.currency1);
        POSM.modifyLiquidities{value: amount0Max}(abi.encode(actions, mintParams), block.timestamp + 300);

        // 3. Revoke Permit2 approval for tighter security (only needed during initialization)
        // approve(address(PERMIT2), 0);

        poolInitialized = true;
        // Pool initialized
    }

    /// @notice Enable token transfers (one-time, irreversible)
    /// @dev Call this in bundle launch to open trading (owner or operator)
    function enableTrading() external onlyOwnerOrOperator {
        if (tradingEnabled) revert AlreadyProcessed();
        tradingEnabled = true;
        emit TradingEnabled(block.timestamp);
    }

    /// @notice Add operator for day-to-day operations
    function addOperator(address _operator) external onlyOwner {
        if (_operator == address(0)) revert InvalidAddress();
        if (!operators[_operator]) {
            operators[_operator] = true;
            emit OperatorAdded(_operator);
        }
    }

    /// @notice Remove operator
    function removeOperator(address _operator) external onlyOwner {
        if (operators[_operator]) {
            operators[_operator] = false;
            emit OperatorRemoved(_operator);
        }
    }


    /// @notice Override transfer to enforce trading enabled
    /// @dev Allows transfers before trading for:
    ///      - Owner transfers
    ///      - Contract-initiated transfers (pool initialization)
    ///      - Transfers to/from POSM and POOL_MANAGER (liquidity operations)
    ///      - Burning (to == address(0))
    /// @notice Override transfer to enforce trading enabled
    /// @dev Before trading enabled, ONLY these senders are allowed:
    ///      - Owner
    ///      - Contract itself (for pool initialization)
    ///      - POSM (for liquidity operations)
    ///      - POOL_MANAGER (for pool operations)
    ///      Also allows burning (to == address(0))
    function transfer(
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        if (!tradingEnabled) {
            // Whitelist: only specific senders can transfer before trading
            bool isAllowedSender =
                msg.sender == owner() ||
                msg.sender == address(this) ||
                msg.sender == address(POSM) ||
                msg.sender == address(POOL_MANAGER);

            // Allow burning
            bool isBurn = to == address(0);

            // Restrict POOL_MANAGER destinations before trading enabled (anti-snipe)
            // Prevents swaps to random users while allowing liquidity operations
            if (msg.sender == address(POOL_MANAGER)) {
                bool isAllowedDestination =
                    to == address(POOL_MANAGER) ||
                    to == address(POSM) ||
                    to == owner() ||
                    to == address(this) ||
                    to == address(0); // burning
                
                if (!isAllowedDestination) {
                    revert ContractPaused();
                }
            }

            if (!isAllowedSender && !isBurn) {
                revert ContractPaused();
            }
        }
        return super.transfer(to, amount);
    }

    /// @notice Override transferFrom to enforce trading enabled
    /// @dev Before trading enabled, ONLY these are allowed:
    ///      - POSM or POOL_MANAGER as caller (for liquidity operations)
    ///      - Owner as sender (owner-initiated transfers)
    ///      - Contract as sender (contract-initiated transfers)
    ///      - Minting/burning (from/to == address(0))
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        if (!tradingEnabled) {
            // Whitelist: POSM and POOL_MANAGER can call transferFrom
            bool isAllowedCaller =
                msg.sender == address(POSM) ||
                msg.sender == address(POOL_MANAGER);

            // Whitelist: Owner and contract can be the 'from' address
            bool isAllowedFrom =
                from == owner() ||
                from == address(this);

            // Allow minting/burning
            bool isMintOrBurn = from == address(0) || to == address(0);

            // Restrict POOL_MANAGER destinations before trading enabled (anti-snipe)
            // Prevents swaps to random users while allowing liquidity operations
            if (msg.sender == address(POOL_MANAGER)) {
                bool isAllowedDestination =
                    to == address(POOL_MANAGER) ||
                    to == address(POSM) ||
                    to == owner() ||
                    to == address(this) ||
                    to == address(0); // burning
                
                if (!isAllowedDestination) {
                    revert ContractPaused();
                }
            }

            if (!isAllowedCaller && !isAllowedFrom && !isMintOrBurn) {
                revert ContractPaused();
            }
        }
        return super.transferFrom(from, to, amount);
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                            ADMIN - WHITELIST MANAGEMENT                      */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Add meme token to whitelist
    /// @param _token Token address
    /// @param _v2Router Router address (typically Universal Router)
    function addMemeToken(
        address _token,
        address _v2Router
    ) external onlyOwnerOrOperator {
        if (_token == address(0) || _v2Router == address(0))
            revert InvalidAddress();
        if (!approvedRouters[_v2Router]) revert RouterNotApproved();

        if (!whitelistedTokens[_token].isWhitelisted) {
            whitelistedTokens[_token] = MemeToken({
                v2Router: _v2Router,
                isWhitelisted: true
            });

            emit MemeTokenWhitelisted(_token, _v2Router);
        }
    }

    /// @notice Remove meme token from whitelist
    function removeMemeToken(address _token) external onlyOwnerOrOperator {
        if (!whitelistedTokens[_token].isWhitelisted)
            revert TokenNotWhitelisted();
        whitelistedTokens[_token].isWhitelisted = false;
        emit MemeTokenDelisted(_token);
    }

    /// @notice Update V2 router for whitelisted token
    /// @param _token Token address
    /// @param _newRouter New V2 router address
    function updateTokenRouter(
        address _token,
        address _newRouter
    ) external onlyOwner {
        if (_newRouter == address(0)) revert InvalidAddress();
        if (!approvedRouters[_newRouter]) revert RouterNotApproved();
        if (!whitelistedTokens[_token].isWhitelisted)
            revert TokenNotWhitelisted();

        whitelistedTokens[_token].v2Router = _newRouter;
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                            ADMIN - QUEUE MANAGEMENT                          */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Queue meme token purchase with custom target (FIFO)
    /// @param _token Token address (must be whitelisted)
    /// @param _ethAmount ETH to spend (native)
    /// @param _targetMultiplier Target profit in BPS (10000 = 100% = 2x)
    /// @param _maxSlippage Max slippage in BPS
    /// @param _targetEntryPrice Max price to pay per token (in ETH, 1e18 precision), 0 = execute at any price
    function queueMemePurchase(
        address _token,
        uint256 _ethAmount,
        uint256 _targetMultiplier,
        uint256 _maxSlippage,
        uint256 _targetEntryPrice,
        bool _executeToTreasury
    )
        external
        onlyOwnerOrOperator
        validSlippage(_maxSlippage)
        returns (uint256 queueId)
    {
        MemeToken memory tokenInfo = whitelistedTokens[_token];
        if (!tokenInfo.isWhitelisted) revert TokenNotWhitelisted();
        if (_ethAmount == 0) revert InvalidAmount();
        if (_ethAmount > maxSinglePurchase) revert InvalidAmount();
        if (_targetMultiplier < MIN_MULTIPLIER || _targetMultiplier > MAX_MULTIPLIER)
            revert InvalidAmount();
        if (maxQueueSize > 0 && activeQueueCount >= maxQueueSize) revert QueueFull();

        // Assign unique queue ID (backend will manage FIFO order)
        queueId = nextQueueId++;
        totalQueues++;

        queuedPurchases[queueId] = QueuedPurchase({
            queueId: queueId,
            targetAddress: _token,
            ethAmount: _ethAmount,
            targetMultiplier: _targetMultiplier,
            maxSlippage: _maxSlippage,
            targetEntryPrice: _targetEntryPrice,
            queuedAt: block.timestamp,
            executed: false,
            exists: true,
            executeToTreasury: _executeToTreasury
        });

        activeQueueCount++;

        emit TokenPurchaseQueued(
            queueId,
            _token,
            tokenInfo.v2Router,
            _ethAmount,
            _targetMultiplier,
            _maxSlippage,
            _targetEntryPrice,
            msg.sender,
            address(this).balance,
            block.timestamp
        );

        return queueId;
    }

    /// @notice Force immediate meme token purchase (bypasses queue)
    /// @dev Useful for time-sensitive opportunities, does NOT affect FIFO queue
    function forceBuyMeme(
        address _token,
        uint256 _ethAmount,
        uint256 _targetMultiplier,
        uint256 _maxSlippage,
        bool _executeToTreasury
    )
        external
        onlyOwnerOrOperator
        nonReentrant
        whenNotPaused
        returns (uint256 purchaseId)
    {
        MemeToken memory tokenInfo = whitelistedTokens[_token];
        if (!tokenInfo.isWhitelisted) revert TokenNotWhitelisted();
        if (_ethAmount == 0) revert InvalidAmount();
        if (_ethAmount > maxSinglePurchase) revert InvalidAmount();
        if (_targetMultiplier < 1_000 || _targetMultiplier > MAX_MULTIPLIER)
            revert InvalidAmount();

        // Check sufficient ETH for purchase + executor reward
        uint256 totalSpend = _ethAmount + executorReward;
        if (address(this).balance < totalSpend) revert InsufficientBalance();

        // Execute immediately (bypasses FIFO queue)
        purchaseId = _executeMemeTokenPurchase(
            _token,
            _ethAmount,
            _targetMultiplier,
            _maxSlippage,
            0, // queueId = 0 (bypassed queue)
            _executeToTreasury
        );

        // Pay executor reward
        SafeTransferLib.safeTransferETH(msg.sender, executorReward);

        return purchaseId;
    }

    /// @notice Remove specific queue item by ID (backend determines which to clear)
    /// @param queueId The specific queue ID to clear
    /// @dev Renamed from clearQueue - backend now specifies which ID to clear
    function clearQueue(uint256 queueId) external onlyOwnerOrOperator {
        QueuedPurchase storage queue = queuedPurchases[queueId];

        if (!queue.exists || queue.executed) revert NoQueueExists();

        // Delete queue item
        delete queuedPurchases[queueId];
        activeQueueCount--;
        
        emit QueueCleared(queueId);
    }

    /// @notice Remove any queued purchase by queue ID
    /// @param queueId The queue ID to remove
    /// @dev Backend determines which queue item to remove (no on-chain iteration needed)
    function removeQueuedPurchase(uint256 queueId) external onlyOwnerOrOperator {
        QueuedPurchase storage queue = queuedPurchases[queueId];

        // Validate queue item exists and not executed
        if (!queue.exists || queue.executed) {
            revert NoQueueExists();
        }

        // Delete queue item (full delete)
        delete queuedPurchases[queueId];
        activeQueueCount--;
        
        emit QueueRemoved(queueId);
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                         ADMIN - SYSTEM CONFIGURATION                         */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    function setExecutorReward(uint256 _reward) external onlyOwner {
        if (_reward == 0) revert InvalidAmount();
        if (_reward > 1 ether) revert InvalidAmount(); // Sanity check: max 1 ETH reward
        executorReward = _reward;
    }

    function setMaxSaleSlippage(
        uint256 _slippage
    ) external onlyOwner validSlippage(_slippage) {
        maxSaleSlippage = _slippage;
    }

    function setMinForceSellBPS(uint256 _minBPS) external onlyOwner {
        if (_minBPS > BPS_BASE) revert InvalidAmount();
        minForceSellBPS = _minBPS;
    }

    function setMaxBuybackSlippage(
        uint256 _slippage
    ) external onlyOwner validSlippage(_slippage) {
        maxBuybackSlippage = _slippage;
    }

    function setHookAddress(address _hook) external onlyOwner {
        if (_hook == address(0)) revert InvalidAddress();
        hookAddress = _hook;
    }

    function addApprovedRouter(address _router) external onlyOwner {
        if (_router == address(0)) revert InvalidAddress();

        if (!approvedRouters[_router]) {
            // Validate router's WETH matches our WETH (prevents cross-chain mismatches)
            try IUniswapV2Router(_router).WETH() returns (address routerWETH) {
                if (routerWETH != address(WETH)) revert RouterWETHMismatch();
            } catch {
                // Router doesn't implement WETH() - skip validation (non-V2 router)
            }

            approvedRouters[_router] = true;
        }
    }

    function removeApprovedRouter(address _router) external onlyOwner {
        if (approvedRouters[_router]) {
            approvedRouters[_router] = false;
        }
    }

    function setMaxSinglePurchase(uint256 _max) external onlyOwner {
        if (_max == 0) revert InvalidAmount();
        maxSinglePurchase = _max;
    }

    function setMinSpikeThreshold(uint256 _threshold) external onlyOwner {
        if (_threshold == 0) revert InvalidAmount();
        minSpikeThreshold = _threshold;
    }

    /// @notice Set maximum queue size (0 = unlimited)
    /// @dev No technical limit since O(1) operations, but useful for spam/risk management
    /// @param _maxSize Maximum queue items (0 for unlimited)
    function setMaxQueueSize(uint256 _maxSize) external onlyOwner {
        maxQueueSize = _maxSize;
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                        ADMIN - MODULE MANAGEMENT                             */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Add a module contract to the whitelist
    /// @dev Modules can receive funding (push) or pull funds from treasury
    /// @param _module Address of the module contract
    function addModule(address _module) external onlyOwner {
        if (_module == address(0)) revert InvalidAddress();
        if (whitelistedModules[_module]) revert AlreadyProcessed();

        whitelistedModules[_module] = true;
        emit ModuleAdded(_module);
    }

    /// @notice Remove a module contract from the whitelist
    /// @param _module Address of the module contract to remove
    function removeModule(address _module) external onlyOwner {
        if (!whitelistedModules[_module]) revert ModuleNotWhitelisted();

        whitelistedModules[_module] = false;
        emit ModuleRemoved(_module);
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                           ADMIN - EMERGENCY FUNCTIONS                        */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    function pause() external onlyOwner {
        paused = true;
    }

    function unpause() external onlyOwner {
        paused = false;
    }

    /// @notice Force sell token regardless of target price
    /// @param _purchaseId Purchase ID to sell
    /// @param _slippageBPS Slippage tolerance in basis points (e.g., 500 = 5%)
    function forceSell(
        uint256 _purchaseId,
        uint256 _slippageBPS
    ) external onlyOwner nonReentrant noForceOperationReentrancy returns (uint256 amountOut) {
        Purchase storage purchase = purchases[_purchaseId];
        if (purchase.purchaseId == 0) revert PurchaseNotFound();
        if (purchase.sold) revert AlreadyProcessed();
        if (_slippageBPS > BPS_BASE) revert InvalidAmount();

        // Verify we have enough tokens for this specific purchase
        IERC20 token = IERC20(purchase.tokenAddress);
        uint256 amountToSell = purchase.tokenAmount;
        uint256 balance = token.balanceOf(address(this));
        if (balance < amountToSell) revert InsufficientBalance();

        MemeToken memory tokenInfo = whitelistedTokens[purchase.tokenAddress];
        IUniswapV2Router v2Router = IUniswapV2Router(tokenInfo.v2Router);

        // Calculate minAmountOut with slippage and safety checks
        uint256 _minAmountOut = _calculateMinAmountOut(
            amountToSell,
            purchase.tokenAddress,
            tokenInfo.v2Router,
            _slippageBPS,
            purchase.ethSpent
        );

        // Build path: Token → ETH
        address[] memory path = new address[](2);
        path[0] = purchase.tokenAddress;
        path[1] = address(WETH);

        // ═══════════════════════════════════════════════════════════════════════
        // CRITICAL: Update state BEFORE external calls (reentrancy protection)
        // ═══════════════════════════════════════════════════════════════════════

        // Mark as sold immediately to prevent reentrancy
        purchase.sold = true;
        purchase.soldTimestamp = block.timestamp;

        // Remove from active purchases BEFORE external calls
        _removeActivePurchase();

        // ═══════════════════════════════════════════════════════════════════════
        // Now safe to make external calls to potentially malicious token
        // ═══════════════════════════════════════════════════════════════════════

        // Safe approve pattern: set to 0 first, then set amount
        token.approve(address(v2Router), 0);
        token.approve(address(v2Router), amountToSell);

        uint256 ethBefore = address(this).balance;
        v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToSell,
            _minAmountOut,
            path,
            address(this),
            block.timestamp + 300
        );

        // Get actual ETH received (critical for fee-on-transfer tokens)
        uint256 ethAfter = address(this).balance;
        amountOut = ethAfter - ethBefore;

        // Note: All proceeds go directly to buyback, so no net change to currentFeesETH
        // (we receive ETH from sale, then immediately spend it on buyback)

        // Calculate profit or loss (force sells are usually at a loss)
        bool isProfit = amountOut > purchase.ethSpent;
        uint256 profitOrLoss = isProfit
            ? amountOut - purchase.ethSpent
            : purchase.ethSpent - amountOut;

        // Buyback and burn (no executor reward for admin force sells)
        // Calculate minimum tokens out with slippage protection
        uint256 minTokensOut = _calculateMinTokensFromETH(amountOut, maxBuybackSlippage);
        uint256 tokensBurned = _buyAndBurnTokens(amountOut, minTokensOut);

        // Update remaining purchase details (use safe calculation)
        purchase.soldPrice = _safePriceCalculation(amountOut, amountToSell, purchase.tokenAddress);
        purchase.isProfit = isProfit;
        purchase.profitOrLoss = profitOrLoss;

        // Update stats
        if (!isProfit) {
            totalLoss += profitOrLoss;
            failedFlips++;
        } else {
            // Rare: force sell at profit
            totalProfit += profitOrLoss;
            successfulFlips++;
        }

        emit ForceSold(
            _purchaseId,
            amountOut,
            isProfit,
            profitOrLoss,
            tokensBurned,
            false // toTreasury: false for regular forceSell with buyback
        );
    }

    /// @notice Force sell to treasury WITHOUT immediate buyback
    /// @dev ETH stays in treasury for later manual buyback, useful for managing timing
    /// @param _purchaseId Purchase ID to force sell
    /// @param _slippageBPS Slippage tolerance in basis points (e.g., 500 = 5%)
    function forceSellToTreasury(
        uint256 _purchaseId,
        uint256 _slippageBPS
    ) external onlyOwner nonReentrant noForceOperationReentrancy returns (uint256 amountOut) {
        Purchase storage purchase = purchases[_purchaseId];
        if (purchase.purchaseId == 0) revert PurchaseNotFound();
        if (purchase.sold) revert AlreadyProcessed();
        if (_slippageBPS > BPS_BASE) revert InvalidAmount();

        // Verify we have enough tokens for this specific purchase
        IERC20 token = IERC20(purchase.tokenAddress);
        uint256 amountToSell = purchase.tokenAmount;
        uint256 balance = token.balanceOf(address(this));
        if (balance < amountToSell) revert InsufficientBalance();

        MemeToken memory tokenInfo = whitelistedTokens[purchase.tokenAddress];
        IUniswapV2Router v2Router = IUniswapV2Router(tokenInfo.v2Router);

        // Calculate minAmountOut with slippage and safety checks
        uint256 _minAmountOut = _calculateMinAmountOut(
            amountToSell,
            purchase.tokenAddress,
            tokenInfo.v2Router,
            _slippageBPS,
            purchase.ethSpent
        );

        // Build path: Token → ETH
        address[] memory path = new address[](2);
        path[0] = purchase.tokenAddress;
        path[1] = address(WETH);

        // ═══════════════════════════════════════════════════════════════════════
        // CRITICAL: Update state BEFORE external calls (reentrancy protection)
        // ═══════════════════════════════════════════════════════════════════════

        // Mark as sold immediately to prevent reentrancy
        purchase.sold = true;
        purchase.soldTimestamp = block.timestamp;

        // Remove from active purchases BEFORE external calls
        _removeActivePurchase();

        // ═══════════════════════════════════════════════════════════════════════
        // Now safe to make external calls to potentially malicious token
        // ═══════════════════════════════════════════════════════════════════════

        // Safe approve pattern: set to 0 first, then set amount
        token.approve(address(v2Router), 0);
        token.approve(address(v2Router), amountToSell);

        uint256 ethBefore = address(this).balance;
        v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToSell,
            _minAmountOut,
            path,
            address(this),
            block.timestamp + 300
        );

        // Get actual ETH received (critical for fee-on-transfer tokens)
        uint256 ethAfter = address(this).balance;
        amountOut = ethAfter - ethBefore;

        // ETH stays in treasury (address(this).balance) for later manual buyback
        // No immediate buyback - owner can call forceBurn() later

        // Calculate profit or loss (force sells are usually at a loss)
        bool isProfit = amountOut > purchase.ethSpent;
        uint256 profitOrLoss = isProfit
            ? amountOut - purchase.ethSpent
            : purchase.ethSpent - amountOut;

        // Update remaining purchase details (use safe calculation)
        purchase.soldPrice = _safePriceCalculation(amountOut, amountToSell, purchase.tokenAddress);
        purchase.isProfit = isProfit;
        purchase.profitOrLoss = profitOrLoss;

        // Update stats
        if (!isProfit) {
            totalLoss += profitOrLoss;
            failedFlips++;
        } else {
            // Rare: force sell at profit
            totalProfit += profitOrLoss;
            successfulFlips++;
        }

        emit ForceSold(
            _purchaseId,
            amountOut,
            isProfit,
            profitOrLoss,
            0, // tokensBurned: 0 for treasury sales (no immediate buyback)
            true // toTreasury: true for treasury sales
        );
    }

    /// @notice Admin-only manual buyback and burn
    /// @dev Can trigger spike fees if amount >= minSpikeThreshold
    /// @param ethAmount Amount of ETH to spend on buyback
    /// @param slippageBPS Slippage tolerance in basis points (e.g., 500 = 5%)
    function forceBurn(
        uint256 ethAmount,
        uint256 slippageBPS
    ) external onlyOwner nonReentrant noForceOperationReentrancy validSlippage(slippageBPS) returns (uint256 tokensBurned) {
        if (ethAmount == 0) revert InvalidAmount();
        if (ethAmount > address(this).balance) revert InsufficientBalance();

        // Trigger spike fee if amount is large enough (Phase 2 only)
        if (successfulFlips > 0 && ethAmount >= minSpikeThreshold) {
            try IMemeStrategyHook(hookAddress).triggerFeeSpike(ethAmount) {
                // Spike triggered
            } catch {
                // Hook failure doesn't block burn execution
            }
        }

        // Calculate minimum tokens out with slippage protection
        uint256 minTokensOut = _calculateMinTokensFromETH(ethAmount, slippageBPS);

        // Execute buyback and burn with slippage protection
        tokensBurned = _buyAndBurnTokens(ethAmount, minTokensOut);

        emit ForceBurn(
            msg.sender,
            ethAmount,
            tokensBurned,
            block.timestamp
        );

        return tokensBurned;
    }

    function emergencyWithdrawTokens(
        address _token,
        uint256 _amount
    ) external onlyOwner {
        if (_token == address(this)) revert CannotWithdrawStrategyToken();
        SafeTransferLib.safeTransfer(_token, owner(), _amount);
        emit EmergencyWithdraw(_token, _amount, owner());
    }

    function emergencyWithdrawETH(uint256 _amount) external onlyOwner {
        if (_amount > address(this).balance) revert InsufficientBalance();
        SafeTransferLib.safeTransferETH(owner(), _amount);
        emit EmergencyETHWithdraw(_amount, owner());
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                         MODULE FUNDING (PUSH/PULL)                           */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Push ETH funds to a whitelisted module (owner-initiated)
    /// @param _module Module address to fund
    /// @param amount ETH amount to transfer
    /// @dev Only owner can push funds to modules
    function fundModule(address _module, uint256 amount) external onlyOwner nonReentrant {
        if (!whitelistedModules[_module]) revert ModuleNotWhitelisted();
        if (address(this).balance < amount) revert InsufficientBalance();
        SafeTransferLib.safeTransferETH(_module, amount);
    }

    /// @notice Allow whitelisted modules to pull funds from treasury
    /// @param amount ETH amount to pull
    /// @dev Can only be called by whitelisted modules
    function pullFundsFromTreasury(uint256 amount) external nonReentrant {
        if (!whitelistedModules[msg.sender]) revert ModuleNotWhitelisted();
        if (address(this).balance < amount) revert InsufficientBalance();
        SafeTransferLib.safeTransferETH(msg.sender, amount);
    }

    /// @notice Receive ETH from any source
    /// @dev All ETH sent to contract becomes available for treasury operations
    ///      Simplified accounting: address(this).balance is the source of truth
    receive() external payable {
        emit FeesAdded(msg.value, address(this).balance, msg.sender);
    }

    /* ═══════════════════════════════════════════════════════════════════════════ */
    /*                          PUBLIC - EXECUTION FUNCTIONS                        */
    /* ═══════════════════════════════════════════════════════════════════════════ */

    /// @notice Execute a specific queued purchase by ID (backend specifies which to execute)
    /// @dev Backend maintains queue order and tells executor which ID to execute next
    ///      Only executes if current price is at or below targetEntryPrice (or if targetEntryPrice is 0)
    /// @param queueId The specific queue ID to execute
    function executeQueuedPurchase(uint256 queueId)
        external
        nonReentrant
        whenNotPaused
        returns (uint256 purchaseId)
    {
        // Direct access to queue by ID
        QueuedPurchase storage queueStorage = queuedPurchases[queueId];

        // Validate queue item exists and not executed
        if (!queueStorage.exists || queueStorage.executed) {
            revert NoQueueExists();
        }

        // Load into memory for execution
        QueuedPurchase memory queue = queueStorage;

        // Check target entry price if set (0 = no limit)
        if (queue.targetEntryPrice > 0) {
            MemeToken memory tokenInfo = whitelistedTokens[queue.targetAddress];
            IUniswapV2Router v2Router = IUniswapV2Router(tokenInfo.v2Router);

            // Build path for price check
            address[] memory path = new address[](2);
            path[0] = address(WETH);
            path[1] = queue.targetAddress;

            // Get current price per token
            try v2Router.getAmountsOut(1e18, path) returns (uint256[] memory amounts) {
                uint256 currentPricePerToken = amounts[1];

                // Safety: Prevent division by zero
                if (currentPricePerToken == 0) revert InvalidAmount();

                // Current price must be <= targetEntryPrice (more tokens per ETH = better)
                // targetEntryPrice is how much ETH per token we're willing to pay
                // We need to convert: if we spend 1 ETH, we get currentPricePerToken tokens
                // Price per token = 1e18 / currentPricePerToken
                // ✅ Use safe calculation to prevent overflow
                uint256 pricePerToken = _safePriceCalculation(1e18, currentPricePerToken, queue.targetAddress);

                if (pricePerToken > queue.targetEntryPrice) {
                    revert TargetNotReached(); // Price too high
                }
            } catch {
                revert NoV2Liquidity();
            }
        }

        // Check sufficient ETH
        uint256 totalSpend = queue.ethAmount + executorReward;
        if (address(this).balance < totalSpend) revert InsufficientBalance();

        // Execute meme token purchase
        purchaseId = _executeMemeTokenPurchase(
            queue.targetAddress,
            queue.ethAmount,
            queue.targetMultiplier,
            queue.maxSlippage,
            queue.queueId,
            queue.executeToTreasury
        );

        // Pay executor reward
        SafeTransferLib.safeTransferETH(msg.sender, executorReward);

        // Mark queue as executed and remove from queue
        queueStorage.executed = true;
        delete queuedPurchases[queueId];
        activeQueueCount--;
    }

    /// @notice Execute sale when target reached (anyone can call)
    /// @dev Standard execution: sells meme token and immediately buyback MEMS
    /// @param _purchaseId Purchase ID to sell
    function executeSale(
        uint256 _purchaseId
    ) external nonReentrant whenNotPaused returns (uint256 profit) {
        return _executeSaleInternal(_purchaseId, true); // true = do buyback
    }

    /// @notice Execute sale to treasury WITHOUT immediate buyback (unpredictable timing)
    /// @dev Same as executeSale() but ETH stays in treasury for later manual buyback
    /// @param _purchaseId Purchase ID to sell
    function executeSaleToTreasury(
        uint256 _purchaseId
    ) external nonReentrant whenNotPaused returns (uint256 profit) {
        return _executeSaleInternal(_purchaseId, false); // false = no buyback
    }

    /// @notice Internal sale execution logic (shared by executeSale and executeSaleToTreasury)
    /// @param _purchaseId Purchase ID to sell
    /// @param doBuyback Whether to immediately buyback and burn MEMS tokens
    function _executeSaleInternal(
        uint256 _purchaseId,
        bool doBuyback
    ) internal returns (uint256) {
        Purchase storage purchase = purchases[_purchaseId];
        if (purchase.purchaseId == 0) revert PurchaseNotFound();
        if (purchase.sold) revert AlreadyProcessed();

        // Validate correct execution function
        if (doBuyback && purchase.executeToTreasury) revert WrongExecutionFunction();
        if (!doBuyback && !purchase.executeToTreasury) revert WrongExecutionFunction();

        // Verify we have enough tokens for this specific purchase
        IERC20 token = IERC20(purchase.tokenAddress);
        uint256 amountToSell = purchase.tokenAmount;
        uint256 balance = token.balanceOf(address(this));
        if (balance < amountToSell) revert InsufficientBalance();

        // Get current price from V2 for target verification
        MemeToken memory tokenInfo = whitelistedTokens[purchase.tokenAddress];
        IUniswapV2Router v2Router = IUniswapV2Router(tokenInfo.v2Router);
        address[] memory path = new address[](2);
        path[0] = purchase.tokenAddress;
        path[1] = address(WETH);

        uint256[] memory expectedOut;
        try v2Router.getAmountsOut(amountToSell, path) returns (
            uint256[] memory amounts
        ) {
            expectedOut = amounts;
        } catch {
            revert TargetNotReached(); // If price query fails, can't verify target
        }

        // Use safe calculation for current price
        uint256 currentPrice = _safePriceCalculation(expectedOut[1], amountToSell, purchase.tokenAddress);

        // Verify target reached
        if (currentPrice < purchase.targetPrice) revert TargetNotReached();

        // Calculate min ETH out with slippage
        uint256 minETHOut = (expectedOut[1] * (BPS_BASE - maxSaleSlippage)) / BPS_BASE;

        // ═══════════════════════════════════════════════════════════════════════
        // Update state BEFORE external calls (reentrancy protection)
        // ═══════════════════════════════════════════════════════════════════════

        // Mark as sold immediately to prevent reentrancy
        purchase.sold = true;
        purchase.soldTimestamp = block.timestamp;

        // Remove from active purchases BEFORE external calls
        _removeActivePurchase();

        // Safe approve pattern: set to 0 first, then set amount
        token.approve(address(v2Router), 0);
        token.approve(address(v2Router), amountToSell);

        uint256 totalReceived = address(this).balance;
        v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToSell,
            minETHOut,
            path,
            address(this),
            block.timestamp + 300
        );

        // Get actual ETH received (critical for fee-on-transfer tokens)
        totalReceived = address(this).balance - totalReceived;

        // Calculate actual slippage
        uint256 actualSlippage = expectedOut[1] > totalReceived
            ? (((expectedOut[1] - totalReceived) * BPS_BASE) / expectedOut[1])
            : 0;

        // Verify we have enough to pay executor reward
        if (totalReceived < executorReward) revert InsufficientBalance();

        // Calculate profit or loss
        bool isProfit = totalReceived > purchase.ethSpent;
        uint256 profitOrLoss = isProfit
            ? totalReceived - purchase.ethSpent
            : purchase.ethSpent - totalReceived;

        // Execute buyback if requested
        uint256 tokensBurned = 0;
        if (doBuyback) {
            uint256 forBuyback = totalReceived - executorReward;
            // Calculate minimum tokens out with slippage protection
            uint256 minTokensOut = _calculateMinTokensFromETH(forBuyback, maxBuybackSlippage);
            tokensBurned = _buyAndBurnTokens(forBuyback, minTok

Tags:
ERC20, Multisig, Mintable, Burnable, Pausable, Swap, Liquidity, Upgradeable, Multi-Signature, Factory|addr:0x7e3336dd43e9f477a10bdd4203d3cefcf4b1f5eb|verified:true|block:23691456|tx:0xe42d80ddeddadf013b039bba069b49f25cbd60dbdc69d1b8b142a50b24b16946|first_check:1761842488

Submitted on: 2025-10-30 17:41:31

Comments

Log in to comment.

No comments yet.