CurveDexModule

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": {
    "contracts/swap/CurveDexModule.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.30;\r
\r
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";\r
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";\r
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";\r
\r
/**\r
 * @title  CurveDexModule\r
 * @author Andrei Averin — CTO dsf.finance\r
 * @notice Single-pool Curve module for UniversalRouter\r
 * @dev    Relies on Curve MetaRegistry; routes always contain exactly one Curve pool.\r
 */\r
\r
/**\r
 * @title  IPoolRegistry\r
 * @notice Interface for accessing whitelisted Curve pools (copied from Registry).\r
 */\r
interface IPoolRegistry {\r
    struct PoolProfile {\r
        bool exIndexUint;\r
        bool exHasEthFlag;\r
    }\r
    \r
    function getVerifiedPools(address tokenA, address tokenB) external view returns (address[] memory);\r
    function getPoolProfile(address pool) external view returns (bool exists, PoolProfile memory profile);\r
}\r
\r
/* ──────────── External interfaces ──────────── */\r
\r
interface ICurveMetaRegistry {\r
    function find_pools_for_coins(address _from, address _to) external view returns (address[] memory);\r
    function get_coin_indices(address, address, address) external view returns (int128, int128, bool);\r
}\r
\r
interface ICurvePoolIntWithEth {\r
    function exchange(int128, int128, uint256, uint256, bool) external returns (uint256);\r
}\r
\r
interface ICurvePoolU256WithEth {\r
    function exchange(uint256, uint256, uint256, uint256, bool) external returns (uint256);\r
}\r
\r
interface ICurvePoolInt {\r
    function get_dy(uint256, uint256, uint256) external view returns (int256);\r
}\r
\r
interface ICurvePool {\r
    function get_dy(int128, int128, uint256) external view returns (uint256);\r
    function get_dy_underlying(int128, int128, uint256) external view returns (uint256);\r
    function exchange(int128, int128, uint256, uint256) external returns (uint256);\r
    function exchange_underlying(int128, int128, uint256, uint256) external returns (uint256);\r
    function coins(int128 arg0) external view returns (address);\r
    function underlying_coins(uint256) external view returns (address);\r
}\r
\r
interface ICurvePoolNew {\r
    function exchange(uint256, uint256, uint256, uint256) external returns (uint256);\r
    function exchange_underlying(uint256, uint256, uint256, uint256) external returns (uint256);\r
}\r
\r
/* ──────────── Aggregator interfaces ──────────── */\r
\r
interface IDexModule {\r
    function getBestRoute(\r
        address           tokenIn,\r
        address           tokenOut,\r
        uint256           amountIn\r
    ) external view returns (\r
        DexRoute memory   best1HopRoute,\r
        uint256           amountOut1Hop,\r
        DexRoute memory   best2HopRoute,\r
        uint256           amountOut2Hop\r
    );\r
\r
    function swapRoute(\r
        DexRoute calldata route,\r
        address           to,\r
        uint256           percent\r
    ) external returns (\r
        uint256           amountOut\r
    );\r
\r
    function simulateRoute(\r
        DexRoute calldata route,\r
        uint256 percent\r
    ) external view returns (uint256 amountOut);\r
}\r
\r
struct DexRoute {\r
    bytes[] data;\r
}\r
\r
struct Quote {\r
    address pool;\r
    int128  i;\r
    int128  j;\r
    bool    useUnderlying;\r
    uint256 amountOut;\r
}\r
\r
struct RouteStep {\r
    address tokenIn;\r
    address tokenOut;\r
    address pool;\r
    int128  i;\r
    int128  j;\r
    bool    useUnderlying;\r
    uint256 amountIn;\r
}\r
\r
/* ────────────────── Dex module ───────────────── */\r
\r
contract CurveDexModule is IDexModule, Ownable {\r
    using SafeERC20 for IERC20;\r
\r
    // Events\r
    event PoolRegistryUpdated(address indexed previousRegistry, address indexed newRegistry);\r
\r
    // Immutable addresses\r
    ICurveMetaRegistry public constant META_REGISTRY = ICurveMetaRegistry(0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC);\r
\r
    // External Pool Registry for whitelisting pools\r
    IPoolRegistry public POOL_REGISTRY;\r
\r
    // Tokens\r
    address public constant CRV_USD = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E;\r
    address public constant USDT    = 0xdAC17F958D2ee523a2206206994597C13D831ec7;\r
    address public constant USDC    = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;\r
    address public constant DAI     = 0x6B175474E89094C44Da98b954EedeAC495271d0F;\r
    address public constant _3CRV   = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490;\r
    address public constant WETH    = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;\r
    address public constant WBTC    = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;\r
    address public constant FRAX    = 0x853d955aCEf822Db058eb8505911ED77F175b99e;\r
\r
    address[8] internal HUB_TOKENS = [CRV_USD, USDT, USDC, DAI, _3CRV, WETH, WBTC, FRAX];\r
\r
    // Selectors\r
    bytes4 constant EX_INT128  = ICurvePool.exchange.selector;\r
    bytes4 constant EXU_INT128 = ICurvePool.exchange_underlying.selector;\r
    bytes4 constant EX_U256    = ICurvePoolNew.exchange.selector;\r
    bytes4 constant EXU_U256   = ICurvePoolNew.exchange_underlying.selector;\r
\r
    bytes4 constant DY_INT128  = ICurvePool.get_dy.selector;\r
    bytes4 constant DYU_INT128 = ICurvePool.get_dy_underlying.selector;\r
    bytes4 constant DY_U256    = bytes4(keccak256("get_dy(uint256,uint256,uint256)"));\r
    bytes4 constant DYU_U256   = bytes4(keccak256("get_dy_underlying(uint256,uint256,uint256)"));\r
\r
    bytes4 constant EX_INT128_ETH  = bytes4(keccak256("exchange(int128,int128,uint256,uint256,bool)"));\r
    bytes4 constant EX_U256_ETH    = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256,bool)"));\r
