I_Ljy

Posted on Mar 29, 2022Read on Mirror.xyz

關於算稳FRAX,你需要知道的一些事

原文: How Frax beats the Rest by jackchong.eth & 0xkowloon.eth

編譯: TheBlockBugle

穩定幣最簡單的定義是:一種數字貨幣,其價值通常與 "穩定 "儲備資產美元錨定。穩定幣在加密貨幣的總市值中占了很大一部分,市場規模達1800億美元以上。對於機構來說,穩定幣是進入更廣泛的數字資產市場的一個楔子:在保留低波動性的好處的同時,為法定貨幣提供更高的收益和更快的結算。

盡管加密貨幣承諾去中心化,但穩定幣仍然被中心化產品所主導。80%以上的穩定幣市場由法幣支持的資產代表,如$USDC和$USDT。這些產品是由中心化實體發行的,分別是Circle和Tether。

越來越多的中心化發行人受到了更多的監管審查。在Tether醜聞的推動下,國會、美聯儲和財政部都盯上了穩定幣。在最近的一份公開報告中,美聯儲曾經認為穩定幣是一個挑戰貨幣政策和金融系統安全的 "抵押品天坑"。

隨著中心化的穩定幣受到攻擊,去中心化的穩定幣變得更有吸引力。在2020年12月,法幣支持的穩定幣市值是算法穩定幣的21倍。今天,這一比例僅為5倍!

這篇文章是對Frax協議的深入研究:$FRAX一個由算法支持的穩定幣。自2021年10月以來,其市值已經增長了近530%,20億美元的協議總價值,使其成為目前增長最快的協議。

建立一個反脆弱的算法支持的穩定幣是非常困難的,其需有三個核心競爭力得到滿足:

  1. 穩定性(低波動性)
  2. 在一段時期內保持與所選資產的掛鉤
  3. 高效用(與更廣泛的生態系統整合)

在《文明:西方與其他國家》一書中,历史学家尼尔-弗格森歸納了西方比世界其他國家("其他國家")經濟更發達的六個原因。他將這些因素,如競爭、產權和醫藥,命名為 "殺手級應用",蜻蜓公司的Haseeb Qureshi稱之為第一層城市。巴拉吉把一個數字化的原生國家稱為'加密文明'。

在這裏,我們稱Frax如同像西方一樣的文明,這也是對尼爾-弗格森的致敬。你先聽聽我們的說法:我們已經確定了3個殺手級應用,使Frax能夠擊敗其他穩定幣。

這篇文章也是對Frax代碼庫的第一次技術性深入研究!

研究涉及協議設計的Frax V2和協議技術的V3合約

什麽是 Frax?

根据稳定币的类型学(目前还没有一致的范式),$Frax可以被归类为 "部分加密支持的稳定币",具有以下特性:

  1. 以加密貨幣為後盾:由加密貨幣資產而非法幣支持。
  2. 部分抵押:被鑄造的$FRAX的價值部分由金庫的資產支持。
  3. 去中心化--由社區管理,不為中央實體所擁有(例如,$USDT由Tether控股公司擁有,$USDC由Circle擁有)。
  4. 雙代幣模式。該協議還發行了一個二級代幣$FXS,用於協議治理,並從協議活動中獲得一定比例的利潤。
  5. 與1美元的名義美元錨定,而不是自由浮動。

抵押率

抵押率(CR)決定了用戶需要多少抵押品才能鑄造一個$FRAX(在撰寫本文時CR為85%)。$FRAX是與1美元錨定的穩定幣。另一方面,$FXS是Frax Finance的治理代幣。它們共同構成了穩定幣的稅收模式。這意味著,為了從$FRAX上鑄造1美元,需要存入價值0.85美元的$USDC(或其他可接受的抵押品)和價值0.15美元的$FXS。相反,如果你想贖回你的$FRAX,你會收到價值0.85美元的USDC和價值0.15美元的FXS。

**注:**Frax的CR是一個動態比率。CR根據市場的需求和供應力量而波動。它根據$FRAX的擴張和回縮而變化。當對$FRAX的需求推動上升時,CR就會下降。需要更少的抵押品和更多的$FXS來鑄造$FRAX。

由於Frax的CR機製反映了中央銀行的工作方式,分析家們將Frax比作鏈上的 "自動中央銀行"。

現在我們對Frax的基礎知識有了些了解,讓我們來看看Frax的殺手級應用!

殺手級應用

應用1:Ve機制

目前,DeFi領域最熱門的發展是 "Curve 戰爭"。

Curve戰爭指的是自動做市商(AMM)曲線金融上的流動性協議之間的競爭。它是最受歡迎的去中心化掉期交易所,擁有近180億美元的TVL。與競爭對手相比,它的主要區別在於低滑點和費用。

為什麽戰場在Curve,而不是Uniswap或其他DEX?這是由於Curve圍繞其代幣的協議設計:$CRV。

Curve代幣經濟學有兩個特點:1.投票托管模式:當你為Curve上的交易池提供流動性時,你會賺取該交易池的所有交易費用的份額。此外,你還可以賺取一些CRV美元,作為提供流動性的獎金激勵。通過押註和 "鎖定 "你的$CRV,你將獲得$veCRV。它授予代幣持有人治理權。

$CRV鎖越久,分發的$veCRV越多

2.計量系統:$veCRV代幣持有者可以投票決定每個流動性池獲得多少$CRV獎勵,或者用Curve的術語說是 "計量權重"。一個池子得到的投票越多,就會有更多的$CRV獎勵給該池子的流動性提供者。更有吸引力的獎勵促使更多的流動性提供者投入到被激勵的池子中。

為什麽這很重要?對於任何DeFi協議,流動性是王道。而Curve擁有最深的流動性之一,擁有180億美元的TVL。一個新生的協議可以把它的原生代幣和一對代幣(例如$FRAX - $ETH)存入池中,以便增加流動性。另外,一個協議可以 "作弊",找到一些方法來增加他們池子的$CRV獎勵。其他人會看到這種高APY,自然而然地成為流動性的提供者。

Convex作為$CRV聚合器,其工作方式是:1. 用戶$CRV存入Convex,以換取$cvxCRV;2. Convex把用戶的$CRV押在Curve上,換取$veCRV;3. Convex從Curve賺取獎勵,它將這些獎勵重新分配給$cvxCRV的投保人;4. $cvxCRV也是立即流動的,不像Curve是鎖定的。

Convex變得像Curve本身一樣,但有了流動性的額外好處。正因為如此,它後來積累了大部分的CRV代幣。現在,它控製了Curve的52%的投票權,並且自成立以來為$veCRV鎖定了2億$CRV。對於希望激勵流動性的協議,他們要麽購買$CVX代幣,要麽 "賄賂"$CVX代幣持有者為其協議投票。

上面的截圖取自賄賂平臺Votium。它顯示了賄賂價值1美元的$vlCVX(投票鎖定的Convex代幣)的費用,允許其持有人用Convex持有的$veCRV對Curve的計量權進行投票。協議賄賂CVX持有人投票支持他們的流動性池。有了更高的儀表權重,Curve就會發出更高的APY獎勵;有了更高的APY,流動性池就能吸引更多的流動性。

Frax作為$CVX的最大持有者,將其在Convex的$veCRV池中的份額引導到$FRAX計價的池中,從而獲得更多的$CRV獎勵。

此外,與其他穩定幣協議相比,FRAX也是每周在Votium上支付最多賄賂的協議。

有了更好的回報(更高的APY),Frax吸引了更多的流動性提供者,推動了進一步的采用。Frax更深的流動性保證了美元錨定變得更穩定,更容易維護。

Curve上最大的Frax池是FRAX3CRV池,它允許用戶將FRAX與USDT、USDC和DAI這三個主要賭註進行交換。由於存放了大約15億美元的FRAX,該池中的大型掉期對價格影響很小。這為$FRAX提供了一個緩沖區,以應對尾部風險的賣出壓力。

換句話說,流動性帶來了流動性。

打不過就加入!

Curve在流動性戰爭中的主導地位是不可否認的。那麽從戰略上講,每個協議都應該努力成為Curve那樣的人。理想情況下,每個協議都希望其他協議能在你之上組成。然而,Lindy效應決定了Curve可能會在一段時間內保持不受挑戰。因此,如果你不能打敗Curve,那就成為Curve吧!

為此,Frax采用了Curve的tokenomics模式,實施投票托管治理模式,$FXS持有者可以鎖定他們的代幣以換取$veFXS。雖然$veCRV持有者決定了Curve本身的特定池子的Curve排放的儀表權重,但$veFXS持有者對儀表進行投票,引導$FXS在不同DEX的不同池子中排放。例如,最大的池子是Uniswap V3 $FRAX/$USDC,有超過45%的釋放獎勵。

最近,Convex與Frax合作,進一步調整這兩個協議的激勵計劃,特別是圍繞將veFXS鎖定在CVX協議中。Convex認為自己不僅僅是一個veCRV的聚合器。它有一個更大的野心:為所有種類的ve-tokens提供投票權聚合器。

通過使用$FXS排放,Frax能夠通過激勵流動性提供者向$FRAX計價池入股來深化流動性,這與Curve的方式很相似。為了保持$FXS排放的片斷被引導到他們的池子裏,流動性提供者用他們的$FXS獎勵來換取$veFXS的治理權。隨著$FRAX滲透到更多的協議和互換對中,它的總市值也在增加。當$FRAX的主導地位增加時,$FXS的排放量也變得更有價值。很快,每個協議都想擁有$FXS量表的一些片斷,以直接激勵他們的流動性對!

換句話說,Frax戰爭就是Curve戰爭,金錢樂高變成了一個基盤

第二,目前各協議之間正在為其代幣成為新興DAO的流動性配對選擇而進行爭奪。我們把這稱為 "DAO流動性戰爭"。在這個領域競爭的玩家包括Rift Finance、Fei & RariOlympusDAO以及其他新興協議。

如果說曲線戰爭是關於流動性,那麽DAO流動性戰爭就是關於實用性。每個穩定幣都希望成為整個生態系統中的首選計價貨幣。如果Frax在DAO流動性戰爭中獲勝,它通過提供$FRAX作為流動性對的一方,將一個新興的DAO鎖定在一個相互依賴的關系中。

隨著越來越多的交易對使用$FRAX,它的效用會增加。當$FRAX成為DAO選擇的主要儲備貨幣時,它開始成為鏈上的美元等價物。DAO流動性戰爭的贏家被加冕為整個生態系統開展業務和交易的可信面額。

DAO流動性戰爭的戰利品就是協議霸權!

應用2:Frax的彈性

自推出以來,$FRAX從未脫離其掛鉤。與其同行的算法穩定幣(如Iron Finance & Fei)相比,這一點相當了不起。

我們將其強大的錨定歸功於雙代幣機制:$FXS被設計為$FRAX的補充性代幣,它吸收了與美元錨定的$FRAX的波動性,同時將協議價值分配給其代幣持有人。

工作方式:1. $FXS的供應上限為1億個代幣;2. 通過回購和燃燒將協議收入輸送給$FXS代幣持有人;3. 將$FXS和$USDC作為FRAX的抵押品 使FRAX在DeFi中不可或缺,增加收入並提升$FXS價格;4. 抵押品比率隨著$FXS變得更有價值而下降;5.最終,$FRAX成為完全支持$FXS的,沒有任何其他穩定幣的抵押品,更不用說中心化的$USDC了。

這裏的悖論是,要建立一個有彈性的完全算法的穩定幣,你需要從部分抵押開始。最終,你甚至可能以超額抵押而告終! 這種非微妙的路線圖類似於Web2的 "從下往上攻擊 "的策略。

Frax價格指數(FPI)

Frax一直在其與美元掛鉤的穩定幣上疊加抗通脹能力。2022年1月1日,聯合創始人山姆在推特上說,Frax團隊正在開發Frax價格指數($FPI),這是一種新的原生穩定幣。

