Liquity 协议 — 稳定池、清算与赎回(第二部分)

  • zealynx
  • 发布于 2023-05-30 18:39
  • 阅读 72

本文深入分析了 Liquity 协议的核心机制,重点介绍了稳定池、清算和赎回的实现原理。通过对多个核心 Solidity 合约代码的解析,详细展示了协议如何处理债务抵消、抵押品分配以及在正常和恢复模式下的系统安全性维护。

前一篇文章中,我开始从智能合约的角度解释 Liquity 协议,其中我介绍了它的原生 Token 和它的 BorrowerOperations.sol 等内容。

在本文中,我们将继续深入探讨该协议中三个最重要且最有趣的部分:

Stability Pool

Stability Pool(稳定池),顾名思义,确保了协议保持稳定并能以最高效率运行。

当任何 Trove 被清算时,将从 Stability Pool 的余额中销毁与该 Trove 剩余债务相对应的 LUSD 数量,以偿还其债务。作为交换,来自该 Trove 的全部抵押品将转移到 Stability Pool 中。

从池中移除必要数量的 LUSD 以抵消债务,然后将锁定的以太坊按比例分配给池中的质押者。这些被称为“清算收益”。

StabilityPool.sol

包含 Stability Pool 操作的功能:进行存款,以及提取复利存款、累积的 ETH 和 LQTY 收益。

持有 LUSD Stability Pool 存款和清算中为存款人准备的 ETH 收益。

StabilityPool 的一些主要函数包括:

  • provideToSP(uint _amount, address _frontEndTag) — 允许稳定币持有者向 Stability Pool 存入 _amount 数量的 LUSD。
  • withdrawFromSP(uint _amount) — 允许稳定币持有者从 Stability Pool 中提取 _amount 数量的 LUSD,最高可达其剩余稳定池存款的价值。
  • withdrawETHGainToTrove(address _hint) — 将用户全部累积的 ETH 收益发送到用户活跃的 Trove,并根据债务吸收产生的累积损失更新其稳定池存款。
  • registerFrontEnd(uint _kickbackRate) — 将一个地址注册为前端,并在 [0,1] 范围内设置其选择的回扣率。

与 StabilityPool.sol 一起,这些合约为系统各自的部分持有以太币和/或 Token,并包含极少的逻辑:

ActivePool.sol

持有活跃 Trove 的总以太币余额并记录总稳定币债务。

1function sendETH(address _account, uint _amount) external override {
2    _requireCallerIsBOorTroveMorSP();
3    ETH = ETH.sub(_amount);
4    emit ActivePoolETHBalanceUpdated(ETH);
5    emit EtherSent(_account, _amount);
6

7    (bool success, ) = _account.call{ value: _amount }("");
8    require(success, "ActivePool: sending ETH failed");
9}
10

11function increaseLUSDDebt(uint _amount) external override {
12    _requireCallerIsBOorTroveM();
13    LUSDDebt = LUSDDebt.add(_amount);
14    ActivePoolLUSDDebtUpdated(LUSDDebt);
15}
16

17function decreaseLUSDDebt(uint _amount) external override {
18    _requireCallerIsBOorTroveMorSP();
19    LUSDDebt = LUSDDebt.sub(_amount);
20    ActivePoolLUSDDebtUpdated(LUSDDebt);
21}

DefaultPool.sol

持有已清算 Trove 的总以太币余额,并记录待重新分配给活跃 Trove 的总稳定币债务。

如果一个 Trove 在 DefaultPool 中有待处理的以太币/债务“奖励”,那么在它下一次进行借款人操作、赎回或清算时,这些奖励将被应用到该 Trove。

1function sendETHToActivePool(uint _amount) external override {
2    _requireCallerIsTroveManager();
3    address activePool = activePoolAddress; // 缓存以节省一个 SLOAD
4    ETH = ETH.sub(_amount);
5    emit DefaultPoolETHBalanceUpdated(ETH);
6    emit EtherSent(activePool, _amount);
7

8    (bool success, ) = activePool.call{ value: _amount }("");
9    require(success, "DefaultPool: sending ETH failed");
10}

CollSurplusPool.sol

持有来自已完全赎回的 Trove 以及在恢复模式(Recovery Mode)下被清算且 ICR > MCR 的 Trove 的 ETH 盈余。当受到 BorrowerOperations.sol 的指示时,将盈余发送回所属的借款人。

1function accountSurplus(address _account, uint _amount) external override {
2    _requireCallerIsTroveManager();
3

4    uint newAmount = balances[_account].add(_amount);
5    balances[_account] = newAmount;
6

7    emit CollBalanceUpdated(_account, newAmount);
8}
9

10function claimColl(address _account) external override {
11    _requireCallerIsBorrowerOperations();
12    uint claimableColl = balances[_account];
13    require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");
14

15    balances[_account] = 0;
16    emit CollBalanceUpdated(_account, 0);
17

18    ETH = ETH.sub(claimableColl);
19    emit EtherSent(_account, claimableColl);
20

21    (bool success, ) = _account.call{ value: claimableColl }("");
22    require(success, "CollSurplusPool: sending ETH failed");
23}

GasPool.sol

持有总 LUSD 清算准备金。当 Trove 被开启时,LUSD 被移入 GasPool;当 Trove 被清算或关闭时,LUSD 被移出。


