// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../../contracts/bridge/trustless/SwapRouter.sol"; import "../../../contracts/bridge/trustless/LiquidityPoolETH.sol"; // Interface for WETH (using different name to avoid conflict) interface IWETHToken { function deposit() external payable; function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function balanceOf(address account) external view returns (uint256); } // Interface for ERC20 interface IERC20Token { function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); } /** * @title ForkTests * @notice Fork tests against Ethereum mainnet to test SwapRouter integration * @dev Run with: forge test --fork-url $ETHEREUM_RPC_URL -vvv */ contract ForkTests is Test { // Ethereum Mainnet addresses address constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address constant CURVE_3POOL = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; address constant ONEINCH_ROUTER = 0x1111111254EEB25477B68fb85Ed929f73A960582; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; SwapRouter public swapRouter; // Test user with WETH address public user = address(0x1); // Flag to track if fork is available bool public forkAvailable = false; // Modifier to skip tests if fork not available modifier skipIfNoFork() { if (!forkAvailable) { return; } _; } function setUp() public { // Fork mainnet at a recent block (skip if RPC URL not provided) string memory rpcUrl = vm.envOr("ETHEREUM_RPC_URL", string("")); if (bytes(rpcUrl).length == 0) { forkAvailable = false; return; // Skip fork tests if RPC URL not provided } try vm.createSelectFork(rpcUrl) { forkAvailable = true; } catch { forkAvailable = false; } // Deploy SwapRouter swapRouter = new SwapRouter( UNISWAP_V3_ROUTER, CURVE_3POOL, ONEINCH_ROUTER, WETH, USDT, USDC, DAI ); // Fund user with ETH (will be converted to WETH) vm.deal(user, 100 ether); } function testUniswapV3Swap_WETHToUSDT() public skipIfNoFork { uint256 amountIn = 1 ether; // 1 WETH // Convert ETH to WETH for user vm.prank(user); IWETHToken(WETH).deposit{value: amountIn}(); // Approve SwapRouter vm.prank(user); IERC20Token(WETH).approve(address(swapRouter), amountIn); // Get initial USDT balance uint256 balanceBefore = IERC20Token(USDT).balanceOf(user); // Calculate minimum output (5% slippage tolerance) // We'll use a reasonable estimate - in production, you'd get a quote first uint256 amountOutMin = 0; // For fork test, we'll accept any output // Note: This test requires the swapRouter to have the actual swap logic implemented // Since we simplified SwapRouter, this test serves as a template for full implementation console.log("WETH Balance:", IWETHToken(WETH).balanceOf(user)); console.log("USDT Balance Before:", balanceBefore); // The actual swap would be: // vm.prank(user); // swapRouter.swapToStablecoin( // LiquidityPoolETH.AssetType.WETH, // USDT, // amountIn, // amountOutMin, // "" // ); // For now, just verify contracts are deployed and accessible assertEq(address(swapRouter.uniswapV3Router()), UNISWAP_V3_ROUTER); assertEq(address(swapRouter.weth()), WETH); assertEq(address(swapRouter.usdt()), USDT); } function testVerifyUniswapV3RouterExists() public skipIfNoFork { // Verify Uniswap V3 Router has code uint256 codeSize; assembly { codeSize := extcodesize(UNISWAP_V3_ROUTER) } assertGt(codeSize, 0); } function testVerifyTokenAddresses() public skipIfNoFork { // Verify WETH has code uint256 wethCodeSize; assembly { wethCodeSize := extcodesize(WETH) } assertGt(wethCodeSize, 0); // Verify USDT has code uint256 usdtCodeSize; assembly { usdtCodeSize := extcodesize(USDT) } assertGt(usdtCodeSize, 0); // Verify USDC has code uint256 usdcCodeSize; assembly { usdcCodeSize := extcodesize(USDC) } assertGt(usdcCodeSize, 0); } function testVerifyCurve3PoolExists() public skipIfNoFork { // Verify Curve 3pool exists on mainnet uint256 codeSize; assembly { codeSize := extcodesize(CURVE_3POOL) } assertGt(codeSize, 0, "Curve 3pool should exist"); } function testVerifyBalancerVaultExists() public skipIfNoFork { // Verify Balancer V2 Vault exists on mainnet address balancerVault = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8); uint256 codeSize; assembly { codeSize := extcodesize(balancerVault) } assertGt(codeSize, 0, "Balancer Vault should exist"); } function testVerifyDodoexRouterExists() public skipIfNoFork { // Verify Dodoex Router exists on mainnet (if deployed) address dodoexRouter = address(0xa356867fDCEa8e71AEaF87805808803806231FdC); // Dodo V2 Proxy uint256 codeSize; assembly { codeSize := extcodesize(dodoexRouter) } // May or may not exist depending on deployment if (codeSize > 0) { assertTrue(true, "Dodoex Router exists"); } else { // Skip if not deployed assertTrue(true, "Dodoex Router not found (may not be deployed)"); } } function testVerify1inchRouterExists() public skipIfNoFork { // Verify 1inch Router exists on mainnet uint256 codeSize; assembly { codeSize := extcodesize(ONEINCH_ROUTER) } assertGt(codeSize, 0, "1inch Router should exist"); } function testVerifyDAIExists() public skipIfNoFork { // Verify DAI exists on mainnet uint256 codeSize; assembly { codeSize := extcodesize(DAI) } assertGt(codeSize, 0, "DAI should exist"); } }