$FPI將與去中心化的消費者價格指數(CPI)掛鉤,在Chainlink為FRAX打造的定製CPI諭令之上加入加密貨幣原生元素。代幣持有者根據報告的CPI增長,每月看到他們的代幣以美元計價的價值增加。這是可能的,因為Frax從基礎的FPI國庫中賺取收益,該國庫是由用戶用FRAX鑄造和贖回FPI而產生的。

具體機製目前還不清楚,但從初步文件來看,$FPI具有類似於貨幣市場基金的屬性:具有保證季度收益率的存款余額,具有高度流動性。不過,在鏈上維持這種錨定,可能需要一些創新的錨定機製。薩姆在推特空間活動上說,通過采取追蹤12個月(TTM)的CPI,一年內所有的波動都被平滑掉了。$FPI還得到了Frax和Fei提供的深度流動性的支持。它的目標是成為DAO貢獻者報酬的交換單位。最終,目標是創建一個加密貨幣原生的CPI,代表全球一籃子商品的生活成本中位數。

在我們等待更多細節的時候,這是一個真正令人興奮的功能,可能會啟動穩定幣2.0。

我們持謹慎的樂觀態度:最終,穩定幣體現了可編程貨幣的真正命運。與名義美元脫鉤!

應用3:算法市場操作(AMO)

2021年3月,Frax v2公布了其迄今為止最好的創新,即算法市場操作(AMO)。

為了理解這一點,讓我們看一下法幣世界中的類似情況。

中央銀行,如美聯儲,通過參與 "公開市場操作 "來控製貨幣供應。通過從市場上購買(或出售)國債,美聯儲有效地增加(或減少)貨幣系統中美元的總流通量。

AMO使用類似的策略。Frax協議影響著整個DeFi生態系統中$FRAX的供應。此外,社區中的任何人也可以通過治理提出一個AMO策略。

對於任何AMO策略,我們都可以將其分解為四個關鍵部分--解除抵押、市場操作、再抵押和燃燒。

先讓我們來看看Curve AMO策略

解除抵押:該算法確定閑置在Frax協議的財政部有多少多余的抵押品,以$USDC和$FRAX為單位。超額抵押品指的是超過抵押品比率所需的額外資產數量,以支持$FRAX的總供應。

市場操作:AMO將閑置的$USDC和$FRAX註入FRAX3CRV Curve池中,通過這樣做加深流動性並加強美元锚定。

重新抵押:當國庫過於接近抵押率時,AMO將從Curve 3pool中移除流動性並重新抵押協議。

燒毀:基于FXS1559標準,該協議利用將多余的抵押品部署到LP中的互換費用所產生的收入,能夠以抵押比率鑄造新的$FRAX,隨後回購$FXS以進行燒毀。

AMO以高資本效率的方式為協議賺取LP獎勵和交易費用,然後將利潤分配給$veFXS持有人。該協議還通過幹預市場為$FRAX穩定幣提供流動性來加強其錨定。這類似於目前中央銀行的操作,如香港金融管理局,部署外匯儲備以維持貨幣錨定(如港元-美元)。

Frax把美元錨定掌握在自己手中,而其他穩定幣則依靠套利者和投機者來維持錨定。

技術深究

Frax Finance的核心邏輯存在於資金池合約中,它控製著$FRAX何時可以被鑄造和贖回。目前的主網部署是一個$USDC池,只允許單一抵押品。但$FRAX正朝著多抵押品支持的穩定幣方向發展,所以我們要看一下V3版本的合約。

1.鑄造$FRAX

只要滿足以下條件,任何人都可以通過調用FraxPoolV3#mintFrax函數來鑄造$FRAX。

抵押品類型已被支持,且它在修改器collateralEnabled中能被檢查到。

modifier collateralEnabled(uint256 col_idx) { require(enabled_collaterals[collateral_addresses[col_idx]], "Collateral disabled"); _; }

只有合約所有者和時間鎖地址有權啟用/禁用抵押品。該協議的大部分特權功能只能由合同所有者或鏈上治理者通過時間鎖合同調用。

使用上述抵押品的鑄幣活動沒有暫停:

require(mintPaused[col_idx] == false, "Minting is paused");

目前的FRAX價格高於鑄幣廠的價格閾值,最初的閾值被設定為1.01美元。鑒於協議希望將$FRAX穩定在1美元左右,當價格高於1美元時,允許膨脹的代幣供應是沒有意義的。

require(getFRAXPrice() >= mint_price_threshold, "Frax price too low");

$FRAX的美元价格从Chainlink获取。大多数Chainlink的价格反馈都会返回一个8位小数的数字。然后该函数通过乘以 PRICE_PRECISION (1e6),然后除以 10 ** 8,将其修剪到小数点后 6 位(由于没有本地小数类型,在 Solidity 中经常看到小数转换。另外,总是先乘后除,以避精度的损失!)。

function getFRAXPrice() public view returns (uint256) {
    (uint80 roundID, int price, , uint256 updatedAt, uint80 answeredInRound) = priceFeedFRAXUSD.latestRoundData();
    require(price >= 0 && updatedAt!= 0 && answeredInRound >= roundID, "Invalid chainlink price");

    return uint256(price).mul(PRICE_PRECISION).div(10 ** chainlink_frax_usd_decimals);
}

2.所需抵押品的計算

支持1個$FRAX所需的抵押品($USDC)的數量取決於$FRAX的全球抵押品比率。在直接進行抵押品計算之前,我們檢查一下這個全球抵押品比率是如何調整的。

變量global_collateral_ratio存在於Frax合約中。它的初始值為1e7,意味著每個FRAX都有1美元的完全支持。這個比率可以通過調用函數refreshCollateralRatio在每一個refresh_cooldown秒(目前是1小時)進行調整。這個函數檢索FRAX的美元價格(這次不是直接獲取$FRAX/美元的價格,而是通過FRAX/ETH和ETH/USD計算FRAX/美元)。只有當$FRAX在價格帶之外交易時,才能調整抵押率,目前的價格帶設置為5000(<0.995美元和>1.005美元)。如果$FRAX在價格帶內交易,沒有理由通過調整來破壞協議的穩定性。

