Flash Loan

閃電貸是一種在去中心化金融(DeFi)中獨有的、無擔保的借貸方式。它允許使用者在一次區塊鏈交易內,借入巨額的資產,並在同一筆交易中償還。

這整個過程,從借款到還款,都必須在區塊鏈的單一交易中完成。如果沒有在交易結束前償還借款,那麼整個交易會被自動取消(revert),就像從未發生過一樣。

這個特性讓閃電貸成為「無擔保」的借款,因為它不要求抵押品。合約本身就是你的擔保,如果違約,交易就會失敗,資金會回到原處,借貸雙方都不會有損失。


閃電貸的運作原理

閃電貸款的特點:

  • 無需抵押:用戶可借入巨額資金(例如,數百萬美元的代幣),只要在交易結束前償還。
  • 原子性:所有操作(借貸、操作、償還)在同一交易內完成,無需等待區塊確認。
  • 低成本:閃電貸款費用通常很低(例如,Aave 收取 0.09% 的費用),使攻擊成本低廉。

閃電貸的運作機制很精巧,我們可以把它拆解成三個步驟:

  1. 借款: 智能合約允許你從資產池(例如 Aave 或 dYdX)借入一筆資金。此時,你可以在你的交易中處理這筆資金。
  2. 執行操作: 你的智能合約利用這筆借來的資金,進行一連串的操作,例如:
    • 在多個去中心化交易所(DEX)之間進行套利。
    • 清算債務,或執行抵押品交換。
    • 將資產從一個協議轉移到另一個協議。
  3. 還款: 在所有操作完成後,你的合約必須將借款本金加上一筆小額費用,歸還到資產池中。

這三個步驟都發生在同一筆區塊鏈交易中。如果步驟 3 的還款操作沒有被成功執行,那麼區塊鏈會自動回溯(revert)整個交易,所有資產都會回到交易前的狀態。這確保了借貸協議的資金安全。

影響

閃電貸款攻擊可能導致以下嚴重後果:

  1. 資金損失:攻擊者可耗盡協議的儲備資金或竊取抵押資產,導致用戶和協議損失數百萬美元。
  2. 市場擾亂:價格操縱或流動性耗盡可能導致市場價格劇烈波動,影響其他用戶的交易。
  3. 生態系統損害:攻擊損害用戶對協議的信任,導致用戶流失、協議採用率下降,以及財務和聲譽損失。

閃電貸套利詳解

閃電貸套利是利用閃電貸的巨額資金,快速地在不同交易所之間捕捉並放大價格差異,從中獲取利潤的一種手法。核心在於自動化原子化,整個過程都在一筆區塊鏈交易中完成。

讓我們用一個具體的例子來模擬這個手法的完整流程。

情境設定

假設在區塊鏈上有兩個去中心化交易所(DEX),都提供代幣 X 和穩定幣 USDC 的交易對:

  • DEX A: 代幣 X 的價格是 $100 USDC
  • DEX B: 代幣 X 的價格是 $110 USDC

套利者發現了這 $10 USDC 的價差,並決定利用閃電貸來進行套利。

套利步驟

套利者會編寫一個智能合約,將以下所有操作打包在同一筆交易中執行:

步驟 1:借入資金(閃電貸)

  • 套利者的智能合約向閃電貸協議(例如 Aave)發出請求,借入 1,000,000 USDC
  • 這筆資金現在可以被套利者的合約使用,但必須在交易結束前歸還。

步驟 2:在低價交易所買入

  • 套利者的合約收到 1,000,000 USDC 後,立即在 DEX A 進行交易。
  • 因為 DEX A 的價格是 $100,所以套利者用 1,000,000 USDC 買入了 10,000 個代幣 X($1,000,000 / $100)。
  • 小提醒: 這筆大額買入會輕微影響 DEX A 的流動性,使代幣 X 的價格稍微上漲,但因為閃電貸金額巨大,價差仍有利可圖。

步驟 3:在高價交易所賣出

  • 緊接著,套利者的合約將剛剛在 DEX A 買到的 10,000 個代幣 X,拿到 DEX B 進行賣出。
  • DEX B 的價格是 $110,所以攻擊者賣出 10,000 個代幣 X,換回 1,100,000 USDC(10,000 * $110)。

