355 lines
11 KiB
Solidity
355 lines
11 KiB
Solidity
// 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);
|
|
}
|
|
}
|
|
|