\r
    // Modifiers\r
    modifier onlyERC20(address token) {\r
        require(token != address(0), "Zero token address");\r
        _;\r
    }\r
\r
    constructor(address _poolRegistry) Ownable(msg.sender) {\r
        require(_poolRegistry != address(0), "Curve: zero registry address");\r
        POOL_REGISTRY = IPoolRegistry(_poolRegistry);\r
    }\r
\r
    /* ──────────────── External VIEW ─────────────── */\r
\r
    /**\r
     * @notice Simulates an exchange along a predefined Curve route using a percentage of the original amountIn.\r
     * @dev    This is useful for analyzing split swaps. The percentage is applied to the amountIn of the first hop,\r
     *         which is stored in the route data by the getBestRoute function.\r
     * @param  route     DexRoute containing serialized route steps.\r
     * @param  percent   Percentage of the original amountIn to use (e.g., 10 for 10%). Must be 1 to 100.\r
     * @return amountOut Simulated output token amount.\r
     */\r
    function simulateRoute(\r
        DexRoute calldata route,\r
        uint256 percent\r
    ) external view returns (uint256 amountOut) {\r
        uint256 hops = route.data.length;\r
        require(hops > 0, "Curve: empty route");\r
        require(percent > 0 && percent <= 100, "Curve: invalid percent");\r
\r
        // 1. Decode the first hop to get the original amountIn\r
        (\r
            , // address tokenIn\r
            , // address tokenOut\r
            , // address pool\r
            , // int128 i\r
            , // int128 j\r
            , // bool useUnderlying\r
            uint256 originalAmountIn // amountIn\r
        ) = abi.decode(\r
            route.data[0],\r
            (address, address, address, int128, int128, bool, uint256)\r
        );\r
        \r
        // Calculate the fractional amountIn\r
        uint256 actualAmountIn = (originalAmountIn * percent) / 100;\r
        require(actualAmountIn > 0, "Curve: zero split amount");\r
\r
        uint256 currentAmountIn = actualAmountIn;\r
\r
        // 2. Iterate through all steps and simulate the exchange\r
        for (uint256 n; n < hops; ) {\r
            // Decode pool, i, j, useUnderlying for the current hop\r
            (\r
                , // address _tokenIn\r
                , // address _tokenOut\r
                address pool,\r
                int128 i,\r
                int128 j,\r
                bool useUnderlying,\r
                // uint256 amountIn (ignored, as we use currentAmountIn)\r
            ) = abi.decode(\r
                route.data[n],\r
                (address, address, address, int128, int128, bool, uint256)\r
            );\r
\r
            // Evaluate the quote for the current hop\r
            (bool ok, uint256 out) = _safeStaticGetDy(pool, i, j, useUnderlying, currentAmountIn);\r
            require(ok, "Curve: quote simulation failed");\r
\r
            // The output of this hop becomes the input for the next hop\r
            currentAmountIn = out; \r
\r
            unchecked { ++n; }\r
        }\r
        \r
        // The final output of the last hop is the result\r
        amountOut = currentAmountIn;\r
    }\r
\r
    /**\r
     * @notice Returns exchange routes that only use whitelisted Curve pools.\r
     * @param  tokenIn           Input token address.\r
     * @param  tokenOut          Output token address.\r
     * @param  amountIn          Input token amount.\r
     * @return whitelistedRoutes Array of possible exchange routes (one hop each) with whitelisted pools.\r
     */\r
    function getVerifedRoutes(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) external view returns (Quote[][] memory whitelistedRoutes)\r
    {\r
        whitelistedRoutes = _getWhitelistedRoutes(tokenIn, tokenOut, amountIn);\r
    }\r
    \r
    /**\r
     * @notice Returns all possible Curve exchange routes: direct and via hub tokens.\r
     * @param  tokenIn  Address of the token being exchanged.\r
     * @param  tokenOut Address of the token we want to receive.\r
     * @param  amountIn Amount of the input token.\r
     * @return routes   Array of exchange routes (1 or 2 hops).\r
     */\r
    function getAllRoutes(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) external view returns (Quote[][] memory routes)\r
    {\r
        (address[] memory directPools, Quote[] memory directQuotes) = _allSingleHops(tokenIn, tokenOut, amountIn);\r
\r
        uint256 maxRoutes = directQuotes.length + HUB_TOKENS.length;\r
        Quote[][] memory tmp = new Quote[][](maxRoutes);\r
        uint256 cnt = 0;\r
        \r
        /* ----------- 1-hop ---------- */\r
        for (uint256 i; i < directPools.length && cnt < maxRoutes; ++i) {\r
            Quote[] memory directHop = new Quote[](1);\r
            directHop[0] = Quote({\r
                pool: directPools[i],\r
                i: directQuotes[i].i,\r
                j: directQuotes[i].j,\r
                useUnderlying: directQuotes[i].useUnderlying,\r
                amountOut: directQuotes[i].amountOut\r
            });\r
            tmp[cnt++] = directHop;\r
        }\r
\r
        /* ---------- 2-hops ---------- */\r
        for (uint8 h; h < HUB_TOKENS.length; ++h) {\r
            address hub = HUB_TOKENS[h];\r
            if (hub == tokenIn || hub == tokenOut) continue;\r
\r
            Quote[] memory hop1 = _bestQuote(tokenIn, hub, amountIn);\r
            if (hop1.length == 0) continue;\r
\r
            Quote[] memory hop2 = _bestQuote(hub, tokenOut, hop1[0].amountOut);\r
            if (hop2.length == 0) continue;\r
\r
            // Fix: Initialize path with the correct size (2 for two hops)\r
            Quote[] memory path = new Quote[](2);\r
            path[0] = hop1[0];\r
            path[1] = hop2[0];\r
            tmp[cnt++] = path;\r
        }\r
\r
        routes = new Quote[][](cnt);\r
        for (uint256 i; i < cnt; ++i) routes[i] = tmp[i];\r
    }\r
