现代DeFi 借贷协议 - Fluid + Vault 是如何构建的

  • mixbytes
  • 发布于 2024-06-16 18:52
  • 阅读 46

Fluid是一个现代多层协议,具有基础层和多个实施不同DeFi机制的二级层。本文详细介绍了Fluid的流动性层以及Vault协议的设计与实现,强调了其使用Uniswap V3类似的价格点,允许流动性操作和风险管理的创新方法。整体架构展示了流动性和借贷操作的高效性,具有独特的保护机制。文章逻辑清晰且内容丰富,适合对DeFi协议开发有深入兴趣的读者。

引言

Fluid是一个现代的多层次协议,底层负责主要功能,次层可以实现各种先进的DeFi机制。Fluid的关键特性在其文档中强调,能够利用来自多个源的流动性,保护协议中资金的突然移动,以及允许低清算处罚、高贷款价值比率和Gas效率的基础协议。

Fluid的基本“流动性层”为用户的抵押-债务位置提供重入保护、市场的速率限制,以及从预言机提供/借入的利率和财务模型管理。次级“协议层”允许基于流动性层实施多个协议。今天,项目的GitHub上有三个协议:

我们将主要集中于Vault协议(在代码中称为Vault),因为文章标题中包含“现代”一词,而第一个(“借贷”)协议是提供ERC-4626股份的“基础”借贷,提供所有基本借贷机制以及流动性层的速率限制特性。“stETH”似乎是一个专用的质押ETH协议,但最有趣的机制是在Vault协议中实现的。由于所有协议使用相同的基础层,我们无法完全避免对其他协议的引用,但将主要集中于Vault。

让我们开始吧。

高级设计

主要流动性层在 liquidity 部分中展示,由两个主要模块组成:

  • adminModule,负责治理行动、设置模型参数、汇率和代币配置
  • userModule,负责基本操作,伴随协议中供应/取款和借款/偿还的操作

底层协议,如Vault或借贷,在实现它们自己的逻辑后,使用流动性层的这些基本操作。例如,Vault利用NFT存储用户的抵押-债务位置,这些位置在价格刻度范围内分布(类似于Uniswap V3和CrvUSD),在所有外部Vault逻辑完成后,将基础操作应用于流动性层(例如 在这里在这里)。

这种设计看起来有点像Euler V2设计(在我们之前的 文章中描述),它在基础层提供一些类型的保护,但同时允许在许多不同的DeFi场景中“封装”这个基础逻辑。

让我们深入一下。

核心

治理和模型管理

首先,让我们关注 adminModule,其中包含治理功能。这些Fluid中的功能允许你:

  • 配置和 收集 协议收入
  • 设置 借入/供应利率,根据当前使用的模型
  • 设置 交换价格和不同代币的利率
  • 其他一些与我们的文章无关的功能

设置交换价格和利率的能力尤为重要。在Fluid中,这些通过_exchangePricesAndConfig[token_] 映射进行管理,每种代币的配置在 这里 中描述。值得注意的是,许多Fluid值,包括配置,保存为256位字段,因此Fluid状态的大小与其他协议相比非常紧凑。

主要操作

Fluid协议,包括Vault,在两个不同的层上运行。基本“流动性”层,位于 userModule 中,包含两个基本功能:_supplyOrWithdraw() 和 _borrowOrPayback()。这些功能与可以在Fluid协议中多个位置找到的通用operate()功能集成(例如 在这里(在借贷中), 在这里(在stETH中),或 在这里(在Vault中))。

这个operate()函数执行所有协议中标准的操作,管理流动性的两个方面:供应和借款。它的目的是多元化的:更新协议的储备,通过执行限制保护市场免受剧烈波动,并作为用户进行的任何操作的“安全包装”。例如,在_borrowOrPayback()函数内部,我们可以找到预先后计算的限制。这确保协议的储备的任何突然变化(可能由外部协议逻辑触发)都会被这些限制所控制。

