Files
smom-dbis-138/contracts/bridge/trustless/DualRouterBridgeSwapCoordinator.sol
2026-03-02 12:14:09 -08:00

187 lines
7.1 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 "./InboxETH.sol";
import "./LiquidityPoolETH.sol";
import "./SwapRouter.sol";
import "./EnhancedSwapRouter.sol";
import "./ChallengeManager.sol";
/**
* @title DualRouterBridgeSwapCoordinator
* @notice Coordinates bridge release + swap using either basic SwapRouter or EnhancedSwapRouter
* @dev Verifies claim finalization, releases from liquidity pool, then swaps via chosen router
*/
contract DualRouterBridgeSwapCoordinator is ReentrancyGuard {
using SafeERC20 for IERC20;
InboxETH public immutable inbox;
LiquidityPoolETH public immutable liquidityPool;
SwapRouter public immutable swapRouter;
EnhancedSwapRouter public immutable enhancedSwapRouter;
ChallengeManager public immutable challengeManager;
event BridgeSwapExecuted(
uint256 indexed depositId,
address indexed recipient,
LiquidityPoolETH.AssetType inputAsset,
uint256 bridgeAmount,
address stablecoinToken,
uint256 stablecoinAmount,
bool usedEnhancedRouter
);
error ZeroDepositId();
error ZeroRecipient();
error ClaimNotFinalized();
error ClaimChallenged();
error InsufficientOutput();
constructor(
address _inbox,
address _liquidityPool,
address _swapRouter,
address _enhancedSwapRouter,
address _challengeManager
) {
require(_inbox != address(0), "DualRouterBridgeSwapCoordinator: zero inbox");
require(_liquidityPool != address(0), "DualRouterBridgeSwapCoordinator: zero liquidity pool");
require(_swapRouter != address(0), "DualRouterBridgeSwapCoordinator: zero swap router");
require(_enhancedSwapRouter != address(0), "DualRouterBridgeSwapCoordinator: zero enhanced swap router");
require(_challengeManager != address(0), "DualRouterBridgeSwapCoordinator: zero challenge manager");
inbox = InboxETH(payable(_inbox));
liquidityPool = LiquidityPoolETH(payable(_liquidityPool));
swapRouter = SwapRouter(payable(_swapRouter));
enhancedSwapRouter = EnhancedSwapRouter(payable(_enhancedSwapRouter));
challengeManager = ChallengeManager(payable(_challengeManager));
}
/**
* @notice Execute bridge release + swap to stablecoin
* @param depositId Deposit ID
* @param recipient Recipient address (should match claim recipient)
* @param outputAsset Asset type from bridge (ETH or WETH)
* @param stablecoinToken Target stablecoin address (USDT, USDC, or DAI)
* @param amountOutMin Minimum stablecoin output (slippage protection)
* @param routeData Optional route data for basic SwapRouter (ignored when useEnhancedRouter is true)
* @param useEnhancedRouter If true, use EnhancedSwapRouter; otherwise use basic SwapRouter
* @return stablecoinAmount Amount of stablecoin received
*/
function bridgeAndSwap(
uint256 depositId,
address recipient,
LiquidityPoolETH.AssetType outputAsset,
address stablecoinToken,
uint256 amountOutMin,
bytes calldata routeData,
bool useEnhancedRouter
) external nonReentrant returns (uint256 stablecoinAmount) {
if (depositId == 0) revert ZeroDepositId();
if (recipient == address(0)) revert ZeroRecipient();
ChallengeManager.Claim memory claim = challengeManager.getClaim(depositId);
if (claim.depositId == 0) revert("DualRouterBridgeSwapCoordinator: claim not found");
if (!claim.finalized) revert ClaimNotFinalized();
if (claim.challenged) revert ClaimChallenged();
if (claim.recipient != recipient) revert("DualRouterBridgeSwapCoordinator: recipient mismatch");
uint256 bridgeAmount = claim.amount;
liquidityPool.releaseToRecipient(depositId, address(this), bridgeAmount, outputAsset);
if (useEnhancedRouter) {
(stablecoinAmount, ) = _swapViaEnhancedRouter(outputAsset, stablecoinToken, bridgeAmount, amountOutMin);
} else {
stablecoinAmount = _swapViaBasicRouter(outputAsset, stablecoinToken, bridgeAmount, amountOutMin, routeData);
}
if (stablecoinAmount < amountOutMin) revert InsufficientOutput();
IERC20(stablecoinToken).safeTransfer(recipient, stablecoinAmount);
emit BridgeSwapExecuted(
depositId,
recipient,
outputAsset,
bridgeAmount,
stablecoinToken,
stablecoinAmount,
useEnhancedRouter
);
return stablecoinAmount;
}
function _swapViaBasicRouter(
LiquidityPoolETH.AssetType outputAsset,
address stablecoinToken,
uint256 bridgeAmount,
uint256 amountOutMin,
bytes calldata routeData
) internal returns (uint256) {
if (outputAsset == LiquidityPoolETH.AssetType.ETH) {
return swapRouter.swapToStablecoin{value: bridgeAmount}(
outputAsset,
stablecoinToken,
bridgeAmount,
amountOutMin,
routeData
);
}
address wethAddress = liquidityPool.getWeth();
IERC20(wethAddress).approve(address(swapRouter), bridgeAmount);
return swapRouter.swapToStablecoin(
outputAsset,
stablecoinToken,
bridgeAmount,
amountOutMin,
routeData
);
}
function _swapViaEnhancedRouter(
LiquidityPoolETH.AssetType outputAsset,
address stablecoinToken,
uint256 bridgeAmount,
uint256 amountOutMin
) internal returns (uint256, EnhancedSwapRouter.SwapProvider) {
// SwapProvider(0) = UniswapV3; used as "auto" / first in default routing
EnhancedSwapRouter.SwapProvider preferred = EnhancedSwapRouter.SwapProvider.UniswapV3;
if (outputAsset == LiquidityPoolETH.AssetType.ETH) {
return enhancedSwapRouter.swapToStablecoin{value: bridgeAmount}(
outputAsset,
stablecoinToken,
bridgeAmount,
amountOutMin,
preferred
);
}
address wethAddress = liquidityPool.getWeth();
IERC20(wethAddress).approve(address(enhancedSwapRouter), bridgeAmount);
return enhancedSwapRouter.swapToStablecoin(
outputAsset,
stablecoinToken,
bridgeAmount,
amountOutMin,
preferred
);
}
/**
* @notice Check if claim can be swapped
*/
function canSwap(uint256 depositId) external view returns (bool canSwap_, string memory reason) {
ChallengeManager.Claim memory claim = challengeManager.getClaim(depositId);
if (claim.depositId == 0) return (false, "Claim not found");
if (!claim.finalized) return (false, "Claim not finalized");
if (claim.challenged) return (false, "Claim was challenged");
return (true, "");
}
receive() external payable {}
}