Initial commit
This commit is contained in:
354
contracts/core/FlashLoanRouter.sol
Normal file
354
contracts/core/FlashLoanRouter.sol
Normal file
@@ -0,0 +1,354 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user