深入探讨DeFi借贷清算机制及其实现原理、风险与用例
原文标题:《DeFi Lending Concepts Part 2: Liquidations》
原文作者:Tal
原文编译:Kxp,BlockBeats
这篇文章是三篇系列文章中的第二篇,讨论 DeFi 借贷协议的工作原理——它们的关键组成部分、公式和用例。在我们之前的文章中,我们回顾了 DeFi 的核心操作——借贷,以及不同协议如何选择实现这些操作(还记得「份额 Token」吗?)。在这篇文章中,我们将重点介绍我们认为最令人兴奋的 DeFi 借贷概念之一:清算。
过度抵押和坏账
你可能会从我们之前的博客文章中想起,协议用户只能对其提供给协议的抵押品的百分比进行资产借贷。这是有道理的,因为协议需要确保如果你无法偿还债务,它可以从你那里收回其资产(或任何其他价值相等的资产)。这种资产抵押的过程始于传统金融,例如今天,一个人可以把他们的房子或兰博基尼作为贷款偿还的抵押品。
抵押依赖于抵押品价格保持其价值的前提条件——尽管房屋或兰博基尼的价格无法保证,但它们各自的价值相对不太不稳定,而比起 ERC 20 或 NFT 等资产来说,更为稳定。
在大多数 DeFi 借贷协议中,你的抵押贷款资产必须比你贷款的价值更有价值,也就是所谓的过度抵押。
如果借贷协议想要保持财务稳定,只允许过度抵押的贷款是有利的。想象一下,你提供了一些资产作为抵押品,而这些资产的价值突然低于你从协议中借到的资产价值。现在,你的抵押品价值低于你欠协议的债务,你就没有还款的动力。毕竟,在偿还贷款的过程中,你将获救的抵押品现在的价值低于实际偿还贷款所需的金额。这种贷款现在已经无力偿还。
每笔无力偿还的贷款对其所在的协议都是有害的。从无力偿还的贷款中产生的债务会在协议中产生不安全因素,毕竟,债务的数量是借出者无法从协议中收回的资产的数量。为了强调这些坏账有多糟糕:如果在协议上出现了类似传统金融「银行挤兑」的情况,最后一批从协议中提取他们的资产的用户将无法这样做。
当然,那些有大量坏账的协议对用户的吸引力较小。
清算和清算阈值
我们已经确定,当贷款抵押品价值低于借款人带利息的债务价值时(也称为低于抵押),借款人的债务会对其所在的借贷协议的健康状况构成威胁。为了防止低于抵押的持仓增加,协议允许第三方(不一定是协议的用户)偿还低于抵押(或接近低于抵押)的债务。通过偿还低于抵押的债务,这些被称为清算人的第三方有权以折扣的价格要求归还其被覆盖的债务人的抵押品。这个过程被称为清算。
你可能会想知道:为什么协议要依赖第三方来清算不健康的持仓?毕竟,协议可以将自动清算机制编码进其代码中。
发送清算交易的成本非常高。如果协议自动发送这些昂贵的交易,resulting gas costs 将会增加其运营成本,从而削弱其利润。
此外,自动清算系统的设计非常困难。协议不仅必须考虑是否应自动清算一个持仓,而且必须考虑何时这样做,并以反映市场波动率的速率进行清算。通过激励专门的第三方来清算这些持仓,这个过程要简单得多。
清算本质上不是有利可图的——对于这个过程来说,债务人的抵押品必须价值高于他们欠债的金额。如果清算人没有保证这个过程会有利可图,他们不会清算一个持仓。
那么何时一个持仓才能被清算?这个条件由协议决定,是分配给每个资产的清算阈值的函数。
在清算阈值方面,时间非常重要。正如我们所知道的那样,如果一个持仓的债务价值超过其抵押价值,清算这些持仓对清算人来说是不赚钱的,协议也会面临坏账。因此,安全的清算阈值为清算人提供足够的时间,在持仓达到无法偿还的状态之前清算它们。
现在我们理解了每个参与方保持持仓健康的动机,我们将展示协议实际如何实现这些机制:
Compound:账户流动性
Compound 涉及到一个名为 AccountLiquidity 的参数,计算 Compound 主合约 Comptroller 中的 Liquidation Threshold。
Comptroller 有一个名为 getAccountLiquidity() 的函数,返回有关账户流动性的信息。在内部,此函数调用 getHypotheticalAccountLiquidityInternal():
我们在这里看到,该函数的主逻辑被限定在一个 for 循环范围内。这表明计算账户流动性是通过迭代所有市场完成的,其中账户参与。换句话说,在计算账户流动性时,考虑到了用户借贷或作为抵押品的所有资产。
从我们之前的博客文章中回想一下,cTokenBalance 是用户为抵押而提供的基础资产数量。在这个例子中,我们还可以看到 borrowBalance 和一些神秘的 exchangeRateMantissa,它们都从 getAccountSnapshot() 返回。
在我们之前的博客文章中讨论的一般化 exchangeRate 变量中,我们写道:
「一个任意的利率可以增加铸造的 Token 数量,如果 exchangeRate < 1 ,则可以减少 Token 数量,如果 exchangeRate > 1 ,则可以增加 Token 数量。」
这也适用于 exchangeRateMantissa,它表示 cToken 与基础资产之间的汇率。
正如我们在这个例子中看到的,Comptroller 在获取了上面提到的三个参数之后,将首先获取当前正在迭代的特定市场的 collateralFactor。这个 collateralFactor 信息是指用户可以根据其抵押品借多少钱的指标。从这个定义中,我们可以假设每个抵押品的存款可以抵押不同的借款金额。
之所以这个金额在不同资产之间有所不同,主要是因为每个资产在协议眼中都有自己的「风险」,通常是指资产价值随时间波动的程度。
Compound 的治理根据市场状况改变抵押因素,但在任何时候,他们的抵押因素不能超过 0.9 ——最多可以借出你存入的抵押品的 90 %:
然后,我们看到调用 oracle.getUnderlyingPrice(asset),它调用一个名为 Oracle 的外部合约。
Oracle 是一种有趣的机制,值得一篇专门的博客文章(敬请期待)。为了简洁起见,我们现在所解释的是,Oracle 是用于在借贷协议中获取某个资产价格的合约,价格通常以协议使用的某种公共货币(通常是 USD、ETH 或协议使用的稳定币)为基础。
现在,我们已经涵盖了影响单个市场健康状况的所有因素,因此我们将写下计算单个市场 AccountLiquidity 的方程式:
注意:在 Compound 中,资产的价格以美元(USD)计价。
这是一个相当长的变量列表,但如果你试着记住我们的「份额 Token」文章中的 Compound 部分,你会发现以下表达式:
简单表示了用户 cToken 的基础资产价值。
此外,borrowBalance_{user} 变量,如你在这里所见,是用户借用的资产总余额,包括其中应计利息。
现在,我们已经到达了以下备选 AccountLiquidity 方程式的点:
Maker
另一个设置清算不足抵押头寸阈值的协议是 Maker。
让我们检查该协议部署用于处理清算的两个合约:
· Dog:在迁移到 liquidations 2.0 (Maker 治理称之为)之后部署的。此处的清算函数为 bark()。
· Cat:liquidations 1.2 ,bite()。
· grab():VAT 合约,用作在部署猫合约之前进行清算的方法。
让我们看一下 bite() 中的片段:
以及从 bark() 中的类似片段:
你可能会注意到两者具有相同的 not-unsafe 消息。因此,对于每个清算函数,Vault 的安全要求都相同,并且可以用以下等式表示:
我们可以使用这个等式来定义一个不等式,以便 Vault(Maker 用于头寸的名称)仍然是安全的:
优化一下:
我们建议我们的读者前往 MakerDAO 术语表,扩展我们提供的有关 Maker 生态系统中不同变量名称和术语的信息。
或者,你可以相信我们在此概述的内容:
• spot_{ilk} 在这个不等式中用作抵押品的价格,以 DAI 计价,除以抵押品的清算比率(由治理决定)
• ink_{urn} 是头寸的抵押品余额
• rate_{ilk} 是特定抵押品类型的累计债务。当与 art_{urn} 相乘,这是一个头寸借入的标准化债务金额,我们可以得到以 DAI 计价的总债务
为了简化我们刚刚涵盖的内容,不使用 Maker 术语,我们将这样表示:
注意:Maker 决定将抵押品和债务的价值计价为 DAI——协议的稳定币。
AAVE V2——健康因子
AAVE V2 还定义了自己的阈值 HealthFactor。具有 H_{f} < 1 的健康因素值的用户可以被清算。
定义如下:
显然,当用户没有债务时,他们的头寸无法被清算,因此健康因子默认为 type(uint 256).max。
否则,健康因子被定义为:
当清算阈值由治理独立定义,目前由 Gauntlet 代表提供协议的所有风险参数,包括 LiquidationThresholds。
破产头寸分析
现在我们已经讨论了坏账的概念,接下来我们将提供一个真实世界的例子,以强调其重要性。
我们要讨论的头寸是 AAVE V2 上的以下账户:0x 227 cAa 7 eF 6 D 955 A 92 F 483 d B2B D 01172997 A 1 a 623 。
让我们通过在 AAVE V2 借贷协议上调用 getUserAccountData 函数来调查其当前情况:
现在让我们分解上面的内容,来看看这个头寸的情况有多糟糕:
· 总欠债 ETH: 17.83508595148699 ETH
· 总抵押 ETH: 0.013596360502551568 ETH
这就是我们需要了解的所有内容,这个头寸有麻烦了——抵押品的价值只是欠款的一小部分。
那么这个头寸是如何陷入困境的呢?
为了回答这个问题,我们可以查看该用户在 AAVE 上执行的最新操作:
看起来一切都很好,直到块 13514857 ,在该块中,用户从 AAVE 借出了一些资产。让我们看看他们做了什么:
债务人借了 700, 000 MANA,快速查看 MANA 的美元价格将揭示该价格为:
每个 MANA 单位 0.00032838 ETH。
通过简单的乘法,我们知道该用户通过以下方式增加了协议的债务: 0.00032838 * 700000 = 229.866 ETH
值得一提的是,在该块的 USD 价格是 4417.40 美元。
请注意上图中发生的存款操作,发生在借款几个小时后的块 13517657 。让我们看看市场上是否有什么事情动摇了我们用户的信心:
上面是发送到 AAVE V2 价格 Oracle 的 RPC 调用,以获取指定块中 1 个 MANA 单位的 wei 值。
如果我们使用这些数据转换上述价格,我们可以看到发生了什么:
0.00033625 * 700000 = 235.375 ETH
在短短几个小时内,债务增加了 5.5 ETH,价值 24000 美元。
由于我们知道这个头寸的故事结局,我们知道它在某个时候是可清算的,因此让我们检查是否有涉及该用户地址的 liquidationCall 调用:
一旦我们找到第一个清算事件,我们就可以了解为什么用户在借款后不久就存入资产:
在这里,我们可以看到第一次清算发生在块 13520838 。这次清算发生在用户存入资金之前(在存款交易的约 7 分钟之前)。
然后,在 13520838-13522070 块之间发生了一系列小的清算,这些清算最终价值相当高:
让我们检查清算人在这些块之间从用户处夺取的所有抵押资产类型:
我们可以看到只有 2 种资产,DAI(稳定币)和 ETH。
以及它们的数量:
~ 50 ETH
~ 387663 DAI
有人可能会问,为什么清算会分成这么小的块?
当像这样庞大的头寸被一次性清算时,市场会将这样大量的抵押品收购解释为这些资产类型的卖出信号。请记住:根据协议的清算奖励政策,以折扣购买清算中获得的资产。
一次大规模的清算会引发一系列清算,随着卖出压力的上升,其他市场参与者可能也会卖出其资产,导致资产价格进一步「崩盘」,进而导致协议中其他头寸的更多清算。
因此,协议通常限制单个清算可以夺取的资产数量。AAVE 版本的此限制,作为变量,如下所示:
正如我们所看到的,限制百分比为 50 %,这意味着只有头寸债务的一半被允许在一次清算中偿还。
清算人有动机将其清算拆分成较小的块。如果在清算时市场上没有足够的流动性来提供抵押品资产,那么将清算拆分成较小的块,清算人更有可能获得清算资产,并从他们的清算中获利。
此外,如果市场上没有足够的流动性来获取债务资产,则清算人可能需要花费很多费用来获得首先要偿还未充足抵押的用户的债务。
最后,想象一下试图清算大量某种 Token,而没有拥有那么多。如果你去 DEX 并尝试交换一些 WETH 或其他资产以获得这一 Token,你可能会遇到非常高的 Gas 费,这会使你的清算变得无利可图。
回到我们的例子,为了检查链中一系列清算之后的头寸参数,我们需要解析从 getUserAccountData 返回给我们的数据:
然后我们使用 cast 查询链:
最后解析输出:
在这里,我们看到清算对头寸的影响:几乎没有剩余的抵押品,精确到 0.6 ETH。但是债务呢?高达 45.26716296709878 ETH。
这个块的 MANA 价格是多少?
0.000862110734985458 ETH。
如果你还记得,我们的用户仅仅几个小时前以 0.00032838 ETH 的价格借了 MANA。这相当于开了一个股票的空头头寸,而这支股票的价格升了 2.65 倍。
这些清算人在价格下跌到无法获利的程度之前无法及时清算完整个头寸,我们留下了一个破产的头寸。
现在,我们可以意识到有效的流动性阈值在防止协议产生坏账方面的重要性。
总结
虽然我们不能确定是否有一个方程来定义头寸的流动性阈值,但我们肯定可以看到协议之间的相似之处:
· 所有协议都将其阈值定义为某种抵押品与债务的函数(无论是比率还是差异)。
· 所有协议都给治理留下了一些空间,以便根据市场条件的变化决定每种抵押品风险参数的价值,因为有些资产比其他资产更具有波动性。
· 所有协议都使用预言机以一种广泛接受的货币(例如 ETH,USD,DAI)对其抵押品和债务价格进行标价。
我们已经看到 Maker 和 AAVE 选择使用相同的方程来表示头寸的安全性: