EthStrategy

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

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

/**
 * @title EthStrategy
 * @notice Ethereum Strategy Token (ETHSTR)
 * @dev 90% of fees kept as ETH, 10% buys BTCSTR
 */
contract EthStrategy is DualStrategyUpgradeable {
    using SafeTransferLib for address;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initialize the EthStrategy contract
     */
    function initialize(
        uint256 _maxSupply,
        address _poolManager,
        address _positionManager,
        address _permit2,
        address _universalRouter,
        address _btcstrToken,
        address _treasury,
        address _creatorFeeWallet,
        FeeConfig memory _feeConfig,
        PoolConfig memory _poolConfig
    ) external initializer {
        __DualStrategy_init(
            "Ethereum Strategy",
            "ETHSTR",
            _maxSupply,
            _poolManager,
            _positionManager,
            _permit2,
            _universalRouter,
            address(0),  // Main token is ETH (native)
            _btcstrToken,
            _treasury,
            _creatorFeeWallet,
            _feeConfig,
            _poolConfig
        );
    }

    /**
     * @notice Process main asset (ETH) - no swap needed, keep as reserve
     * @param ethAmount Amount of ETH received
     * @return Amount of ETH kept (same as input)
     */
    function _processMainAsset(uint256 ethAmount) internal override returns (uint256) {
        // For ETHSTR, we keep ETH as-is (no swap)
        // ETH stays in contract as reserve

        if (ethAmount > 0) {
            // Record as "batch" even though we're just holding ETH
            uint256 batchId = mainAssetBatchCounter++;

            // Price per ETH is 1 ETH = 1 ETH, so pricePerToken = 1e18
            uint256 pricePerToken = 1e18;
            uint256 targetPrice = (pricePerToken * (100 + PROFIT_TARGET_PERCENT)) / 100;

            mainAssetBatches[batchId] = Batch({
                amount: ethAmount,
                purchasePrice: pricePerToken,
                targetPrice: targetPrice,
                timestamp: block.timestamp,
                active: true
            });

            mainAssetActiveBatchIds.push(batchId);
            emit MainAssetBatchPurchased(batchId, ethAmount, pricePerToken);
        }

        return ethAmount;
    }

    /**
     * @notice Buy more ETH with contract balance (no-op for ETHSTR since main asset is ETH)
     * @dev This is a pass-through function for compatibility
     */
    function buyMainAsset(uint256 ethAmount, uint256 /* minOut */)
        external
        onlyTreasury
        nonReentrant
        whenNotPaused
        returns (uint256 batchId)
    {
        require(address(this).balance >= ethAmount, "Insufficient balance");

        // For ETHSTR, "buying" ETH is just recording it
        _processMainAsset(ethAmount);

        return mainAssetBatchCounter - 1;
    }

    /**
     * @notice Sell ETH batch back to strategy tokens
     */
    function sellMainAssetBatch(uint256 batchId, uint256 minStrategyOut)
        external
        onlyTreasury
        nonReentrant
        whenNotPaused
    {
        Batch storage batch = mainAssetBatches[batchId];
        require(batch.active, "Batch not active");

        uint256 ethAmount = batch.amount;

        // Swap ETH → ETHSTR
        uint256 balanceBefore = balanceOf(address(this));

        _swapExactInputSingle(
            address(0),  // ETH
            address(this),  // ETHSTR
            ethAmount,
            minStrategyOut,
            poolKey.fee,
            poolKey.tickSpacing
        );

        uint256 balanceAfter = balanceOf(address(this));
        uint256 strategyReceived = balanceAfter - balanceBefore;

        // Burn the strategy tokens
        _burn(address(this), strategyReceived);

        // Mark batch as inactive
        batch.active = false;
        _removeFromActiveBatches(batchId, true);

        emit MainAssetBatchSold(batchId, ethAmount, strategyReceived);
    }

    /**
     * @notice Sell BTCSTR batch back to ETH
     */
    function sellBTCSTRBatch(uint256 batchId, uint256 minETHOut)
        external
        onlyTreasury
        nonReentrant
        whenNotPaused
    {
        Batch storage batch = btcstrBatches[batchId];
        require(batch.active, "Batch not active");

        uint256 btcstrAmount = batch.amount;
        uint256 balanceBefore = address(this).balance;

        // Swap BTCSTR → ETH
        _swapExactInputSingle(
            address(BTCSTR_TOKEN),
            address(0),  // ETH
            btcstrAmount,
            minETHOut,
            btcstrSwapConfig.fee,
            btcstrSwapConfig.tickSpacing
        );

        uint256 balanceAfter = address(this).balance;
        uint256 ethReceived = balanceAfter - balanceBefore;

        // Distribute ETH
        _distributeETH(ethReceived);

        // Mark batch as inactive
        batch.active = false;
        _removeFromActiveBatches(batchId, false);

        emit BTCSTRBatchSold(batchId, btcstrAmount, ethReceived);
    }

    /**
     * @notice Distribute ETH to treasury and creator
     */
    function _distributeETH(uint256 ethAmount) internal {
        uint256 creatorFee = (ethAmount * CREATOR_FEE_PERCENT) / 100;
        uint256 treasuryAmount = ethAmount - creatorFee;

        if (creatorFee > 0) {
            pendingWithdrawals[creatorFeeWallet] += creatorFee;
        }

        if (treasuryAmount > 0) {
            pendingWithdrawals[treasury] += treasuryAmount;
        }
    }

    /**
     * @notice Remove batch from active list
     */
    function _removeFromActiveBatches(uint256 batchId, bool isMainAsset) internal {
        uint256[] storage activeBatchIds = isMainAsset ? mainAssetActiveBatchIds : btcstrActiveBatchIds;

        for (uint256 i = 0; i < activeBatchIds.length; i++) {
            if (activeBatchIds[i] == batchId) {
                activeBatchIds[i] = activeBatchIds[activeBatchIds.length - 1];
                activeBatchIds.pop();
                break;
            }
        }
    }

    /**
     * @notice Withdraw pending fees
     */
    function withdrawFees() external nonReentrant {
        uint256 amount = pendingWithdrawals[msg.sender];
        require(amount > 0, "No fees to withdraw");

        pendingWithdrawals[msg.sender] = 0;
        msg.sender.forceSafeTransferETH(amount);

        emit FeeWithdrawn(msg.sender, amount);
    }
}
"
    },
    "src/DualStrategyUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {Slot0} from "@uniswap/v4-core/src/types/Slot0.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