當$FRAX在價格帶以上交易時,降低抵押率被認為是可以接受的,因為價格反映了對貨幣的信心,反之亦然。為了不通過急劇改變抵押品比率造成價格波動,抵押品比率在每個刷新期只能上升/下降frax_step(0.25%)。該代碼還確保了抵押品比率在0%和100%之間。

if (one_to_one_override || global_collateral_ratio >= PRICE_PRECISION) { 
    // 1-to-1, overcollateralized, or user selects override
    collat_needed = getFRAXInCollateral(col_idx, frax_amt);
    fxs_needed = 0;
} else if (global_collateral_ratio == 0) { 
    // Algorithmic
    collat_needed = 0;
    fxs_needed = frax_amt.mul(PRICE_PRECISION).div(getFXSPrice());
} else { 
    // Fractional
    uint256 frax_for_collat = frax_amt.mul(global_collateral_ratio).div(PRICE_PRECISION);
    uint256 frax_for_fxs = frax_amt.sub(frax_for_collat);
    collat_needed = getFRAXInCollateral(col_idx, frax_for_collat);
    fxs_needed = frax_for_fxs.mul(PRICE_PRECISION).div(getFXSPrice());
}

抵押品計算:抵押品比率,在0-100%之間,決定了鑄造一個$FRAX所需的$USDC支持量。剩余的價值由$FXS代幣來支持。例如,如果抵押品比率為90%,鑄造一個$FRAX需要90c的$USDC,其余部分將由10c的$FXS代幣覆蓋。它還使用Chainlink oracle來獲得$FXS價格。

if (one_to_one_override || global_collateral_ratio >= PRICE_PRECISION) { 
    // 1-to-1, overcollateralized, or user selects override
    collat_needed = getFRAXInCollateral(col_idx, frax_amt);
    fxs_needed = 0;
} else if (global_collateral_ratio == 0) { 
    // Algorithmic
    collat_needed = 0;
    fxs_needed = frax_amt.mul(PRICE_PRECISION).div(getFXSPrice());
} else { 
    // Fractional
    uint256 frax_for_collat = frax_amt.mul(global_collateral_ratio).div(PRICE_PRECISION);
    uint256 frax_for_fxs = frax_amt.sub(frax_for_collat);
    collat_needed = getFRAXInCollateral(col_idx, frax_for_collat);
    fxs_needed = frax_for_fxs.mul(PRICE_PRECISION).div(getFXSPrice());
}

有趣的是,函數getFRAXInCollateral依賴於手動設置的抵押品價格來計算所需的抵押品(很可能是因為目前只有$USDC)。 collateral_prices[col_idx]是由特權賬戶通過函數setCollateralPrice設置的。這有可能成為未來的一個問題,最終應該由一個甲骨文來取代這種手動設置。

function getFRAXInCollateral(uint256 col_idx, uint256 frax_amount) public view returns (uint256) {
    return frax_amount.mul(PRICE_PRECISION).div(10 ** missing_decimals[col_idx]).div(collateral_prices[col_idx]);
}

最後,該功能向用戶鑄造$FRAX,且它收取少量的鑄幣費,支持鑄造的$FRAX的$FXS被燒毀。要註意的是,每種抵押品類型都有一個上限,只有特權賬戶才有能力提高上限。

total_frax_mint = (frax_amt.mul(PRICE_PRECISION.sub(minting_fee[col_idx]))).div(PRICE_PRECISION);

require(freeCollatBalance(col_idx).add(collat_needed) <= pool_ceilings[col_idx], "Pool ceiling");

FXS.pool_burn_from(msg.sender, fxs_needed);

TransferHelper.safeTransferFrom(collateral_addresses[col_idx], msg.sender, address(this), collat_needed);

FRAX.pool_mint(msg.sender, total_frax_mint);

3.贖回FRAX

任何人都可以通過調用FraxPoolV3#redeemFrax存入$FRAX來贖回$USDC/$FXS。該機製(包括條件檢查)類似於鑄造$FRAX,其中最終輸出的$USDC/$FXS數額也取決於全球抵押品比率。

// Prevent unnecessary redemptions that could adversely affect the FXS price
require(getFRAXPrice() <= redeem_price_threshold, "Frax price too high");

// There is a redemption fee, like the minting fee.
uint256 frax_after_fee = (frax_amount.mul(PRICE_PRECISION.sub(redemption_fee[col_idx]))).div(PRICE_PRECISION);

// Assumes $1 FRAX in all cases
if(global_collateral_ratio >= PRICE_PRECISION) { 
    // 1-to-1 or overcollateralized
    collat_out = getFRAXInCollateral(col_idx, frax_after_fee);
    fxs_out = 0;
} else if (global_collateral_ratio == 0) { 
    // Algorithmic
    fxs_out = frax_after_fee
                    .mul(PRICE_PRECISION)
                    .div(getFXSPrice());
    collat_out = 0;
} else { 
    // Fractional
    collat_out = getFRAXInCollateral(col_idx, frax_after_fee)
                    .mul(global_collateral_ratio)
                    .div(PRICE_PRECISION);
    fxs_out = frax_after_fee
                    .mul(PRICE_PRECISION.sub(global_collateral_ratio))
                    .div(getFXSPrice()); // PRICE_PRECISIONS CANCEL OUT
}

該資金池需要有足夠的抵押品才能提取。

require(collat_out <= (ERC20(collateral_addresses[col_idx])).balanceOf(address(this)).sub(unclaimedPoolCollateral[col_idx]), "Insufficient pool collateral");

贖回機製不是直接的 "存入$FRAX並立即獲得$USDC/$FXS",因為Frax Finance想要防止閃電貸款攻擊(從系統中取出$FRAX/抵押物,使用AMM交易新價格,然後再鑄回系統)。在贖回和贖回者實際收取抵押品之間有兩個區塊的延遲。