\r
    /**\r
     * @notice Selects the best Curve exchange route for 1-hop and 2-hop separately.\r
     * @dev    Returns       both best options, allowing the caller to compare options considering gas costs.\r
     * @param  tokenIn       Address of the token we are giving away\r
     * @param  tokenOut      Address of the token we want to receive\r
     * @param  amountIn      Amount of the input token\r
     * @return best1HopRoute Serialized best 1-hop route (DexRoute).\r
     * @return amountOut1Hop Maximum amount of the token received for 1-hop.\r
     * @return best2HopRoute Serialized best 2-hop route (DexRoute).\r
     * @return amountOut2Hop Maximum amount of the token received for 2-hop.\r
     */\r
    function getBestRoute(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) external view override returns (\r
        DexRoute memory best1HopRoute,\r
        uint256 amountOut1Hop,\r
        DexRoute memory best2HopRoute,\r
        uint256 amountOut2Hop\r
    ) {\r
        if (!_preSwapValidateRoute(tokenOut)) return (best1HopRoute, 0, best2HopRoute, 0);\r
        \r
        // --- 1-hop: tokenIn -> tokenOut ---\r
        Quote[] memory directRoute = _bestQuote(tokenIn, tokenOut, amountIn);\r
        if (directRoute.length == 1 && _preSwapValidateRoute(tokenOut)) {\r
        // if (directRoute.length == 1) {\r
            amountOut1Hop = directRoute[0].amountOut;\r
            \r
            // build 1-hop DexRoute\r
            best1HopRoute.data = new bytes[](1);\r
            best1HopRoute.data[0] = abi.encode(\r
                tokenIn,\r
                tokenOut,\r
                directRoute[0].pool,\r
                directRoute[0].i,\r
                directRoute[0].j,\r
                directRoute[0].useUnderlying,\r
                amountIn\r
            );\r
        }\r
\r
        // --- 2-hops via hubs ---\r
        uint256 maxAmountOut2Hop = 0;\r
        address hubUsed = address(0);\r
        Quote[] memory bestTmp2Hop; // local holder for building best 2-hop route\r
\r
        for (uint8 h = 0; h < HUB_TOKENS.length; ++h) {\r
            address hub = HUB_TOKENS[h];\r
            if (hub == tokenIn || hub == tokenOut) continue;\r
            if (!_preSwapValidateRoute(hub)) continue;\r
\r
            Quote[] memory hop1 = _bestQuote(tokenIn, hub, amountIn);\r
            if (hop1.length == 0) continue;\r
\r
            Quote[] memory hop2 = _bestQuote(hub, tokenOut, hop1[0].amountOut);\r
            if (hop2.length == 0) continue;\r
\r
            if (hop2[0].amountOut > maxAmountOut2Hop) {\r
                maxAmountOut2Hop = hop2[0].amountOut;\r
                hubUsed = hub;\r
\r
                // pack temp best for building route below\r
                bestTmp2Hop  = new Quote[](2);\r
                bestTmp2Hop[0] = hop1[0];\r
                bestTmp2Hop[1] = hop2[0];\r
            }\r
        }\r
        \r
        if (maxAmountOut2Hop > 0) {\r
            amountOut2Hop = maxAmountOut2Hop;\r
\r
            // build 2-hop DexRoute: tokenIn -> hubUsed -> tokenOut\r
            best2HopRoute.data = new bytes[](2);\r
            best2HopRoute.data[0] = abi.encode(\r
                tokenIn,\r
                hubUsed,\r
                bestTmp2Hop[0].pool,\r
                bestTmp2Hop[0].i,\r
                bestTmp2Hop[0].j,\r
                bestTmp2Hop[0].useUnderlying,\r
                amountIn\r
            );\r
            best2HopRoute.data[1] = abi.encode(\r
                hubUsed,\r
                tokenOut,\r
                bestTmp2Hop[1].pool,\r
                bestTmp2Hop[1].i,\r
                bestTmp2Hop[1].j,\r
                bestTmp2Hop[1].useUnderlying,\r
                0 // amountIn for intermediate token = 0, as it is determined by the contract balance in swapRoute\r
            );\r
        }\r
        // If no route is found, amountOut will be 0 and DexRoute.data will be empty (default)\r
    }\r
\r
    /**\r
     * @notice Simulates a single-hop exchange in a specific pool.\r
     * @param  tokenIn   Address of the token we are giving away.\r
     * @param  tokenOut  Address of the token we want to receive.\r
     * @param  amountIn  Amount of the input token.\r
     * @param  pool      Address of the Curve pool to use.\r
     * @return amountOut Estimated amount of the token received.\r
     */\r
    function simulateSingleSwap(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn,\r
        address pool\r
    ) external view returns (uint256 amountOut) {\r
        Quote memory q = _evaluateQuote(pool, tokenIn, tokenOut, amountIn);\r
        return q.amountOut;\r
    }\r
\r
    /**\r
     * @notice Decodes a serialized route into an array of steps.\r
     * @param  route DexRoute containing serialized data for each hop.\r
     * @return steps Array of route steps (RouteStep[]).\r
     */\r
    function decodeRoute(DexRoute calldata route)\r
        external\r
        pure\r
        returns (RouteStep[] memory steps)\r
    {\r
        uint256 len = route.data.length;\r
        steps = new RouteStep[](len);\r
\r
        for (uint256 n; n < len; ++n) {\r
            (\r
                address tokenIn,\r
                address tokenOut,\r
                address pool,\r
                int128  i,\r
                int128  j,\r
                bool    useUnderlying,\r
                uint256 amountIn\r
            ) = abi.decode(\r
                    route.data[n],\r
                    (address, address, address, int128, int128, bool, uint256)\r
                );\r
\r
            steps[n] = RouteStep({\r
                tokenIn:    tokenIn,\r
                tokenOut:   tokenOut,\r
                pool:       pool,\r
                i:          i,\r
                j:          j,\r
                useUnderlying: useUnderlying,\r
                amountIn:   amountIn\r
            });\r
        }\r
    }\r
