// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ISwapRouter} from "../bridge/trustless/interfaces/ISwapRouter.sol"; interface IDODOTwoHopSwapExactIn { function swapExactIn(address pool, address tokenIn, uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut); } /** * @title TwoHopDodoToUniswapV3MultiHopExternalUnwinder * @notice Unwinds through two DODO PMM hops followed by a final Uniswap V3 exactInput path. * @dev `data` = * abi.encode( * address poolA, * address poolB, * address midToken, * uint256 minMidOut, * address intermediateToken, * uint256 minIntermediateOut, * bytes uniswapPath * ) * Route shape: * tokenIn --(poolA via DODO)--> midToken * midToken --(poolB via DODO)--> intermediateToken * intermediateToken --(Uniswap V3 path)--> tokenOut */ contract TwoHopDodoToUniswapV3MultiHopExternalUnwinder { using SafeERC20 for IERC20; address public immutable integration; address public immutable router; error BadParams(); constructor(address integration_, address router_) { if (integration_ == address(0) || router_ == address(0)) revert BadParams(); integration = integration_; router = router_; } function unwind(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes calldata data) external returns (uint256 amountOut) { if (tokenIn == address(0) || tokenOut == address(0) || tokenIn == tokenOut || amountIn == 0) revert BadParams(); ( address poolA, address poolB, address midToken, uint256 minMidOut, address intermediateToken, uint256 minIntermediateOut, bytes memory uniswapPath ) = abi.decode(data, (address, address, address, uint256, address, uint256, bytes)); if (poolA == address(0) || poolB == address(0) || midToken == address(0) || intermediateToken == address(0)) { revert BadParams(); } if (midToken == tokenIn || midToken == tokenOut || intermediateToken == tokenIn || intermediateToken == midToken) { revert BadParams(); } if (uniswapPath.length < 43) revert BadParams(); IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); IERC20(tokenIn).forceApprove(integration, amountIn); uint256 midOut = IDODOTwoHopSwapExactIn(integration).swapExactIn(poolA, tokenIn, amountIn, minMidOut); if (midOut == 0) revert BadParams(); IERC20(midToken).forceApprove(integration, midOut); uint256 intermediateOut = IDODOTwoHopSwapExactIn(integration).swapExactIn(poolB, midToken, midOut, minIntermediateOut); if (intermediateOut == 0) revert BadParams(); IERC20(intermediateToken).forceApprove(router, intermediateOut); amountOut = ISwapRouter(router).exactInput( ISwapRouter.ExactInputParams({ path: uniswapPath, recipient: msg.sender, deadline: block.timestamp + 300, amountIn: intermediateOut, amountOutMinimum: minAmountOut }) ); } }