// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {UniswapV3ExternalUnwinder} from "../../contracts/flash/UniswapV3ExternalUnwinder.sol"; contract UniswapV3ExternalUnwinderForkTest is Test { // SwapRouter (not SwapRouter02): exactInputSingle selector matches `ISwapRouter` in this repo. address constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a; bool public forkAvailable; UniswapV3ExternalUnwinder internal unwinder; function setUp() public { string memory rpcUrl = vm.envOr("ETHEREUM_MAINNET_RPC", string("")); if (bytes(rpcUrl).length == 0) { forkAvailable = false; return; } try vm.createSelectFork(rpcUrl) { forkAvailable = true; } catch { forkAvailable = false; return; } unwinder = new UniswapV3ExternalUnwinder(UNISWAP_V3_ROUTER); } modifier skipIfNoFork() { if (!forkAvailable) { return; } _; } function testFork_knownRoute_WETHToUSDC_singleHopWorks() public skipIfNoFork { deal(WETH, address(this), 1 ether); IERC20(WETH).approve(address(unwinder), type(uint256).max); uint256 before = IERC20(USDC).balanceOf(address(this)); uint256 amountOut = unwinder.unwind(WETH, USDC, 1 ether, 1, abi.encode(uint24(3000))); uint256 afterBal = IERC20(USDC).balanceOf(address(this)); assertGt(amountOut, 0, "amountOut > 0"); assertEq(afterBal - before, amountOut, "USDC received"); } function testFork_cWUSDCToUSDC_routeUnavailableOnUniswapV3() public skipIfNoFork { deal(CWUSDC, address(this), 1_000_000); IERC20(CWUSDC).approve(address(unwinder), 1_000_000); vm.expectRevert(); unwinder.unwind(CWUSDC, USDC, 1_000_000, 1, abi.encode(uint24(3000))); } }