// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../interfaces/IFlashLoanRouter.sol"; /** * @title FlashLoanRouter * @notice Multi-provider flash loan aggregator * @dev Routes flash loans to Aave, Balancer, Uniswap V3, or DAI flash mint */ contract FlashLoanRouter is IFlashLoanRouter, Ownable, ReentrancyGuard { using SafeERC20 for IERC20; // Callback interface interface IFlashLoanReceiver { function onFlashLoan( address asset, uint256 amount, uint256 fee, bytes calldata data ) external returns (bytes32); } // Aave v3 Pool interface IPoolV3 { function flashLoanSimple( address receiverAddress, address asset, uint256 amount, bytes calldata params, uint16 referralCode ) external; } // Balancer Vault interface IBalancerVault { function flashLoan( address recipient, address[] memory tokens, uint256[] memory amounts, bytes memory userData ) external; } // Uniswap V3 Pool interface IUniswapV3Pool { function flash( address recipient, uint256 amount0, uint256 amount1, bytes calldata data ) external; } // DAI Flash Mint interface IDaiFlashMint { function flashMint( address receiver, uint256 amount, bytes calldata data ) external; } // Provider addresses address public aavePool; address public balancerVault; address public daiFlashMint; // Constants bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); uint16 public constant AAVE_REFERRAL_CODE = 0; // Flash loan state struct FlashLoanState { address caller; FlashLoanProvider provider; bytes callbackData; bool inProgress; } FlashLoanState private flashLoanState; event ProviderAddressUpdated(FlashLoanProvider provider, address oldAddress, address newAddress); modifier onlyInFlashLoan() { require(flashLoanState.inProgress, "Not in flash loan"); _; } modifier onlyNotInFlashLoan() { require(!flashLoanState.inProgress, "Flash loan in progress"); _; } constructor( address _aavePool, address _balancerVault, address _daiFlashMint, address initialOwner ) Ownable(initialOwner) { aavePool = _aavePool; balancerVault = _balancerVault; daiFlashMint = _daiFlashMint; } /** * @notice Execute a flash loan */ function flashLoan( FlashLoanParams memory params, bytes memory callbackData ) external override nonReentrant onlyNotInFlashLoan { require(params.asset != address(0), "Invalid asset"); require(params.amount > 0, "Invalid amount"); // Determine provider if needed (for liquidity-weighted selection) FlashLoanProvider provider = params.provider; // Set flash loan state flashLoanState = FlashLoanState({ caller: msg.sender, provider: provider, callbackData: callbackData, inProgress: true }); // Execute flash loan based on provider if (provider == FlashLoanProvider.AAVE) { _flashLoanAave(params.asset, params.amount); } else if (provider == FlashLoanProvider.BALANCER) { _flashLoanBalancer(params.asset, params.amount); } else if (provider == FlashLoanProvider.UNISWAP) { _flashLoanUniswap(params.asset, params.amount); } else if (provider == FlashLoanProvider.DAI_FLASH) { _flashLoanDai(params.amount); } else { revert("Invalid provider"); } // Clear state flashLoanState.inProgress = false; delete flashLoanState; } /** * @notice Execute multi-asset flash loan */ function flashLoanBatch( FlashLoanParams[] memory params, bytes memory callbackData ) external override nonReentrant onlyNotInFlashLoan { require(params.length > 0, "Empty params"); require(params.length <= 10, "Too many assets"); // Reasonable limit // For simplicity, execute sequentially // In production, could optimize for parallel execution for (uint256 i = 0; i < params.length; i++) { flashLoan(params[i], callbackData); } } /** * @notice Aave v3 flash loan */ function _flashLoanAave(address asset, uint256 amount) internal { require(aavePool != address(0), "Aave pool not set"); emit FlashLoanInitiated(asset, amount, FlashLoanProvider.AAVE); bytes memory params = abi.encode(flashLoanState.caller, flashLoanState.callbackData); IPoolV3(aavePool).flashLoanSimple( address(this), asset, amount, params, AAVE_REFERRAL_CODE ); } /** * @notice Balancer flash loan */ function _flashLoanBalancer(address asset, uint256 amount) internal { require(balancerVault != address(0), "Balancer vault not set"); emit FlashLoanInitiated(asset, amount, FlashLoanProvider.BALANCER); address[] memory tokens = new address[](1); tokens[0] = asset; uint256[] memory amounts = new uint256[](1); amounts[0] = amount; bytes memory userData = abi.encode(flashLoanState.caller, flashLoanState.callbackData); IBalancerVault(balancerVault).flashLoan(address(this), tokens, amounts, userData); } /** * @notice Uniswap V3 flash loan */ function _flashLoanUniswap(address asset, uint256 amount) internal { // Uniswap V3 requires pool address - simplified here // In production, would need to determine pool from asset pair revert("Uniswap V3 flash loan not fully implemented"); } /** * @notice DAI flash mint */ function _flashLoanDai(uint256 amount) internal { require(daiFlashMint != address(0), "DAI flash mint not set"); emit FlashLoanInitiated(address(0), amount, FlashLoanProvider.DAI_FLASH); // DAI address bytes memory data = abi.encode(flashLoanState.caller, flashLoanState.callbackData); IDaiFlashMint(daiFlashMint).flashMint(address(this), amount, data); } /** * @notice Aave flash loan callback */ function executeOperation( address asset, uint256 amount, uint256 premium, bytes calldata params ) external returns (bool) { require(msg.sender == aavePool, "Invalid caller"); require(flashLoanState.inProgress, "Not in flash loan"); (address receiver, bytes memory callbackData) = abi.decode(params, (address, bytes)); // Calculate total repayment uint256 totalRepayment = amount + premium; // Call receiver callback bytes32 result = IFlashLoanReceiver(receiver).onFlashLoan( asset, amount, premium, callbackData ); require(result == CALLBACK_SUCCESS, "Callback failed"); // Repay flash loan IERC20(asset).safeApprove(aavePool, totalRepayment); emit FlashLoanRepaid(asset, totalRepayment); return true; } /** * @notice Balancer flash loan callback */ function receiveFlashLoan( address[] memory tokens, uint256[] memory amounts, uint256[] memory feeAmounts, bytes memory userData ) external { require(msg.sender == balancerVault, "Invalid caller"); require(flashLoanState.inProgress, "Not in flash loan"); require(tokens.length == 1, "Single asset only"); (address receiver, bytes memory callbackData) = abi.decode(userData, (address, bytes)); address asset = tokens[0]; uint256 amount = amounts[0]; uint256 fee = feeAmounts[0]; // Call receiver callback bytes32 result = IFlashLoanReceiver(receiver).onFlashLoan( asset, amount, fee, callbackData ); require(result == CALLBACK_SUCCESS, "Callback failed"); // Repay flash loan uint256 totalRepayment = amount + fee; IERC20(asset).safeApprove(balancerVault, totalRepayment); emit FlashLoanRepaid(asset, totalRepayment); } /** * @notice Get available liquidity from Aave */ function getAvailableLiquidity( address asset, FlashLoanProvider provider ) external view override returns (uint256) { if (provider == FlashLoanProvider.AAVE) { // Query Aave liquidity (simplified) // In production, would query Aave Pool's available liquidity return type(uint256).max; // Placeholder } else if (provider == FlashLoanProvider.BALANCER) { // Query Balancer liquidity return type(uint256).max; // Placeholder } else if (provider == FlashLoanProvider.DAI_FLASH) { // DAI flash mint has no limit return type(uint256).max; } return 0; } /** * @notice Get flash loan fee */ function getFlashLoanFee( address asset, uint256 amount, FlashLoanProvider provider ) external view override returns (uint256) { if (provider == FlashLoanProvider.AAVE) { // Aave v3: 0.05% premium return (amount * 5) / 10000; } else if (provider == FlashLoanProvider.BALANCER) { // Balancer: variable fee return (amount * 1) / 10000; // 0.01% placeholder } else if (provider == FlashLoanProvider.DAI_FLASH) { // DAI flash mint: 0% fee (plus gas cost) return 0; } return 0; } /** * @notice Update provider addresses */ function setAavePool(address newPool) external onlyOwner { address oldPool = aavePool; aavePool = newPool; emit ProviderAddressUpdated(FlashLoanProvider.AAVE, oldPool, newPool); } function setBalancerVault(address newVault) external onlyOwner { address oldVault = balancerVault; balancerVault = newVault; emit ProviderAddressUpdated(FlashLoanProvider.BALANCER, oldVault, newVault); } function setDaiFlashMint(address newFlashMint) external onlyOwner { address oldFlashMint = daiFlashMint; daiFlashMint = newFlashMint; emit ProviderAddressUpdated(FlashLoanProvider.DAI_FLASH, oldFlashMint, newFlashMint); } }