Skip to content

闪电贷

概述

闪电贷允许在单笔交易内无抵押借贷任意金额,条件是交易结束前偿还本金 + 手续费(0.09%)。

常见用途

用途描述
套利跨 DEX 价格差异套利
清算清算不健康仓位获取奖励
抵押品交换一键更换抵押品类型
债务再融资切换协议或利率模式

基础实现

单资产闪电贷

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {FlashLoanSimpleReceiverBase} from "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import {IPoolAddressesProvider} from "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract SimpleFlashLoan is FlashLoanSimpleReceiverBase {
    constructor(IPoolAddressesProvider provider) 
        FlashLoanSimpleReceiverBase(provider) {}
    
    function execute(address asset, uint256 amount) external {
        POOL.flashLoanSimple(
            address(this),  // receiver
            asset,
            amount,
            abi.encode(msg.sender), // params
            0  // referralCode
        );
    }
    
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Invalid caller");
        require(initiator == address(this), "Invalid initiator");
        
        // ========== 业务逻辑 ==========
        // 此时拥有 `amount` 数量的 `asset`
        
        address user = abi.decode(params, (address));
        // ... 执行操作
        
        // ========== 偿还 ==========
        uint256 totalDebt = amount + premium;
        IERC20(asset).approve(address(POOL), totalDebt);
        
        return true;
    }
}

多资产闪电贷

solidity
contract MultiFlashLoan is FlashLoanReceiverBase {
    constructor(IPoolAddressesProvider provider) 
        FlashLoanReceiverBase(provider) {}
    
    function execute(
        address[] calldata assets,
        uint256[] calldata amounts
    ) external {
        uint256[] memory modes = new uint256[](assets.length);
        // modes: 0=不保留债务, 1=稳定利率, 2=浮动利率
        
        POOL.flashLoan(
            address(this),
            assets,
            amounts,
            modes,
            address(this),
            "",
            0
        );
    }
    
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Invalid caller");
        
        // 业务逻辑...
        
        // 偿还所有资产
        for (uint i = 0; i < assets.length; i++) {
            IERC20(assets[i]).approve(address(POOL), amounts[i] + premiums[i]);
        }
        
        return true;
    }
}

实战案例

套利机器人

solidity
contract ArbitrageBot is FlashLoanSimpleReceiverBase {
    IUniswapV2Router public immutable routerA;
    IUniswapV2Router public immutable routerB;
    address public owner;
    
    struct ArbParams {
        address tokenA;
        address tokenB;
        bool buyOnA;
        uint256 minProfit;
    }
    
    function executeArbitrage(
        address tokenA,
        address tokenB,
        uint256 amount,
        bool buyOnA,
        uint256 minProfit
    ) external {
        require(msg.sender == owner, "Only owner");
        
        bytes memory params = abi.encode(ArbParams({
            tokenA: tokenA,
            tokenB: tokenB,
            buyOnA: buyOnA,
            minProfit: minProfit
        }));
        
        POOL.flashLoanSimple(address(this), tokenA, amount, params, 0);
    }
    
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Invalid caller");
        
        ArbParams memory arb = abi.decode(params, (ArbParams));
        uint256 totalDebt = amount + premium;
        
        // 构建路径
        address[] memory path = new address[](2);
        path[0] = arb.tokenA;
        path[1] = arb.tokenB;
        
        address[] memory reversePath = new address[](2);
        reversePath[0] = arb.tokenB;
        reversePath[1] = arb.tokenA;
        
        IUniswapV2Router buyRouter = arb.buyOnA ? routerA : routerB;
        IUniswapV2Router sellRouter = arb.buyOnA ? routerB : routerA;
        
        // 买入
        IERC20(arb.tokenA).approve(address(buyRouter), amount);
        uint[] memory buyAmounts = buyRouter.swapExactTokensForTokens(
            amount, 0, path, address(this), block.timestamp
        );
        
        // 卖出
        IERC20(arb.tokenB).approve(address(sellRouter), buyAmounts[1]);
        uint[] memory sellAmounts = sellRouter.swapExactTokensForTokens(
            buyAmounts[1], totalDebt + arb.minProfit, reversePath, address(this), block.timestamp
        );
        
        // 验证利润
        require(sellAmounts[1] >= totalDebt + arb.minProfit, "No profit");
        
        // 偿还
        IERC20(asset).approve(address(POOL), totalDebt);
        
        // 转移利润
        IERC20(asset).transfer(owner, sellAmounts[1] - totalDebt);
        
        return true;
    }
}

清算机器人

