// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; import {SimpleERC3156FlashVault} from "../../contracts/flash/SimpleERC3156FlashVault.sol"; import {SwapFlashWorkflowBorrower, IDODOStyleSwapExactIn} from "../../contracts/flash/SwapFlashWorkflowBorrower.sol"; /// @notice 1:1 swap router for tests (ignores pool). contract MockSwapRouter1to1 is IDODOStyleSwapExactIn { ERC20 public immutable tokenA; ERC20 public immutable tokenB; constructor(ERC20 a, ERC20 b) { tokenA = a; tokenB = b; } function swapExactIn(address, address tokenIn, uint256 amountIn, uint256 minAmountOut) external override returns (uint256 amountOut) { IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn); address tokenOut = tokenIn == address(tokenA) ? address(tokenB) : address(tokenA); amountOut = amountIn; require(amountOut >= minAmountOut, "min"); IERC20(tokenOut).transfer(msg.sender, amountOut); } } contract MockERC20Mint is ERC20 { constructor(string memory n, string memory s) ERC20(n, s) {} function mint(address to, uint256 v) external { _mint(to, v); } } contract SwapFlashWorkflowBorrowerTest is Test { SimpleERC3156FlashVault internal vault; MockERC20Mint internal tokenA; MockERC20Mint internal tokenB; MockSwapRouter1to1 internal router; SwapFlashWorkflowBorrower internal borrower; address internal owner = address(0xA11); address internal user = address(0xB22); function setUp() public { vm.startPrank(owner); vault = new SimpleERC3156FlashVault(owner, 5); tokenA = new MockERC20Mint("A", "A"); tokenB = new MockERC20Mint("B", "B"); tokenA.mint(address(vault), 1_000_000e18); vault.setTokenSupported(address(tokenA), true); vm.stopPrank(); router = new MockSwapRouter1to1(tokenA, tokenB); tokenA.mint(address(router), 10_000_000e18); tokenB.mint(address(router), 10_000_000e18); borrower = new SwapFlashWorkflowBorrower(address(vault)); } function test_roundTripSwap_prefundFeeInBorrowedToken() public { uint256 amount = 50_000e18; uint256 fee = vault.flashFee(address(tokenA), amount); tokenA.mint(address(borrower), fee); SwapFlashWorkflowBorrower.SwapFlashParams memory p = SwapFlashWorkflowBorrower.SwapFlashParams({ integration: address(router), pool: address(0xdead), midToken: address(tokenB), minOutFirst: amount, minOutSecond: amount }); bytes memory data = abi.encode(p); vm.prank(user); vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(tokenA), amount, data); assertEq(vault.totalFeesCollected(address(tokenA)), fee); } }