// Update the protocol's debt to the redeemer in USDC and FXS
redeemCollateralBalances[msg.sender][col_idx] = redeemCollateralBalances[msg.sender][col_idx].add(collat_out);
unclaimedPoolCollateral[col_idx] = unclaimedPoolCollateral[col_idx].add(collat_out);

redeemFXSBalances[msg.sender] = redeemFXSBalances[msg.sender].add(fxs_out);
unclaimedPoolFXS = unclaimedPoolFXS.add(fxs_out);

// The redemption delay starts from the mined block
lastRedeemed[msg.sender] = block.number;

最後,$FRAX被燒毀,$FXS被鑄入池中(address(this),而不是msg.sender!記住塊延遲!)。

FRAX.pool_burn_from(msg.sender, frax_amount);
FXS.pool_mint(address(this), fxs_out);

收集抵押品:在2個區塊過後,被拖欠抵押品的贖回者可以通過調用FraxPoolV3#collectRedemption來領取抵押品。

// 2 blocks must have elapsed
require((lastRedeemed[msg.sender].add(redemption_delay)) <= block.number, "Too soon");

該函數檢查是否真的有代幣要收集,如果沒有任何要發送的代幣,則不進行safeTransfer調用(可能是GAS優化)。

bool sendFXS = false;
bool sendCollateral = false;

// Use Checks-Effects-Interactions pattern
if(redeemFXSBalances[msg.sender] > 0){
    fxs_amount = redeemFXSBalances[msg.sender];
    redeemFXSBalances[msg.sender] = 0;
    unclaimedPoolFXS = unclaimedPoolFXS.sub(fxs_amount);
    sendFXS = true;
}

if(redeemCollateralBalances[msg.sender][col_idx] > 0){
    collateral_amount = redeemCollateralBalances[msg.sender][col_idx];
    redeemCollateralBalances[msg.sender][col_idx] = 0;
    unclaimedPoolCollateral[col_idx] = unclaimedPoolCollateral[col_idx].sub(collateral_amount);
    sendCollateral = true;
}

// Send out the tokens
if(sendFXS){
    TransferHelper.safeTransfer(address(FXS), msg.sender, fxs_amount);
}
if(sendCollateral){
    TransferHelper.safeTransfer(collateral_addresses[col_idx], msg.sender, collateral_amount);
}

4. FXS回購

當協議持有的抵押品超過需要時,$FXS持有者可以調用FraxPoolV3#buybackFxs來抽走大家的包。

協議需要首先計算系統中的多余抵押品價值。理論上,可用於回購的超額抵押品數量只是協議中的當前抵押品價值與當前抵押品比率所需的抵押品價值之間的差額。但從現實來看,由於波動性大,拿出所有多余的抵押品並不是一個好主意,尤其是Frax計劃在未來引入非穩定的抵押品類型。只要到了抵押品線的右邊,Frax就會立即變得抵押品不足。為此,Frax對每小時可以離開協議的抵押品數量實施了限製(bbkMaxColE18OutPerHour--每小時回購最大抵押品1e18出)。

函數comboCalcBbkRct在給定已使用的小時限製、最大小時限製和理論小時限製的情況下,計算實際可用於回購的$FXS金額。

function buybackAvailableCollat() public view returns (uint256) {
    uint256 total_supply = FRAX.totalSupply();
    uint256 global_collateral_ratio = FRAX.global_collateral_ratio();
    uint256 global_collat_value = FRAX.globalCollateralValue();

    if (global_collateral_ratio > PRICE_PRECISION) global_collateral_ratio = PRICE_PRECISION; // Handles an overcollateralized contract with CR > 1
    uint256 required_collat_dollar_value_d18 = (total_supply.mul(global_collateral_ratio)).div(PRICE_PRECISION); // Calculates collateral needed to back each 1 FRAX with $1 of collateral at current collat ratio
    
    if (global_collat_value > required_collat_dollar_value_d18) {
        // Get the theoretical buyback amount
        uint256 theoretical_bbk_amt = global_collat_value.sub(required_collat_dollar_value_d18);

        // See how much has collateral has been issued this hour
        uint256 current_hr_bbk = bbkHourlyCum[curEpochHr()];

        // Account for the throttling
        return comboCalcBbkRct(current_hr_bbk, bbkMaxColE18OutPerHour, theoretical_bbk_amt);
    }
    else return 0;
}

然後,它確保$FXS要燃燒的美元價值不大於超額抵押品價值,以便不違反抵押品比率。

uint256 fxs_dollar_value_d18 = fxs_amount.mul(fxs_price).div(PRICE_PRECISION);
require(fxs_dollar_value_d18 <= available_excess_collat_dv, "Insuf Collat Avail For BBK");

要移除的抵押品價值是根據$FXS的美元價值計算的。回購費用由協議收取。 missing_decimals是一個數組,用於說明FRAX(18)和其抵押品(USDC-6)之間的小數差異。它是在合同構建過程中被設置的。

// In the constructor...
missing_decimals.push(uint256(18).sub(ERC20(_collateral_addresses[i]).decimals()));
// Get the equivalent amount of collateral based on the market value of FXS provided 
uint256 collateral_equivalent_d18 = fxs_dollar_value_d18.mul(PRICE_PRECISION).div(collateral_prices[col_idx]);
col_out = collateral_equivalent_d18.div(10 ** missing_decimals[col_idx]); // In its natural decimals()

// Subtract the buyback fee
col_out = (col_out.mul(PRICE_PRECISION.sub(buyback_fee[col_idx]))).div(PRICE_PRECISION);

$FXS被烧毁,抵押品被转移到燃烧器,每小时回购的计数器值被更新。

// Take in and burn the FXS, then send out the collateral
FXS.pool_burn_from(msg.sender, fxs_amount);
TransferHelper.safeTransfer(collateral_addresses[col_idx], msg.sender, col_out);

// Increment the outbound collateral, in E18, for that hour
// Used for buyback throttling
bbkHourlyCum[curEpochHr()] += collateral_equivalent_d18;

5. 重新抵押