\r
    /* ─────────── External STATE-CHANGING ────────── */\r
\r
    /**\r
     * @notice Performs an exchange along a predefined Curve route\r
     * @dev    The first hop uses amountIn from route; the rest use the entire available balance of the intermediate token\r
     * @param  route     DexRoute containing serialized route steps\r
     * @param  to        Final token recipient\r
     * @param  percent   Percentage of the route's original amountIn to use (1 to 100).\r
     * @return amountOut Total amount of output token received\r
     */\r
    function swapRoute(\r
        DexRoute calldata route,\r
        address           to,\r
        uint256           percent\r
    ) external returns (uint256 amountOut) {\r
        uint256 hops = route.data.length;\r
        require(hops > 0, "Curve: empty route");\r
        require(to != address(0), "Curve: invalid recipient");\r
        require(percent > 0 && percent <= 100, "Curve: invalid percent");\r
        address lastTokenOut = address(0);\r
\r
        for (uint256 n; n < hops; ++n) {\r
            (\r
                address tokenIn,\r
                address tokenOut,\r
                address pool,\r
                int128  i,\r
                int128  j,\r
                bool    useUnderlying,\r
                uint256 amountIn\r
            ) = abi.decode(\r
                route.data[n],\r
                (address, address, address, int128, int128, bool, uint256)\r
            );\r
\r
            require(pool != address(0), "Curve: invalid pool");\r
\r
            if (n > 0) require(tokenIn == lastTokenOut, "Curve: route mismatch");\r
\r
            uint256 actualIn;\r
            if (n == 0) {\r
                require(amountIn > 0, "Curve: zero input");\r
                uint256 requiredIn = (amountIn * percent) / 100;\r
                require(requiredIn > 0, "Curve: zero split amount");\r
\r
                uint256 pre = IERC20(tokenIn).balanceOf(address(this));\r
                _pullToken(tokenIn, requiredIn);\r
                uint256 post = IERC20(tokenIn).balanceOf(address(this));\r
                actualIn = post - pre;\r
                require(actualIn > 0, "Curve: zero input");\r
                // We collect input tokens from the user for the first hop\r
            } else {\r
                // For subsequent hops — the entire balance of the intermediate token\r
                actualIn = IERC20(tokenIn).balanceOf(address(this));\r
                require(actualIn > 0, "Curve: no hop input");\r
            }\r
\r
            // We perform a hop: approve + exchange; minOut=0 (we will check the total slippage after the cycle)\r
            amountOut = _swap(\r
                pool,\r
                tokenIn,\r
                tokenOut,\r
                i,\r
                j,\r
                useUnderlying,\r
                actualIn,\r
                0\r
            );\r
\r
            lastTokenOut  = tokenOut;\r
        }\r
\r
        _deliverToken(lastTokenOut, to, amountOut);\r
    }\r
\r
    /**\r
     * @notice Performs a single-hop exchange in a specified pool.\r
     * @dev    The user must have approved this contract for tokenIn before calling this.\r
     * @param  tokenIn   Address of the token we are giving away.\r
     * @param  tokenOut  Address of the token we want to receive.\r
     * @param  amountIn  Exact amount of the input token to swap.\r
     * @param  pool      Address of the Curve pool to use.\r
     * @param  minOut    Minimum allowable output token amount (for slippage protection).\r
     * @param  to Final  token recipient.\r
     * @return amountOut Actual amount of output token received.\r
     */\r
    function manualSwapRoute(\r
        address pool,\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn,\r
        uint256 minOut,\r
        address to\r
    ) external returns (uint256 amountOut) {\r
        require(pool != address(0) && to != address(0) && amountIn > 0, "Curve: bad arguments");\r
        \r
        (bool ok, int128 i, int128 j, bool useUnderlying) = _safeGetCoinIndices(pool, tokenIn, tokenOut);\r
        require(ok, "Curve: coinIndices fail");\r
\r
        useUnderlying = _normalizeUnderlyingForWETH(useUnderlying, tokenIn, tokenOut);\r
\r
        _pullToken(tokenIn, amountIn);\r
        _smartApprove(tokenIn, pool, amountIn);\r
\r
        amountOut = _executeExchange(\r
                pool,\r
                useUnderlying,\r
                i,\r
                j,\r
                amountIn,\r
                minOut,\r
                tokenOut\r
            );\r
\r
        require(amountOut >= minOut, "Curve: slippage");\r
\r
        _deliverToken(tokenOut, to, amountOut);\r
    }\r
\r
    /* ────────────────── ADMIN ─────────────────── */\r
\r
    /**\r
     * @notice Allows the contract owner to change the address of the external Pool Registry (Whitelist).\r
     * @param  _newPoolRegistry Address of the new IPoolRegistry contract.\r
     */\r
    function setPoolRegistry(address _newPoolRegistry) external onlyOwner {\r
        require(_newPoolRegistry != address(0), "Curve: zero registry address");\r
        emit PoolRegistryUpdated(address(POOL_REGISTRY), _newPoolRegistry);\r
        POOL_REGISTRY = IPoolRegistry(_newPoolRegistry);\r
    }\r
\r
    /* ────────────── INTERNAL HELPERS ────────────── */\r
