// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "../../contracts/balancer/FlashLoanReceiverSecure.sol"; /// @title Integration Tests with Real Arbitrum Mainnet Data /// @notice Tests against actual deployed contracts on Arbitrum /// @dev Requires forking from Arbitrum mainnet contract FlashLoanReceiverSecureIntegrationTest is Test { FlashLoanReceiverSecure public flashLoan; // Real Arbitrum mainnet addresses address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; address constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; address constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; address constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; address constant ARB = 0x912CE59144191C1204E64559FE8253a0e49E6548; address constant DAI = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; // Real DEX addresses address constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address constant SUSHISWAP_ROUTER = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; // Whale addresses with large token balances address constant WETH_WHALE = 0xF977814e90dA44bFA03b6295A0616a897441aceC; // Binance address constant USDC_WHALE = 0x47c031236e19d024b42f8AE6780E44A573170703; // Bridge address constant ARB_WHALE = 0xF3FC178157fb3c87548bAA86F9d24BA38E649B58; // Arbitrum Foundation address public owner; address public user; function setUp() public { // Fork Arbitrum mainnet (requires ARBITRUM_RPC_URL env var) string memory rpcUrl = vm.envString("ARBITRUM_RPC_URL"); uint256 forkId = vm.createFork(rpcUrl); vm.selectFork(forkId); // Verify we're on Arbitrum require(block.chainid == 42161, "Not on Arbitrum mainnet"); owner = address(this); user = address(0x123); // Deploy FlashLoanReceiverSecure with real Balancer Vault flashLoan = new FlashLoanReceiverSecure(BALANCER_VAULT); // Fund contract with ETH for gas vm.deal(address(flashLoan), 10 ether); } // Allow test contract to receive ETH receive() external payable {} /// ============================================ /// REAL CONTRACT INTERACTION TESTS /// ============================================ function testIntegration_RealBalancerVault() public view { // Verify we're using the real Balancer Vault assertEq(address(flashLoan.vault()), BALANCER_VAULT); // Verify Balancer Vault has code (is deployed) uint256 size; address vaultAddr = BALANCER_VAULT; assembly { size := extcodesize(vaultAddr) } assertGt(size, 0, "Balancer Vault not deployed"); } function testIntegration_RealTokenBalances() public { // Check real token balances of whales uint256 wethBalance = IERC20(WETH).balanceOf(WETH_WHALE); uint256 usdcBalance = IERC20(USDC).balanceOf(USDC_WHALE); uint256 arbBalance = IERC20(ARB).balanceOf(ARB_WHALE); assertGt(wethBalance, 0, "WETH whale has no balance"); assertGt(usdcBalance, 0, "USDC whale has no balance"); assertGt(arbBalance, 0, "ARB whale has no balance"); emit log_named_decimal_uint("WETH Whale Balance", wethBalance, 18); emit log_named_decimal_uint("USDC Whale Balance", usdcBalance, 6); emit log_named_decimal_uint("ARB Whale Balance", arbBalance, 18); } function testIntegration_RealTokenMetadata() public { // Verify real token properties assertEq(IERC20Metadata(WETH).decimals(), 18, "WETH decimals"); assertEq(IERC20Metadata(USDC).decimals(), 6, "USDC decimals"); assertEq(IERC20Metadata(USDT).decimals(), 6, "USDT decimals"); assertEq(IERC20Metadata(ARB).decimals(), 18, "ARB decimals"); assertEq(IERC20Metadata(DAI).decimals(), 18, "DAI decimals"); assertEq(IERC20Metadata(WETH).symbol(), "WETH", "WETH symbol"); assertEq(IERC20Metadata(USDC).symbol(), "USDC", "USDC symbol"); assertEq(IERC20Metadata(ARB).symbol(), "ARB", "ARB symbol"); } /// ============================================ /// REAL FLASH LOAN TESTS /// ============================================ function testIntegration_SmallFlashLoan() public { // Borrow 1 WETH from Balancer uint256 loanAmount = 1 ether; // Prepare flash loan parameters IERC20[] memory tokens = new IERC20[](1); tokens[0] = IERC20(WETH); uint256[] memory amounts = new uint256[](1); amounts[0] = loanAmount; // Create simple arbitrage path (just for testing repayment) bytes memory userData = _createSimpleTestPath(); // Record initial balances uint256 vaultWETHBefore = IERC20(WETH).balanceOf(BALANCER_VAULT); // Execute flash loan vm.prank(owner); // Note: This will fail because we need a profitable path // But it tests the real Balancer Vault interaction vm.expectRevert(); // Will revert with "Insufficient funds for repayment" flashLoan.executeArbitrage(tokens, amounts, userData); // Verify vault balance unchanged (flash loan reverted) assertEq(IERC20(WETH).balanceOf(BALANCER_VAULT), vaultWETHBefore); } function testIntegration_MultiTokenFlashLoan() public { // Borrow multiple tokens IERC20[] memory tokens = new IERC20[](2); tokens[0] = IERC20(WETH); tokens[1] = IERC20(USDC); uint256[] memory amounts = new uint256[](2); amounts[0] = 0.1 ether; amounts[1] = 100 * 10**6; // 100 USDC bytes memory userData = _createSimpleTestPath(); // Execute flash loan (will fail without profitable path) vm.prank(owner); vm.expectRevert(); flashLoan.executeArbitrage(tokens, amounts, userData); } /// ============================================ /// REAL TOKEN TRANSFER TESTS /// ============================================ function testIntegration_ReceiveRealWETH() public { uint256 amount = 1 ether; // Impersonate WETH whale vm.startPrank(WETH_WHALE); // Transfer WETH to contract IERC20(WETH).transfer(address(flashLoan), amount); vm.stopPrank(); // Verify transfer assertEq(IERC20(WETH).balanceOf(address(flashLoan)), amount); } function testIntegration_WithdrawRealWETH() public { uint256 amount = 0.5 ether; // Fund contract with real WETH vm.prank(WETH_WHALE); IERC20(WETH).transfer(address(flashLoan), amount); uint256 ownerBalanceBefore = IERC20(WETH).balanceOf(owner); // Withdraw WETH flashLoan.withdrawProfit(WETH, amount); assertEq(IERC20(WETH).balanceOf(owner), ownerBalanceBefore + amount); assertEq(IERC20(WETH).balanceOf(address(flashLoan)), 0); } function testIntegration_EmergencyWithdrawRealTokens() public { // Fund contract with multiple real tokens vm.prank(WETH_WHALE); IERC20(WETH).transfer(address(flashLoan), 0.1 ether); vm.prank(USDC_WHALE); IERC20(USDC).transfer(address(flashLoan), 100 * 10**6); // Emergency withdraw WETH uint256 wethAmount = IERC20(WETH).balanceOf(address(flashLoan)); flashLoan.emergencyWithdraw(WETH); assertEq(IERC20(WETH).balanceOf(address(flashLoan)), 0); assertGt(IERC20(WETH).balanceOf(owner), 0); // Emergency withdraw USDC uint256 usdcAmount = IERC20(USDC).balanceOf(address(flashLoan)); flashLoan.emergencyWithdraw(USDC); assertEq(IERC20(USDC).balanceOf(address(flashLoan)), 0); assertGt(IERC20(USDC).balanceOf(owner), 0); } /// ============================================ /// REAL DEX INTERACTION TESTS /// ============================================ function testIntegration_UniswapV3RouterExists() public { // Verify Uniswap V3 Router is deployed uint256 size; address router = UNISWAP_V3_ROUTER; assembly { size := extcodesize(router) } assertGt(size, 0, "Uniswap V3 Router not deployed"); } function testIntegration_SushiswapRouterExists() public { // Verify SushiSwap Router is deployed uint256 size; address router = SUSHISWAP_ROUTER; assembly { size := extcodesize(router) } assertGt(size, 0, "SushiSwap Router not deployed"); } /// ============================================ /// REAL LIQUIDITY TESTS /// ============================================ function testIntegration_BalancerHasLiquidity() public { // Check Balancer Vault has significant WETH liquidity for flash loans uint256 wethBalance = IERC20(WETH).balanceOf(BALANCER_VAULT); uint256 usdcBalance = IERC20(USDC).balanceOf(BALANCER_VAULT); assertGt(wethBalance, 100 ether, "Insufficient WETH liquidity"); assertGt(usdcBalance, 100_000 * 10**6, "Insufficient USDC liquidity"); emit log_named_decimal_uint("Balancer WETH Liquidity", wethBalance, 18); emit log_named_decimal_uint("Balancer USDC Liquidity", usdcBalance, 6); } /// ============================================ /// REAL PRICE ORACLE TESTS /// ============================================ function testIntegration_TokenPriceConsistency() public { // Get real token balances from whales uint256 wethBalance = IERC20(WETH).balanceOf(WETH_WHALE); uint256 usdcBalance = IERC20(USDC).balanceOf(USDC_WHALE); // Basic sanity checks assertGt(wethBalance, 10 ether, "WETH whale balance too low"); assertGt(usdcBalance, 10_000 * 10**6, "USDC whale balance too low"); emit log("Real token balances verified"); } /// ============================================ /// CONTRACT STATE VERIFICATION /// ============================================ function testIntegration_ContractConfiguration() public view { // Verify contract is properly configured assertEq(flashLoan.owner(), owner); assertEq(address(flashLoan.vault()), BALANCER_VAULT); assertEq(flashLoan.MAX_SLIPPAGE_BPS(), 50); assertEq(flashLoan.MAX_PATH_LENGTH(), 5); assertEq(flashLoan.BASIS_POINTS(), 10000); } function testIntegration_ContractHasGas() public view { // Verify contract has ETH for gas assertGt(address(flashLoan).balance, 0); } /// ============================================ /// HELPER FUNCTIONS /// ============================================ function _createSimpleTestPath() private pure returns (bytes memory) { // Create minimal path structure for testing // This is intentionally unprofitable to test flash loan mechanics address[] memory tokens = new address[](2); tokens[0] = WETH; tokens[1] = USDC; address[] memory exchanges = new address[](1); exchanges[0] = UNISWAP_V3_ROUTER; uint24[] memory fees = new uint24[](1); fees[0] = 3000; // 0.3% fee tier bool[] memory isV3 = new bool[](1); isV3[0] = true; return abi.encode( tokens, exchanges, fees, isV3, 0, // minProfit 50 // slippageBps ); } } interface IERC20Metadata { function decimals() external view returns (uint8); function symbol() external view returns (string memory); function name() external view returns (string memory); }