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
\r
\r
/**\r
* @notice Performs a single-hop exchange in a specified pool with manual override of pool profile flags.\r
* @dev Useful for debugging or pools not yet registered in CurvePoolRegistry.\r
* @param pool Address of the Curve pool to use.\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 minOut Minimum allowable output token amount (for slippage protection).\r
* @param to Final token recipient.\r
* @param exIndexUint Force flag for uint256 indices (true = use uint256, false = int128).\r
* @param exHasEthFlag Force flag for use_eth argument (true = append bool, false = omit).\r
* @return amountOut Actual amount of output token received.\r
*/\r
function manualSwapRouteManualProfile(\r
address pool,\r
address tokenIn,\r
address tokenOut,\r
uint256 amountIn,\r
uint256 minOut,\r
address to,\r
bool exIndexUint,\r
bool exHasEthFlag\r
) external returns (uint256 amountOut) {\r
require(pool != address(0) && to != address(0) && amountIn > 0, "Curve: bad args");\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
uint256 iu = uint256(uint128(i));\r
uint256 ju = uint256(uint128(j));\r
bool callOk; bytes memory ret;\r
uint256 beforeBal = IERC20(tokenOut).balanceOf(address(this));\r
\r
// Select proper exchange signature manually\r
if (useUnderlying) {\r
if (exIndexUint) {\r
(callOk, ret) = pool.call(abi.encodeWithSelector(EXU_U256, iu, ju, amountIn, minOut));\r
} else {\r
(callOk, ret) = pool.call(abi.encodeWithSelector(EXU_INT128, i, j, amountIn, minOut));\r
}\r
} else {\r
if (exIndexUint) {\r
if (exHasEthFlag) {\r
(callOk, ret) = pool.call(abi.encodeWithSelector(EX_U256_ETH, iu, ju, amountIn, minOut, false));\r
} else {\r
(callOk, ret) = pool.call(abi.encodeWithSelector(EX_U256, iu, ju, amountIn, minOut));\r
}\r
} else {\r
if (exHasEthFlag) {\r
(callOk, ret) = pool.call(abi.encodeWithSelector(EX_INT128_ETH, i, j, amountIn, minOut, false));\r
} else {\r
(callOk, ret) = pool.call(abi.encodeWithSelector(EX_INT128, i, j, amountIn, minOut));\r
}\r
}\r
}\r
\r
require(callOk, "Curve: manual 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
\r
require(amountOut >= minOut, "Curve: slippage");\r
\r
_deliverToken(tokenOut, to, amountOut);\r
}\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 ("me
Submitted on: 2025-11-01 11:41:57
Comments
Log in to comment.
No comments yet.