\r
    /**\r
     * @notice Searches for the best Curve pool for token exchange\r
     * @param  from     Input token\r
     * @param  to       Output token\r
     * @param  amountIn Input token amount\r
     * @return arr      An array of length 1 with the best quote or an empty array\r
     */\r
    function _bestQuote(\r
            address from,\r
            address to,\r
            uint256 amountIn\r
    ) internal view returns (Quote[] memory arr) {\r
        Quote memory q = _directBest(from, to, amountIn);\r
        if (q.amountOut == 0) return new Quote[](0);\r
\r
        arr = new Quote[](1);\r
        arr[0] = Quote(q.pool, q.i, q.j, q.useUnderlying, q.amountOut);\r
    }\r
\r
    /**\r
     * @notice Returns the best Quote from all available Curve pools between from and to\r
     * @param  from     Address of the token we are giving away\r
     * @param  to       Address of the token we want to receive\r
     * @param  amountIn Amount of the input token\r
     * @return best     Quote structure with the highest amountOut\r
     */\r
    function _directBest(address from, address to, uint256 amountIn)\r
        internal\r
        view\r
        returns (Quote memory best)\r
    {\r
        address[] memory pools = _getPools(from, to);\r
        for (uint256 k; k < pools.length; ++k) {\r
            Quote memory q = _evaluateQuote(pools[k], from, to, amountIn);\r
            if (q.amountOut == 0) continue;\r
\r
            if (q.pool != address(0)) {\r
                address actualOutToken = _resolvePoolOutputToken(q.pool, q.j, q.useUnderlying);\r
                if (actualOutToken != to) continue;\r
            }\r
\r
            if (q.amountOut > best.amountOut) best = q;\r
        }\r
    }\r
\r
    /**\r
     * @notice Tries to resolve the actual output token from a Curve pool using either coins(i) or underlying_coins(i)\r
     * @param  pool        The Curve pool address\r
     * @param  index       The token index\r
     * @param  useUnderlying Whether to query underlying_coins or coins\r
     * @return resolved    The token address resolved\r
     */\r
    function _resolvePoolOutputToken(address pool, int128 index, bool useUnderlying) internal view returns (address resolved) {\r
        uint256 idx = uint256(uint128(index));\r
        bytes memory callData = abi.encodeWithSignature(\r
            useUnderlying ? "underlying_coins(uint256)" : "coins(uint256)",\r
            idx\r
        );\r
\r
        (bool ok, bytes memory out) = pool.staticcall(callData);\r
        if (ok && out.length >= 32) {\r
            resolved = abi.decode(out, (address));\r
        }\r
    }\r
\r
    /**\r
     * @notice Returns a list of Curve pools between two tokens.\r
     * @dev    First checks verified pools, if none are found, uses Curve MetaRegistry.\r
     * @param  from  Token A\r
     * @param  to    Token B\r
     * @return pools List of pools found.\r
     */\r
    function _getPools(address from, address to) internal view returns (address[] memory pools) {\r
        address[] memory verified = POOL_REGISTRY.getVerifiedPools(from, to);\r
        if (verified.length > 0) {\r
            return verified;\r
        }\r
        return META_REGISTRY.find_pools_for_coins(from, to);\r
    }\r
\r
    /**\r
     * @notice Returns routes using only whitelisted Curve pools\r
     * @param  tokenIn  Input token\r
     * @param  tokenOut Output token\r
     * @param  amountIn Amount of input token\r
     * @return routes   Array of routes with one hop\r
     */\r
    function _getWhitelistedRoutes(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) internal view returns (Quote[][] memory) {\r
        address[] memory verified = POOL_REGISTRY.getVerifiedPools(tokenIn, tokenOut);\r
        \r
        Quote[][] memory tmpWhitelisted = new Quote[][](verified.length);\r
        uint256 whitelistedCount = 0;\r
        \r
        for (uint256 i = 0; i < verified.length; ++i) {\r
            address pool = verified[i];\r
            Quote memory q = _evaluateQuote(pool, tokenIn, tokenOut, amountIn);\r
            \r
            if (q.amountOut > 0) {\r
                Quote[] memory hop = new Quote[](1);\r
                hop[0] = q;\r
                tmpWhitelisted[whitelistedCount++] = hop;\r
            }\r
        }\r
        \r
        Quote[][] memory result = new Quote[][](whitelistedCount);\r
        for (uint256 i = 0; i < whitelistedCount; ++i) {\r
            result[i] = tmpWhitelisted[i];\r
        }\r
        return result;\r
    }\r
    \r
    /**\r
     * @notice Secure call to MetaRegistry to get token indices in the Curve pool\r
     * @param  pool          Curve pool address\r
     * @param  from          Input token\r
     * @param  to            Output token\r
     * @return success       Whether the call was successful\r
     * @return i             Input token index\r
     * @return j             Output token index\r
     * @return useUnderlying Flag for using the underlying path\r
     */\r
    function _safeGetCoinIndices(address pool, address from, address to)\r
        internal view returns (bool success, int128 i, int128 j, bool useUnderlying)\r
    {      \r
        // First, let's try MetaRegistry\r
        bytes memory data = abi.encodeWithSelector(ICurveMetaRegistry.get_coin_indices.selector, pool, from, to);\r
        (success, data) = address(META_REGISTRY).staticcall(data);\r
        if (success && data.length >= 96) {\r
            (i, j, useUnderlying) = abi.decode(data, (int128, int128, bool));\r
            return (true, i, j, useUnderlying);\r
        } \r
\r
        // searching for indexes directly in the pool\r
        // try coins(uint256)\r
        (bool okI, uint256 idxI) = _findIndexByCoins(pool, from);\r
        (bool okJ, uint256 idxJ) = _findIndexByCoins(pool, to);\r
        if (okI && okJ) {\r
            return (true, int128(uint128(idxI)), int128(uint128(idxJ)), false);\r
        }\r
\r
        // try underlying_coins(uint256)\r
        (okI, idxI) = _findIndexByUnderlyingCoins(pool, from);\r
        (okJ, idxJ) = _findIndexByUnderlyingCoins(pool, to);\r
        if (okI && okJ) {\r
            return (true, int128(uint128(idxI)), int128(uint128(idxJ)), true);\r
        }\r
\r
        // did not find\r
        return (false, 0, 0, false);\r
    }\r