operate()函数接受经过签名的 supplyAmount_ 和 borrowAmount_ 值,允许利用单个函数执行供应/取款和借款/还款操作。这两个金额以token_单位计量、操作一个单一代币。协议中的所有抵押-债务位置都按照“每代币”存储。每个位置保持使用当前代币的单位限制(无需任何转换)或转换为原始USD值(抵押和债务采用相同的计量单位-USD)。

当用户需要将代币转移到协议时,供应/偿还情况通过用户提供的 liquidityCallback() 处理(此处的重入保护非常重要)。

接下来-从 exchangePricesAndConfig 提取两价格:供应价格和借款价格 在这里

然后,工作流分为两个分支:一个 用于供应/取款, 另一个 用于借款/偿还。在这些操作之后,_totalAmounts[token_] 状态会被更新。操作一个单一代币会导致只更新该特定代币在协议中的余额。

随后,协议更新交换价格、利用率和比例。如果更改很大或最后一次更新被认为过时,则会 更新。有关利用率和价格更新的阈值加载在 这里。对于小的变化,仅更新 supplyExchangePrice 和 borrowExchangePrice( 在这里)。

在最后一步,必须的代币数量会被 发送到 用户。

供应和借款

在“供应/取款”分支的 _supplyOrWithdraw() 函数中,协议支持两种类型的供应:一种是累积利息的,另一种是非利息模型。

这两种供应由两个单独的 分支 进行更新。关键数据结构 _userSupplyData 将用户地址映射到每个供应代币使用的uint256值。该值包含所有相关用户供应信息,包括金额、标志和时间戳,打包为单个uint256值中的位区域。

如上所述,供应者无法提取所有的供应代币。当用户提供代币时,使用两个函数:calcWithdrawalLimitBeforeOperate() 和 calcWithdrawalLimitAfterOperate() 计算新的提取限制,限制用户的最终提取限制。

在 _borrowOrPayback() 函数中,涉及“借款/偿还”方面,计算用户的借贷限制两次:操作之前和之后。用户的借款数据包含在 _userBorrowData 结构体中,该结构体作为uint256值进行打包。

如你所见,流动性层的 userModule 中没有清算。这些可清算头寸的管理由底层协议处理。流动性层仅负责跟踪债务、供应和预言机价格。

现在,让我们更深入地探讨特定协议 - Vault。

Vault协议

Vault协议建立在Fluid流动性层之上。Vault实现了一种高效的清算机制,通过将单个头寸合并为组(刻度)来防止单个头寸的清算,大大降低了在波动市场条件下产生坏债的风险。此外,该协议还纳入了坏债吸收特性,确保清算者的清算不会无利可图。

Vault中用户的抵押/债务位置作为NFT存储,包含在 positionData 结构中,其中最重要的值是位置的 tick

Vault协议的关键理念受Uniswap V3集中流动性和价格刻度启发(你可以在 这里 阅读更多内容)。在Uniswap V3中,刻度响应两个代币之间的交换比率变化(价格)的变化。在Fluid的Vault中,刻度代表用户位置中的债务-抵押比率的变化。Vault中的刻度定义为:

其中 t 是刻度数

用户从某个特定的刻度开始,表示当前的抵押/债务比率,他们的位置“属于”该刻度。当抵押的美元价格上涨时,这就没问题,新用户简单地在更高的刻度上“落脚”。但是,当抵押的美元价格下跌时,位置的刻度比例变得“可清算”。它在Vault的 白皮书 中的图片中演示(强烈推荐阅读)。

抵押的价格下降,导致抵押/债务比率降低。具有更高抵押-债务比率的头寸面临清算,使用足够的资金将其移动到“更安全”的刻度。

Fluid中实施的另一个重要机制是“分支”。请查看图示:(来自白皮书):

