引言
隨著去中心化金融(DeFi) 生態系統的迅速發展,AAVE V2 作為領先的去中心化借貸協議之一,在提供創新借貸與流動性管理解決方案方面始終處于行業前沿。其獨特的無信任機制和高效的資本利用率吸引了大量用戶和機構的參與。然而,隨著其應用的普及及所涉及的資金規模逐步擴大,安全審計和風控措施的重要性日益凸顯。本手冊將深入探討 AAVE V2 協議的核心設計、關鍵功能及相關審計要點。
AAVE V2 是一個基于以太坊區塊鏈構建的開放式借貸平臺,允許用戶存入各種 ERC-20 代幣并從中賺取利息,同時也允許以支付利息的形式借用市場中的代幣。通過引入“利率市場”的概念,AAVE V2 實現了去中心化的資金池管理和自動化的利率調整機制。此外,AAVE V2 還提供了閃電貸、抵押貸款和代幣交換等高級功能,以滿足用戶的多樣化需求,進一步鞏固了其在 DeFi 領域的領先地位。
AAVE V2 的整體架構設計圍繞用戶、資金流動管理、抵押機制、清算流程以及利率策略等關鍵功能展開,旨在提供高效且安全的去中心化借貸服務。以下是綜合分析:
用戶:用戶可以進行存款、借款、償還、提取、借貸利率模式交換、閃電貸以及委托信用等多種操作。用戶與協議交互時,會根據其操作自動鑄造或銷毀相應的 aTokens,代表其在協議中的存款權益,并根據利率策略獲得收益。
委托信用:用戶可以將自己的信用額度委托給其他用戶,擴展了協議的靈活性和使用場景。
LendingPool:作為核心模塊負責處理所有用戶的操作請求,包括存款、借款、償還、借貸利率模式交換、閃電貸和清算,并更新利率和狀態。
Collateral Manager:管理抵押資產,確保用戶借款行為安全可控。當抵押資產不足時,會觸發清算流程來保護系統的整體流動性。
Libraries:封裝儲蓄金邏輯,驗證邏輯,通用功能邏輯,如清算和借貸操作的計算,為 LendingPool 提供支持。
GenericLogic 計算并驗證用戶狀態,包括資產評估、抵押品價值計算、健康系數等操作。
ReserveLogic 用于管理儲備金池,追蹤和更新每種資產的存款量、借款量以及當前利率情況。
ValidationLogic 負責驗證用戶的操作是否符合協議規則,在用戶進行存款、借款、還款、清算、閃電貸、切換債務模式等操作時,對抵押品和負債進行嚴格檢查。
Debt Tokens:用來跟蹤用戶的借款負債,與貸出資金數額 1:1。債務代幣分別有固定利率和可變利率選項(如 DebtDAI Stable、DebtDAI Variable 等),且債務代幣不可轉移。
aTokens:用戶存入資產時會生成 1:1 的 aTokens 錨定底層資產,這些 aTokens 會不斷增值以反映存款所賺取的利息。其中由此引入與本金余額一起存儲為一個比率,稱為縮放余額 scaled balance (ScB)。
公式如下:
Oracles Proxy:依賴外部預言機 (Chainlink) 提供資產市場價格數據,用于評估用戶抵押資產的價值,確保借貸行為的定價準確性和系統的穩定性。
Lending Rate Oracle:根據系統的狀態和市場情況,提供動態的借貸利率,優化資本利用率和流動性。
Configurator:用于配置系統參數,如不同資產的風險參數和借貸限額,管理儲備金的各種操作,包括激活、借款、抵押、凍結、更新參數及在緊急情況下啟用或禁用功能。確保協議可以根據市場變化進行動態調整。
Liquidation Manager:當用戶抵押品價值下降至清算門檻以下時,管理清算操作,保護系統的資金安全。清算人可以通過清算操作獲得獎勵。
Reserves Balances:存儲系統的儲備資金數據,用于計算和調整利率策略。
Interest Rate Strategy:根據市場和用戶需求,動態調整利率以實現最佳資本配置,同時考慮流動性風險,確保系統在不同市場條件下的靈活性和穩定性。
盡管存在兩種利率模型(穩定型和浮動型),但是其模型計算都類似于一個拐點型模型。在拐點最優利用率下的 slope1 和超過最優利用率的 slope2 分段計算,且在這個條件下也分為固定利率模型和可變利率模型。
公式如下:
用戶通過調用 LendingPool 合約的 deposit 函數進行存款,該函數接受四個參數:資產地址、存款金額、接收方地址及推薦碼。首先驗證合約未處于啟用狀態,然后通過 ValidationLogic.validateDeposit 驗證存款金額必須大于 0,同時確認儲備處于激活狀態且未被凍結。接著系統會更新儲備狀態,調用 reserve.updateState() 更新流動性累積指數和可變借款指數,并計算時間段內產生的利息,其中一部分利息會被鑄造并轉入協議國庫。
公式如下:
隨后通過 reserve.updateInterestRates 根據最新的供需關系動態調整流動性利率、穩定借款利率和可變借款利率(都由 DefaultReserveInterestRateStrategy.calculateInterestRates 函數計算更新)。資產轉移環節,系統將用戶的基礎資產轉入 aToken 合約,同時鑄造等額 aToken 給用戶所提交的 onBehalfOf 地址。其中,aToken 采用縮放機制 (scaled balance) 處理利息累積。如果是用戶首次存款,系統會自動將該資產標記為用戶的抵押品。
與 Compound 相比,AAVE V2 的存款過程有以下主要特點:
支持指定接收方地址(onBehalfOf)。
通過 ValidationLogic 合約進行存款驗證。
更新流動性累積指數和可變借款指數計算并分配協議國庫利息。
同時調整流動性、穩定借款和可變借款三種利率。
使用 aToken 的縮放機制(scaled balance) 處理利息。
首次存款自動標記為抵押品。
用戶通過調用 withdraw 函數進行提現操作。首先取指定資產的儲備數據,包括對應的 aToken 地址,檢查此用戶在 aToken 中的余額。接下來,調用 ValidationLogic.validateWithdraw 函數來驗證提現請求,包括檢查提現金額是否有效、用戶余額是否足夠、儲備是否處于活動狀態等。其中通過 GenericLogic.balanceDecreaseAllowed 對用戶的健康系數以及提現是否影響抵押品進行檢查,類似于 compound 中 getHypotheticalAccountLiquidityInternal 函數的作用。在 balanceDecreaseAllowed 函數中,calculateUserAccountData 和 calculateHealthFactorFromBalances 函數計算取出資金后的清算閥值并檢查用戶總抵押,總借貸數額以及用戶當前的健康系數,以此來判斷是否用戶健康系數處于流動性閥值的安全狀態。
HF 計算公式如下:
隨后更新儲備的狀態,并更新利率,將提現金額傳遞給函數。若用戶請求的提現金額等于其當前余額,則更新用戶配置,將該儲備標記為不再作為抵押使用。最后銷毀用戶的 aToken,并將提現的資產轉賬到指定的地址。
與 Compound 相比,AAVE V2 的提現過程有以下主要特點:
使用 aToken 代表用戶在協議中的存款,提現實際上是銷毀 aToken。
允許用戶提現到指定地址(通過 to 參數),增加了靈活性。
提供了部分提現和全額提現的選項。
在提現驗證中,AAVE 使用了更復雜的 balanceDecreaseAllowed 函數來檢查提現對用戶整體抵押品狀況的影響。
AAVE 的提現過程直接更新了利率,而不是像 Compound 那樣通過 accrueInterest 函數來更新。
用戶通過 borrow 函數進行借貸,執行借款會先從價格預言機獲取資產的當前價格,將借款金額轉換為 ETH 等價。隨后通過 ValidationLogic.validateBorrow 檢查以及 GenericLogic.calculateUserAccountData 用戶借款是否合法,計算包括 onBehalfOf 地址的總抵押資產、總債務、當前貸款價值比率(LTV)、清算閾值和健康因子以及市場的穩定性等(類似于 Compound 的 getHypotheticalAccountLiquidityInternal),是否有足夠的抵押資產借貸。reserve.updateState 更新儲備狀態,如利率和借款指數(這一步類似于 Compound 中的 accrueInterest),用于計算并更新利息。
隨后根據用戶選擇進行的 interestRateMode(穩定利率或浮動利率)生成債務,選擇不同的利率模型的代幣合約來鑄造代幣。同時,鑄造代幣時也會進行檢查,如果 onBehalfOf 地址不是調用者,則會在代幣合約中減去其對調用用戶的借貸授權。如果是用戶的首次借款,會將其配置為活躍借款者。DebtToken 鑄造給用戶后,協議會通過 updateInterestRates 更新借款利率,反映借款后的新利率和儲備池的變化。如果用戶請求釋放借款的底層資產,協議會將資產直接轉移給用戶。
與 Compound 相比,AAVE V2 的借貸過程有以下主要特點:
支持穩定和可變兩種利率模式。
使用單獨的驗證邏輯合約進行借貸驗證。
使用債務代幣(DebtToken) 來表示用戶的借款。
支持信用委托,允許用戶代表其他地址進行借款。
用戶通過 repay 函數進行還款,首先獲取用戶的當前債務(包括穩定債務 stableDebt 和浮動債務 variableDebt)。根據用戶選擇的利率模式(穩定或浮動),由 ValidationLogic.validateRepay 驗證用戶的還款操作合法性,包括用戶的債務余額是否足夠進行還款。根據用戶選擇的利率模式來確定還款的具體債務類型(穩定利率或浮動利率)。如果用戶要還的金額小于當前債務余額,系統會使用用戶提供的還款金額進行部分還款;否則,將償還所有債務。更新儲備的狀態 updateState,用于計算并更新協議中的利息、借貸量以及借貸指數。隨后燃燒相應的穩定債務代幣,并通過 updateInterestRates 更新借款利率。此時,如果用戶的所有債務(包括穩定和浮動債務)在還款后為零,則會將該用戶的借款狀態標記為 false,表示用戶不再借款。最后用戶將還款金額從其賬戶轉移到協議的 aToken 合約地址。
與 Compound 相比,AAVE V2 的還款過程有以下主要特點:
支持穩定和浮動兩種利率模式的還款。
使用 DebtToken 來表示和管理債務,還款時燃燒對應債務代幣。
支持部分還款和全額還款,并分別處理穩定債務和浮動債務。
支持用戶通過信用委托為其他地址還款。
用戶通過 lendingpool 的 liquidationCall 函數進行清算,函數通過代理模式調用 LendingPoolCollateralManager 的 liquidationCall 函數,確保函數的成功執行。首先 GenericLogic.calculateUserAccountData 獲取抵押品資產及債務資產的儲備數據和用戶的配置信息,計算用戶的健康因子,并通過 getUserCurrentDebt 獲取用戶的當前穩定和可變負債。
ValidationLogic.validateLiquidationCall 函數驗證清算調用的合法性,包括檢查用戶的健康因子、債務狀態和抵押品配置。若健康因子小于閥值,已作為抵押品,且兩種債務都不為 0 則驗證通過。接著計算用戶的最大可清算債務,并確定實際需要清算的債務數量。如果清算的債務超過用戶的可用抵押物,將調整清算金額。
如果清算人選擇接收被清算人抵押的底層資產,需要確保抵押物儲備中有足夠的流動性。更新債務儲備的狀態,并根據清算人是否接收 aToken 情況,燃燒相應數量的可變和穩定債務代幣。更新債務的利率,反映清算后的市場情況。清算人獎勵如果選擇接收 aToken,清算人將獲得相應數量的 aToken。如果不接受 aToken,則更新其抵押狀態和抵押物的利率,從用戶賬戶中燃燒掉對應數量的 aToken ,將底層資產轉移給清算人。最后,將清算所需的債務資產從清算人轉移到相應的儲備 aToken 中,完成清算過程。
與 Compound 相比,AAVE V2 的清算過程有以下主要特點:
支持多種抵押品和債務資產的組合清算。
允許清算人選擇接收 aToken 或 底層資產。
清算過程更加模塊化,將驗證邏輯、計算邏輯等分離到不同的函數中。
支持穩定利率和可變利率兩種債務類型的清算。
用戶通過 lendingpool 的 flashLoan 函數進行閃電貸。作為借貸協議的閃電貸,可以允許當前閃電貸立刻還款或是作為債務來后續還款,其中以傳入的 modes 參數不同而決定。0 為立刻還款,1 為作為穩定型債務,2 為浮動型債務。
函數首先通過 ValidationLogic.validateFlashloan 檢查輸入參數匹配,計算閃電貸所需的溢價成本,并直接將所需的 aToken 轉給接收者地址。調用接受者的 executeOperation 操作實現預設的閃電貸。AAVE 實現的閃電貸操作已包括了兌換,兌換清算,以及兌換償還操作。在 executeOperation 完成以上操作后,記錄需償還的閃電貸金額和相應的費用。如果用戶選擇以非債務模式歸還資金:系統更新儲備狀態,累積儲備流動性以及更新流動性指數。最后再從請求者轉移資金和費用至儲備池。若用戶選擇以債務模式處理,則調用 _executeBorrow,開啟相應的債務頭寸。
在 AAVE V2 中,用戶可以通過 swapBorrowRateMode 函數在穩定利率模式和浮動利率模式之間切換。首先通過 getUserCurrentDebt 函數獲取用戶在目標資產上的當前穩定利率債務和浮動利率債務,確定用戶的債務狀況。接著調用 ValidationLogic.validateSwapRateMode 函數驗證切換操作是否合法。其中檢查用戶是否有足夠的穩定或浮動債務以支持模式切換,確保切換目標模式符合資產的配置和用戶的債務情況。調用 reserve.updateState 更新資產儲備的狀態,確保儲備數據最新。隨后就是對于兩種債務代幣的相互轉換,燃燒穩定債務代幣鑄造浮動債務代幣或是燃燒浮動債務代幣鑄造穩定債務代幣。轉換完成后,reserve.updateInterestRates 更新目標資產利率,確保反映當前市場狀態和用戶債務的變化。
在 AAVE 和 Compound 中,都存在空市場中精度損失而造成的漏洞問題。如果在一個空市場的情況(即沒有用戶在市場中進行借貸),由于 cumulateToLiquidityIndex 函數中 liquidityIndex 的值依賴于合約對應的底層資產代幣的數量,所以攻擊者可以通過閃電貸向合約存入大量的底層資產代幣來操縱 aToken 的價格。
與之前 Compound fork 項目 Hundred Finance 第一次被黑相似,在 HopeLend 事件中,攻擊者先操縱 liquidityIndex 將 hETHWBTC 兌 WBTC 的價值控制為 1:1,通過兌換底層資產以及借貸的方式又提高 liquidityIndex 的值。隨后通過循環的閃電貸不斷調用 _handleFlashLoanRepayment 函數。
在 cumulateToLiquidityIndex 函數中,rayDiv 的精度損失會再次放大 reserve.liquidityIndex 的數值,最終放大了能兌換出的 WBTC。(攻擊交易:https://etherscan.io/tx/0x1a7ee0a7efc70ed7429edef069a1dd001fbff378748d91f17ab1876dc6d10392)
審計要點:在審計時,需要關注兌換率的計算方式是否容易被操控以及舍入的方式是否恰當,同時可以建議項目團隊在新的市場創建后立刻鑄造 aToken,以防止市場為空進而被操控。
與之前 Compound fork 項目 Hundred Finance 第二次被黑相同,在 Agave 攻擊事件中,攻擊者在沒有任何負債的情況下調用了 liquidateCall 函數來清算自己。而清算的代幣是 Gnosis Chain 鏈上使用的 ERC-677 標準代幣, 該類代幣轉賬時會外部調用接收地址的函數, 所以使得清算合約調用了攻擊合約,攻擊合約在此過程中存入了 2728 個通過閃電貸獲取的 WETH,鑄造出 2728 aWETH,并以此為抵押,借出了 Agave 項目中所有可用資產。外部調用結束后,liquidationCall 函數直接清算了攻擊者之前存入的 2728 aWETH,并將其轉給清算者。
(參考來源:https://x.com/danielvf/status/1503756428212936710;?攻擊交易:https://gnosis.blockscout.com/tx/0xa262141abcf7c127b88b4042aee8bf601f4f3372c9471dbd75cb54e76524f18e)
審計要點:在審計中,需要關注借貸功能的相關代碼是否符合 CEI (Checks-Effects-Interactions) 規范或者是否存在防重入鎖,并且需要考慮具有回調功能的代幣造成的影響。
在 Blizz Finance 項目被黑事件中,由于當時 LUNA 的價格持續暴跌,協議使用的 Chainlink 價格信息變得不準確,導致可以用價格高昂的 LUNA 抵押品借入資金。同時項目沒有現有的故障安全機制,盡管看起來已經提前發出警報,但并沒有及時建立預防措施來防止損失。當價格跌破該水平時,使得任何人都可以按市場價格購買(遠低于 0.10 美元的價格)大量 LUNA,并將其作為抵押品(價值 0.10 美元)從平臺借出資金。
(參考來源:https://x.com/BlizzFinance/status/1524911400992243761)
審計要點:在審計時,需要關注計算抵押品價值時采用的預言機喂價機制是否容易被外部操控,可以建議項目方采用多種價格來源進行綜合評估,以規避單一價格來源造成的風險。同時也需要注意項目是否存在合理的暫停機制,來預防此類突發情況。
在 AAVE 協議與 Para Swap 協議的交互中,Aave Collateral Repay Adapter V3 合約的 _buyOnParaSwap 函數存在多個安全隱患。該函數通過調用 safeApprove 方法,在 tokenTransferProxy 上設置 assetToSwapFrom 的限額為 maxAmountToSwap,但未考慮未進行兌換或部分兌換的情況,導致在未完全使用限額的情況下,存在剩余額度保持不變。此外,函數依賴外部合約調用 (augustus.call(buyCalldata)) 執行兌換,并且未對 paraswapData 傳參進行充分驗證與限制,從而允許攻擊者通過惡意構造的 paraswapData 操控解碼后的 buyCalldata 和 augustus 合約地址,繞過預期的兌換邏輯或完全避免兌換。由于該函數在兌換后未減少或檢查 assetToSwapFrom 的代幣限額,即使兌換失敗或被繞過,攻擊者仍可利用未變化的高額限額提取合約中的代幣,從而實現未經授權的資金轉移。出現因缺乏對輸入數據和交換結果的驗證,以及未能有效管理代幣限額的情況,導致合約被攻擊者利用。
(攻擊交易:https://etherscan.io/tx/0xc27c3ec61c61309c9af35af062a834e0d6914f9352113617400577c0f2b0e9de)
審計要點:在審計時,需特別關注與外部第三方協議的交互代碼。重點評估外部合約的輸入和輸出是否經過嚴格限制,交互邏輯是否對協議核心模型或資金安全產生潛在影響,輸入數據是否經過清理和驗證,防止惡意數據引發安全問題。通過嚴格審查外部交互的代碼邏輯以及數據驗證機制,可有效降低此類漏洞風險。
在 Polygon 鏈上,AAVE 部署過程中,由于 InterestRateStrategy 設置不兼容的問題導致功能異常,錯誤地為 WETH 設置了不兼容的利率策略。
錯誤設置的 InterestRateStrategy 合約中的 interface 如下:
而 AAVE V2 的 LendingPool 實現的代碼如下:
(參考來源:https://x.com/mookim_eth/status/1659589328727859205)
由于接口不兼容,新 InterestRateStrategy 無法正常被 LendingPool 調用,直接導致 AAVE V2 的 WETH 池功能中斷,用戶無法存入或提取 ETH。
審計要點:在審計時,需確保代碼(或 fork)中關鍵組件的接口完全兼容。同時,盡管以上問題并不是由多鏈特性導致的原因,但是審計時仍需要注意不同鏈的特性下是否會造成非預期的結果。
AAVE 的存款和取款會通過 setUsingAsCollateral 函數設置 usingAsCollateral 來實現,從而靈活地管理抵押策略。當外部協議或合約通過 AAVE 借貸函數第一次借入資金時,借貸函數會將 usingAsCollateral 設置為 true。當外部協議或合約從 AAVE 完全提取資金時,AAVE 中協議處理程序的 usingAsCollateral 狀態將被設置為 false。但實際上,AAVE 在計算取款需要燒掉的 aToken 數量時,此時如果由于算術精度誤差,協議處理程序中可能還有極少的 aToken 剩余。因此,當協議處理程序下次向 AAVE 存款時,usingAsCollateral 將不會變動,依然設置為 true,由于協議處理程序合約中沒有調用 setUserUseReserveAsCollateral 函數的接口,這可能導致協議處理程序無法再執行借款操作。
審計要點:在審計時,需要對所調用的協議有充分的熟悉度,充分了解其特性的情況下,判斷是否對于其與外部協議交互存在一定的兼容性問題,如代幣兼容性、調用實現邏輯兼容性等。
寫在最后
本手冊深入探討了 AAVE V2 協議的核心設計、關鍵功能及相關審計要點,我們希望該手冊可以更好地幫助開發人員和安全研究人員識別潛在風險并確保協議的安全運行。由于篇幅限制,本文省略了部分代碼和圖片,讀者可點擊文末的閱讀原文跳轉至 GitHub 閱讀完整版(https://github.com/slowmist/AAVE-V2-Security-Audit-Checklist)。