\r
    /**\r
     * @notice Searches for the token index in the Curve pool by calling coins(uint256) on all possible slots.\r
     * @dev    Used as a fallback if MetaRegistry.get_coin_indices() does not return any indices.\r
     *         Safely terminates the loop on a call error or when the coin range is exceeded.\r
     * @param  pool  Curve pool address (LiquidityPool).\r
     * @param  token Token address for which to find the index.\r
     * @return found Whether the token index was successfully found in the pool.\r
     * @return index Token index (starting from 0) if found, otherwise 0.\r
     *\r
     * Example:\r
     * (true, 1) means that token is in the pool at coins(1).\r
     */\r
    function _findIndexByCoins(address pool, address token) internal view returns (bool, uint256) {\r
        // up to 8 coins — enough for tricrypto/metapool\r
        for (uint256 k = 0; k < 8; k++) {\r
            (bool ok, bytes memory out) = pool.staticcall(abi.encodeWithSignature("coins(uint256)", k));\r
            if (!ok) break; // no further indexes\r
            if (out.length >= 32 && abi.decode(out, (address)) == token) return (true, k);\r
        }\r
        // int128\r
        for (uint256 k = 0; k < 8; k++) {\r
            (bool ok, bytes memory out) = pool.staticcall(abi.encodeWithSignature("coins(int128)", int128(int256(k))));\r
            if (!ok) { break; }\r
            if (out.length >= 32 && abi.decode(out, (address)) == token) return (true, k);\r
        }\r
        return (false, 0);\r
    }\r
\r
    /**\r
     * @notice Searches for the token index among underlying_coins(uint256) in the Curve pool.\r
     * @dev    Used as a fallback for meta pools and factory pools,\r
     *         where the main exchange logic uses the underlying layer of tokens.\r
     * @param  pool  Curve pool address (LiquidityPool).\r
     * @param  token Token address for which to find the index among underlying_coins.\r
     * @return found Whether the underlying token index was found successfully.\r
     * @return index Token index (starting from 0) if found, otherwise 0.\r
     *\r
     * Example:\r
     * (true, 2) means that the token is in underlying_coins(2).\r
     */\r
    function _findIndexByUnderlyingCoins(address pool, address token) internal view returns (bool, uint256) {\r
        for (uint256 k = 0; k < 8; k++) {\r
            (bool ok, bytes memory out) = pool.staticcall(abi.encodeWithSignature("underlying_coins(uint256)", k));\r
            if (!ok) break;\r
            if (out.length >= 32 && abi.decode(out, (address)) == token) return (true, k);\r
        }\r
        // int128\r
        for (uint256 k = 0; k < 8; k++) {\r
            (bool ok, bytes memory out) = pool.staticcall(abi.encodeWithSignature("underlying_coins(int128)", int128(int256(k))));\r
            if (!ok) { break; }\r
            if (out.length >= 32 && abi.decode(out, (address)) == token) return (true, k);\r
        }\r
        return (false, 0);\r
    }\r
\r
    /**\r
     * @notice Secure call to get_dy or get_dy_underlying with fallback to uint256 version\r
     * @param  pool          Curve pool address\r
     * @param  i             Input token index\r
     * @param  j             Output token index\r
     * @param  useUnderlying Whether to use underlying\r
     * @param  amountIn      Input token amount\r
     * @return success       Whether the call was successful\r
     * @return result        Output token amount\r
     */\r
    function _safeStaticGetDy(\r
        address pool,\r
        int128 i,\r
        int128 j,\r
        bool useUnderlying,\r
        uint256 amountIn\r
    ) internal view returns (bool success, uint256 result)\r
    {\r
        // 1) uint256\r
        bytes4 selB = useUnderlying ? DYU_U256 : DY_U256;\r
        bytes memory data = abi.encodeWithSelector(selB, uint256(uint128(i)), uint256(uint128(j)), amountIn);\r
        (success, data) = pool.staticcall(data);\r
        if (success && data.length >= 32) {\r
            result = abi.decode(data, (uint256));\r
            if (result == 0) {\r
                int256 rSigned = abi.decode(data, (int256));\r
                if (rSigned > 0) result = uint256(rSigned);\r
            }\r
            return (true, result);\r
        }\r
\r
        // 2) int128\r
        bytes4 selA = useUnderlying ? DYU_INT128 : DY_INT128;\r
        data = abi.encodeWithSelector(selA, i, j, amountIn);\r
        (success, data) = pool.staticcall(data);\r
        if (success && data.length >= 32) {\r
            result = abi.decode(data, (uint256));\r
            if (result == 0) {\r
                // int256\r
                int256 rSigned = abi.decode(data, (int256));\r
                if (rSigned > 0) result = uint256(rSigned);\r
            }\r
            return (true, result);\r
        }\r
    }\r
\r
    /**\r
     * @notice Builds a quote for a pair of tokens and a specific Curve pool\r
     * @param  pool     Curve pool address\r
     * @param  from     Input token\r
     * @param  to       Output token\r
     * @param  amountIn Input token amount\r
     * @return quote    Quote structure with evaluation results\r
     */\r
    function _evaluateQuote(address pool, address from, address to, uint256 amountIn)\r
        internal view returns (Quote memory quote)\r
    {\r
        if (pool == address(0)) return quote;\r
\r
        (bool indexOk, int128 i, int128 j, bool und) = _safeGetCoinIndices(pool, from, to);\r
        if (!indexOk) return quote;\r
        und = _normalizeUnderlyingForWETH(und, from, to);\r
\r
        (bool ok, uint256 out) = _safeStaticGetDy(pool, i, j, und, amountIn);\r
        if (!ok) return quote;\r
\r
        quote = Quote(pool, i, j, und, out);\r
    }\r