步驟 4:還款與獲利

  • 在交易結束前,套利者的合約需要償還閃電貸的本金 1,000,000 USDC,並支付一筆小額手續費(通常為借款金額的 0.09%)。
  • 假設手續費為 900 USDC(1,000,000 * 0.09%)。
  • 攻擊者最終償還了 1,000,900 USDC
  • 攻擊者最初從 DEX B 賣出代幣 X 獲得 1,100,000 USDC,償還借款後,最終的利潤是 99,100 USDC(1,100,000 – 1,000,900)。

整個買入、賣出、還款的流程都在一筆交易中自動完成,這就是閃電貸套利的核心。如果不是閃電貸,套利者需要有自己的 1,000,000 USDC 才能啟動這個套利,而閃電貸讓這個過程變得無需本金


閃電貸合約運作方式

實際的閃電貸應用中會有兩個角色:

  1. 閃電貸提供者(Flash Loan Provider):這是提供閃電貸服務的協議,例如 AavedYdX。它們維護一個資金池,並編寫了一個智慧合約,這個合約負責在借款人請求時,借出資產並啟動回呼程序。
  2. 閃電貸借款人(Flash Loan Borrower):這是發起閃電貸請求的使用者或智慧合約。借款人的合約必須實現一個特定的**回呼(callback)**函數,以便在借到錢後,能夠執行其自定義的邏輯。

第一部分:閃電貸服務提供者

這個合約(FlashLoanProvider)負責借出資產,並在借出後呼叫借款人合約的回呼函數。

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

import "./IERC20.sol"; 

interface IFlashLoanReceiver {
    function onFlashLoan(address token, uint256 amount) external;
}

contract FlashLoanProvider {
    function flashLoan(address _token, uint256 _amount) external {
        IERC20(_token).transfer(msg.sender, _amount);

        IFlashLoanReceiver(msg.sender).onFlashLoan(_token, _amount);

        uint256 balanceAfter = IERC20(_token).balanceOf(address(this));
        require(balanceAfter >= _amount + 1,"Flash loan not repaid");
    }
}

第二部分:借款人合約

這個合約(FlashLoanBorrower)負責發出請求並實現回呼函數來處理借來的資產。

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

import "./IERC20.sol";  
import "./IFlashLoanReceiver.sol";  

contract FlashLoanBorrower is IFlashLoanReceiver {
    address public providerAddress; 

    constructor(address _providerAddress) {
        providerAddress = _providerAddress;
    }

    function requestLoan(address _token, uint256 _amount) external {
        FlashLoanProvider(providerAddress).flashLoan(_token, _amount);
    }

    function onFlashLoan(address _token, uint256 _amount) external override {
        // Here you would execute your core logic...

        uint256 amountToRepay = _amount + 1;  
        require(IERC20(_token).balanceOf(address(this)) >= amountToRepay,"Not enough funds to repay loan");
        IERC20(_token).transfer(providerAddress, amountToRepay);
    }
}


閃電貸服務提供者的範例代碼解說

程式碼的核心功能是:將資金借出給呼叫者,並立即呼叫其回呼函數,以確保在單一交易內完成整個借貸循環。程式碼分為幾個部分來解釋:

1. 介面 (Interface) 定義

interface IFlashLoanReceiver {
    function onFlashLoan(address token, uint256 amount) external;
}
  • IFlashLoanReceiver 是一個介面(Interface),它定義了一個名為 onFlashLoan 的函數。
  • 任何想要借用閃電貸的智慧合約都必須實作這個介面。
  • 這就像是閃電貸提供者與借款人之間的合約:你必須承諾你的合約有這個 onFlashLoan 函數,我才能將錢借給你。

2. 服務提供者合約