Liquidations

Liquity 的清算机制包括用 Stability Pool 抵消清算债务,如果池中没有足够的 LUSD,则将剩余债务按抵押品数量排序重新分配给所有活跃的 Trove。

清算仅在两种情况下发生:

  • 当 Trove 的抵押率低于 110% 的最低抵押率时
  • 如果系统处于恢复模式(Recovery Mode)

被清算的 Trove 的所有者被允许保留他们借出的 LUSD,但仍会损失资金,因为他们将无法取回抵押品(抵押品的价值总是高于贷款价格)。

谁可以清算 Trove?

一旦 Trove 低于 110% 的最低抵押率(Minimum Collateral Ratio),任何人都可以清算它。发起人会收到 Gas 补偿(200 LUSD + Trove 抵押品的 0.5%)作为此项服务的奖励。

正常模式(Normal Mode)

Liquity 有两种不同的清算模式。在正常模式中:

1function _liquidateNormalMode(
2    IActivePool _activePool,
3    IDefaultPool _defaultPool,
4    address _borrower,
5    uint _LUSDInStabPool
6) internal returns (LiquidationValues memory singleLiquidation)
7{
8    [...]
9    (singleLiquidation.entireTroveDebt,
10     singleLiquidation.entireTroveColl,
11     vars.pendingDebtReward,
12     vars.pendingCollReward) = getEntireDebtAndColl(_borrower);
13

14    _movePendingTroveRewardsToActivePool(
15        _activePool, _defaultPool,
16        vars.pendingDebtReward, vars.pendingCollReward
17    );
18    _removeStake(_borrower);
19

20    [...]
21    (singleLiquidation.debtToOffset,
22     singleLiquidation.collToSendToSP,
23     singleLiquidation.debtToRedistribute,
24     singleLiquidation.collToRedistribute) =
25        _getOffsetAndRedistributionVals(
26            singleLiquidation.entireTroveDebt,
27            collToLiquidate,
28            _LUSDInStabPool
29        );
30

31    _closeTrove(_borrower, Status.closedByLiquidation);
32

33    [...]
34}

恢复模式(Recovery Mode)

如果 Liquity 系统的总抵押率 (TCR) 低于关键系统抵押率 (CCR, 150%),则会触发恢复模式。在恢复模式期间,ICR 低于 TCR 的 Trove 也可以被清算,为系统提供额外的保护。


Redemptions

Redemption(赎回)是以面值将 LUSD 兑换为 ETH 的过程,就好像 1 LUSD 的价值恰好等于 $1。也就是说,通过 x LUSD,你可以换回价值 x 美元的 ETH。

用户可以随时无限制地将其 LUSD 赎回为 ETH。但是,可能会对赎回金额收取赎回费。

什么是 LUSD 赎回?

LUSD 赎回是 Liquity 最独特的功能之一。简单来说:赎回机制使 LUSD 持有者能够随时按面值将 LUSD 赎回为底层的 ETH 抵押品。

TroveManager.sol — 赎回逻辑

赎回的逻辑位于 TroveManager.sol 中,它开始搜索第一个 ICR 大于或等于 MCR (110%) 的 Trove。

1function redeemCollateral(
2    uint _LUSDamount,
3    address _firstRedemptionHint,
4    address _upperPartialRedemptionHint,
5    address _lowerPartialRedemptionHint,
6    uint _partialRedemptionHintNICR,
7    uint _maxIterations,
8    uint _maxFeePercentage
9)
10    external
11    override
12{
13    [...]
14

15    if (_isValidFirstRedemptionHint(
16            contractsCache.sortedTroves,
17            _firstRedemptionHint,
18            totals.price))
19    {
20        currentBorrower = _firstRedemptionHint;
21    } else {
22        currentBorrower = contractsCache.sortedTroves.getLast();
23        // 查找第一个 ICR >= MCR 的 Trove
24        while (currentBorrower != address(0)
25            && getCurrentICR(currentBorrower, totals.price) < MCR)
26        {
27            currentBorrower = contractsCache.sortedTroves.getPrev(
28                currentBorrower
29            );
30        }
31    }
32

33    [...]
34}

赎回机制按照 ICR 从低到高的顺序遍历排序后的 Trove 列表,从每个 Trove 进行赎回,直到覆盖全部 LUSD 金额。这种设计自然地首先针对风险最高的头寸,从而加强了系统的健康。修改这种遍历逻辑的 Liquity 分叉——例如通过跳过某些 Trove 或以不同方式限制迭代——经常会引发关于部分赎回的边界情况。有关 DeFi 借贷协议中相关的漏洞模式,请参阅我们关于 Oracle 操纵重入攻击的指南。


联系我们

在 Zealynx,我们专注于 DeFi 协议安全审计——从像 Liquity 这样的借贷系统到复杂的稳定币机制和清算逻辑。无论你是在构建基于 CDP 的协议还是分叉现有的协议,我们的团队都随时准备帮助你交付安全的代码。在聘请审计师之前,请查看我们的审计前清单以确保你的代码库已准备就绪。联系我们开始对话。

想通过更多像这样的深入分析保持领先吗?订阅我们的时事通讯,确保你不会错过未来的见解。

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

0 条评论

请先 登录 后评论
zealynx
zealynx
江湖只有他的大名,没有他的介绍。