當$FRAX的抵押品支持價值低於抵押品比率時,保險庫需要被充實以保持協議的健康。為了激勵人們幫助重新抵押,該協議給出了$FXS的獎金,由獎金率定義。

这與回購類似,重新抵押的可用金額有一個小時的限製,所以實際價值將小於理論上的可用價值。

function recollatAvailableFxs() public view returns (uint256) {
    uint256 fxs_price = getFXSPrice();

    // Get the amount of collateral theoretically available
    uint256 recollat_theo_available_e18 = recollatTheoColAvailableE18();

    // Get the amount of FXS theoretically outputtable
    uint256 fxs_theo_out = recollat_theo_available_e18.mul(PRICE_PRECISION).div(fxs_price);

    // See how much FXS has been issued this hour
    uint256 current_hr_rct = rctHourlyCum[curEpochHr()];

    // Account for the throttling
    return comboCalcBbkRct(current_hr_rct, rctMaxFxsOutPerHour, fxs_theo_out);
}

理論可用金額是最低抵押品價值(抵押品比率乘以FRAX總供應量)與有效抵押品價值(基於抵押品價值的實際抵押品比率乘以FRAX總供應量)之間的差額。

function recollatTheoColAvailableE18() public view returns (uint256) {
    uint256 frax_total_supply = FRAX.totalSupply();
    uint256 effective_collateral_ratio = FRAX.globalCollateralValue().mul(PRICE_PRECISION).div(frax_total_supply); // Returns it in 1e6
    
    uint256 desired_collat_e24 = (FRAX.global_collateral_ratio()).mul(frax_total_supply);
    uint256 effective_collat_e24 = effective_collateral_ratio.mul(frax_total_supply);

    // Return 0 if already overcollateralized
    // Otherwise, return the deficiency
    if (effective_collat_e24 >= desired_collat_e24) return 0;
    else {
        return (desired_collat_e24.sub(effective_collat_e24)).div(PRICE_PRECISION);
    }
}

該協議还提供了一個獎金,但同時也收取了一個再抵押費用。最終離開協議的$FXS金額必須是≤可用於再抵押的$FXS。

fxs_out = collateral_amount_d18.mul(PRICE_PRECISION.add(bonus_rate).sub(recollat_fee[col_idx])).div(fxs_price);

// Make sure there is FXS available
require(fxs_out <= fxs_actually_available, "Insuf FXS Avail For RCT");

这也確保在抵押存款後,池子的上限不會被突破。

require(freeCollatBalance(col_idx).add(collateral_amount) <= pool_ceilings[col_idx], "Pool ceiling");

最後,發送msg.sender $FXS並更新每小時重新抵押的計數器。

// Take in the collateral and pay out the FXS
TransferHelper.safeTransferFrom(collateral_addresses[col_idx], msg.sender, address(this), collateral_amount);
FXS.pool_mint(msg.sender, fxs_out);

// Increment the outbound FXS, in E18
// Used for recollat throttling
rctHourlyCum[curEpochHr()] += fxs_out;

6. 算法市場運營控製器

算法市場操作控製器,或AMO造幣商,可以借用抵押品,將多余的抵押品部署到算法市場運營(AMO)。

function amoMinterBorrow(uint256 collateral_amount) external onlyAMOMinters {
        // Checks the col_idx of the minter as an additional safety check
        uint256 minter_col_idx = IFraxAMOMinter(msg.sender).col_idx();

        // Checks to see if borrowing is paused
        require(borrowingPaused[minter_col_idx] == false, "Borrowing is paused");

        // Ensure collateral is enabled
        require(enabled_collaterals[collateral_addresses[minter_col_idx]], "Collateral disabled");

        // Transfer
        TransferHelper.safeTransfer(collateral_addresses[minter_col_idx], msg.sender, collateral_amount);
    }

一個AMO礦工是一個特權角色:只能由合同所有者或治理者通過函數addAMOMinter添加;必須由函數collatDollarBalance的定義。

uint256 collat_val_e18 = IFraxAMOMinter(amo_minter_addr).collatDollarBalance();
require(collat_val_e18 >= 0, "Invalid AMO");

合约FraxAMOMinter是AMO礦工的實現的基础。它持有一個由其控製下的目的地AMO數組,並運行其內部计算,以跟蹤已借入的數量。當一個目的地AMO想要借款時,它可以調用giveCollatToAMO。該函數檢查抵押品借款上限是否達到,將借款金額添加到目的地AMO的余額中。同时它还要求資金池提供抵押品,將抵押品轉移到目的地AMO,並通過syncDollarBalances同步所有AMO的$FRAX和抵押品總余額。

function giveCollatToAMO(
    address destination_amo,
    uint256 collat_amount
) external onlyByOwnGov validAMO(destination_amo) {
    int256 collat_amount_i256 = int256(collat_amount);

    require((collat_borrowed_sum + collat_amount_i256) <= collat_borrow_cap, "Borrow cap");
    collat_borrowed_balances[destination_amo] += collat_amount_i256;
    collat_borrowed_sum += collat_amount_i256;

    // Borrow the collateral
    pool.amoMinterBorrow(collat_amount);

    // Give the collateral to the AMO
    TransferHelper.safeTransfer(collateral_address, destination_amo, collat_amount);

    // Sync
    syncDollarBalances();
}

syncDollarBalances在每個AMO中循環,以獲得其$FRAX和抵押品余額。每個AMO必須有一個名為dollarBalances的函數,返回$FRAX和抵押品余額到一個元組。映射的correction_offsets_amos用於修復不正確的余額核算,可以由合同所有者或治理者通過setAMOCorrectionOffsets設置。

function syncDollarBalances() public {
    uint256 total_frax_value_d18 = 0;
    uint256 total_collateral_value_d18 = 0; 
    for (uint i = 0; i < amos_array.length; i++){ 
        // Exclude null addresses
        address amo_address = amos_array[i];
        if (amo_address != address(0)){
            (uint256 frax_val_e18, uint256 collat_val_e18) = IAMO(amo_address).dollarBalances();
            total_frax_value_d18 += uint256(int256(frax_val_e18) + correction_offsets_amos[amo_address][0]);
            total_collateral_value_d18 += uint256(int256(collat_val_e18) + correction_offsets_amos[amo_address][1]);
        }
    }
    fraxDollarBalanceStored = total_frax_value_d18;
    collatDollarBalanceStored = total_collateral_value_d18;
}

