Files
smom-dbis-138/contracts/bridge/trustless/SwapRouter.sol
defiQUG f19c771760 refactor(bridge): trustless swap stack and fork test cleanups
Tighten EnhancedSwapRouter, InboxETH, SwapRouter, MerkleProofVerifier; align
DEXIntegration and ForkTests with updated behavior.

Made-with: Cursor
2026-04-12 06:44:20 -07:00

180 lines
6.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./LiquidityPoolETH.sol";
import "./interfaces/ISwapRouter.sol";
import "./interfaces/IWETH.sol";
import "./interfaces/ICurvePool.sol";
import "./interfaces/IAggregationRouter.sol";
/**
* @title SwapRouter
* @notice Swaps ETH/WETH to stablecoins via Uniswap V3, Curve, or 1inch
* @dev Primary: Uniswap V3, Secondary: Curve, Optional: 1inch aggregation
*/
contract SwapRouter is ReentrancyGuard {
using SafeERC20 for IERC20;
enum SwapProvider {
UniswapV3,
Curve,
OneInch
}
// Contract addresses (Ethereum Mainnet)
address public immutable uniswapV3Router; // legacy SwapRouter on Ethereum mainnet
address public immutable curve3Pool; // 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7
address public immutable oneInchRouter; // 0x1111111254EEB25477B68fb85Ed929f73A960582 (optional)
// Token addresses (Ethereum Mainnet)
address public immutable weth; // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
address public immutable usdt; // 0xdAC17F958D2ee523a2206206994597C13D831ec7
address public immutable usdc; // 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
address public immutable dai; // 0x6B175474E89094C44Da98b954EedeAC495271d0F
// Uniswap V3 fee tiers (0.05% = 500, 0.3% = 3000, 1% = 10000)
uint24 public constant FEE_TIER_LOW = 500; // 0.05%
uint24 public constant FEE_TIER_MEDIUM = 3000; // 0.3%
uint24 public constant FEE_TIER_HIGH = 10000; // 1%
event SwapExecuted(
SwapProvider provider,
LiquidityPoolETH.AssetType inputAsset,
address inputToken,
address outputToken,
uint256 amountIn,
uint256 amountOut
);
error ZeroAmount();
error ZeroAddress();
error InsufficientOutput();
error InvalidAssetType();
error SwapFailed();
/**
* @notice Constructor
* @param _uniswapV3Router Uniswap V3 SwapRouter address
* @param _curve3Pool Curve 3pool address
* @param _oneInchRouter 1inch Router address (can be address(0) if not used)
* @param _weth WETH address
* @param _usdt USDT address
* @param _usdc USDC address
* @param _dai DAI address
*/
constructor(
address _uniswapV3Router,
address _curve3Pool,
address _oneInchRouter,
address _weth,
address _usdt,
address _usdc,
address _dai
) {
require(_uniswapV3Router != address(0), "SwapRouter: zero Uniswap router");
require(_curve3Pool != address(0), "SwapRouter: zero Curve pool");
require(_weth != address(0), "SwapRouter: zero WETH");
require(_usdt != address(0), "SwapRouter: zero USDT");
require(_usdc != address(0), "SwapRouter: zero USDC");
require(_dai != address(0), "SwapRouter: zero DAI");
uniswapV3Router = _uniswapV3Router;
curve3Pool = _curve3Pool;
oneInchRouter = _oneInchRouter;
weth = _weth;
usdt = _usdt;
usdc = _usdc;
dai = _dai;
}
/**
* @notice Swap to stablecoin using best available route
* @param inputAsset Input asset type (ETH or WETH)
* @param stablecoinToken Target stablecoin address (USDT, USDC, or DAI)
* @param amountIn Input amount
* @param amountOutMin Minimum output amount (slippage protection)
* @return amountOut Output amount
*/
function swapToStablecoin(
LiquidityPoolETH.AssetType inputAsset,
address stablecoinToken,
uint256 amountIn,
uint256 amountOutMin,
bytes calldata
) external payable nonReentrant returns (uint256 amountOut) {
if (amountIn == 0) revert ZeroAmount();
if (stablecoinToken == address(0)) revert ZeroAddress();
if (!_isValidStablecoin(stablecoinToken)) revert("SwapRouter: invalid stablecoin");
// Convert ETH to WETH if needed
if (inputAsset == LiquidityPoolETH.AssetType.ETH) {
IWETH(weth).deposit{value: amountIn}();
inputAsset = LiquidityPoolETH.AssetType.WETH;
}
// Approve WETH for swap
IERC20 wethToken = IERC20(weth);
// Use forceApprove for OpenZeppelin 5.x (or approve directly)
wethToken.approve(uniswapV3Router, amountIn);
if (oneInchRouter != address(0)) {
wethToken.approve(oneInchRouter, amountIn);
}
// Try Uniswap V3 first (primary)
uint256 outputAmount = _executeUniswapV3Swap(stablecoinToken, amountIn, amountOutMin);
if (outputAmount >= amountOutMin) {
// Transfer output to caller
IERC20(stablecoinToken).safeTransfer(msg.sender, outputAmount);
emit SwapExecuted(SwapProvider.UniswapV3, inputAsset, weth, stablecoinToken, amountIn, outputAmount);
return outputAmount;
}
// Try Curve for stable/stable swaps (if USDT/USDC/DAI and routeData provided)
// Note: Curve 3pool doesn't support WETH directly, would need intermediate swap
// For now, revert if Uniswap fails
revert SwapFailed();
}
/**
* @notice Execute Uniswap V3 swap (internal)
* @param stablecoinToken Target stablecoin
* @param amountIn Input amount
* @param amountOutMin Minimum output
* @return amountOut Output amount
*/
function _executeUniswapV3Swap(
address stablecoinToken,
uint256 amountIn,
uint256 amountOutMin
) internal returns (uint256 amountOut) {
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: weth,
tokenOut: stablecoinToken,
fee: FEE_TIER_MEDIUM, // 0.3% fee tier
recipient: address(this),
deadline: block.timestamp + 300, // 5 minutes
amountIn: amountIn,
amountOutMinimum: amountOutMin,
sqrtPriceLimitX96: 0 // No price limit
});
amountOut = ISwapRouter(uniswapV3Router).exactInputSingle(params);
return amountOut;
}
/**
* @notice Check if token is a valid stablecoin
* @param token Token address to check
* @return True if valid stablecoin
*/
function _isValidStablecoin(address token) internal view returns (bool) {
return token == usdt || token == usdc || token == dai;
}
// Allow contract to receive ETH
receive() external payable {}
}