撰文: 宋嘉吉、任鶴義
來源:吉時通信
摘要:上一篇報告從底層語言特點,對比了Move 和Solidity(以太坊)的優勢和特點。作為Web3 的基礎性研究,本篇從閃電貸這一最具特色的應用角度出發,分析了以太坊和Move 分別如何實現閃電貸,Move 怎樣規避了閃電貸攻擊?
以太坊合約之間的交互是通過互通消息實現的狀態一致,且允許重入、動態調用,這一特點為實現閃電貸提供了基礎。期間,合約之間的函數可以互相來回調用——調用過程中會發生控制權轉移。如果DeFi 項目平台合約有漏洞,套利者能夠利用其合約的惡意代碼調用相應函數進行資產盜取——利用合約之間的狀態同步信息差,在一個流程未結束時雙花資產(以太坊資產只是賦值),或者重複執行函數(本該不被允許的邏輯)進行盜取。
重入攻擊的前提是攻擊者部署的合約存在惡意代碼,但最核心的因素是:
-
以太坊合約調用時控制權存在轉移,這為惡意合約提供了主動權;
-
且流程在結束之前可以重入(重複調用),惡意合約可以利用漏洞反複調用函數實現資產盜取(如一筆取款流程未結束時反复提取很多次);
-
加上以太坊賬戶資產是以數值餘額的形式存在,因此存在反复盜取資產(雙花)的可能,或者在流程結束之前惡意合約就可以修改相關賬戶資產餘額數值實現盜取。
Move 提出了閃電貸的新一種運行流程——燙手山芋模式,根本上棄用可重入。 「燙手山芋(hot-potato)」模式是一種沒有key、store、copy 和drop 能力的結構,是Move 程序在交易執行期間僅使用一次的結構。由於沒有drop、key 或store 能力,因此燙手山芋只能通過調用「銷毀」函數來完結流程——這就如其名所示意的一樣,這是一塊燙手山芋,在流程中任何處置都是「燙手」的,只能交給銷毀函數來完結。具體流程如下:
-
閃電貸智能合約在工作時會創建了「燙手山芋」(hot-potato)的收據(receipt);
-
套利者向閃電貸合約貸款時,閃電貸合約發送貸款資金和一個燙手山芋收據(receipt);
-
套利者利用貸款資金進行套利操作;
-
套利者還款時,調用還款函數(repay),將資金和收據(receipt)發給還款函數,收據被還款函數接收後銷毀。
閃電貸完成的前提的最後正確還款、流程結束,在流程結束之前惡意合約可以在以太坊系統實現重複調用、修改相應的資產賬戶賦值實現盜取。 Move 系統閃電貸流程結束的前提除了正確歸還資金之外,還需要將燙手山芋這個資源進行一次性回收銷毀處理,這確保了閃電貸的原子性。
風險提示:區塊鏈商業模式落地不及預期;監管政策的不確定性。
1. 核心觀點
閃電貸作為以太坊DeFi 生態最具特色的應用,其基礎是以太坊使用的Solidity 語言允許動態調用和重入。雖然這種動態調用是智能合約開放性、可組合性的重要體現,但其帶來的控制權轉移和對合約函數的重複調用帶來了不小的安全隱患,行業內利用閃電貸攻擊有漏洞的DeFi 資金池事件時有發生。由於Move 語言不允許動態調用和重入,它使用一種「燙手山芋」模式很簡單地實現了閃電貸,因此完全可以避免以太坊那樣的安全問題。
兩種系統在生態應用的工作模式有著明顯的區別,這種差別的根本在於Move 底層語言的特點,我們在前一篇報告《Web3 底層語言:Move 彌補了Solidity 哪些不足? 》已有詳述。本篇報告從閃電貸應用角度來比較一下兩者實現應用的不同玩法。
2.以太坊閃電貸的基礎:動態調用與可重入
閃電貸(Flash Loan)是一種原生的DeFi 新產物,可以理解為極速貸款。用戶只需要在同一筆交易(區塊)中完成借貸、套利、償還並支付一筆手續費,由於是原子交易,那麼借款人無需抵押任何資產即可實現借貸,提供了一種無本金套利方案。
我們在上一篇報告《Web3 底層語言:Move 彌補了Solidity 哪些不足? 》中提到的:
「對於模塊化和合約組合性方面,Solidity(如以太坊)上面的Contract 合約通過library(相當於靜態庫)進行消息的傳遞,從而實現Contract 合約之間的調用、交互。而Move 語言使用了模塊(module) 和腳本(script) 的設計,前者類似於Contract 合約,Move 語言的合約組合性則是模塊之間的組合,通過傳遞資源(即前文提到的resources)。關於組合性方面,Solidity 和Move 的區別非常明顯。」
以太坊合約之間的交互是通過互通消息實現的狀態一致,且允許重入、動態調用,就是說合約之間的函數可以互相來回調用——調用過程中會發生控制權轉移。這一特點為實現閃電貸提供了基礎。
具體來說,在一個以太坊交易中,可以進行轉賬操作以及其他一些列合約操作,通過調用智能合約中的功能函數,執行多項複雜功能——也就是說,一筆基於以太坊的交易可以融合一系列複雜交易:將藉款、套利、償還等一系列交易操作融合到一起成為可能。 Flash Loan 中所有操作都在一個區塊時間中完成,按照現在的以太坊的出塊速度,Merge 後也就是12 秒——核心並非是12 秒,而是這一系列的交易要能夠最終盈利並償還,如果沒有做到這一點,這筆交易就不會被打包寫入區塊,相當於借款人借款、套利(失敗)這些操作並不是有效交易,只是臨時狀態——即原子交易。因此,用戶必須通過編程將需要執行的所有步驟形成一項智能合約交易並完成借貸、使用和償還的三個步驟。
上述複雜的交易操作由幾個合約之間的動態調用實現,且這種調用是可重入的(也就是可以反複調用)。
目前用戶也可以通過如FURUCOMBO 這一類第三方項目,對閃電貸完成更簡易的插件性編程,無需實際編寫代碼完成智能合約的設計,最終實現閃電貸需要的全套操作。具體的套利流程如下圖所示(利用FURUCOMBO 平台,具體兌價均為示例),目前Kyberswap 平台上的價格情況1 sUSD =0.9927 DAI,而Uniswap 上1 DAI=1.2411 sUSD,用戶發現這兩個平台的DAI-sUSD 交易對價格存在較大的套利空間,即可通過FURUCOMBO 的界面,設計套利過程。包括:1、從AAVE 借貸平台的閃電貸功能藉出100 DAI;2、通過Uniswap 將100 DAI 兌換成約122 個sUSD 代幣;3、通過Kyberswap 平台將sUSD 代幣兌換成約122 DAI 代幣;4、償還從AAVE 借出的100 DAI 代幣以及手續費0.09 DAI;5、整個利用閃電貸的套利流程在一個以太坊交易內完成,並獲利約22 DAI。
如果在這一筆以太坊交易內借貸的資金沒有得到償還,那麼整筆借貸交易不會被打包進入區塊中,相當於借貸並沒有實際發生,所以藉貸方的資金不會受任何影響——中間的借貸和套利過程只是臨時狀態,並未被礦工打包確認。基於閃電貸的特性和時效要求,目前其最廣泛的應用是套利交易。套利者無需自身使用資產進行套利操作,只需要通過閃電貸獲得所需的資金量完成套利交易,並及時償還借貸的資金。這極大的降低了套利者的准入門檻,因為理論上任何一個人都可以成為套利者,並且擁有沒有上限的套利資金進行操作。
從上述流程可以看到,以太坊合約控制權在FURUCOMBO 閃電合約- 套利者賬戶合約-Uniswap 合約-Kyperswap 合約- 閃電貸合約之間切換,且可以進行動態調用相應的函數,如果DeFi 項目平台合約有漏洞,套利者能夠利用其合約的惡意代碼調用相應函數進行資產盜取——利用合約之間的狀態同步信息差,在一個流程未結束時雙花資產(以太坊資產只是賦值),或者重複執行函數(本該不被允許的邏輯)進行盜取。
對於Move 生態,由於資產並非簡單的賦值,且禁止動態調用和可重入,從根本上杜絕了風險,其具體實現我們在後面會闡述。
3. Move 與Solidity 閃電貸的具體實現有怎樣的區別?
3.1.以太坊閃電貸雙刃劍:動態調用和可重入性
從實現方式上來看,以太坊EVM(基於Solidity)具有動態調度,可以通過可重入實現閃電貸。如我們在上一篇報告《Web3 底層語言:Move 彌補了Solidity 哪些不足? 》中提到的:
「以太坊(Solidity)的資產是由相應的合約控制,如果把Token A 合約比喻為保險箱,保險箱會給所有用戶分配一個數值餘額,來表達用戶所有擁有的Token A 資產數量,但資產本身還是放在Token A 合約的保險箱內。而Move 用戶賬戶本身就是一個單獨的大保險箱,由用戶自己控制,所有的Token 資產都放在這個保險箱內。且這些Token 並不是以數字的形式存在,而是不可複制的、權限受用戶控制的資源(類型)。」
因此以太坊EVM 實現閃電貸套利的流程是:
-
用戶將調用控制權交給閃電貸合約;
-
閃電貸合約調用來自外部的套利合約程序中的執行函數,將請求的借款金額發送給套利合約,套利合約進行套利操作;
-
套利合約完成套利,將藉款歸還給閃電貸合約,執行函數完成工作,控制權還給閃電貸合約;
-
閃電貸合約檢查還款金額是否正確,正確則套利交易成功,否則失敗。
在上面這個過程中,相應的函數是動態調用的,閃電貸合約需要檢查歸還金額是否正確才會結束,因此控制權的轉移過程是隨時可以發生的,也就是可重入的——同時需要注意的事,以太坊賬戶資產是以數值餘額的形式存在,重入會帶來雙花的可能,這也是存在漏洞的地方。調用外部合約的主要危險之一是它們可以接管控制,而這些來自外部的合約程序一旦有漏洞,攻擊者可以通過反複調用實現攻擊,即可重入攻擊。
可重入攻擊造就了以太坊歷史上最嚴重攻擊之一,直接導致了以太坊分叉,即2016 年6 月17 日的The DAO 崩潰事件。黑客部署了一個合約,作為「投資者」在The DAO 中儲存一些ETH。然後黑客通過調用The DAO 合約中的withdraw 函數,使得The DAO 合約給黑客提款,由於黑客合約(fallback 函數)存在惡意漏洞——其沒有結束的邏輯,於是The DAO 始終未能完成提款(此時The DAO 並不知道黑客收到了提款並將其賬戶餘額變為0)並拿回控制權,黑客通過不斷調用withdraw 函數發送超過其初始存款ETH 數量。
在閃電貸攻擊事件中,攻擊者往往通過惡意合約實現可重入攻擊。如2022 年3 月16 日,黑客通過閃電貸借款,利用借貸項目Hundred Finance 的漏洞實時重入攻擊,最終獲利約2363 ETH。具體流程並不復雜,由於Hundred Finance 是先轉賬後記賬,黑客通過閃電貸借款,利用攻擊合約存入Hundred Finance 借款池實現抵押貸款,而攻擊合約部署的onTokenTransfer 函數在記賬之前實現重複調用借款函數(重入攻擊),以一筆抵押資產不斷從不同借款池提取借款,由於是先轉賬後記賬,當記賬時(流程結束)黑客已經實現了攻擊並獲利。攻擊的核心是:流程未結束之前,攻擊者合約可以反複調用相關函數實現資產盜取。
就好比說,攻擊者合約將資產抵押給A 銀行(項目A 借款池) 進行貸款,銀行A 在未完成記賬結算之前就開始放款,而銀行A 完成記賬結算流程意味著閃電貸交易要成功(原子性),這時候資產被A 入庫記賬。但由於銀行A 和其他銀行之間的信息不同步(合約函數之間的狀態不同步),攻擊者在A 銀行還未完成資產入庫記賬時(即被攻擊合約貸款流程未結束時,這時候閃電貸工作流程還未結束,合約函數調用還可以進行),再次以該資產在B、C 等其他銀行進行抵押貸款(重入),待流程結束,攻擊者已經完成閃電貸攻擊並獲利。
重入攻擊的前提攻擊者部署的合約存在惡意代碼,但最核心的因素是:
-
以太坊合約調用時控制權存在轉移,這為惡意合約提供了主動權;
-
且流程在結束之前可以重入(重複調用),惡意合約可以利用漏洞反複調用函數實現資產盜取(如一筆取款流程未結束時反复提取很多次);
-
加上以太坊賬戶資產是以數值餘額的形式存在,因此存在反复盜取資產(雙花)的可能,或者在流程結束之前惡意合約就可以修改相關賬戶資產餘額數值實現盜取。
「可重入」是實現閃電貸的基礎,然而一旦目標合約有漏洞,攻擊者便可以實施重入攻擊。這在我們前一篇報告中有詳述。
3.2. MOVE 的「燙手山芋」:沒有重入的閃電貸
Move 語言禁止動態調用和可重入,這從根本上杜絕了重入攻擊。但Move 系統的資產作為資源類型,一旦借出就相當於發生了真實轉移,該如何確保閃電貸順利還款呢?
Move 提出了閃電貸的新一種運行流程——燙手山芋模式,根本上棄用可重入。 「燙手山芋(hot-potato)」模式是一種沒有key、store、copy 和drop 能力的結構,是Move 程序在交易執行期間僅使用一次的結構。由於沒有drop、key 或store 能力,因此燙手山芋只能通過調用「銷毀」函數來完結流程——這就如其名所示意的一樣,這是一塊燙手山芋,在流程中任何處置都是「燙手」的,只能交給銷毀函數來完結。具體流程如下:
-
閃電貸智能合約在工作時會創建了「燙手山芋」(hot-potato)的收據(receipt);
-
套利者向閃電貸合約貸款時,閃電貸合約發送貸款資金和一個燙手山芋收據(receipt);
-
套利者利用貸款資金進行套利操作;
-
套利者還款時,調用還款函數(repay),將資金和收據(receipt)發給還款函數,收據被還款函數接收後銷毀。
我們在前一篇報告中已經分析過,賬戶資產和收據(receipt)都是一種資源類型,「不能複制、丟棄或重用,可以被安全地存儲和轉移」,因此收據(receipt)必須被處理(且只能使用一次),而非像以太坊那樣的對賬戶進行數值賦予處理就行。因此燙手山芋模式可以確保被借出的資產(資源類型,一旦借出就真實發生轉移了)必須被歸還。收據(receipt)作為燙手山芋,就好比是定時一個引爆器(這裡的定時,是閃電貸套利作為一個原子交易的「時間」,並非具體的時長),資金和引爆器一起綁定被借出,而任何一方都無法收留引爆器,它必須被還回原處得以拆除——否則交易都無法完成,因此閃電貸資金可以確保會被歸還。
閃電貸完成的前提的最後正確還款、流程結束,在流程結束之前惡意合約可以在以太坊系統實現重複調用、修改相應的資產賬戶賦值實現盜取。 Move 系統閃電貸流程結束的前提除了正確歸還資金之外,還需要將燙手山芋這個資源進行一次性回收銷毀處理,這確保了閃電貸的原子性。
從應用角度看,Web3 的底層代表要在保證開放性、可重構的基礎上提高代碼安全性。在Web3 中,代碼不僅包含信息,還直接涉及資產調用,保證用戶資產安全是重中之重,否則Web3 將是黑暗森林。以太坊的生態讓大家看到了智能合約的活力,下一個時代將在此基礎上向安全性、合規性繼續演進,這也是我們當下關注Web3 底層語言進化的核心邏輯,或者這孕育著下一輪的創新浪潮。