7. 特殊的AMOS

Frax正在開發許多不同的AMO,但讓我們看看其中的一個,看看它究竟是如何運作的。合同InvestorAMO_V3將$USDC存入Yearn、Aave和Compound,以賺取利息以及耕種其代幣。為了執行超額抵押品部署,合同所有者或時間鎖定的治理者必須通過execute來執行交易,這只是一個通用代理,將任意字節傳遞到_to地址(應該是AMO礦工)。

function execute(
    address _to,
    uint256 _value,
    bytes calldata _data
) external onlyByOwnGov returns (bool, bytes memory) {
    (bool success, bytes memory result) = _to.call{value:_value}(_data);
    return (success, result);
}

一旦AMO在其合同中收到了$USDC,特權賬戶可以將$USDC部署到協議中。

function yDepositUSDC(uint256 USDC_amount) public onlyByOwnGovCust {
    require(allow_yearn, 'yearn strategy is currently off');
    collateral_token.approve(address(yUSDC_V2), USDC_amount);
    yUSDC_V2.deposit(USDC_amount);
}

function aaveDepositUSDC(uint256 USDC_amount) public onlyByOwnGovCust {
    require(allow_aave, 'AAVE strategy is currently off');
    collateral_token.approve(address(aaveUSDC_Pool), USDC_amount);
    aaveUSDC_Pool.deposit(collateral_address, USDC_amount, address(this), 0);
}

function compoundMint_cUSDC(uint256 USDC_amount) public onlyByOwnGovCust {
    require(allow_compound, 'Compound strategy is currently off');
    collateral_token.approve(address(cUSDC), USDC_amount);
    cUSDC.mint(USDC_amount);
}

AMO的美元余額是其未分配的$USDC加上部署到3個協議的$USDC之和。這是前面提到的AMO礦工的syncDollarBalances的必要功能。

function dollarBalances() public view returns (uint256 frax_val_e18, uint256 collat_val_e18) {
    frax_val_e18 = (showAllocations()[4]).mul(10 ** missing_decimals);
    collat_val_e18 = frax_val_e18;
}

function showAllocations() public view returns (uint256[5] memory allocations) {
    // All numbers given are assuming xyzUSDC, etc. is converted back to actual USDC
    allocations[0] = collateral_token.balanceOf(address(this)); // Unallocated
    allocations[1] = (yUSDC_V2.balanceOf(address(this))).mul(yUSDC_V2.pricePerShare()).div(1e6); // yearn
    allocations[2] = aaveUSDC_Token.balanceOf(address(this)); // AAVE
    allocations[3] = (cUSDC.balanceOf(address(this)).mul(cUSDC.exchangeRateStored()).div(1e18)); // Compound. Note that cUSDC is E8

    uint256 sum_tally = 0;
    for (uint i = 0; i < 4; i++){ 
        if (allocations[i] > 0){
            sum_tally = sum_tally.add(allocations[i]);
        }
    }

    allocations[4] = sum_tally; // Total USDC Value
}

同時,AMO不僅在賺取$USDC的收益。它還在賺取COMP和AAVE! 你可以調用showRewards來查看AMO賺了多少錢,並通過調用各種索賠獎勵函數來收集其獎勵。

// 1. show rewards
// 2. claim rewards
// 3. transfer rewards out of the contract
function showRewards() external view returns (uint256[3] memory rewards) {
    // IMPORTANT
    // Should ONLY be used externally, because it may fail if COMP.balanceOf() fails
    rewards[0] = COMP.balanceOf(address(this)); // COMP
    rewards[1] = stkAAVE.balanceOf(address(this)); // stkAAVE
    rewards[2] = AAVE.balanceOf(address(this)); // AAVE
}

function compoundCollectCOMP() public onlyByOwnGovCust {
    address[] memory cTokens = new address[](1);
    cTokens[0] = address(cUSDC);
    CompController.claimComp(address(this), cTokens);
}

function aaveCollect_stkAAVE() public onlyByOwnGovCust {
    address[] memory the_assets = new address[](1);
    the_assets[0] = address(aaveUSDC_Token);
    uint256 rewards_balance = AAVEIncentivesController.getRewardsBalance(the_assets, address(this));
    AAVEIncentivesController.claimRewards(the_assets, rewards_balance, address(this));
}

function withdrawRewards() public onlyByOwnGovCust {
    COMP.transfer(msg.sender, COMP.balanceOf(address(this)));
    stkAAVE.transfer(msg.sender, stkAAVE.balanceOf(address(this)));
    AAVE.transfer(msg.sender, AAVE.balanceOf(address(this)));
}

當收益率不再令人滿意時,特權賬戶可以把錢取出來,並把抵押品還給協議。 receiveCollatFromAMO從AMO中取出$USDC,減少AMO的借款余額,並同步所有池的$FRAX和抵押品余額。

// 1. withdraw USDC from protocols
// 2. send collateral back through the AMO minter
function yWithdrawUSDC(uint256 yUSDC_amount) public onlyByOwnGovCust {
    yUSDC_V2.withdraw(yUSDC_amount);
}

function aaveWithdrawUSDC(uint256 aUSDC_amount) public onlyByOwnGovCust {
    aaveUSDC_Pool.withdraw(collateral_address, aUSDC_amount, address(this));
}

function compoundRedeem_cUSDC(uint256 cUSDC_amount) public onlyByOwnGovCust {
    // NOTE that cUSDC is E8, NOT E6
    cUSDC.redeem(cUSDC_amount);
}

function giveCollatBack(uint256 collat_amount) external onlyByOwnGovCust {
    collateral_token.approve(address(amo_minter), collat_amount);
    amo_minter.receiveCollatFromAMO(collat_amount);
}

