ERC-4626是以太坊改進方案中的一個代幣化金庫提案,雖然應用場景不廣泛,但作者推薦學習它。它是少數還沒有完整程式碼實現的ERC之一,並且被以太坊官方包含的代幣標準之一。 ERC-4626的規範遵循是完全實現ERC-20並用於表示份額。合約代碼來自OpenZeppelin代碼庫,包括存款、鑄幣、提領、贖回等功能。它有一個專門的聯盟生態網站,並且已經有一些金庫協定和應用程式相容。最後,文章分析了一個實際應用例子,Aladdin DAO的一個金庫合約。
ERC-4626作為一個出現2年多的以太坊改進方案,被討論的似乎不多,遠不如ERC-20、ERC-721等,原因在於應用場景相對來說不是很廣泛,但作者還是比較推薦學習它,理由如下:
它是OpenZeppelin 為數不多還沒有完整代碼實現的ERC 之一,同時也是被以太坊官方包含的代幣標準(目前共有5 個,另外幾個是ERC-20、ERC-721、ERC-777、ERC ) -1155)其中之一,對於合約開發者和審計人員來說是非常重要的。它是一個非常適合新手學習DeFi 的ERC,其輸入代幣/輸出份額的設計理念,在流動性協議、質押等DeFi 特性中都有廣泛的應用。金庫與DeFi 借貸、質押業務聯繫是非常緊密的。
以下我們從提案內容、承諾實現、應用生態、安全等幾個面向來全面解析ERC-4626。
# 01 什麼是ERC-4626
ERC-4626是一個使用單一基礎ERC-20的代幣化金庫。
具有單一底層EIP-20 代幣的代幣化金庫。
首先,它是一個基於ERC-20的提議,並且完全相容。
其次,理解金庫(金庫)的概念,它不是國庫(金庫)。現在的國庫組織基本上就是一個契約錢包,主要以Gnosis Safe 為主,主要提供安全的資金進出功能。但對於一個來說,除了資金出入外,還可以讓資金流動產生收益。
該提案產生的動機:代幣化的金庫缺乏標準,導致市場上的許多金庫實現細節不一樣,例如網路市場、聚合器、生息代幣等。這使得在協定方面的聚合器和插件整合工作發生變化得困難,容易出錯並浪費開發資源。
該提議當前狀態:Final,意味著是相對比較穩定的標準了。
#02
規範
遵循ERC-4626 的概念必須完全實現ERC-20,用於表示份額(shares)。下面是幾個簡單的。
資產(asset):由金庫管理的基礎代幣(底層代幣),遵循ERC-20標準。貢獻(share):金庫代幣,也可以稱為vToken。它和資產有一個比例關係。費用(fee):在資產或支出改變時,金庫收取的一筆金額。可以是存款、收益、資產管理、等。滑點(slippage):貢獻和存款的公佈價格和實際經濟差異。以下是關於DeFi 領域滑點概念的更多解讀。
滑點是指交易的預期價格與實際執行價格之間的差異。此時單筆交易與執行交易之間存在延遲,交易的資產價格發生變化時,就會出現滑點。
例如,你在AMM 礦池子裡發現有20 個ETH 和80 個USDT,那麼你預期的ETH 價格是4 USDT/ETH。但是,如果你打算花費20 個USDT 在礦池子裡進行兌換,最終只會得到4 個ETH,而不是預期的5 個ETH,這意味著你產生了1 USDT/ETH 的滑點損失。你的實際購買價格將是5 USDT,而不是4 USDT。
滑點在快速變化的市場或高波動性資產以及流動性設定的長尾資產中尤其常見。無論如何,它對交易表現有重大影響,在下單交易時考慮滑點非常重要。
#03合約分析
合約程式碼來自OpenZeppelin智能合約程式碼庫:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol
ERC-4626 合約繼承自ERC-20,這部分未概述,它本身也是一個抽象合約,該合約必須實現的介面如下:
// 傳回用於Vault 記帳、儲值、提現的底層代幣地址。 function asset() 外部視圖returns (address assetTokenAddress);// 返回Vault「管理」的底層資產總量.functiontotalAssets() external view returns (uint256totalManagedAssets);// 在滿足所有條件的理想情況下,返回Vault 將兌換Vault 將兌換所提供資產數量的份額數量。 view returns (uint256 Shares);// 在滿足所有條件的理想情況下,返回Vault 將用所提供的股份數量交易所的資產數量。 ;// 透過存款調用,返回接收者可以存入Vault 的標的資產的最大金額。 function maxDeposit(address receive) external view returns (uint256 maxAssets);// 允許鏈上或鏈下-鏈用戶在給定當前鏈上條件的情況下,模擬當前區塊的存款效果。函數previewDeposit(uint256 assets) external view returns (uint256 share);// 透過存入準確數量的標的資產,向接收者鑄造Vault 份額tokens.function Deposit(uint256assets,addressreceiver)external returns (uint256sha);呼叫返回可以為接收者鑄造的Vault份額的最大數量。 function maxMint(addressreceiver)externalviewreturns (uint256 maxShares);// 允許鏈上或鏈下用戶在給定當前鏈上條件的情況下模擬當前區塊的鑄幣效果。 // Mints 透過存入底層代幣的數量,將Vault 份額準確地分配給接收者。 function mint(uint256 Shares, Address receiveer) external returns (uint256 assets);// 返回可以從所有者餘額中提取的底層資產的最大數量在Vault 中,透過提現調用。 function maxWithdraw(address Owner) external view returns (uint256 maxAssets);// 允許鏈上或鏈外用戶模擬當前區塊提現的效果,給定當前的-chainconditions.functionpreviewWithdraw(uint256assets);的股份並將底層代幣的資產準確地發送給receiver.functionwithdraw(uint256assets,addressreceiver,addressowner)externalreturns(uint256 share);// 透過贖回調用,返回可以從Vault 中的所有者餘額中贖回的Vault 份額的最大數量。 function maxRedeem(address Owner) external view returns (uint256 maxShares);// 允許鏈上或鏈下用戶在給定當前鏈上條件的情況下模擬當前區塊的贖回效果。 function PreviewRedeem(uint256 Shares) external view returns (uint256 assets);// 準確銷毀所有者的份額並發送底層資產代幣到接收者。功能贖回(uint256股份,地址接收者,地址所有者)外部回報(uint256資產);
介面還是相當豐富的,大部分比較簡單,可以分為讀和寫2大類。
寫
寫入資料介面主要是儲值、鑄幣、提現、贖回。
存款,存款,確定數量資產轉入金庫。同時鑄造(鑄造)股份。可以使用previewDeposit方法提前查看可以鑄造多少股份。
function Deposit(uint256 assets, 地址接收者) public virtual returns (uint256) { uint256 maxAssets = maxDeposit(receiver); if (assets > maxAssets) { 恢復ERC4626ExceededMaxDeposit(receiver, assets,Deassets,Deassets;
uint256 股份= PreviewDeposit(資產); _deposit(_msgSender(), 接收者, 資產, 股份);
返回股份;}
function _deposit(地址呼叫者, 地址接收者, uint256 資產, uint256 股票) 內部虛擬{ SafeERC20.safeTransferFrom(_asset, 呼叫者, 地址(this), 資產); _mint(接收者, 股份);
發出存款(調用者、接收者、資產、股份);}
撤回,提現,確定數量資產轉出金庫,同時進行(burn)股份。可以使用previewWithdraw方法提前查看撤資了多少股份。
functionwithdraw(uint256 資產,地址接收者,地址所有者) public virtual returns (uint256) { uint256 maxAssets = maxWithdraw(owner); if (assets > maxAssets) { revert ERC4626Exceededed);
uint256 股= PreviewWithdraw(資產); _withdraw(_msgSender(), 接收者, 所有者, 資產, 股);
返回股份;}
function _withdraw( 地址呼叫者, 地址接收者, 地址所有者, uint256 資產, uint256 股份) 內部虛擬{ if (調用者!= 所有者) { _spendAllowance(所有者, 呼叫者, 股份); } _burn(所有者, 股份); SafeERC20.safeTransfer(_asset, 接收者, 資產);
發出Withdraw(調用者、接收者、所有者、資產、股份);}
鑄幣,鑄造,使用股份參數,實際上這個方法直接於存入,鑄造的股份來計算存入的資產。可以使用previewMint方法提前查看取出多少資產。贖回,贖回,使用股份參數,該方法只需撤回,重新調用股票來計算需要轉出的資產。可以使用預覽贖回方法提前查看贖回多少資產。
由於滑點的存在,使用預覽方法查看預計的數字,可能是不準確的,也是當前常見的問題,可能會產生一些安全問題,後面會講到。
讀
前面講的幾個預覽方法,和公開的convertToShares,convertToAssets,實際上內部都是呼叫了_convertToShares、_convertToAssets 方法。
這2個核心方法就是計算資產和份額的比例關係的,這裡面涉及到變數有份額供應量、當前總資產、小數點繳納、小數點取整方式。
function _convertToShares(uint256 assets, Math.Rounding rounding) 內部視圖虛擬收益(uint256) { return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), TotalAssets() + 1, rounding);
函數_convertToAssets(uint256 Shares, Math.Rounding rounding) 內部視圖虛擬回傳(uint256) { return Shares.mulDiv(totalAssets() + 1,totalSupply() + 10 ** _decimalsOffset(), rounding)}
函數mulDiv(uint256 x, uint256 分0) { 結果+= 1; } 回傳結果;}
以上是ERC-4626 抽象合約的基本實現,實際的金庫合約很複雜。
對金庫合約來說,有2個比較重要的功能實現,一個是存取功能,資產和股份的換算;另一個是獲得收益的方式。下面我們會進行舉例講解。
#04
生態與應用
類似其他一些熱門的EIP,ERC-4626也有一個專門人維護的聯盟生態(https://erc4626.info/),收集了目前已經相容ERC-4626的一些網關協議和應用,另外還有新聞、開源庫、安全等資訊。如果你的金庫配備了ERC-4626,也可以在上面提交申請。
以下我們分析一個應用程式例子,Aladdin DAO 的AladdinCRVV2 金庫(https://concentrator.aladdin.club/vaults/)。 Aladdin DAO 有很多金庫合約,這只是其中一個比較活躍的。
阿拉丁CRVV2 金庫
該金庫透過質押cvxCRV代幣,來獲得收益。
該金庫合約是可升級合約
(https://etherscan.io/address/0x2b95A1Dcc3D405535f9ed33c219ab38E8d7e0884),透過github程式碼可以找出先前版本是不相容ERC-4626的
該金庫資產是cvxCRV
(https://etherscan.io/address/0x62B9c7356A2Dc64a1969e19C23e4f579F9810Aa7)。轉換cvxCRV可以在Curve命名的Convex上透過質押CVX獲得,也可以使用CRV cvxCRV(過程不可逆)
初始化時,會設定策略(https://etherscan.io/address/0x94cc627db80253056b2130aac39abb252a75f345),用於存款時質押以獲得效益。策略可更改存款時,請調用策略進行質押,最終計算份額。質押合約為(https://etherscan.io/address/0xaa0C3f5F7DFD688C6E646F66CD2a6B66ACdbE434)
函數存款(uint256 _assets,位址_receiver)公共重寫nonReentrant返回(uint256){if(_assets == uint256(-1)){_assets = IERC20Upgradeable(CVXCRV).balanceOf(msg.sender); }
位址_策略=策略; IERC20Upgradeable(CVXCRV).safeTransferFrom(msg.sender, _strategy, _assets); IConcentratorStrategy(_strategy).deposit(_receiver, _assets);
返回_deposit(_receiver, _assets);}
function _deposit(address _recipient, uint256 _amount) 內部回傳(uint256) { require(_amount > 0, “AladdinCRV: 零金額存款”); uint256 _totalUnderlying = 總Underlying; uint256 _tocSupply);
uint256 _shares; if (_totalSupply == 0) { _shares = _amount; } else { _shares = _amount.mul(_totalSupply) / _totalUnderlying; } _mint(_recipient, _shares)總額;
// 來自IAladdinCRV 的遺留事件發出Deposit(msg.sender, _recipient, _amount);
發出存款(msg.sender,_recipient,_amount,_shares); 返回_股;}
這裡的質押調用流程比較長,也是這個金庫收益的核心邏輯,以下是過程。
AladdinCRVV2 合約的存款方法中呼叫IConcentratorStrategy(_strategy).deposit(_receiver, _assets) ;
2.策略合約(https://etherscan.io/address/0x94cc627db80253056b2130aac39abb252a75f345)的充值方法中呼叫ICvxCrvStakeWrapper(wrapper).stake(_amount, address(this));
3. 盤點合約
(https://etherscan.io/address/0xaa0C3f5F7DFD688C6E646F66CD2a6B66ACdbE434)的權益方法中呼叫IRewardStake(cvxCrvStake).stake(_amount);4. cvxCrvCC20050750606000107500002020202020200200098(J.F. d24108E434A7587e)質押法最終完成質押流程
提款時,透過策略進行取消質押操作,同時考慮股份。取款有手續費。
函式withdraw(address _recipient, uint256 _shares, uint256 _minimumOut, WithdrawOption _option) public override nonReentrant returns (uint256 _withdrawed) { if (_shares == uint256(-1) };
if (_option == WithdrawOption.Withdraw) { _withdrawed = _withdraw(_shares, _recipient, msg.sender); require(_withdrawed >= _minimumOut, “AladdinCRV: 輸出不足”); } else { swithdrawed 設 = _vv. ), msg.sender); _withdrawed = _withdrawAs(_recipient, _withdrawed, _minimumOut, _option); }
// 來自IAladdinCRV 的遺留事件發出Withdraw(msg.sender, _recipient, _shares, _option);}
function _withdraw( uint256 _shares, address _receiver, address _owner) 內部回傳(uint256 _withdrawable) { require(_shares > 0, “AladdinCRV: 零份額提現”); require(_shares
if (totalSupply() == 0) { // 如果使用者最後提現,退出前收穫// 第一個參數其實沒有使用。 _harvest(msg.sender, 0); _totalUnderlying = 總Underlying; // `totalUnderlying` 在`_harvest` 中更新。 _withdrawable = _totalUnderlying; IConcentratorStrategy(策略).withdraw(_receiver, _withdrawable); } else { // 否則計算份額並取消質押_withdrawable = _amount; // 減去一小筆時提款費用,以防止使用者「計時」/ / 收穫。費用保持質押狀態,因此// 重新分配給所有剩餘參與者。 uint256 _withdrawFeePercentage = getFeeRate(WITHDRAW_FEE_TYPE, _owner); uint256 _withdrawFee = (_withdrawable * _withdrawFeePercentage) / FEE_PRECISION _withdrawable * _withdrawFeePercentage) / FEE_PRECISION; _withdrawdable; draw(_receiver, _withdrawable); } TotalUnderlying = _totalUnderlying – _withdrawable;
發出Withdraw(msg.sender, _receiver, _owner, _withdrawable, _shares);
返回_withdrawable;}
注意存款和存款,有多種操作選擇,還挺方便的,節省gas。程式碼太多,這裡就不貼出來了。存款,預設將cvxCRV代幣存入金庫。另外還有depositWithCRV 方便CRV也可以另外還可以在提款時,自己再質押,將cvxCRV 轉換成CVX ,將cvxCRV 轉換成ETH
以上就是金庫契約的基本解析,功能還是比較豐富的,它的本質就是資產質押生息,為什麼要這樣設計呢,主要還是在於cvxCrvStake 契約的設計,質押cvxCRV 收益描述“通過質押cvxCRV,你”重新從veCRV獲得通常的獎勵(來自Curve 的3crv 治理費分配+ 任何空投),加上Convex LP 增加的CRV 收入的10% 的份額,以及除此之外的CVX 代幣。 ”,而一起通過金庫質押的代幣數量越多時,獲得的收益也越大。
安全
對ERC-4626金庫來說,增加的安全問題相當於防通貨膨脹攻擊(Inflation Attack)。
當使用者存入代幣時,根據份額計算公式(份額= 資產* 總供應量/ 總資產),計算結果是有小數據點的,一般是下一代取整。
透過下圖可以看到,在用戶存入500代幣的資產時,小數取整所損失的資產取決於匯率(人民幣和代幣資產對應關係)。如果匯率是橘色曲線的匯率,我們得到的小於1股,損失了100%。但是,如果匯率是綠色曲線的匯率,得到5000股,四捨五入損失最多在0.02%。
那如果我們專注於將損失限制在最大0.5%,我們需要獲得至少200股。綠色匯率只需要20個代幣,但橘色匯率需要200000個代幣。
透過上幾個例子可以分析出來,藍色和綠色的橢圓形對比黃色和橙色的橢圓形更加安全,是設計更安全的金庫。
所以通貨膨脹攻擊的主要方式就是,透過某種方式將利率曲線向右移動,讓少量存款者損失份額,達到攻擊目的。
攻擊方式
通貨膨脹攻擊主要是透過捐贈(donate)。
攻擊者先存入1 個代幣到金庫合約。隨後他所獲得的股份為1 ,totalSupply 為1。攻擊者直接向金庫合約變更發送1e5個代幣。此時totalAssets發生了變化,為1e5 + 1,而totalSupply並沒有發生。當受援者存入少於1e5個代幣時(x),所獲得的份額為:x * 1 / (1e5 + 1),只要x小於1e5,根據小數遞進取整原則,受援者所所得的份額為0 。即使存入的代幣大於1e5,因為攻擊者之前所佔份額為100%,那麼受害者所獲得的份額也最大減少。
抵禦攻擊
抵禦攻擊的措施有3種:
設定滑點。在前面我們介紹了滑點的概念,透過設定滑點承受範圍(slippage opportunity)如果在某個滑點承受範圍內沒有收到預期的數量,則撤銷(revert)交易。這是處理滑點問題的標準範式。為金庫增加足夠的國有資產,增加攻擊成本。這種方式作者在Blast 質押合約中實現,質押質押時,合約要求ETH 和USD 數量大於1000。將「虛擬流動性」加入的金庫,讓價格計算行為跟金庫中有足夠的資產一樣。抵禦方式分成2部分: 在股票和資產之間做精度偏差。將虛擬份額和虛擬資產納入匯率計算中。
具體實作就是重寫OpenZeppelin 提供的標準函式庫程式碼_decimalsOffset() 方法,這種方式即不用設定滑點,也不用注入足夠的初始資金,是非常龐大的飢餓攻擊方式。
# 05擴展
RC-4626作為一個比較基礎的金庫提案,不可能滿足所有需求,有一些提案也對停止做出了擴展,例如ERC-7535、EIP-7540。
ERC-7535
前面提到ERC-4626只能使用ERC-20作為基礎資產,這個提案主要是現貨資產(原生資產)也可以當作基礎資產(底層資產),例如ETH在金庫中使用。
EIP-7540
這個針對ERC-4626 的擴展引入了對非同步存款和贖回過程(稱為「請求」)的支持。它包括用於啟動和檢查這些請求狀態的新方法。現有的從ERC-4626 中使用的方法,如存款、鑄幣、提取和贖回,被用於執行可認領的請求。關於是否添加存款、贖回或雙向的非同步流程,由實現者自行決定。
潛在範例:
非同步存款和贖回流程:透過引入「請求」概念,可以實現非同步的存款和贖回流程,提供更靈活的操作方式。使用者體驗:該提案強調了使用者體驗的重要性,並引入標準的發現機制,幫助使用者和前置應用程式更好地了解非同步操作的持續時間和延遲。功能擴展:EIP-7540透過添加新的方法,擴展了ERC-4626的功能,使得可以非同步請求存款和贖回,並且可以查看這些請求的狀態。
#06總結
以上,就是關於ERC-4626相關的全部解析。
因為歷史原因,目前有許多金庫並不遵循ERC-4626 的,仍然在某些相容,例如dForce,但無法應用更廣。也有一些金庫已經升級到遵循ERC-4626,例如Aladdin DAO 的約定( https://github.com/AladdinDAO/deployments/blob/main/deployments.mainnet.md)。
金庫應用程式除了質押生息外,還可以將股票做為質押物借出或再質押,從而產生收益。另外透過金庫來募資也是一個比較應用場景,它的一些好的基礎功能可以提供很好的支援。
該提案的推出,本質上是為了提升金庫與DeFi 生態整合效率,並減少開發成本。而金庫本身的作用,隨著DeFi 市場的成長,還有更多挖礦的空間。
資訊來源:0x資訊編譯自網際網路。版權歸作者Kahn所有,未經許可,不得轉載