// 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 IDODOMultiHopSwapExactIn { function swapExactIn(address pool, address tokenIn, uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut); } /** * @title DODOToUniswapV3MultiHopExternalUnwinder * @notice Unwinds through a DODO PMM first hop and a Uniswap V3 final hop. * @dev `data` must be abi.encode(address dodoPool, address intermediateToken, uint256 minIntermediateOut, bytes uniswapPath). * `uniswapPath` is the standard exactInput path beginning with `intermediateToken` and ending with `tokenOut`. */ contract DODOToUniswapV3MultiHopExternalUnwinder { using SafeERC20 for IERC20; address public immutable integration; address public immutable router; error BadParams(); constructor(address integration_, address router_) { 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 dodoPool, address intermediateToken, uint256 minIntermediateOut, bytes memory uniswapPath) = abi.decode(data, (address, address, uint256, bytes)); if (dodoPool == address(0) || intermediateToken == address(0) || intermediateToken == tokenIn || intermediateToken == tokenOut) { revert BadParams(); } if (uniswapPath.length < 43) revert BadParams(); IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); IERC20(tokenIn).forceApprove(integration, amountIn); uint256 intermediateOut = IDODOMultiHopSwapExactIn(integration).swapExactIn(dodoPool, tokenIn, amountIn, minIntermediateOut); IERC20(intermediateToken).forceApprove(router, intermediateOut); amountOut = ISwapRouter(router).exactInput( ISwapRouter.ExactInputParams({ path: uniswapPath, recipient: msg.sender, deadline: block.timestamp + 300, amountIn: intermediateOut, amountOutMinimum: minAmountOut }) ); } }