// in FraxAMOMinter...
function receiveCollatFromAMO(uint256 usdc_amount) external validAMO(msg.sender) {
    int256 collat_amt_i256 = int256(usdc_amount);

    // Give back first
    TransferHelper.safeTransferFrom(collateral_address, msg.sender, address(pool), usdc_amount);

    // Then update the balances
    collat_borrowed_balances[msg.sender] -= collat_amt_i256;
    collat_borrowed_sum -= collat_amt_i256;

    // Sync
    syncDollarBalances();
}

8. 將FRAX直接鑄造成AMOs

這一功能使FRAX協議與其他算法穩定幣不同。例如,Frax的Curve AMO模塊允許$FRAX在$USDC之外直接將$FRAX鑄入Curve的元池以收緊锚定,同時清楚地知道如果$FRAX打破其锚定,它可以獲得多少抵押品。Curve‘s AMO可以調用AMO礦工的函數mintFraxForAMO,然後調用自己的函數metapoolDeposit來向Curve註入流動性。只要目的地AMO的鑄幣上限和新的全球抵押品比率不被突破,AMO礦工就可以自由鑄幣。

function mintFraxForAMO(address destination_amo, uint256 frax_amount) external onlyByOwnGov validAMO(destination_amo) {
    int256 frax_amt_i256 = int256(frax_amount);

    // Make sure you aren't minting more than the mint cap
    require((frax_mint_sum + frax_amt_i256) <= frax_mint_cap, "Mint cap reached");
    frax_mint_balances[destination_amo] += frax_amt_i256;
    frax_mint_sum += frax_amt_i256;

    // Make sure the FRAX minting wouldn't push the CR down too much
    // This is also a sanity check for the int256 math
    uint256 current_collateral_E18 = FRAX.globalCollateralValue();
    uint256 cur_frax_supply = FRAX.totalSupply();
    uint256 new_frax_supply = cur_frax_supply + frax_amount;
    uint256 new_cr = (current_collateral_E18 * PRICE_PRECISION) / new_frax_supply;
    require(new_cr >= min_cr, "CR would be too low");

    // Mint the FRAX to the AMO
    FRAX.pool_mint(destination_amo, frax_amount);

    // Sync
    syncDollarBalances();
}
// 1. Deposit USDC to 3pool
// 2. Deposit 3CRV and FRAX to meta pool
function metapoolDeposit(uint256 _frax_amount, uint256 _collateral_amount) external onlyByOwnGov returns (uint256 metapool_LP_received) {
    uint256 threeCRV_received = 0;
    if (_collateral_amount > 0) {
        // Approve the collateral to be added to 3pool
        collateral_token.approve(address(three_pool), _collateral_amount);

        // Convert collateral into 3pool
        uint256[3] memory three_pool_collaterals;
        three_pool_collaterals[1] = _collateral_amount;
        {
            uint256 min_3pool_out = (_collateral_amount * (10 ** missing_decimals)).mul(liq_slippage_3crv).div(PRICE_PRECISION);
            three_pool.add_liquidity(three_pool_collaterals, min_3pool_out);
        }

        // Approve the 3pool for the metapool
        threeCRV_received = three_pool_erc20.balanceOf(address(this));

        // WEIRD ISSUE: NEED TO DO three_pool_erc20.approve(address(three_pool), 0); first before every time
        // May be related to https://github.com/vyperlang/vyper/blob/3e1ff1eb327e9017c5758e24db4bdf66bbfae371/examples/tokens/ERC20.vy#L85
        three_pool_erc20.approve(frax3crv_metapool_address, 0);
        three_pool_erc20.approve(frax3crv_metapool_address, threeCRV_received);
    }
    
    // Approve the FRAX for the metapool
    FRAX.approve(frax3crv_metapool_address, _frax_amount);

    {
        // Add the FRAX and the collateral to the metapool
        uint256 min_lp_out = (_frax_amount.add(threeCRV_received)).mul(slippage_metapool).div(PRICE_PRECISION);
        metapool_LP_received = frax3crv_metapool.add_liquidity([_frax_amount, threeCRV_received], min_lp_out);
    }

    return metapool_LP_received;
}

當治理者決定是時候移除流動性($FRAX或$USDC或兩者)時,它可以調用函數metapoolWithdrawFrax/metapoolWithdraw3pool/metapoolWithdrawAtCurRatio 來從metapool中提取$FRAX,從3pool中提取$USDC。它可以選擇在同一交易中燒掉提取的$FRAX(也可以單獨調用burnFrax)。AMO礦工燒掉從AMO提取的$FRAX,並減少AMO的$FRAX鑄幣廠中余額。

function burnFraxFromAMO(uint256 frax_amount) external validAMO(msg.sender) {
    int256 frax_amt_i256 = int256(frax_amount);

    // Burn first
    FRAX.pool_burn_from(msg.sender, frax_amount);

    // Then update the balances
    frax_mint_balances[msg.sender] -= frax_amt_i256;
    frax_mint_sum -= frax_amt_i256;

    // Sync
    syncDollarBalances();
}

總結:Let A Thousand Coins Shine

$Frax不是市場上唯一的算法穩定。它也不會是唯一的一個。 歸根結底,穩定幣是滿足特定工作任務(JTBD)的產品。你是一個尋求杠桿化的投機者嗎?超額抵押是適合你的。到那裏去買$DAI和$MIM。你是一個管理1000萬美元以上國債的DAO嗎?到那裏去找$FEI和$FRAX。你是一個正在尋找低風險加密貨幣風險的機構投資者嗎?去那裏買$UST和$UXD吧。每個人都有自己的看法。

每個穩定幣都有明顯不同的風險狀況和使用情況。即便如此,我們對$FRAX仍將是未來DeFi協議的一個基準表示樂觀,因為它有3個殺手鐧:類似Curve的属性,錨定機製和AMOs。

原文鏈接:

https://mirror.xyz/jackchong.eth/iB_teRKgBaKm4OTKFmjf8hFAM55C1_i0Z2kf-KkYz2I