contract FlashLoanProvider {
    function flashLoan(address _token, uint256 _amount) external {
    // ...
}

這個合約(FlashLoanProvider)是閃電貸的核心。它包含了閃電貸的主要邏輯。

3. 閃電貸的主要邏輯

這是整個閃電貸的核心功能。當一個外部地址呼叫這個函數時,會執行以下步驟:

    IERC20(_token).transfer(msg.sender, _amount);
    IFlashLoanReceiver(msg.sender).onFlashLoan(_token, _amount);
    uint256 balanceAfter = IERC20(_token).balanceOf(address(this));
    require( balanceAfter >= _amount + 1, "Flash loan not repaid");
  1. 借出資產IERC20(_token).transfer(msg.sender, _amount);
    • 這一行將指定的代幣 _token 和金額 _amountFlashLoanProvider 合約轉移給發起交易的人 (msg.sender)。此時,借款人正式獲得了資金。
  2. 呼叫回呼函數IFlashLoanReceiver(msg.sender).onFlashLoan(_token, _amount);
    • 這是閃電貸的精髓。合約會立刻呼叫借款人合約中已實作的 onFlashLoan 函數。
    • 在這個函數中,借款人的合約會執行所有操作(例如套利、償還債務)。所有邏輯都必須在這裡完成。
  3. 檢查還款require(balanceAfter >= _amount + 1, "Flash loan not repaid");
    • onFlashLoan 函數執行完畢後,程式碼會回到這裡。
    • 這一行會檢查 FlashLoanProvider 合約的餘額。它要求合約中的代幣餘額必須大於或等於借出的金額加上手續費。
    • 如果這個條件不滿足,require 函數會觸發交易失敗(revert)。
    • + 1:這代表了手續費。在真實的閃電貸協議中,手續費通常是一個小數,而不是 1,這裡的 + 1 是簡化後的範例。

總體來說,如果 onFlashLoan 函數執行成功且順利還款,整個交易就會被確認。反之,如果還款失敗,整筆交易會被撤銷,所有資金都會回到最初的狀態,這就是閃電貸零風險的來源。


借款人合約範例代碼解說

這個合約主要發出閃電貸請求,並在同一筆交易中處理借來的資金,然後還款。程式碼分為幾個部分來解釋如下:

1. 合約定義與繼承

import "./IERC20.sol";  
import "./IFlashLoanReceiver.sol"; 

contract FlashLoanBorrower is IFlashLoanReceiver {
    address public providerAddress; 

    constructor(address _providerAddress) {
        providerAddress = _providerAddress;
    }
}
  • import
    • import “./IERC20.sol”; 假設的IERC20介面
    • import “./IFlashLoanReceiver.sol”; 引入第一部分定義的介面
  • is IFlashLoanReceiver:這行非常關鍵。它表明 FlashLoanBorrower 這個合約繼承了 IFlashLoanReceiver 介面。這意味著它必須包含 IFlashLoanReceiver 介面中定義的 onFlashLoan 函數。
  • providerAddress:這是用來儲存閃電貸服務提供者的合約地址。在合約部署時,你需要指定這個地址,這樣 FlashLoanBorrower 才知道要去哪裡借錢。

2. 請求貸款的函數

function requestLoan(address _token, uint256 _amount) external {
    FlashLoanProvider(providerAddress).flashLoan(_token, _amount);
}
  • requestLoan:這是使用者呼叫的函數,用來發起閃電貸請求。
  • FlashLoanProvider(providerAddress).flashLoan(_token, _amount);:這行程式碼做了兩件事:
    1. 它將 providerAddress 視為一個閃電貸提供者合約。
    2. 它呼叫該合約的 flashLoan 函數,發起借款請求,並將代幣和金額作為參數傳入。

3. 回呼函數:核心邏輯所在

function onFlashLoan(address _token, uint256 _amount) external override {

    // Here you would execute your core logic...

    uint256 amountToRepay = _amount + 1; 
    require(IERC20(_token).balanceOf(address(this)) >= amountToRepay,"Not enough funds to repay loan");
    IERC20(_token).transfer(providerAddress, amountToRepay);
}
  • onFlashLoan:這是閃電貸的精髓所在,它是一個回呼函數。
    • requestLoan 呼叫 FlashLoanProviderflashLoan 函數時,閃電貸提供者會將資金轉給 FlashLoanBorrower,並自動呼叫這個 onFlashLoan 函數。
    • 所有的套利、交易或其他邏輯都在這個函數中執行。
    • external overrideexternal 說明這個函數只能從合約外部呼叫。override 則表示這個函數覆蓋了它繼承自 IFlashLoanReceiver 介面的同名函數。
  • 還款邏輯:在函數的結尾,它必須確保有足夠的錢來償還借款和手續費,並執行轉帳。如果這裡的 require 檢查失敗,整筆交易就會被撤銷。
    • amountToRepay = _amount + 1:這一行計算出你必須償還的總金額,它等於借款金額加上手續費。
      • + 1:這代表了手續費。在真實的閃電貸協議中,手續費通常是一個小數,而不是 1,這裡的 + 1 是簡化後的範例。
    • IERC20(_token).balanceOf(address(this)):這行程式碼會查詢你的合約(FlashLoanBorrower中,指定的代幣餘額還有多少。
    • IERC20(_token).transfer(...):將你應還的總金額,從你的合約中轉帳到閃電貸提供者的合約

總體來說,FlashLoanBorrower 合約就像一個預先編寫好的劇本,它將借款、執行任務和還款的步驟全部串聯在一起,並在單一交易中自動完成。


套利(Arbitrage)與漏洞攻擊(Exploit)的區別

如果一個協議本身沒有漏洞,閃電貸套利只是一種自動化套利**,而不是攻擊。

然而,如果協議的價格機制清算邏輯存在漏洞,閃電貸的巨額資金就能被用來放大這種漏洞。例如,如果 DEX B 的價格預言機讀取價格有延遲,攻擊者就能利用閃電貸在 DEX A 瞬間買入,讓 DEX B 的預言機看到一個過時的低價,從而放大套利收益,這就從單純的套利變成了利用漏洞的攻擊

這種攻擊的成功依賴於區塊鏈交易的「原子性」,也就是所有步驟要么都成功,要么都失敗。如果其中任何一個步驟(例如在 DEX B 賣出)失敗,整個交易都會被回溯,閃電貸會被自動取消,攻擊者不會有任何損失。

這兩者在操作上非常相似,但核心意圖和道德界線上存在根本差異。

  • 套利:利用市場上自然存在的價差來獲利。這是一種正常且健康的市場行為,有助於讓不同交易所的價格趨於一致。套利者只是發現了這個價差,並利用閃電貸這種高效工具來快速執行,從中賺取利潤。
  • 利用漏洞的攻擊人為地製造或放大價差,或利用協議的設計缺陷來獲利。攻擊者不僅僅是在發現機會,更是在創造機會,其行為通常會導致其他用戶或協議本身遭受損失。

具體例子:

為了更好地區分兩者,我們延續先前的例子,並增加一個情境:一個有漏洞的借貸協議

情境一:純粹的閃電貸套利

  • DEX A:ETH 價格為 $2000
  • DEX B:ETH 價格為 $2010

操作:

  1. 攻擊者借入 1,000,000 USDC 的閃電貸。
  2. DEX A 用 1,000,000 USDC 買入 500 ETH
  3. DEX B 用 500 ETH 賣出,獲得 1,005,000 USDC
  4. 償還閃電貸本金和手續費,獲利約 4900 USDC

結果分析:

  • 無人受損:這次操作只是捕捉了市場上既有的價差,沒有任何協議或用戶因為此行為遭受損失。
  • 市場更有效率:DEX A 的 ETH 價格因此輕微上漲,DEX B 的 ETH 價格輕微下跌,最終使得兩者的價格更接近。
  • 這就是健康的套利行為。

情境二:利用漏洞的攻擊

現在我們引入一個新的元素:一個有漏洞的借貸協議

  • DEX A:ETH 價格為 $2000
  • DEX B:ETH 價格為 $2010
  • 借貸協議:依賴 DEX A 的價格來決定用戶的抵押品價值。

攻擊操作:

  1. 攻擊者借入 1,000,000 USDC 的閃電貸。
  2. DEX A 用 1,000,000 USDC 買入大量的 ETH。這會讓 DEX A 的 ETH 價格瞬間暴漲$2100
  3. 攻擊者立即在借貸協議中,以暴漲後的 ETH 作為抵押品,借出大量的其他代幣。
  4. 攻擊者隨後在 DEX A 將先前買入的 ETH 賣出,讓 ETH 價格恢復正常。
  5. 償還閃電貸本金和手續費。

結果分析:

  • 有人受損:因為協議錯誤地相信了被操縱的價格,借出了超過應有價值的代幣,導致協議的資金池產生虧損。攻擊者獲利來源於協議資金池的損失。
  • 人為製造的機會:這個「賺錢」機會並非市場自然存在,而是攻擊者用閃電貸人為製造的價格波動,來欺騙協議的預言機。
  • 這就是惡意的漏洞攻擊。

總結來說,套利是利用「已存在的」價差,而攻擊是利用閃電貸的巨額資金「製造」一個對自己有利的價差或情境,從而從其他用戶或協議本身獲利,導致他人受損。


其他閃電貸款攻擊的範例

以下是常見的閃電貸款攻擊類型:

價格預言機操控(Oracle Manipulation)

  • 情境:許多去中心化金融(DeFi)協議依賴價格預言機(如 Chainlink 或單一交易所的價格)來決定資產價值或清算條件。
  • 攻擊方式
    • 攻擊者借入大量資金(例如,1000 萬美元的代幣)。
    • 使用這些資金在某個去中心化交易所(DEX)進行大額交易,推高或壓低某代幣的價格,影響預言機報價。
    • 利用被操縱的價格觸發目標協議的清算邏輯(如低於抵押率的清算),以低價買入資產。
    • 恢復市場價格,償還閃電貸款,保留獲利。
  • 範例:2020 年的 bZx 攻擊,攻擊者操縱價格預言機,導致協議誤以為抵押品不足,從而清算資產。

流動性池耗盡(Liquidity Pool Draining)

  • 情境:自動做市商(AMM)如 Uniswap 或 SushiSwap 使用流動性池來提供交易流動性。
  • 攻擊方式
    • 攻擊者借入大量代幣,通過大額交易耗盡某一流動性池的資產。
    • 如果 AMM 的定價或兌換邏輯存在漏洞(例如,未正確處理極端交易量),攻擊者可能以低成本換取大量其他代幣。
    • 償還閃電貸款,保留獲利。
  • 範例:某些早期 AMM 協議因未限制大額交易而被攻擊。流動性提供者(LPs)原本存放在池子裡的資產,會因為攻擊者的行為而大量流失,最終只剩下價值不高的代幣,蒙受巨大的財務損失。由於流動性池資金不足,其他用戶也無法正常進行交易或提款,嚴重影響了協議的運作。

套利攻擊(Arbitrage Exploits)

  • 情境:不同交易所之間的代幣價格存在差異。
  • 攻擊方式
    • 攻擊者借入大量資金,在價格較低的交易所買入代幣,然後在價格較高的交易所賣出,賺取差價。
    • 如果目標協議有漏洞(例如,未正確更新價格或流動性),攻擊者可放大套利收益。
    • 償還閃電貸款,保留套利利潤。
  • 範例:利用 DEX 價格差異進行套利,結合漏洞放大收益。

真實案例

  • bZx 攻擊(2020 年):攻擊者利用閃電貸款操縱價格預言機,觸發不當清算,竊取約 100 萬美元的資產。
  • Harvest Finance 攻擊(2020 年):攻擊者利用閃電貸款操縱 Curve 池的價格,導致協議損失約 2400 萬美元。

修復方法

為防止閃電貸款攻擊,開發者應採取以下措施:

避免在關鍵邏輯中依賴閃電貸款

  • 限制敏感功能(如清算或價格更新)僅在可驗證且穩定的條件下執行。
  • 例如,限制單筆交易中可操作的資金量或頻率,減少操縱可能性。

穩健的預言機設計

  • 使用時間加權平均價格(TWAP),而不是單一交易所的即時價格,以防止短時間內的價格操縱。
  • 採用去中心化預言機(如 Chainlink),它們聚合多個數據源,難以被操縱。

全面測試

  • 在測試中模擬閃電貸款攻擊場景,包括極端交易量、價格操縱和邊界條件。
  • 使用模糊測試(fuzz testing)檢查協議在異常輸入下的行為。

訪問控制

  • 限制關鍵功能的訪問權限,例如僅允許授權用戶或合約調用敏感操作。
  • 使用白名單或權限控制,防止未授權的閃電貸款交易。

總結

閃電貸款攻擊利用區塊鏈交易的原子性和低成本借貸,結合其他漏洞(如預言機操控或邏輯錯誤),造成嚴重損失。攻擊者通過操縱價格、耗盡流動性或套利獲利,影響協議和市場穩定。修復方法包括穩健的預言機設計、全面測試、訪問控制和避免依賴易操縱的邏輯。開發者應特別注意閃電貸款場景的測試,並採用多層防護措施以確保協議安全。