\r
    /**\r
     * @notice Performs Curve exchange with fallback to uint256 version of selector\r
     * @param  pool          Curve pool address\r
     * @param  useUnderlying Whether to use underlying option\r
     * @param  i             Input token index\r
     * @param  j             Output token index\r
     * @param  amountIn      Input token amount\r
     * @param  minAmountOut  Minimum allowable output\r
     * @param  tokenOut      Output token address (for balance verification)\r
     * @return amountOut     Actual amount of token received\r
     */\r
    function _executeExchange(\r
        address   pool,\r
        bool      useUnderlying,\r
        int128    i,\r
        int128    j,\r
        uint256   amountIn,\r
        uint256   minAmountOut,\r
        address   tokenOut\r
    ) internal returns (uint256 amountOut) {\r
        (bool hasProf, IPoolRegistry.PoolProfile memory p) = POOL_REGISTRY.getPoolProfile(pool);\r
       \r
        uint256 iu = uint256(uint128(i));\r
        uint256 ju = uint256(uint128(j));\r
        bool ok; bytes memory ret;\r
\r
        uint256 beforeBal = IERC20(tokenOut).balanceOf(address(this));\r
\r
        if (hasProf) {\r
\r
            if (useUnderlying) {\r
                // underlying exchange(...)\r
                if (p.exIndexUint) {\r
                    // exchange_underlying(uint256,uint256,uint256,uint256)\r
                    (ok, ret) = pool.call(abi.encodeWithSelector(EXU_U256, iu, ju, amountIn, minAmountOut));\r
                } else {\r
                    // exchange_underlying(int128,int128,uint256,uint256)\r
                    (ok, ret) = pool.call(abi.encodeWithSelector(EXU_INT128, i, j, amountIn, minAmountOut));\r
                }\r
            } else {\r
                // non-underlying exchange(...)\r
                if (p.exIndexUint) {\r
                    if (p.exHasEthFlag) {\r
                        // exchange(uint256,uint256,uint256,uint256,bool) with use_eth=false\r
                        (ok, ret) = pool.call(abi.encodeWithSelector(EX_U256_ETH, iu, ju, amountIn, minAmountOut, false));\r
                    } else {\r
                        // exchange(uint256,uint256,uint256,uint256)\r
                        (ok, ret) = pool.call(abi.encodeWithSelector(EX_U256, iu, ju, amountIn, minAmountOut));\r
                    }\r
                } else {\r
                    if (p.exHasEthFlag) {\r
                        // exchange(int128,int128,uint256,uint256,bool) with use_eth=false\r
                        (ok, ret) = pool.call(abi.encodeWithSelector(EX_INT128_ETH, i, j, amountIn, minAmountOut, false));\r
                    } else {\r
                        // exchange(int128,int128,uint256,uint256)\r
                        (ok, ret) = pool.call(abi.encodeWithSelector(EX_INT128, i, j, amountIn, minAmountOut));\r
                    }\r
                }\r
            }\r
\r
            require(ok, "Curve: swap failed");\r
\r
            if (ret.length >= 32) {\r
            amountOut = abi.decode(ret, (uint256));\r
            } else {\r
                amountOut = IERC20(tokenOut).balanceOf(address(this)) - beforeBal;\r
            }\r
            require(amountOut >= minAmountOut, "Curve: slippage");\r
            return amountOut;\r
        }\r
\r
        // ───────── non-profile: только современная сигнатура ─────────\r
        if (useUnderlying) {\r
            // exchange_underlying(uint256,uint256,uint256,uint256)\r
            (ok, ret) = pool.call(abi.encodeWithSelector(EXU_U256, iu, ju, amountIn, minAmountOut));\r
        } else {\r
            // exchange(uint256,uint256,uint256,uint256,bool) c use_eth=false\r
            (ok, ret) = pool.call(abi.encodeWithSelector(EX_U256_ETH, iu, ju, amountIn, minAmountOut, false));\r
        }\r
\r
        require(ok, "Curve: swap failed");\r
\r
        if (ret.length >= 32) {\r
            amountOut = abi.decode(ret, (uint256));\r
        } else {\r
            amountOut = IERC20(tokenOut).balanceOf(address(this)) - beforeBal;\r
        }\r
        require(amountOut >= minAmountOut, "Curve: slippage");\r
    }\r
\r
    /**\r
     * @notice Performs a one-hop exchange: approve + exchange\r
     * @param  pool          Curve pool address\r
     * @param  tokenIn       Input token\r
     * @param  tokenOut      Output token\r
     * @param  i             Input token index\r
     * @param  j             Output token index\r
     * @param  useUnderlying Whether to use underlying\r
     * @param  amountIn      Input token amount\r
     * @param  minOut        Minimum allowable output token amount\r
     * @return amountOut     Output token amount\r
     */\r
    function _swap(\r
        address pool,\r
        address tokenIn,\r
        address tokenOut,\r
        int128  i,\r
        int128  j,\r
        bool    useUnderlying,\r
        uint256 amountIn,\r
        uint256 minOut\r
    ) internal returns (uint256 amountOut) {\r
        require(pool != address(0) && amountIn > 0, "Curve: bad arguments");\r
\r
        // Approve pool if necessary (USDT-compatible)\r
        _smartApprove(tokenIn, pool, amountIn);\r
\r
        // Perform exchange (with fallback to uint256 indexes inside _executeExchange)\r
        amountOut = _executeExchange(\r
            pool,\r
            useUnderlying,\r
            i,\r
            j,\r
            amountIn,\r
            minOut,\r
            tokenOut\r
        );\r
    }\r
\r
    /**\r
     * @notice Securely set allowance on Curve pool, taking into account non-standard tokens\r
     * @param  token   Token for which permission is being set\r
     * @param  spender Curve pool address\r
     * @param  amount  Minimum required allowance amount\r
     */\r
    function _smartApprove (address token, address spender, uint256 amount) internal {\r
        if (IERC20(token).allowance(address(this), spender) < amount) {\r
            IERC20(token).forceApprove(spender, type(uint256).max);\r
        }\r
    }\r