import {IUniversalRouter} from "./interfaces/IUniversalRouter.sol";
import {Commands} from "./libraries/Commands.sol";

/**
 * @title DualStrategyUpgradeable
 * @notice Base contract for dual-asset strategy tokens
 * @dev Splits fees: 90% to main asset, 10% to BTCSTR
 */
abstract contract DualStrategyUpgradeable is
    Initializable,
    ERC20Upgradeable,
    ReentrancyGuardUpgradeable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    using PoolIdLibrary for PoolKey;
    using StateLibrary for IPoolManager;
    using SafeTransferLib for address;

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

    struct FeeConfig {
        uint256 creatorFeePercent;  // Percentage of ETH fees to creator
        uint256 treasuryPercent;    // Percentage of ETH fees to treasury
        uint256 profitTargetPercent; // Profit target for token sales
    }

    struct PoolConfig {
        uint24 lpFee;              // Liquidity pool fee
        int24 tickSpacing;         // Tick spacing for pool
        uint256 token0Amount;      // Initial ETH amount
        uint256 token1Amount;      // Initial token amount
        uint160 startingPrice;     // Starting price for pool
        int24 tickLower;           // Lower tick boundary
        int24 tickUpper;           // Upper tick boundary
        uint128 liquidity;         // Initial liquidity amount
    }

    struct SwapPoolConfig {
        uint24 fee;                // Pool fee for swaps
        int24 tickSpacing;         // Tick spacing for swaps
    }

    struct Batch {
        uint256 amount;            // Amount of tokens
        uint256 purchasePrice;     // Price per token (scaled by 1e18)
        uint256 targetPrice;       // Target sell price
        uint256 timestamp;         // When batch was created
        bool active;               // Is batch active
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                   IMMUTABLE-LIKE VARIABLES               */
    /* ═══════════════════════════════════════════════════════ */

    IV4Router private _router;
    IPositionManager private _POSM;
    IAllowanceTransfer private _PERMIT2;
    IUniversalRouter private _UNIVERSAL_ROUTER;

    address public creator;
    address public creatorFeeWallet;
    address public treasury;

    // Main trading token (e.g., HYPE for HYPESTR, address(0) for ETHSTR)
    IERC20 public MAIN_TOKEN;

    // BTCSTR token address
    IERC20 public BTCSTR_TOKEN;

    uint256 public MAX_SUPPLY;
    uint256 public CREATOR_FEE_PERCENT;
    uint256 public TREASURY_PERCENT;
    uint256 public PROFIT_TARGET_PERCENT;

    // Fee split constants
    uint256 public constant MAIN_ASSET_PERCENT = 90;  // 90% to main asset
    uint256 public constant BTCSTR_PERCENT = 10;      // 10% to BTCSTR

    // Pool configuration storage
    PoolConfig private poolConfig;

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

    // Constants
    uint256 public constant MAX_ACTIVE_BATCHES = 100;

    // Pause state
    bool public paused;

    // Slippage tolerances
    uint256 public buybackSlippagePercent;
    uint256 public liquiditySlippagePercent;

    // Pool state
    bool public poolInitialized;
    uint256 public positionTokenId;
    PoolKey public poolKey;
    PoolId public poolId;
    int24 public tickLower;
    int24 public tickUpper;
    uint128 public principalLiquidity; // Original liquidity amount

    // Batch tracking - Main Asset
    uint256 public mainAssetBatchCounter;
    uint256[] public mainAssetActiveBatchIds;
    mapping(uint256 => Batch) public mainAssetBatches;

    // Batch tracking - BTCSTR
    uint256 public btcstrBatchCounter;
    uint256[] public btcstrActiveBatchIds;
    mapping(uint256 => Batch) public btcstrBatches;

    // Fee management
    uint256 public lastFeeCollection;
    mapping(address => uint256) public pendingWithdrawals;

    // Swap pool configurations
    SwapPoolConfig public mainAssetSwapConfig;  // For main asset swaps
    SwapPoolConfig public btcstrSwapConfig;     // For BTCSTR swaps

    // TWAP for pricing
    uint32 public twapWindow; // TWAP time window in seconds

    // Blacklist
    mapping(address => bool) public isBlacklisted;

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

    event FeesCollected(uint256 ethFees, uint256 strategyTokensBurned, uint256 mainAssetAmount, uint256 btcstrAmount);
    event MainAssetBatchPurchased(uint256 indexed batchId, uint256 amount, uint256 pricePerToken);
    event BTCSTRBatchPurchased(uint256 indexed batchId, uint256 amount, uint256 pricePerToken);
    event MainAssetBatchSold(uint256 indexed batchId, uint256 amount, uint256 ethReceived);
    event BTCSTRBatchSold(uint256 indexed batchId, uint256 amount, uint256 ethReceived);
    event StrategyBurned(uint256 amount);
    event CreatorFeePaid(uint256 amount);
    event FeeWithdrawn(address indexed recipient, uint256 amount);
    event PoolInitialized(uint256 positionTokenId, uint128 liquidity);
    event AddressBlacklisted(address indexed account);
    event AddressUnblacklisted(address indexed account);

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

    error Paused();
    error NotPaused();
    error NoFeesToCollect();
    error NoFeesToWithdraw();
    error PoolAlreadyInitialized();
    error PoolNotInitialized();
    error NotTreasury();
    error InvalidBatch();
    error TooManyActiveBatches();
    error InsufficientContractBalance();
    error ZeroTokensReceived();
    error NotValidSwap();
    error InvalidContract();
    error InvalidAddress();
    error InvalidPoolConfiguration();
    error Blacklisted();

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

    modifier onlyTreasury() {
        if (msg.sender != treasury) revert NotTreasury();
        _;
    }

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

    modifier whenPaused() {
        if (!paused) revert NotPaused();
        _;
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    INITIALIZATION                        */
    /* ═══════════════════════════════════════════════════════ */

    function __DualStrategy_init(
        string memory _name,
        string memory _symbol,
        uint256 _maxSupply,
        address _poolManager,
        address _positionManager,
        address _permit2,
        address _universalRouter,
        address _mainToken,
        address _btcstrToken,
        address _treasury,
        address _creatorFeeWallet,
        FeeConfig memory _feeConfig,
        PoolConfig memory _poolConfig
    ) internal onlyInitializing {
        __ERC20_init(_name, _symbol);
        __ReentrancyGuard_init();
        __Ownable_init(msg.sender);
        __UUPSUpgradeable_init();

        // Validate addresses
        if (_mainToken != address(0) && _mainToken.code.length == 0) revert InvalidContract();
        if (_btcstrToken == address(0) || _btcstrToken.code.length == 0) revert InvalidContract();
        if (_creatorFeeWallet == address(0) || _treasury == address(0)) revert InvalidContract();

        // Set immutable-like variables
        _router = IV4Router(_poolManager);
        _POSM = IPositionManager(_positionManager);
        _PERMIT2 = IAllowanceTransfer(_permit2);
        _UNIVERSAL_ROUTER = IUniversalRouter(_universalRouter);

        creator = msg.sender;
        creatorFeeWallet = _creatorFeeWallet;
        treasury = _treasury;
        MAIN_TOKEN = IERC20(_mainToken);
        BTCSTR_TOKEN = IERC20(_btcstrToken);

        MAX_SUPPLY = _maxSupply;
        CREATOR_FEE_PERCENT = _feeConfig.creatorFeePercent;
        TREASURY_PERCENT = _feeConfig.treasuryPercent;
        PROFIT_TARGET_PERCENT = _feeConfig.profitTargetPercent;

        // Validate and store pool configuration
        if (_poolConfig.liquidity == 0) revert InvalidPoolConfiguration();
        poolConfig = _poolConfig;

        // Default slippage
        buybackSlippagePercent = 20; // 20% for buyback
        liquiditySlippagePercent = 5; // 5% for liquidity

        // Default TWAP window
        twapWindow = 1800; // 30 minutes
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    FEE COLLECTION                        */
    /* ═══════════════════════════════════════════════════════ */

    /**
     * @notice Collects fees from the position and processes them
     * @dev Splits fees: 90% main asset, 10% BTCSTR
     */
    function collectAndProcessFees() external nonReentrant returns (
        uint256 mainAssetAmount,
        uint256 btcstrAmount
    ) {
        if (!poolInitialized) revert NoFeesToCollect();

        // Get accumulated fees
        (uint256 fees0, uint256 fees1) = getAccumulatedFees(poolKey);

        if (fees0 == 0 && fees1 == 0) revert NoFeesToCollect();

        // Collect fees from pool
        bytes memory actions = abi.encodePacked(
            uint8(Actions.DECREASE_LIQUIDITY),
            uint8(Actions.TAKE),  // Take ETH
            uint8(Actions.TAKE)   // Take tokens separately
        );

        bytes[] memory params = new bytes[](3);
        params[0] = abi.encode(positionTokenId, 0, 0, 0, "");
        params[1] = abi.encode(poolKey.currency0, address(this), fees0);
        params[2] = abi.encode(poolKey.currency1, address(this), fees1);

        _POSM.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);

        uint256 ethFees = fees0;
        uint256 strategyFees = fees1;

        // Burn the strategy tokens
        if (strategyFees > 0) {
            _burn(address(this), strategyFees);
            emit StrategyBurned(strategyFees);
        }

        // Split ETH fees: 90% main asset, 10% BTCSTR
        uint256 mainAssetETH = (ethFees * MAIN_ASSET_PERCENT) / 100;
        uint256 btcstrETH = (ethFees * BTCSTR_PERCENT) / 100;

        // Process main asset (overridden by child contracts)
        mainAssetAmount = _processMainAsset(mainAssetETH);

        // Buy BTCSTR with 10%
        btcstrAmount = _buyBTCSTR(btcstrETH);

        // Update state
        lastFeeCollection = block.timestamp;

        emit FeesCollected(ethFees, strategyFees, mainAssetAmount, btcstrAmount);

        return (mainAssetAmount, btcstrAmount);
    }

    /**
     * @notice Process main asset - implemented by child contracts
     * @param ethAmount Amount of ETH to process
     * @return Amount of main asset obtained
     */
    function _processMainAsset(uint256 ethAmount) internal virtual returns (uint256);

    /**
     * @notice Buy BTCSTR with ETH
     * @param ethAmount Amount of ETH to spend
     * @return Amount of BTCSTR obtained
     */
    function _buyBTCSTR(uint256 ethAmount) internal returns (uint256) {
        if (ethAmount == 0) return 0;

        uint256 balanceBefore = BTCSTR_TOKEN.balanceOf(address(this));

        // Swap ETH → BTCSTR via Universal Router
        _swapExactInputSingle(
            address(0),  // ETH
            address(BTCSTR_TOKEN),
            ethAmount,
            0,  // Min amount will be calculated based on slippage
            btcstrSwapConfig.fee,
            btcstrSwapConfig.tickSpacing
        );

        uint256 balanceAfter = BTCSTR_TOKEN.balanceOf(address(this));
        uint256 btcstrReceived = balanceAfter - balanceBefore;

        if (btcstrReceived > 0) {
            // Create BTCSTR batch
            uint256 batchId = btcstrBatchCounter++;
            uint256 pricePerToken = (ethAmount * 1e18) / btcstrReceived;
            uint256 targetPrice = (pricePerToken * (100 + PROFIT_TARGET_PERCENT)) / 100;

            btcstrBatches[batchId] = Batch({
                amount: btcstrReceived,
                purchasePrice: pricePerToken,
                targetPrice: targetPrice,
                timestamp: block.timestamp,
                active: true
            });

            btcstrActiveBatchIds.push(batchId);
            emit BTCSTRBatchPurchased(batchId, btcstrReceived, pricePerToken);
        }

        return btcstrReceived;
    }

    /**
     * @notice Get accumulated fees from the pool
     */
    function getAccumulatedFees(PoolKey memory _poolKey) public view returns (uint256 fees0, uint256 fees1) {
        if (!poolInitialized) return (0, 0);

        IPoolManager poolManager = _POSM.poolManager();
        PoolId _poolId = _poolKey.toId();

        // Get position info
        (
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128
        ) = poolManager.getPositionInfo(
            _poolId,
            address(_POSM),
            poolConfig.tickLower,
            poolConfig.tickUpper,
            bytes32(positionTokenId)
        );

        // Get current fee growth
        (
            uint256 feeGrowthInside0X128,
            uint256 feeGrowthInside1X128
        ) = poolManager.getFeeGrowthInside(_poolId, poolConfig.tickLower, poolConfig.tickUpper);

        // Calculate fees
        if (liquidity > 0) {
            uint256 delta0 = feeGrowthInside0X128 - feeGrowthInside0LastX128;
            uint256 delta1 = feeGrowthInside1X128 - feeGrowthInside1LastX128;

            fees0 = (delta0 * liquidity) / (1 << 128);
            fees1 = (delta1 * liquidity) / (1 << 128);
        }

        return (fees0, fees1);
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    SWAP FUNCTIONS                        */
    /* ═══════════════════════════════════════════════════════ */

    /**
     * @notice Internal swap function using Universal Router
     */
    function _swapExactInputSingle(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut,
        uint24 fee,
        int24 tickSpacing
    ) internal {
        bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.V4_SWAP)));
        bytes[] memory inputs = new bytes[](1);

        inputs[0] = abi.encode(
            IV4Router.ExactInputSingleParams({
                poolKey: PoolKey({
                    currency0: tokenIn == address(0) ? Currency.wrap(address(0)) : Currency.wrap(tokenIn),
                    currency1: Currency.wrap(tokenOut),
                    fee: fee,
                    tickSpacing: tickSpacing,
                    hooks: IHooks(address(0))
                }),
                zeroForOne: tokenIn < tokenOut,
                amountIn: uint128(amountIn),
                amountOutMinimum: uint128(minAmountOut),
                hookData: ""
            })
        );

        // Execute swap
        _UNIVERSAL_ROUTER.execute{value: tokenIn == address(0) ? amountIn : 0}(
            commands,
            inputs,
            block.timestamp + 60
        );
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                   BATCH MANAGEMENT                       */
    /* ═══════════════════════════════════════════════════════ */

    /**
     * @notice Get active main asset batches
     */
    function getMainAssetActiveBatches() external view returns (uint256[] memory) {
        return mainAssetActiveBatchIds;
    }

    /**
     * @notice Get active BTCSTR batches
     */
    function getBTCSTRActiveBatches() external view returns (uint256[] memory) {
        return btcstrActiveBatchIds;
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    ADMIN FUNCTIONS                       */
    /* ═══════════════════════════════════════════════════════ */

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

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

    function setMainAssetSwapConfig(uint24 _fee, int24 _tickSpacing) external onlyOwner {
        mainAssetSwapConfig = SwapPoolConfig({fee: _fee, tickSpacing: _tickSpacing});
    }

    function setBTCSTRSwapConfig(uint24 _fee, int24 _tickSpacing) external onlyOwner {
        btcstrSwapConfig = SwapPoolConfig({fee: _fee, tickSpacing: _tickSpacing});
    }

    function setLiquiditySlippage(uint256 _slippagePercent) external onlyOwner {
        require(_slippagePercent > 0 && _slippagePercent <= 50, "Invalid slippage");
        liquiditySlippagePercent = _slippagePercent;
    }

    function setBuybackSlippage(uint256 _slippagePercent) external onlyOwner {
        require(_slippagePercent > 0 && _slippagePercent <= 50, "Invalid slippage");
        buybackSlippagePercent = _slippagePercent;
    }

    function blacklistAddress(address account) external onlyOwner {
        isBlacklisted[account] = true;
        emit AddressBlacklisted(account);
    }

    function unblacklistAddress(address account) external onlyOwner {
        isBlacklisted[account] = false;
        emit AddressUnblacklisted(account);
    }

    function renounceOwnership() public override onlyOwner {
        _transferOwnership(address(0));
    }

    function version() public pure virtual returns (string memory) {
        return "1.0.0";
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    LIQUIDITY FUNCTIONS                   */
    /* ═══════════════════════════════════════════════════════ */

    function loadLiquidity() external onlyOwner {
        Currency currency0 = Currency.wrap(address(0)); // ETH
        Currency currency1 = Currency.wrap(address(this)); // STRATEGY

        tickLower = poolConfig.tickLower;
        tickUpper = poolConfig.tickUpper;

        PoolKey memory key = PoolKey(
            currency0,
            currency1,
            poolConfig.lpFee,
            poolConfig.tickSpacing,
            IHooks(address(0))
        );

        poolKey = key;
        poolId = key.toId();
        bytes memory hookData = new bytes(0);

        principalLiquidity = poolConfig.liquidity; // Store principal

        (
            bytes memory actions,
            bytes[] memory mintParams
        ) = _mintLiquidityParams(
                key,
                tickLower,
                tickUpper,
                poolConfig.liquidity,
                poolConfig.token0Amount,
                poolConfig.token1Amount,
                address(this),
                hookData
            );

        bytes[] memory params = new bytes[](2);

        params[0] = abi.encodeWithSelector(
            _POSM.initializePool.selector,
            key,
            poolConfig.startingPrice,
            hookData
        );

        params[1] = abi.encodeWithSelector(
            _POSM.modifyLiquidities.selector,
            abi.encode(actions, mintParams),
            block.timestamp + 60
        );

        uint256 valueToPass = poolConfig.token0Amount;

        // Approve for position manager
        _approve(address(this), address(_PERMIT2), type(uint256).max);

        _PERMIT2.approve(
            address(this),
            address(_POSM),
            type(uint160).max,
            type(uint48).max
        );

        positionTokenId = _POSM.nextTokenId();

        _POSM.multicall{value: valueToPass}(params);

        poolInitialized = true;
        lastFeeCollection = block.timestamp;

        emit PoolInitialized(positionTokenId, principalLiquidity);
    }

    function _mintLiquidityParams(
        PoolKey memory key,
        int24 _tickLower,
        int24 _tickUpper,
        uint128 _liquidity,
        uint256 _amount0Max,
        uint256 _amount1Max,
        address _recipient,
        bytes memory _hookData
    ) internal pure returns (bytes memory, bytes[] memory) {
        bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
        bytes[] memory params = new bytes[](2);

        params[0] = abi.encode(
            key,
            _tickLower,
            _tickUpper,
            _liquidity,
            _amount0Max,
            _amount1Max,
            _recipient,
            _hookData
        );

        params[1] = abi.encode(key.currency0, key.currency1);

        return (actions, params);
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    TRANSFER OVERRIDES                    */
    /* ═══════════════════════════════════════════════════════ */

    function transfer(address to, uint256 amount) public override returns (bool) {
        if (isBlacklisted[msg.sender] || isBlacklisted[to]) revert Blacklisted();
        return super.transfer(to, amount);
    }

    function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
        if (isBlacklisted[from] || isBlacklisted[to]) revert Blacklisted();
        return super.transferFrom(from, to, amount);
    }

    /* ═══════════════════════════════════════════════════════ */
    /*                    UPGRADE AUTHORIZATION                 */
    /* ═══════════════════════════════════════════════════════ */

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /**
     * @notice Receive ETH
     */
    receive() external payable {}
}
"
    },
    "lib/solady/src/utils/SafeTransferLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /// @dev The ERC20 `totalSupply` query has failed.
    error TotalSupplyQueryFailed();

    /// @dev The Permit2 operation has failed.
    error Permit2Failed();

    /// @dev The Permit2 amount must be less than `2**160 - 1`.
    error Permit2AmountOverflow();

    /// @dev The Permit2 approve operation has failed.
    error Permit2ApproveFailed();

    /// @dev The Permit2 lockdown operation has failed.
    error Permit2LockdownFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /// @dev The unique EIP-712 domain separator for the DAI token contract.
    bytes32 internal constant DAI_DOMAIN_SEPARATOR =
        0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;

    /// @dev The address for the WETH9 contract on Ethereum mainnet.
    address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    /// @dev The canonical Permit2 address.
    /// [Github](https://github.com/Uniswap/permit2)
    /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
    address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /// @dev The canonical address of the `SELFDESTRUCT` ETH mover.
    /// See: https://gist.github.com/Vectorized/1cb8ad4cf393b1378e08f23f79bd99fa
    /// [Etherscan](https://etherscan.io/address/0x00000000000073c48c8055bD43D1A53799176f0D)
    address internal constant ETH_MOVER = 0x00000000000073c48c8055bD43D1A53799176f0D;

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

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Force transfers ETH to `to`, without triggering the fallback (if any).
    /// This method attempts to use a separate contract to send via `SELFDESTRUCT`,
    /// and upon failure, deploys a minimal vault to accrue the ETH.
    function safeMoveETH(address to, uint256 amount) internal returns (address vault) {
        /// @solidity memory-safe-assembly
        assembly {
            to := shr(96, shl(96, to)) // Clean upper 96 bits.
            for { let mover := ETH_MOVER } iszero(eq(to, address())) {} {
                let selfBalanceBefore := selfbalance()
                if or(lt(selfBalanceBefore, amount), eq(to, mover)) {
                    mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                    revert(0x1c, 0x04)
                }
                if extcodesize(mover) {
                    let balanceBefore := balance(to) // Check via delta, in case `SELFDESTRUCT` is bricked.
                    mstore(0x00, to)
                    pop(call(gas(), mover, amount, 0x00, 0x20, codesize(), 0x00))
                    // If `address(to).balance >= amount + balanceBefore`, skip vault workflow.
                    if iszero(lt(balance(to), add(amount, balanceBefore))) { break }
                    // Just in case `SELFDESTRUCT` is changed to not revert and do nothing.
                    if lt(selfBalanceBefore, selfbalance()) { invalid() }
                }
                let m := mload(0x40)
                // If the mover is missing or bricked, deploy a minimal vault
                // that withdraws all ETH to `to` when being called only by `to`.
                // forgefmt: disable-next-item
                mstore(add(m, 0x20), 0x33146025575b600160005260206000f35b3d3d3d3d47335af1601a5760003dfd)
                mstore(m, or(to, shl(160, 0x6035600b3d3960353df3fe73)))
                // Compute and store the bytecode hash.
                mstore8(0x00, 0xff) // Write the prefix.
                mstore(0x35, keccak256(m, 0x40))
                mstore(0x01, shl(96, address())) // Deployer.
                mstore(0x15, 0) // Salt.
                vault := keccak256(0x00, 0x55)
                pop(call(gas(), vault, amount, codesize(), 0x00, codesize(), 0x00))
                // The vault returns a single word on success. Failure reverts with empty data.
                if iszero(returndatasize()) {
                    if iszero(create2(0, m, 0x40, 0)) { revert(codesize(), codesize()) } // For gas estimation.
                }
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

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

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function trySafeTransferFrom(address token, address from, address to, uint256 amount)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x34, 0) // Store 0 for the `amount`.
                    mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                    pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                    mstore(0x34, amount) // Store back the original `amount`.
                    // Retry the approval, reverting upon failure.
                    success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    if iszero(and(eq(mload(0x00), 1), success)) {
                        // Check the `extcodesize` again just in case the token selfdestructs lol.
                        if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                            mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                            revert(0x1c, 0x04)
                        }
                    }
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul( // The arguments of `mul` are evaluated from right to left.
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }

    /// @dev Performs a `token.balanceOf(account)` check.
    /// `implemented` denotes whether the `token` does not implement `balanceOf`.
    /// `amount` is zero if the `token` does not implement `balanceOf`.
    function checkBalanceOf(address token, address account)
        internal
        view
        returns (bool implemented, uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            implemented :=
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                )
            amount := mul(mload(0x20), implemented)
        }
    }

    /// @dev Returns the total supply of the `token`.
    /// Reverts if the token does not exist or does not implement `totalSupply()`.
    function totalSupply(address token) internal view returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x18160ddd) // `totalSupply()`.
            if iszero(
                and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20))
            ) {
                mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`.
                revert(0x1c, 0x04)
            }
            result := mload(0x00)
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// If the initial attempt fails, try to use Permit2 to transfer the token.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
        if (!trySafeTransferFrom(token, from, to, amount)) {
            permit2TransferFrom(token, from, to, amount);
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
    /// Reverts upon failure.
    function permit2TransferFrom(address token, address from, address to, uint256 amount)
        internal
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(m, 0x74), shr(96, shl(96, token)))
            mstore(add(m, 0x54), amount)
            mstore(add(m, 0x34), to)
            mstore(add(m, 0x20), shl(96, from))
            // `transferFrom(address,address,uint160,address)`.
            mstore(m, 0x36c78516000000000000000000000000)
            let p := PERMIT2
            let exists := eq(chainid(), 1)
            if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
            if iszero(
                and(
                    call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
                    lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
                )
            ) {
                mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
            }
        }
    }

    /// @dev Permit a user to spend a given amount of
    /// another user's tokens via native EIP-2612 permit if possible, falling
    /// back to Permit2 if native permit fails or is not implemented on the token.
    function permit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        bool success;
        /// @solidity memory-safe-assembly
        assembly {
            for {} shl(96, xor(token, WETH9)) {} {
                mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
                if iszero(
                    and( // The arguments of `and` are evaluated from right to left.
                        lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
                        // Gas stipend to limit gas burn for tokens that don't refund gas when
                        // an non-existing function is called. 5K should be enough for a SLOAD.
                        staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
                    )
                ) { break }
                // After here, we can be sure that token is a contract.
                let m := mload(0x40)
                mstore(add(m, 0x34), spender)
                mstore(add(m, 0x20), shl(96, owner))
                mstore(add(m, 0x74), deadline)
                if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
                    mstore(0x14, owner)
                    mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
                    mstore(
                        add(m, 0x94),
                        lt(iszero(amount), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
                    )
                    mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
                    // `nonces` is already at `add(m, 0x54)`.
                    // `amount != 0` is already stored at `add(m, 0x94)`.
                    mstore(add(m, 0xb4), and(0xff, v))
                    mstore(add(m, 0xd4), r)
                    mstore(add(m, 0xf4), s)
                    success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
                    break
                }
                mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
                mstore(add(m, 0x54), amount)
                mstore(add(m, 0x94), and(0xff, v))
                mstore(add(m, 0xb4), r)
                mstore(add(m, 0xd4), s)
                success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
                break
            }
        }
        if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
    }

    /// @dev Simple permit on the Permit2 contract.
    function simplePermit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(m, 0x927da105) // `allowance(address,address,address)`.
            {
                let addressMask := shr(96, not(0))
                mstore(add(m, 0x20), and(addressMask, owner))
                mstore(add(m, 0x40), and(addressMask, token))
                mstore(add(m, 0x60), and(addressMask, spender))
                mstore(add(m, 0xc0), and(addressMask, spender))
            }
            let p := mul(PERMIT2, iszero(shr(160, amount)))
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
                    staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
                )
            ) {
                mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(p))), 0x04)
            }
            mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
            // `owner` is already `add(m, 0x20)`.
            // `token` is already at `add(m, 0x40)`.
            mstore(add(m, 0x60), amount)
            mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
            // `nonce` is already at `add(m, 0xa0)`.
            // `spender` is already at `add(m, 0xc0)`.
            mstore(add(m, 0xe0), deadline)
            mstore(add(m, 0x100), 0x100) // `signature` offset.
      

Tags:
ERC20, Multisig, Mintable, Burnable, Pausable, Swap, Liquidity, Upgradeable, Multi-Signature, Factory|addr:0x1ac61c38117079cbe443274ea7bbc2d073f12106|verified:true|block:23507434|tx:0xcff76655fa964d606812f274f156134d8fea1e9b75385cd90234ec4017b84746|first_check:1759648299

Submitted on: 2025-10-05 09:11:41

Comments

Log in to comment.

No comments yet.