分支是在抵押价格下降且清算变得必要的范围内发生的过程。当一个分支处于活动状态时,“属于”该分支的头寸可以被清算。分支可能与其他分支合并;例如,当达到基础水平时,分支2将合并到分支1中。这个过程在白皮书中得到了更好的描述,而我们的目标是回顾实现。因此,让我们看看 branchData 结构。最关键的元素是 minimaTick - 该分支的最小刻度,当达到时使该分支过时或合并 - 以及分支 debt。分支促进从不健康刻度中单调和顺序地消除债务。

对于所有操作,Vault协议使用一个单一的入口点: operate() 函数。虽然它与流动性层中的 operate() 函数同名,但其操作方式不同,通过与表示用户 position 的NFT进行交互。这一操作对于存款/取款和借款/偿还是“通用”的,因此,在操作期间的所有更改都会在特殊的操作结构 OperateMemoryVars 中被追踪,该结构累积关于用户位置的前后刻度的信息。由于Vault的清算在此过程中不直接访问单个用户的头寸数据,因此每当访问位置时,必须更新该数据。例如,如果一个位置的刻度 变为 可清算,则它必须在操作的 结束 时更新到“最近可用”的刻度。

在operate()函数中的供应和借款操作将新的 colRaw 和 debtRaw 字段写入操作变量( 在这里在这里),使用 borrowExPrice 和 supplyExPrice 计算位置债务/抵押品的原始值。下一步展示与刻度的工作的是 部分,在该部分中债务被添加到某个刻度,使用 _addDebtToTickWrite() 函数。采用比例选择刻度编号需要进行对数函数计算。此计算在 tickMath.sol 中使用预计算的值完成,类似于Uniswap V3。

将不同比例分配给不同刻度的机制使用“非精确”的代币数量,导致协议中出现灰尘现象。因此,在Fluid中,部分逻辑需要处理这些“灰尘”值。例如,上述 _addDebtToTickWrite() 函数还返回一个必须在用户头寸中 保存 的 rawDust_ 值,以保持所有值的同步。最终,灰尘在协议中 吸收,然后可以通过管理员功能 absorbDustDebt() 进行处理。

但当然,Vault协议评审中最有趣的部分是 liquidate() 函数,允许一次清算多个用户的头寸。首先,清算 更新 借入/供应价格,这代表了与基础流动性层的重要连接,为多个协议提供供应/借款的交换价格( 在这里)。

在一些预言机检查后(稍后会描述),Vault 吸收 被清算刻度以上的刻度的债务。此外,liquidate() 函数可以在设置了 absorb_ 标志的情况下清算协议中的坏债务。

清算的核心是 循环,该循环在每次迭代中 累积 债务和抵押品。每次迭代“属于”当前的清算分支,“跟踪”正在清算的刻度。refTick在 这里 确定,该值稍后用于向前移动清算刻度( 在这里在这里 考虑一些边缘情况。

对于已知比例,单一刻度内的最终债务/抵押计算 在这里。之后,当前刻度被推进(如上所示),然后更新当前分支的信息( 在这里在这里)。

在清算过程中没有访问任何用户的单独头寸!该代码仅操作刻度和分支。这是Vault协议的一个关键特征:该协议无缝重新平衡所有用户头寸的抵押/债务比率,而无需“干预”它们。

从算法的角度来看,这种机制在某种程度上类似于Uniswap V3费用机制,其中流动性提供和费用累积仅使用刻度的内部数据和全局累加器进行管理(在 文章 中描述),而没有直接与流动性提供者的头寸互动。然而,重要的是要区分这些协议。在Fluid中,刻度管理抵押/债务比率,而非价格,并且它们排除了全局费用累加器。刻度的机制也不同。

看起来将价格、比例、数量(我们尚未知晓的其他东西)范围细分成离散刻度的想法在DeFi中是有意义的,这可以避免与DeFi协议的“每个头寸”互动。

这种设计对所有用户(借款人/供应者和交易者/清算者)非常高效。清算无缝进行,适度限制价格和成交量的波动,通过清算“波浪”包裹用户头寸的全景。忠于其名字,该协议确实是“Fluid”的 :)

