// 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"; import {ICrossChainFlashBridge} from "./interfaces/ICrossChainFlashBridge.sol"; /** * @title CrossChainFlashBorrower * @notice ERC-3156 borrower: flash `token` on **this** chain → bridge `bridgeAmount` out → repay `amount + fee` to the flash vault from **on-hand** balance. * @dev **Atomicity is single-chain only.** CCIP / bridge finality is asynchronous; destination delivery cannot complete inside this callback. * **Prefunding:** before `flashLoan`, this contract should hold at least `bridgeAmount + fee` of `token` * (or `amount + fee` if `bridgeAmount == amount`) so repayment succeeds after the bridge pulls `bridgeAmount`. * Encode `CrossChainFlashParams` in `data`. */ contract CrossChainFlashBorrower is IERC3156FlashBorrower { using SafeERC20 for IERC20; bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); address public immutable trustedLender; struct CrossChainFlashParams { address bridge; uint256 bridgeAmount; uint64 destinationChainSelector; address recipientOnDestination; bytes bridgeExtraData; uint256 nativeBridgeFee; } 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(); _run(abi.decode(data, (CrossChainFlashParams)), token, amount, fee); return _RETURN_VALUE; } function _run(CrossChainFlashParams memory p, address token, uint256 amount, uint256 fee) internal { if (p.bridge == address(0) || p.recipientOnDestination == address(0)) revert BadParams(); if (p.bridgeAmount == 0 || p.bridgeAmount > amount) revert BadParams(); IERC20 t = IERC20(token); t.forceApprove(p.bridge, p.bridgeAmount); ICrossChainFlashBridge(p.bridge).bridgeTokensFrom{value: p.nativeBridgeFee}( token, p.bridgeAmount, p.destinationChainSelector, p.recipientOnDestination, p.bridgeExtraData ); uint256 need = amount + fee; if (t.balanceOf(address(this)) < need) revert InsufficientToRepay(); t.safeTransfer(msg.sender, need); } receive() external payable {} }