// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @dev Matches `DODOPMMIntegration.swapExactIn` surface (any registered pool). interface IDODOStyleSwapExactIn { function swapExactIn(address pool, address tokenIn, uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut); } /** * @title SwapFlashWorkflowBorrower * @notice ERC-3156 borrower: flash `token` → swap to `midToken` → swap back to `token` → repay `amount + fee`. * @dev `data` must be `abi.encode(SwapFlashParams)`. The caller chooses `integration` — use only trusted DODO/PMM routers. * `pool` is typically the same for both legs (e.g. cUSDT/USDT round-trip). Set mins from off-chain quotes. */ contract SwapFlashWorkflowBorrower is IERC3156FlashBorrower { using SafeERC20 for IERC20; bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); address public immutable trustedLender; struct SwapFlashParams { address integration; address pool; address midToken; uint256 minOutFirst; uint256 minOutSecond; } error UntrustedLender(); error BadParams(); error InsufficientToRepay(); constructor(address trustedLender_) { trustedLender = trustedLender_; } function onFlashLoan( address, address token, uint256 amount, uint256 fee, bytes calldata data ) external override returns (bytes32) { if (msg.sender != trustedLender) revert UntrustedLender(); SwapFlashParams memory p = abi.decode(data, (SwapFlashParams)); if (p.integration == address(0) || p.pool == address(0) || p.midToken == address(0)) revert BadParams(); if (p.midToken == token) revert BadParams(); IERC20 borrowed = IERC20(token); IERC20 mid = IERC20(p.midToken); borrowed.forceApprove(p.integration, amount); IDODOStyleSwapExactIn(p.integration).swapExactIn(p.pool, token, amount, p.minOutFirst); uint256 midBal = mid.balanceOf(address(this)); mid.forceApprove(p.integration, midBal); IDODOStyleSwapExactIn(p.integration).swapExactIn(p.pool, p.midToken, midBal, p.minOutSecond); uint256 need = amount + fee; if (borrowed.balanceOf(address(this)) < need) revert InsufficientToRepay(); borrowed.safeTransfer(msg.sender, need); return _RETURN_VALUE; } }