预言机

预言机始终是任何借贷协议中至关重要的一部分。在Fluid生态系统中,预言机在两个不同的层次上运作。基础流动性层为所有底层协议提供代币交换价格。这些协议(如Vault)随后基于它们独特的逻辑应用额外的保护层。

Fluid的预言机具有多种不同的 实现,为下一层提供访问 getExchangeRate() 函数的权限。这些实现可以与Uniswap V3 TWAP互动并施加 限制,或使用Chainlink/Redstone提供者并将其作为备用预言机。这样的实现可以像 UniV3CheckCLRSOracle.sol 一样组合,利用Uniswap V3 TWAP价格并与来自Chainlink/Redstone的价格进行比较。这种设计确保了高安全性,难以操控多个预言机源,尤其是增加了价格限制。同时,滞后和限制的价格预言机使该协议在高市场波动时期成为边际交易策略的有吸引力的目标,这在具体条件下可能是好事,也可能是坏事。

实现细节

Fluid的代码中充满了像这样的结构:

这本质上是从uint256中提取值,因为Fluid旨在有效地将多个值打包到256位插槽中。以单个值保存数据的方法看起来像 这样。此外,Fluid代码库积极重用相同的变量,像 这里,这虽然降低了可读性,但减少了Gas消耗。Fluid还主要使用“uint256”作为打包结构,几乎不使用结构体。虽然Solidity结构通常将值打包到一个存储插槽中,Fluid的方法可能是由于使用了许多非标准值大小,如19位、50位和1位(示例见 这里这里)。这种方法的优点在于(Gas效率),但同时要求严格控制非标准值的溢出。这增加了代码量并降低了可读性。

Fluid支持使用原生代币(如ETH)进行操作,这导致代码中出现多个这样的分支(见 这里)。

Fluid使用与Uniswap V3中使用的类似的数学方法,例如找到最重要的位 或使用预先计算的 对数 来根据给定比率确定刻度编号。值通常表示为 BigNumber,由指数和系数组成(可以在 此处 查找转换)。

Fluid协议及其Vault功能的代码中充满了许多有趣的实现细节。遗憾的是,由于文章空间有限,无法详细探讨每一项。

结论

Fluid是一个具有两层结构的项目。第一基本流动性层提供基础框架,存储债务和抵押值,以及协议的整体状态和预言机价格。它还包括像全球速率限制这样的保护措施,以保护市场免受突然移动的影响。第二层通过设计用于不同功能(如借贷、去中心化交易、保管和更多)的多个协议进行呈现。

我们审查了Vault协议,其主要理念是使用类似Uniswap V3的刻度,但应用于抵押/债务比率。这一创新设计把相同抵押/债务比率的用户头寸分组,使清算过程无缝进行,减少处罚成本。清算者作为交易者,获得了一种灵活的方法,将抵押品代币交换为借款的代币,朝向更健康状态移动用户的头寸。清算操作不直接与用户的头寸互动,而是影响整个刻度的非健康抵押/债务比率。这一效果通过“分支”实现,分支是积累器,用于跟踪在抵押价格下降时的清算流程,可以彼此合并,从而使过程更加“流畅”。

实现这些机制并不是简单的,我们对该协议的 审计 确实充满了挑战。然而,从许多角度来看,Fluid + Vault是一个真正独特的项目,它结合了各种有趣的想法,推动去中心化金融朝着越来越高效的设计模式发展。

让我们期待未来的创新想法…

谁是MixBytes?

MixBytes 是一个专注于为EVM兼容和基于Substrate的项目提供全面智能合约审计和技术咨询服务的区块链审计和安全研究专家团队。请加入我们,X 以获取最新的行业趋势和见解。

  • 原文链接: mixbytes.io/blog/modern-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
mixbytes
mixbytes
Empowering Web3 businesses to build hack-resistant projects.