DRLVaultV3

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/INonfungiblePositionManager.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol";
import "@uniswap/v3-core/contracts/libraries/FixedPoint96.sol";
import "./interfaces/IV3SwapRouter.sol";
import "./interfaces/IUniswapV3Factory.sol";
import "./interfaces/IUniswapV3Pool.sol";
import "./interfaces/IDRLFactory.sol";
import "./libraries/PriceTick.sol";
import "./libraries/LiquidityHelper.sol";

interface IWETH9 {
    function deposit() external payable;

    function withdraw(uint256 wad) external;
}

contract DRLVaultV3 is ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Strings for uint256;

    // Constants
    int24 constant MAX_TICK = 887272;
    int24 constant MIN_TICK = -887272;

    // Initialization flag
    bool private initialized;

    // Vault factory
    address public vaultFactory;

    // Vault owner
    address public owner;

    // NonFungiblePositionManager address
    INonfungiblePositionManager public positionManager;

    // QuoterV2 address
    IQuoterV2 public quoterV2;

    // SwapRouter
    IV3SwapRouter public swapRouter;

    // Uniswap v3 factory
    IUniswapV3Factory public uniswapV3Factory;

    // WETH address
    address public WETH;

    // NFT tokenId
    uint256 public lpTokenId;

    // Vault fee
    uint24 public fee;

    uint24 public slippageBps;

    struct UserLiquidity {
        uint256 tokenId;
        uint128 liquidity;
    }

    mapping(address => UserLiquidity) public userLiquidity;

    // token0 (sorted)
    address public token0;

    // token1 (sorted)
    address public token1;

    // Events
    event TokensDeposited(address indexed user, address token, uint256 amount);
    event TokensWithdrawn(address indexed user, address token, uint256 amount);
    event UserClaimed(address indexed user, address token, uint256 amount);
    event FeesCollected(address indexed user, address token, uint256 amount);
    event PositionMinted(address indexed user, uint256 tokenId, int24 tickLower, int24 tickUpper);
    event PositionRemoved(address indexed user, uint256 tokenId);
    event LiquidityRebalanced(
        address indexed user, uint256 oldTokenId, uint256 newTokenId, int24 newTickLower, int24 newTickUpper
    );

    // Errors
    error AddLiquidityFailed();
    error PoolDoesNotExist();
    error NoLiquidityToRebalance();

    // Modifiers
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    modifier onlyOperator() {
        address operator = IDRLFactory(vaultFactory).getOperator();
        require(msg.sender == operator, "Not operator");
        _;
    }

    /// @notice Initialize the vault (replaces constructor for clone pattern)
    /// @dev Can only be called once
    function initialize(
        address _tokenA,
        address _tokenB,
        uint24 _fee,
        address _weth,
        address _operator,
        address _positionManager,
        address _quoterV2,
        address _swapRouter,
        address _v3Factory,
        address _vaultFactory,
        address _owner
    ) external {
        require(!initialized, "Already initialized");
        initialized = true;

        vaultFactory = _vaultFactory;
        owner = _owner;
        fee = _fee;
        WETH = _weth;
        (token0, token1) = sortTokens(_tokenA, _tokenB);
        positionManager = INonfungiblePositionManager(_positionManager);
        quoterV2 = IQuoterV2(_quoterV2);
        swapRouter = IV3SwapRouter(_swapRouter);
        uniswapV3Factory = IUniswapV3Factory(_v3Factory);

        // Default slippage: 50 = 0.5%
        slippageBps = 50;
    }

    /// @notice Add all available liquidity to a new position
    /// @param _fee Pool fee tier
    /// @param _priceLower Lower price bound
    /// @param _priceUpper Upper price bound
    function addLiquidityALL(uint24 _fee, uint256 _priceLower, uint256 _priceUpper) external onlyOperator {
        require(userLiquidity[owner].tokenId == 0, "LP already exist!");

        // Update fee
        fee = _fee;

        // Get Pool address and verify it exists
        address poolAddress = uniswapV3Factory.getPool(token0, token1, _fee);
        if (poolAddress == address(0)) revert PoolDoesNotExist();
        IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);

        int24 currentTick;
        uint160 sqrtPriceX96;
        (sqrtPriceX96, currentTick,,,,,) = pool.slot0();

        int24 _tickSpacing = pool.tickSpacing();
        (int24 tickLower, int24 tickUpper) =
            calculatePriceToTicker(uint160(_priceLower), uint160(_priceUpper), _tickSpacing);

        // Get sqrt ratios
        uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
        uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
        require(sqrtRatioAX96 < sqrtPriceX96 && sqrtPriceX96 < sqrtRatioBX96, "Price not in range");

        uint256 _totalUSDC = IERC20(token0).balanceOf(address(this));
        require(_totalUSDC > 0, "Not enough USDC");

        (uint256 amount0, uint256 amount1) =
            LiquidityHelper.getLiquidityAmounts(_totalUSDC, sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96);

        uint256 requiredUSDC = 0;
        uint256 requiredWETH = 0;
        if (currentTick < tickLower) {
            // Add USDC side liquidity
            requiredUSDC = _totalUSDC;
            requiredWETH = 0;
        } else if (currentTick > tickUpper) {
            // Add WETH side liquidity
            requiredUSDC = 0;
            requiredWETH = swapToWETH(_totalUSDC);
        } else {
            // Add range liquidity
            requiredUSDC = amount0;
            requiredWETH = swapToWETH(amount1);
        }

        IERC20(token0).approve(address(positionManager), requiredUSDC);
        IERC20(token1).approve(address(positionManager), requiredWETH);

        // Mint position with slippage protection
        INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
            token0: token0,
            token1: token1,
            fee: _fee,
            tickLower: tickLower,
            tickUpper: tickUpper,
            amount0Desired: requiredUSDC,
            amount1Desired: requiredWETH,
            amount0Min: (requiredUSDC * (10000 - slippageBps)) / 10000,
            amount1Min: (requiredWETH * (10000 - slippageBps)) / 10000,
            recipient: address(this),
            deadline: block.timestamp + 300
        });

        (uint256 _tokenId, uint128 _liquidity,,) = positionManager.mint(params);

        lpTokenId = _tokenId;
        userLiquidity[owner].tokenId = _tokenId;
        userLiquidity[owner].liquidity = _liquidity;

        emit PositionMinted(owner, _tokenId, tickLower, tickUpper);

        // Swap remaining WETH to USDC
        swapToUsdc();
    }

    /// @notice Rebalance liquidity to a new price range
    /// @param _priceLower New lower price bound
    /// @param _priceUpper New upper price bound
    function rebase(uint256 _priceLower, uint256 _priceUpper) external onlyOperator nonReentrant {
        // 1. Check if liquidity exists
        if (userLiquidity[owner].tokenId == 0) revert NoLiquidityToRebalance();

        uint256 oldTokenId = userLiquidity[owner].tokenId;

        // 2. Remove existing liquidity
        _removeLiquidity();

        // 3. Get current token balances after removing liquidity
        uint256 currentToken0 = IERC20(token0).balanceOf(address(this));
        uint256 currentToken1 = IERC20(token1).balanceOf(address(this));
        require(currentToken0 > 0 || currentToken1 > 0, "No tokens after removal");

        // 4. Get pool info
        address poolAddress = uniswapV3Factory.getPool(token0, token1, fee);
        if (poolAddress == address(0)) revert PoolDoesNotExist();
        IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);

        int24 currentTick;
        uint160 sqrtPriceX96;
        (sqrtPriceX96, currentTick,,,,,) = pool.slot0();

        int24 _tickSpacing = pool.tickSpacing();
        (int24 tickLower, int24 tickUpper) =
            calculatePriceToTicker(uint160(_priceLower), uint160(_priceUpper), _tickSpacing);

        // Get sqrt ratios
        uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
        uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
        require(sqrtRatioAX96 < sqrtPriceX96 && sqrtPriceX96 < sqrtRatioBX96, "Price not in range");

        // 5. Calculate total value in token0 terms
        uint256 token1ValueInToken0 = currentToken1 > 0 ? getQuoteForWETH(fee, currentToken1) : 0;
        uint256 totalValueInToken0 = currentToken0 + token1ValueInToken0;

        // 6. Calculate required token amounts for new range
        uint256 requiredToken0;
        uint256 requiredToken1;

        if (currentTick < tickLower) {
            // Below range: need 100% token0
            requiredToken0 = totalValueInToken0;
            requiredToken1 = 0;

            // Swap all token1 to token0 if needed
            if (currentToken1 > 0) {
                uint256 received = _swapExactInput(token1, token0, currentToken1);
                currentToken0 = IERC20(token0).balanceOf(address(this));
            }
        } else if (currentTick > tickUpper) {
            // Above range: need 100% token1
            requiredToken0 = 0;
            requiredToken1 = currentToken1 + (currentToken0 > 0 ? getQuoteForUSDC(fee, currentToken0) : 0);

            // Swap all token0 to token1 if needed
            if (currentToken0 > 0) {
                uint256 received = _swapExactInput(token0, token1, currentToken0);
                currentToken1 = IERC20(token1).balanceOf(address(this));
            }
        } else {
            // In range: calculate optimal ratio
            (uint256 amount0Needed, uint256 amount1ToSwap) =
                LiquidityHelper.getLiquidityAmounts(totalValueInToken0, sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96);

            requiredToken0 = amount0Needed;

            // Calculate how much token1 we need
            uint256 amount1Needed = getQuoteForUSDC(fee, amount1ToSwap);
            requiredToken1 = amount1Needed;

            // Adjust balances with single swap
            if (currentToken0 > amount0Needed) {
                // Have excess token0, need more token1
                uint256 excessToken0 = currentToken0 - amount0Needed;
                uint256 token1Deficit = amount1Needed > currentToken1 ? amount1Needed - currentToken1 : 0;

                if (token1Deficit > 0) {
                    // Calculate how much token0 to swap
                    uint256 token0ToSwap = excessToken0 < amount1ToSwap ? excessToken0 : amount1ToSwap;
                    if (token0ToSwap > 0) {
                        _swapExactInput(token0, token1, token0ToSwap);
                    }
                }
            } else if (currentToken1 > amount1Needed) {
                // Have excess token1, need more token0
                uint256 excessToken1 = currentToken1 - amount1Needed;
                uint256 token0Deficit = amount0Needed > currentToken0 ? amount0Needed - currentToken0 : 0;

                if (token0Deficit > 0 && excessToken1 > 0) {
                    // Swap excess token1 to token0
                    _swapExactInput(token1, token0, excessToken1);
                }
            }
        }

        // 7. Get final balances after swaps
        uint256 finalToken0 = IERC20(token0).balanceOf(address(this));
        uint256 finalToken1 = IERC20(token1).balanceOf(address(this));

        // 8. Add liquidity in new range
        IERC20(token0).approve(address(positionManager), finalToken0);
        IERC20(token1).approve(address(positionManager), finalToken1);

        INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
            token0: token0,
            token1: token1,
            fee: fee,
            tickLower: tickLower,
            tickUpper: tickUpper,
            amount0Desired: finalToken0,
            amount1Desired: finalToken1,
            amount0Min: (finalToken0 * (10000 - slippageBps)) / 10000,
            amount1Min: (finalToken1 * (10000 - slippageBps)) / 10000,
            recipient: address(this),
            deadline: block.timestamp + 300
        });

        (uint256 _tokenId, uint128 _liquidity,,) = positionManager.mint(params);

        lpTokenId = _tokenId;
        userLiquidity[owner].tokenId = _tokenId;
        userLiquidity[owner].liquidity = _liquidity;

        emit LiquidityRebalanced(owner, oldTokenId, _tokenId, tickLower, tickUpper);

        // 9. Optionally swap remaining dust back to token0
        uint256 remainingToken1 = IERC20(token1).balanceOf(address(this));
        if (remainingToken1 > 0) {
            swapToUsdc();
        }
    }

    /// @notice Internal function to perform exact input swap
    /// @param tokenIn Input token address
    /// @param tokenOut Output token address
    /// @param amountIn Amount to swap
    /// @return amountOut Amount received
    function _swapExactInput(address tokenIn, address tokenOut, uint256 amountIn) internal returns (uint256 amountOut) {
        if (amountIn == 0) return 0;

        IERC20(tokenIn).approve(address(swapRouter), amountIn);

        // Get quote for slippage protection
        uint256 expectedOut;
        if (tokenIn == WETH) {
            expectedOut = getQuoteForWETH(fee, amountIn);
        } else {
            expectedOut = getQuoteForUSDC(fee, amountIn);
        }

        uint256 minAmountOut = (expectedOut * (10000 - slippageBps)) / 10000;

        IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: tokenOut,
            fee: fee,
            recipient: address(this),
            amountIn: amountIn,
            amountOutMinimum: minAmountOut,
            sqrtPriceLimitX96: 0
        });

        amountOut = swapRouter.exactInputSingle(params);
    }

    /// @notice Operator add liquidity with specific parameters
    function addLiquidity(
        address _token0,
        address _token1,
        uint256 amount0ToMint,
        uint256 amount1ToMint,
        uint24 _fee,
        int24 tickLower,
        int24 tickUpper
    ) external onlyOperator {
        require(userLiquidity[owner].tokenId == 0, "Vault liquidity already created!");

        if ((_token0 != token0 && _token1 != token1) && (_token0 != token1 && _token1 != token0)) {
            revert("Liquidity token not supported");
        }

        if (_token0 == WETH) {
            require(address(this).balance >= amount0ToMint, "Not enough WETH balance");
            require(IERC20(_token1).balanceOf(address(this)) >= amount1ToMint, "Not enough token balance");
            IWETH9(_token0).deposit{value: amount0ToMint}();
            uint256 _wethBalance = IERC20(_token0).balanceOf(address(this));
            require(
                _wethBalance >= amount0ToMint,
                string(abi.encodePacked("Token0 Not enough eth balance: ", _wethBalance.toString()))
            );
            require(IERC20(_token1).balanceOf(address(this)) >= amount1ToMint, "Not enough USDC token");
        } else {
            IWETH9(_token1).deposit{value: amount1ToMint}();
            require(IERC20(_token1).balanceOf(address(this)) >= amount1ToMint, "Token1 Not enough eth balance");
            require(IERC20(_token0).balanceOf(address(this)) >= amount0ToMint, "Not enough USDC token");
        }

        IERC20(_token0).approve(address(positionManager), amount0ToMint);
        IERC20(_token1).approve(address(positionManager), amount1ToMint);

        INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
            token0: token0,
            token1: token1,
            fee: _fee,
            tickLower: tickLower,
            tickUpper: tickUpper,
            amount0Desired: amount0ToMint,
            amount1Desired: amount1ToMint,
            amount0Min: (amount0ToMint * (10000 - slippageBps)) / 10000,
            amount1Min: (amount1ToMint * (10000 - slippageBps)) / 10000,
            recipient: address(this),
            deadline: block.timestamp + 300
        });

        (uint256 _tokenId, uint128 _liquidity,,) = positionManager.mint(params);

        lpTokenId = _tokenId;
        userLiquidity[owner].tokenId = _tokenId;
        userLiquidity[owner].liquidity = _liquidity;

        emit PositionMinted(owner, _tokenId, tickLower, tickUpper);
    }

    /// @notice Operator remove all liquidity
    function removeLiquidity() external onlyOperator {
        require(userLiquidity[owner].tokenId != 0, "Vault liquidity does not exist");
        _removeLiquidity();
        swapToUsdc();
    }

    /// @notice Internal function to remove liquidity
    function _removeLiquidity() internal {
        uint128 liquidity = userLiquidity[owner].liquidity;
        uint256 tokenId = userLiquidity[owner].tokenId;

        INonfungiblePositionManager.DecreaseLiquidityParams memory params =
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: tokenId, liquidity: liquidity, amount0Min: 0, amount1Min: 0, deadline: block.timestamp + 300
            });

        positionManager.decreaseLiquidity(params);
        _collect();
        positionManager.burn(tokenId);

        emit PositionRemoved(owner, tokenId);

        // Reset liquidity
        delete userLiquidity[owner];
    }

    /// @notice Get vault balance in token0 (USDC)
    /// @return amount Balance of token0
    function getVaultBalance() external view returns (uint256 amount) {
        return IERC20(token0).balanceOf(address(this));
    }

    /// @notice Owner withdraw tokens
    function withdrawTokens(address token, uint256 amount) external onlyOwner nonReentrant {
        require(token == token1 || token == token0, "Invalid token");
        require(amount > 0, "Invalid amount");

        if (token == WETH) {
            uint256 balance = IERC20(WETH).balanceOf(address(this));
            IWETH9(WETH).withdraw(balance);
            require(address(this).balance >= amount, "Insufficient balance");
            (bool sent,) = msg.sender.call{value: amount}("");
            require(sent, "ETH transfer failed");
        } else {
            IERC20(token).transfer(msg.sender, amount);
        }

        emit TokensWithdrawn(msg.sender, token, amount);
    }

    receive() external payable {}

    fallback() external payable {}

    /// @notice Deposit tokens into vault
    function deposite(uint256 amount1) external payable onlyOwner nonReentrant {
        require(amount1 > 0 || msg.value > 0, "No tokens or ETH sent");

        if (msg.value > 0) {
            emit TokensDeposited(msg.sender, token0 == WETH ? token0 : token1, msg.value);
        }

        if (amount1 > 0) {
            if (token0 != WETH) {
                IERC20(token0).transferFrom(msg.sender, address(this), amount1);
                emit TokensDeposited(msg.sender, token0, amount1);
            } else {
                IERC20(token1).transferFrom(msg.sender, address(this), amount1);
                emit TokensDeposited(msg.sender, token1, amount1);
            }
        }
    }

    /// @notice Sort tokens to ensure token0 < token1
    function sortTokens(address tokenA, address tokenB) internal pure returns (address _token0, address _token1) {
        require(tokenA != tokenB, "Identical addresses");
        require(tokenA != address(0) && tokenB != address(0), "Zero address");

        if (tokenA < tokenB) {
            (_token0, _token1) = (tokenA, tokenB);
        } else {
            (_token0, _token1) = (tokenB, tokenA);
        }
    }

    /// @notice Collect fees from position
    /// @return amount0 Amount of token0 collected
    /// @return amount1 Amount of token1 collected
    function _collect() internal returns (uint256 amount0, uint256 amount1) {
        require(IERC721(address(positionManager)).ownerOf(lpTokenId) == address(this), "Not NFT owner");

        INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
            tokenId: lpTokenId, recipient: address(this), amount0Max: type(uint128).max, amount1Max: type(uint128).max
        });

        (amount0, amount1) = positionManager.collect(params);
        return (amount0, amount1);
    }

    /// @notice Owner collect fees
    function collectFees() external onlyOwner {
        _collect();
        uint256 _feesAmount = swapToUsdc();
        emit FeesCollected(msg.sender, token0, _feesAmount);
        _sendToOwner();
    }

    /// @notice Get LP token ID
    function getLPTokenId() external view onlyOwner returns (uint256) {
        require(userLiquidity[owner].tokenId > 0, "Not owned a tokenId");
        return lpTokenId;
    }

    /// @notice Send all tokens to owner
    function _sendToOwner() internal {
        uint256 usdcBalance = IERC20(token0).balanceOf(address(this));
        uint256 wethBalance = IERC20(token1).balanceOf(address(this));

        if (usdcBalance > 0) {
            TransferHelper.safeTransfer(token0, owner, usdcBalance);
        }

        if (wethBalance > 0) {
            IWETH9(WETH).withdraw(wethBalance);
            (bool sent,) = owner.call{value: wethBalance}("");
            require(sent, "ETH transfer failed");
        }
    }

    /// @notice Swap all ETH/WETH to USDC
    /// @return _amountOut Amount of USDC received
    function swapToUsdc() internal returns (uint256 _amountOut) {
        uint256 ethBalance = address(this).balance;
        uint256 wethBalance = IERC20(WETH).balanceOf(address(this));

        if (ethBalance == 0 && wethBalance == 0) return 0;

        if (ethBalance > 0) {
            IWETH9(WETH).deposit{value: ethBalance}();
        }

        wethBalance = IERC20(WETH).balanceOf(address(this));
        if (wethBalance == 0) return 0;

        uint256 expectedAmountOut = getQuoteForWETH(fee, wethBalance);

        // Fixed slippage calculation
        uint256 _amountOutMinimum = (expectedAmountOut * (10000 - slippageBps)) / 10000;

        IERC20(WETH).approve(address(swapRouter), wethBalance);
        IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
            tokenIn: WETH,
            tokenOut: token0,
            fee: fee,
            recipient: address(this),
            amountIn: wethBalance,
            amountOutMinimum: _amountOutMinimum,
            sqrtPriceLimitX96: 0
        });

        _amountOut = swapRouter.exactInputSingle(params);
    }

    /// @notice Swap USDC to WETH
    /// @param _amount Amount of USDC to swap
    /// @return _amountOut Amount of WETH received
    function swapToWETH(uint256 _amount) public returns (uint256 _amountOut) {
        if (_amount == 0) return 0;

        uint256 tokenBalance;
        address tokenIn;

        if (token0 == WETH) {
            tokenBalance = IERC20(token1).balanceOf(address(this));
            require(tokenBalance >= _amount, "Not enough balance");
            tokenIn = token1;
            IERC20(token1).approve(address(swapRouter), _amount);
        } else {
            tokenBalance = IERC20(token0).balanceOf(address(this));
            require(tokenBalance >= _amount, "Not enough balance");
            IERC20(token0).approve(address(swapRouter), _amount);
            tokenIn = token0;
        }

        uint256 expectedAmountOut = getQuoteForUSDC(fee, _amount);

        // Fixed slippage calculation
        uint256 _amountOutMinimum = (expectedAmountOut * (10000 - slippageBps)) / 10000;

        IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: WETH,
            fee: fee,
            recipient: address(this),
            amountIn: _amount,
            amountOutMinimum: _amountOutMinimum,
            sqrtPriceLimitX96: 0
        });

        _amountOut = swapRouter.exactInputSingle(params);
    }

    /// @notice Swap all USDC to WETH
    function swapAllToWETH() external onlyOwner {
        uint256 tokenBalance;
        address tokenIn;

        if (token0 == WETH) {
            tokenBalance = IERC20(token1).balanceOf(address(this));
            tokenIn = token1;
            IERC20(token1).approve(address(swapRouter), tokenBalance);
        } else {
            tokenBalance = IERC20(token0).balanceOf(address(this));
            IERC20(token0).approve(address(swapRouter), tokenBalance);
            tokenIn = token0;
        }

        if (tokenBalance == 0) return;

        IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
            tokenIn: tokenIn,
            tokenOut: WETH,
            fee: fee,
            recipient: address(this),
            amountIn: tokenBalance,
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0
        });

        swapRouter.exactInputSingle(params);

        uint256 wethBalance = IERC20(WETH).balanceOf(address(this));
        IWETH9(WETH).withdraw(wethBalance);
    }

    /// @notice User claim all vault assets
    function claim() external onlyOwner {
        // Remove liquidity if exists
        if (userLiquidity[owner].tokenId != 0) {
            _removeLiquidity();
        }

        // Swap all to USDC
        swapToUsdc();

        // Transfer all USDC to owner
        if (token0 != WETH) {
            uint256 tokenBalance = IERC20(token0).balanceOf(address(this));
            TransferHelper.safeTransfer(token0, owner, tokenBalance);
            emit UserClaimed(msg.sender, token0, tokenBalance);
        } else {
            uint256 tokenBalance = IERC20(token1).balanceOf(address(this));
            TransferHelper.safeTransfer(token1, owner, tokenBalance);
            emit UserClaimed(msg.sender, token1, tokenBalance);
        }
    }

    /// @notice Calculate price to ticker
    function calculatePriceToTicker(uint160 _lowerPrice, uint160 _upperPrice, int24 _tickSpacing)
        public
        pure
        returns (int24, int24)
    {
        require(_lowerPrice < _upperPrice, "Invalid price range");
        (int24 tickLower, int24 tickUpper) = PriceTick.getTickRangeV2(_lowerPrice, _upperPrice, _tickSpacing);
        return (tickLower, tickUpper);
    }

    /// @notice Get quote for swapping USDC to WETH
    function getQuoteForUSDC(uint24 _fee, uint256 _amountIn) public returns (uint256) {
        IQuoterV2.QuoteExactInputSingleParams memory quoteParams = IQuoterV2.QuoteExactInputSingleParams({
            tokenIn: token0, tokenOut: WETH, fee: _fee, amountIn: _amountIn, sqrtPriceLimitX96: 0
        });

        (uint256 amountOut,,,) = quoterV2.quoteExactInputSingle(quoteParams);
        return amountOut;
    }

    /// @notice Get quote for swapping WETH to USDC
    function getQuoteForWETH(uint24 _fee, uint256 _amountIn) public returns (uint256) {
        IQuoterV2.QuoteExactInputSingleParams memory quoteParams = IQuoterV2.QuoteExactInputSingleParams({
            tokenIn: WETH, tokenOut: token0, fee: _fee, amountIn: _amountIn, sqrtPriceLimitX96: 0
        });

        (uint256 amountOut,,,) = quoterV2.quoteExactInputSingle(quoteParams);
        return amountOut;
    }

    /// @notice Get current price quote
    function getQuote(uint24 _fee) public returns (uint256) {
        IQuoterV2.QuoteExactInputSingleParams memory quoteParams = IQuoterV2.QuoteExactInputSingleParams({
            tokenIn: WETH, tokenOut: token0, fee: _fee, amountIn: 1000000000000000000, sqrtPriceLimitX96: 0
        });

        (uint256 amountOut,,,) = quoterV2.quoteExactInputSingle(quoteParams);
        return amountOut;
    }

    /// @notice Get LP value in USDC
    /// @return _value Value of LP in USDC
    function getLPValue() external returns (uint256 _value) {
        require(userLiquidity[owner].tokenId > 0, "Not valid tokenId");

        (,, address _token0, address _token1, uint24 _fee, int24 tickLower, int24 tickUpper, uint128 liquidity,,,,) =
            positionManager.positions(userLiquidity[owner].tokenId);
        require(liquidity > 0, "No liquidity in this LP");

        address poolAddress = uniswapV3Factory.getPool(token0, token1, fee);
        IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
        (uint160 sqrtPriceX96,,,,,,) = pool.slot0();

        uint160 sqrtLowerX96 = TickMath.getSqrtRatioAtTick(tickLower);
        uint160 sqrtUpperX96 = TickMath.getSqrtRatioAtTick(tickUpper);

        (uint256 amount0, uint256 amount1) =
            LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtLowerX96, sqrtUpperX96, liquidity);

        uint8 decimals0 = 6;
        uint8 decimals1 = 18;

        uint256 priceX96 = (uint256(sqrtPriceX96) * uint256(sqrtPriceX96)) / (1 << 192);

        uint256 token1ValueInUSDC = (amount1 * priceX96 * (10 ** decimals0)) / (10 ** decimals1);

        uint256 lpValuedUSDC = amount0 + token1ValueInUSDC;

        return lpValuedUSDC;
    }

    /// @notice Get slippage BPS
    /// @return _slippageBps Slippage in basis points
    function getSlippageBps() external view returns (uint24 _slippageBps) {
        return slippageBps;
    }

    /// @notice Set slippage BPS
    /// @param _slippage Slippage in basis points (50 = 0.5%, 500 = 5%)
    function setSlippageBps(uint24 _slippage) external onlyOwner {
        require(_slippage <= 1000, "Slippage too high"); // Max 10%
        slippageBps = _slippage;
    }

    /// @notice Get operator address
    function getOperator() external view returns (address) {
        address operator = IDRLFactory(vaultFactory).getOperator();
        return operator;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 *
 * IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
 * by the {ReentrancyGuardTransient} variant in v6.0.
 *
 * @custom:stateless
 */
abstract contract ReentrancyGuard {
    using StorageSlot for bytes32;

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant REENTRANCY_GUARD_STORAGE =
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

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

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

    constructor() {
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

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

    /**
     * @dev A `view` only version of {nonReentrant}. Use to block view functions
     * from being called, preventing reading from inconsistent contract state.
     *
     * CAUTION: This is a "view" modifier and does not change the reentrancy
     * status. Use it only on view functions. For payable or non-payable functions,
     * use the standard {nonReentrant} modifier instead.
     */
    modifier nonReentrantView() {
        _nonReentrantBeforeView();
        _;
    }

    function _nonReentrantBeforeView() private view {
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        _nonReentrantBeforeView();

        // Any calls to nonReentrant after this point will fail
        _reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

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

    function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
        return REENTRANCY_GUARD_STORAGE;
    }
}
"
    },
    "src/interfaces/INonfungiblePositionManager.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";

import "./IPoolInitializer.sol";
import "./IERC721Permit.sol";
import "./IPeripheryPayments.sol";
import "./IPeripheryImmutableState.sol";
import "../libraries/PoolAddress.sol";

/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
interface INonfungiblePositionManager is
    IPoolInitializer,
    IPeripheryPayments,
    IPeripheryImmutableState,
    IERC721Metadata,
    IERC721Enumerable,
    IERC721Permit
{
    /// @notice Emitted when liquidity is increased for a position NFT
    /// @dev Also emitted when a token is minted
    /// @param tokenId The ID of the token for which liquidity was increased
    /// @param liquidity The amount by which liquidity for the NFT position was increased
    /// @param amount0 The amount of token0 that was paid for the increase in liquidity
    /// @param amount1 The amount of token1 that was paid for the increase in liquidity
    event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    /// @notice Emitted when liquidity is decreased for a position NFT
    /// @param tokenId The ID of the token for which liquidity was decreased
    /// @param liquidity The amount by which liquidity for the NFT position was decreased
    /// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
    /// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
    event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    /// @notice Emitted when tokens are collected for a position NFT
    /// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior
    /// @param tokenId The ID of the token for which underlying tokens were collected
    /// @param recipient The address of the account that received the collected tokens
    /// @param amount0 The amount of token0 owed to the position that was collected
    /// @param amount1 The amount of token1 owed to the position that was collected
    event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1);

    /// @notice Returns the position information associated with a given token ID.
    /// @dev Throws if the token ID is not valid.
    /// @param tokenId The ID of the token that represents the position
    /// @return nonce The nonce for permits
    /// @return operator The address that is approved for spending
    /// @return token0 The address of the token0 for a specific pool
    /// @return token1 The address of the token1 for a specific pool
    /// @return fee The fee associated with the pool
    /// @return tickLower The lower end of the tick range for the position
    /// @return tickUpper The higher end of the tick range for the position
    /// @return liquidity The liquidity of the position
    /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
    /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
    /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
    /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation
    function positions(uint256 tokenId)
        external
        view
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    struct MintParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        address recipient;
        uint256 deadline;
    }

    /// @notice Creates a new position wrapped in a NFT
    /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
    /// a method does not exist, i.e. the pool is assumed to be initialized.
    /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
    /// @return tokenId The ID of the token that represents the minted position
    /// @return liquidity The amount of liquidity for this position
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function mint(MintParams calldata params)
        external
        payable
        returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

    struct IncreaseLiquidityParams {
        uint256 tokenId;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
    /// @param params tokenId The ID of the token for which liquidity is being increased,
    /// amount0Desired The desired amount of token0 to be spent,
    /// amount1Desired The desired amount of token1 to be spent,
    /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
    /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
    /// deadline The time by which the transaction must be included to effect the change
    /// @return liquidity The new liquidity amount as a result of the increase
    /// @return amount0 The amount of token0 to acheive resulting liquidity
    /// @return amount1 The amount of token1 to acheive resulting liquidity
    function increaseLiquidity(IncreaseLiquidityParams calldata params)
        external
        payable
        returns (uint128 liquidity, uint256 amount0, uint256 amount1);

    struct DecreaseLiquidityParams {
        uint256 tokenId;
        uint128 liquidity;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    /// @notice Decreases the amount of liquidity in a position and accounts it to the position
    /// @param params tokenId The ID of the token for which liquidity is being decreased,
    /// amount The amount by which liquidity will be decreased,
    /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,
    /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,
    /// deadline The time by which the transaction must be included to effect the change
    /// @return amount0 The amount of token0 accounted to the position's tokens owed
    /// @return amount1 The amount of token1 accounted to the position's tokens owed
    function decreaseLiquidity(DecreaseLiquidityParams calldata params)
        external
        payable
        returns (uint256 amount0, uint256 amount1);

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
    /// @param params tokenId The ID of the NFT for which tokens are being collected,
    /// recipient The account that should receive the tokens,
    /// amount0Max The maximum amount of token0 to collect,
    /// amount1Max The maximum amount of token1 to collect
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);

    /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens
    /// must be collected first.
    /// @param tokenId The ID of the token that is being burned
    function burn(uint256 tokenId) external payable;
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721.sol)

pragma solidity >=0.6.2;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

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

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

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

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

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

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

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

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
     * the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param spender The spender of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or

Tags:
ERC20, ERC721, ERC165, Proxy, Mintable, Burnable, Non-Fungible, Swap, Liquidity, Upgradeable, Factory|addr:0x8aa6b0e10bd6dbaf5159967f92f2e740afe2b4c3|verified:true|block:23731141|tx:0x5f3514f6cfe7bd128b338e25f3d294b8134f59acd6bedf03a575580f98895f9b|first_check:1762344855

Submitted on: 2025-11-05 13:14:17

Comments

Log in to comment.

No comments yet.