solidity
contract LiquidationBot is FlashLoanSimpleReceiverBase {
    ISwapRouter public immutable swapRouter;
    address public owner;
    
    struct LiqParams {
        address collateral;
        address debt;
        address user;
        uint256 debtToCover;
    }
    
    function liquidate(
        address collateral,
        address debt,
        address user,
        uint256 debtToCover
    ) external {
        require(msg.sender == owner, "Only owner");
        
        // 验证可清算
        (, , , , , uint256 hf) = POOL.getUserAccountData(user);
        require(hf < 1e18, "Cannot liquidate");
        
        bytes memory params = abi.encode(LiqParams({
            collateral: collateral,
            debt: debt,
            user: user,
            debtToCover: debtToCover
        }));
        
        POOL.flashLoanSimple(address(this), debt, debtToCover, params, 0);
    }
    
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Invalid caller");
        
        LiqParams memory liq = abi.decode(params, (LiqParams));
        uint256 totalDebt = amount + premium;
        
        // 执行清算
        IERC20(asset).approve(address(POOL), amount);
        POOL.liquidationCall(liq.collateral, liq.debt, liq.user, liq.debtToCover, false);
        
        // 卖出抵押品
        uint256 collateralReceived = IERC20(liq.collateral).balanceOf(address(this));
        IERC20(liq.collateral).approve(address(swapRouter), collateralReceived);
        
        uint256 amountOut = swapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({
            tokenIn: liq.collateral,
            tokenOut: liq.debt,
            fee: 3000,
            recipient: address(this),
            deadline: block.timestamp,
            amountIn: collateralReceived,
            amountOutMinimum: totalDebt,
            sqrtPriceLimitX96: 0
        }));
        
        // 偿还
        IERC20(asset).approve(address(POOL), totalDebt);
        
        // 转移利润
        if (amountOut > totalDebt) {
            IERC20(asset).transfer(owner, amountOut - totalDebt);
        }
        
        return true;
    }
}

抵押品交换

solidity
contract CollateralSwap is FlashLoanSimpleReceiverBase {
    struct SwapParams {
        address user;
        address fromAsset;
        address toAsset;
        uint256 fromAmount;
    }
    
    function swap(address fromAsset, address toAsset, uint256 fromAmount) external {
        uint256 toAmount = _estimateSwap(fromAsset, toAsset, fromAmount);
        
        bytes memory params = abi.encode(SwapParams({
            user: msg.sender,
            fromAsset: fromAsset,
            toAsset: toAsset,
            fromAmount: fromAmount
        }));
        
        POOL.flashLoanSimple(address(this), toAsset, toAmount, params, 0);
    }
    
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Invalid caller");
        
        SwapParams memory swap = abi.decode(params, (SwapParams));
        
        // 1. 存入新抵押品
        IERC20(asset).approve(address(POOL), amount);
        POOL.supply(asset, amount, swap.user, 0);
        
        // 2. 提取旧抵押品(需要用户预先授权 aToken)
        POOL.withdraw(swap.fromAsset, swap.fromAmount, address(this));
        
        // 3. 交换
        uint256 swapped = _swap(swap.fromAsset, asset, swap.fromAmount);
        
        // 4. 偿还
        uint256 totalDebt = amount + premium;
        require(swapped >= totalDebt, "Insufficient");
        IERC20(asset).approve(address(POOL), totalDebt);
        
        // 5. 返还多余
        if (swapped > totalDebt) {
            IERC20(asset).transfer(swap.user, swapped - totalDebt);
        }
        
        return true;
    }
}

手续费

solidity
// 标准手续费: 0.09%
uint256 premium = (amount * 9) / 10000;
uint256 totalDebt = amount + premium;

// 获取实际费率
uint128 premiumTotal = POOL.FLASHLOAN_PREMIUM_TOTAL();
uint128 premiumToProtocol = POOL.FLASHLOAN_PREMIUM_TO_PROTOCOL();

安全检查

必须验证

solidity
function executeOperation(...) external override returns (bool) {
    // 1. 验证调用者是 Pool
    require(msg.sender == address(POOL), "Invalid caller");
    
    // 2. 验证发起者是本合约
    require(initiator == address(this), "Invalid initiator");
    
    // 3. 确保有足够资金偿还
    uint256 totalDebt = amount + premium;
    require(IERC20(asset).balanceOf(address(this)) >= totalDebt, "Insufficient");
    
    // ...
}

其他注意事项

  • 使用 ReentrancyGuard 防止重入
  • DEX 交换设置合理滑点
  • 实现紧急提取功能
  • 充分测试后再部署主网

基于 MIT 许可发布