\r
    /**\r
     * @notice Returns a list of all possible Curve pools between two tokens and the corresponding quotes.\r
     * @param  tokenIn  Input token.\r
     * @param  tokenOut Output token.\r
     * @param  amountIn Amount of input token.\r
     * @return pools    List of pools.\r
     * @return quotes   List of quotes for each pool.\r
     */\r
    function _allSingleHops(address tokenIn, address tokenOut, uint256 amountIn)\r
        internal\r
        view\r
        returns (address[] memory pools, Quote[] memory quotes)\r
    {\r
        pools = META_REGISTRY.find_pools_for_coins(tokenIn, tokenOut);\r
        uint256 len = pools.length;\r
\r
        quotes = new Quote[](len);\r
\r
        for (uint256 i = 0; i < len; ++i) {\r
            quotes[i] = _evaluateQuote(pools[i], tokenIn, tokenOut, amountIn);\r
        }\r
    }\r
\r
    /**\r
     * @notice Transfers tokens from the user to the contract\r
     * @param  token  The token to be received\r
     * @param  amount The number of tokens\r
     */\r
    function _pullToken(address token, uint256 amount) internal onlyERC20(token) {\r
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);\r
    }\r
\r
    /**\r
     * @notice Sends a token to the user\r
     * @param  token  The token to be sent\r
     * @param  to The recipient\r
     * @param  amount The number of tokens\r
     */\r
    function _deliverToken(address token, address to, uint256 amount) internal onlyERC20(token) {\r
        IERC20(token).safeTransfer(to, amount);\r
    }\r
\r
    /**\r
     * @notice Normalizes the `useUnderlying` flag for routes involving WETH.\r
     * @dev    We always treat WETH as regular ERC-20, not native ETH.\r
     * @param  und           The original `useUnderlying` flag obtained from MetaRegistry (get_coin_indices).\r
     * @param  from          The address of the input token.\r
     * @param  to            The address of the output token.\r
     * @return normalizedUnd Normalized `useUnderlying` flag, safe for quotes/swaps.\r
    */\r
    function _normalizeUnderlyingForWETH(\r
        bool und,\r
        address from,\r
        address to\r
    ) internal pure returns (bool) {\r
        // WETH — always follow the ERC-20 path (exchange with int128/uint256 without msg.value)\r
        if (from == WETH || to == WETH) return false;\r
        return und;\r
    }\r
\r
    /**\r
     * @notice Checks if the token is a known interest-bearing wrapper (e.g., cyUSDC, yvDAI, etc.).\r
     * @param  token     The token address.\r
     * @return isWrapped True if the token is an interest-bearing wrapped token.\r
     */\r
    function _isInterestBearingToken(address token) internal view returns (bool isWrapped) {\r
        try IERC20Metadata(token).symbol() returns (string memory symbol) {\r
            bytes memory b = bytes(symbol);\r
            if (\r
                _startsWith(b, "cy") ||\r
                _startsWith(b, "yv") ||\r
                _startsWith(b, "a")  ||\r
                _startsWith(b, "c")  ||\r
                _startsWith(b, "bb-") ||\r
                _startsWith(b, "s")  ||\r
                _startsWith(b, "ma") ||\r
                _startsWith(b, "r")\r
            ) {\r
                return true;\r
            }\r
        } catch {\r
            return false;\r
        }\r
        return false;\r
    }\r
\r
    /**\r
     * @dev Utility function to check if a string starts with a given prefix.\r
     */\r
    function _startsWith(bytes memory full, string memory prefix) internal pure returns (bool) {\r
        bytes memory p = bytes(prefix);\r
        if (full.length < p.length) return false;\r
        for (uint i = 0; i < p.length; i++) {\r
            if (full[i] != p[i]) return false;\r
        }\r
        return true;\r
    }\r
\r
    /**\r
     * @notice Hook before completing the route\r
     * @dev    Example use: unwrap interest-bearing tokens before swap ends\r
     */\r
    function _preSwapValidateRoute(address token) internal view returns (bool) {\r
        return !_isCurveLPTok(token) && !_isInterestBearingToken(token);\r
    }\r
\r
    /**\r
     * @notice Checks whether a token is a known Curve LP-like token.\r
     * @dev    Used to prevent routing into LP tokens which are not suitable as final outputs.\r
     *         Currently hardcoded for known Curve LP tokens such as 3CRV and crvUSD.\r
     *         Extend this function to support additional LP token addresses as needed.\r
     * @param  token The token address to check.\r
     * @return True if the token is considered a Curve LP-like token, false otherwise.\r
     */\r
    function _isCurveLPTok(address token) internal pure returns (bool) {\r
        // LP-подобные Curve токены (можешь расширить список по необходимости)\r
        return token == _3CRV || token == CRV_USD;\r
    }\r
}"
    },
    "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity >=0.6.2;

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

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

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

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

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

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @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 {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @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 _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @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 _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @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 {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @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 rely 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 rely 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}.
     * Opposedly, 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 high-level call (i.e. a regular function call to a contract), 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 data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), 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 data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "@openzeppelin/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);
}
"
    },
    "@openzeppelin/contracts/interfaces/IERC1363.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IE

Tags:
ERC20, ERC165, Multisig, Swap, Liquidity, Multi-Signature, Factory|addr:0xe72c3b07aaeb7dd5a27e517a77751c7d2a176687|verified:true|block:23701321|tx:0xa28def1c7074fe0c5f92a006f7d7a1db962e3c5db31e5a011f56c77e01dce48d|first_check:1761993716

Submitted on: 2025-11-01 11:41:56

Comments

Log in to comment.

No comments yet.