187 lines
7.1 KiB
Solidity
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 {}
|
|
}
|