不是把银行搬上链
第一次理解链上借贷时,很容易把它翻译成一个熟悉的故事:有人有闲钱,有人需要钱,平台把两边撮合起来,收一点利差。这种理解不能说错,但它漏掉了最重要的部分。传统金融里,一笔债成立之前,金融机构会做大量链下工作:认识借款人、理解收入、评估资产、签合同、约定违约后的追索顺序,并在风险真的发生时动用人、法务和时间去处置。
DeFi 不具备这些能力。合约不知道你是谁,也不知道你下个月会不会发工资,更不能去拍卖你链下的一套房。它唯一能确定的是:某个地址把多少资产放进了合约;这些资产此刻在预言机看来值多少钱;以及当一组事先写好的不等式不再成立时,是否允许另一个地址执行一段代码。
所以,加密金融中的债并不是信用债,而更接近一笔持续盯市、随时可能被平仓的抵押融资。用户表面上借到了 USDC,实质上是在把一份未来的价格波动风险卖给协议、流动性提供者和清算人。协议不判断“你是否会还”,它只判断“如果现在把你的抵押物卖掉,能否覆盖你的债”。
这也是为什么借贷、抵押和清算不能被拆成三个独立功能。借款只是入口;抵押是风险缓冲;清算才是系统在坏情形里仍然能够兑现债权的最后保证。一个协议的利率页面做得再漂亮,只要清算在剧烈波动时无法成交,这笔“债”就是没有真正结算能力的承诺。
债务的最小状态机
先把一个单抵押物、单借款资产的仓位写成最简单的状态。设:
C:抵押品数量;P:抵押品的预言机价格;D:当前债务,含已累计的利息;LT:liquidation threshold,清算阈值;CF:close factor,一次清算最多可偿还债务的比例;B:liquidation bonus,清算人获得抵押品的折价。
仓位的抵押品价值为:
collateralValue = C × P
很多产品展示的是 LTV:
LTV = D / collateralValue
但对清算而言,更有用的是健康度:
healthFactor = collateralValue × LT / D
当 healthFactor ≥ 1,仓位安全;当它小于 1,协议允许清算。这里有一个产品上经常被忽略的细节:LT 不是“可借上限”。例如 ETH 的最大 LTV 可以是 75%,清算阈值是 80%。前者控制用户开仓时最多能借多少,后者控制系统何时必须允许第三方介入。两者之间的 5% 不是装饰,它是给价格波动、预言机延迟、交易确认和清算竞争预留的操作空间。
假设 Alice 存入 10 ETH,ETH 的价格为 3,000 USDC,抵押品价值 30,000 USDC。她借出 20,000 USDC,LT = 80%。此时:
healthFactor = 30,000 × 80% / 20,000 = 1.2
如果 ETH 下跌到 2,400 USDC,抵押品价值变成 24,000 USDC:
healthFactor = 24,000 × 80% / 20,000 = 0.96
这不是“Alice 违约了”。她甚至还没有到期日。真正发生的是:系统发现若继续等待,价格再跌一点,抵押品就不足以把债务全数还给资金池。于是协议把处置权从仓位所有者的一部分,暂时交给清算人。
清算不是惩罚,而是结算基础设施
把清算理解成惩罚,会导致错误的产品设计。清算的目标不是让高杠杆用户付出代价,而是让资金池在最差路径下仍具备偿付能力。清算人不是慈善机构,他们要承担链上执行成本、价格继续下跌的风险、DEX 深度不足的风险,以及在同一笔仓位上被其他机器人抢跑的风险。因此协议必须提供激励。
假设 Alice 的仓位已经不健康。清算人偿还 5,000 USDC,协议给他价值 5,000 × (1 + B) 的 ETH。若 B = 5%,在 2,400 USDC 的预言机价格下,清算人获得:
seizedETH = 5,000 × 1.05 / 2,400 = 2.1875 ETH
清算人拿到的不是利润已经锁定的现金,而是 2.1875 ETH。他需要能快速卖掉这些 ETH,才能回收投入的 5,000 USDC。于是 5% bonus 既不能太低,低到没有人愿意在极端行情里执行;也不能太高,高到一次清算把用户从轻微不健康直接推向资不抵债。它是在“清算一定会发生”和“被清算不应被过度惩罚”之间找平衡。
真实风险比公式复杂得多。ETH 从 2,400 跌到 2,300 的数秒里,预言机可能尚未更新;清算机器人提交交易时,Gas 会突然飙升;机器人即使抢到仓位,也可能因市场深度不够,只能以更差的价格卖出 ETH。换言之,bonus 覆盖的是一段时间差中的不确定性,而不是某个静态手续费。
一个刻意简化的 Solidity 演示
下面的合约不是生产代码,也不能直接部署。它省略了 ERC-20 的安全转账、利息指数、精度边界、可升级治理、预言机的 staleness 检查和重入保护,目的是把债务状态机放在同一个视野里。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IPriceOracle {
/// @return priceUsdc ETH 的 USDC 价格,假设精度为 1e18
function ethPrice() external view returns (uint256 priceUsdc);
}
contract MinimalEthLending {
uint256 internal constant WAD = 1e18;
uint256 internal constant LIQUIDATION_THRESHOLD = 0.80e18;
uint256 internal constant LIQUIDATION_BONUS = 0.05e18;
uint256 internal constant CLOSE_FACTOR = 0.50e18;
IPriceOracle public immutable oracle;
mapping(address => uint256) public collateralEth;
mapping(address => uint256) public debtUsdc;
constructor(IPriceOracle _oracle) {
oracle = _oracle;
}
function deposit() external payable {
collateralEth[msg.sender] += msg.value;
}
function healthFactor(address user) public view returns (uint256) {
uint256 debt = debtUsdc[user];
if (debt == 0) return type(uint256).max;
uint256 collateralValue = collateralEth[user] * oracle.ethPrice() / WAD;
return collateralValue * LIQUIDATION_THRESHOLD / debt;
}
function borrow(uint256 amountUsdc) external {
debtUsdc[msg.sender] += amountUsdc;
require(healthFactor(msg.sender) >= WAD, "insufficient collateral");
// 生产环境:从资金池向 msg.sender 转出 USDC
}
function liquidate(address user, uint256 repayUsdc) external {
require(healthFactor(user) < WAD, "position is healthy");
uint256 maxRepay = debtUsdc[user] * CLOSE_FACTOR / WAD;
uint256 actualRepay = repayUsdc > maxRepay ? maxRepay : repayUsdc;
require(actualRepay > 0, "nothing to repay");
uint256 price = oracle.ethPrice();
uint256 seizeEth = actualRepay * (WAD + LIQUIDATION_BONUS) / price;
if (seizeEth > collateralEth[user]) seizeEth = collateralEth[user];
debtUsdc[user] -= actualRepay;
collateralEth[user] -= seizeEth;
payable(msg.sender).transfer(seizeEth);
// 生产环境:从清算人收取 actualRepay USDC,并归还给资金池
}
}
这段代码的核心不是 liquidate 里有几行算术,而是执行顺序。借款后必须立即检查健康度,否则用户可以先借走资产再留下一个已不健康仓位;清算前必须再次读取价格并检查仓位状态,否则任何人都能夺走健康用户的抵押品;清算额必须受 close factor 约束,否则同一笔轻微风险仓位会被一次性剥离全部抵押品。
更重要的是,生产版本不能把预言机看成一个永远正确的 view 函数。至少要检查价格是否为零、更新时间是否过旧、报价币种和债务币种是否一致。在某些资产上,还要考虑价格来源本身是否可以被低流动性市场操纵。预言机不是“拿一个价格回来”的组件,它是协议把现实市场风险翻译成链上状态转换的裁判。
利率不是收益率页面上的数字
借贷协议常把利率写成利用率 U 的函数:
U = totalBorrow / totalLiquidity
在低利用率时,借款利率平缓;在某个 kink 之后,利率陡增。它的作用不是单纯奖励存款人,而是当池子接近被借空时,给借款人一个强烈信号:要么还款,要么为继续占用稀缺流动性付出更高成本。与此同时,高存款利率会吸引新的流动性进入。
从产品角度,利率模型是在为“清算是否有出口”服务。倘若所有可用 USDC 都被借走,即使抵押物价格跌了,清算人也可能没有资金来偿还债务,或者协议没有足够的稳定币把剩余抵押物卖给市场。利用率、储备金、借款上限和抵押品上限,最终都在回答同一个问题:坏情形来临时,债权人能不能及时拿回钱。
抵押率看起来保守,为什么仍然会坏账
“超额抵押”这四个字很容易让人误以为协议天然安全。用户借 1 美元,却先存进 1.3 美元、1.5 美元甚至 2 美元的资产,直觉上已经留了足够多的垫子。但抵押率只是某一个时点、某一条报价、某一种出售假设下的比率。它并不保证协议能真的按这个价格卖掉抵押物,更不保证卖出发生在价格继续下跌之前。
坏账出现得很像一条因果链,而不是某一个参数突然填错。
第一步,是抵押物价格下跌,健康度接近 1。第二步,预言机把下跌写入链上,仓位变得可清算。第三步,清算人评估收益:拿到的 bonus 是否能覆盖 Gas、滑点、竞争和方向风险。第四步,清算交易进入区块,但真实市场的可成交价格已经低于预言机价格,或者稳定币流动性已被别的清算消耗。第五步,仓位抵押物被卖完,债务却没有被全部偿还。到这里,协议才真正拥有坏账。
这条链里最危险的误区是把预言机价格等同于可实现价格。预言机回答的是“一个设计好的价格源在某个更新频率下认为资产值多少”;清算人面对的是“我此刻能以什么价格、在多长时间内、卖出多大数量”。两者在平静市场里会很接近,在极端市场里却会迅速分叉。
例如,某个长尾资产被允许作为抵押品,Oracle 给它的价格是 10 USDC,协议据此允许用户借 70%。用户存入 100,000 枚,借走 700,000 USDC。若现货市场真正能买走的深度只有 100,000 USDC,剩下的大额卖单每卖一部分就把价格砸低一截,那么“价值 1,000,000 USDC 的抵押品”只是账面价值。协议并没有得到一百万美元可立即变现的担保,它只得到了一种在小额交易下看起来值一百万的资产。
因此,风险参数至少应该同时看四件事:价格波动率、预言机质量、链上和链下可实现流动性、以及抵押品与借款资产在危机中的相关性。只看历史价格波动而不看深度,会把“没有人卖过这么多”误当成“可以卖这么多”;只看深度而不看预言机,会把一段暂时被做市维持的价格当成可靠清算价格;只看单资产而不看相关性,则会在所有抵押品同时下跌时发现所谓分散其实不存在。
相关性会在最需要它不存在的时候出现
用户常把多抵押品仓位理解成分散:我不仅抵押 ETH,也抵押 LSD、治理代币、LP token;即使一种跌了,其他资产总会托住仓位。但在加密市场的压力时刻,很多资产共享同一种风险因子:ETH 下跌、风险偏好收缩、杠杆平仓、稳定币需求上升。那些平时相关性不高的资产,会在去杠杆时一起跌,而且越是流动性差、估值依赖叙事的资产,跌得越快。
以 stETH 为例。它在正常时期可以被视为带收益的 ETH 替代物,但它不是 ETH。协议若把 stETH 与 ETH 放进高效率模式,允许用户以更高 LTV 互相借贷,隐含前提是两者价格偏离有限且存在退出流动性。这个前提在常态下合理,在赎回不畅、流动性池失衡或市场恐慌时则会失效。产品所说的“同类资产风险更低”,实际意思不是风险消失,而是协议把风险集中押注在锚定关系不会急剧破裂。
这解释了两个常见设计:隔离模式和 e-mode。隔离模式不是在 UI 里把资产分个组,它是在承认某些资产的尾部风险无法与主资金池共担。它限制可借资产、债务上限和抵押品上限,防止一个长尾资产的价格操纵把整个协议的稳定币储备拖进去。e-mode 则反过来,是协议对某一组高度相关资产开更高资本效率的白名单。它不是“更高级的杠杆按钮”,而是一份带条件的风险交换:你能借得更多,但当锚定关系出现问题时,可用缓冲也更薄。
一个成熟的产品界面不应该只把 e-mode 写成“最高可借额度提升”。它至少需要告诉用户:你进入的是哪一种相关性假设;仓位对哪个价格偏离最敏感;在什么偏离幅度下会进入清算;退出模式后,健康度会如何变化。风险不是藏在 tooltip 里的法律文字,它应该成为用户决定是否开仓的主信息。
预言机不是价格 API
在 Web2 产品里,调用价格接口通常意味着从服务端拿到一个数字;调用失败时,页面显示加载中,稍后重试即可。链上预言机完全不同。价格一旦被读入合约,可能立即决定某个用户能借多少、某个仓位是否可清算、某个清算人能否拿走抵押物。错误不是展示问题,而是资产所有权的状态变化。
所以一个借贷协议不能只写:
uint256 price = oracle.getPrice(asset);
还要问:这个价格是什么时间更新的?它是否为正?它的精度是什么?它和债务资产是否使用同一计价单位?当价格源暂停、超时或偏离时,协议是禁止新增借款、禁止清算,还是切换到备用来源?每一种选择都不是纯技术细节。
如果预言机超时仍允许借款,攻击者可能用过时的高价抵押品借走资产;如果超时就允许清算,清算人可能以陈旧低价掠夺健康仓位;如果系统在预言机异常时完全停摆,用户又失去补仓、还款和自救的通道。没有一个“永远正确”的降级策略,只有针对不同操作、不同资产和不同威胁模型的权衡。
一个最小的防御性读取会像下面这样表达问题:
function checkedPrice(IChainlinkFeed feed) internal view returns (uint256) {
(, int256 answer,, uint256 updatedAt,) = feed.latestRoundData();
require(answer > 0, "invalid price");
require(block.timestamp - updatedAt <= 1 hours, "stale price");
return uint256(answer);
}
但这只是起点。1 hours 对 ETH 可能已经太久,对日内波动较小的稳定资产也许尚可;对低流动性资产,价格本身即使很新,也可能来自能被少量资金影响的市场。更深的一层是把“价格可信度”纳入风险参数:流动性下降、偏离阈值被触发、预言机维护状态异常时,协议是否自动降低 LTV、暂停新增风险、或提高清算折价。
清算瀑布怎样形成
清算本身会卖出抵押品,卖出又会压低抵押品价格,价格下跌会让更多仓位跌破清算线。这就是清算瀑布的基本反馈环。它并不要求协议写错代码,也不要求有恶意攻击者。只要市场杠杆高、抵押品相似、流动性不足,正常执行规则就可能放大市场动作。
假设大量用户都以 ETH 为抵押借入稳定币,并把借来的稳定币再次买 ETH。上涨时,这是一条正反馈:ETH 上涨,健康度提高,用户可以再借,再买;下跌时,反馈反转:ETH 下跌,健康度下降,清算卖 ETH,价格进一步下跌。单个用户的仓位也许健康,系统整体却可能越来越脆弱,因为所有人的缓冲都依赖同一个价格。
产品和风险团队要讨论的不是“清算会不会让用户不舒服”,而是“协议在什么规模的价格冲击下,必须向市场卖出多少抵押品”。这可以被量化为 liquidation-at-risk:在 ETH 下跌 10%、20%、30% 的情景下,多少债务会触发清算;这些清算对应多少 ETH;对应交易场所过去一小时、一天的真实成交深度是多少。若模拟出的待卖数量远大于深度,账面上的清算奖金并不会自动变成可执行的结算能力。
这里也能看出部分清算和全额清算的取舍。部分清算加 close factor 的好处,是用户还有机会通过补充抵押或还款恢复仓位,不会因一次轻微越界而被全部剥离;缺点是若价格快速下跌,协议需要多次清算,每一次都增加延迟和市场影响。全额清算更快地切断风险,但用户体验更残酷,也更容易在价格短暂插针时制造不必要的资产损失。没有通用答案,取决于抵押品的流动性、波动速度、清算人的竞争程度和协议可承受的坏账。
债务人的选择,其实是三种期权的组合
从用户视角,借贷协议提供的不只是“借到稳定币”。当用户抵押 ETH 借出 USDC 而不卖 ETH 时,他保留了 ETH 的上涨敞口,同时获得了一笔可用的稳定币流动性。这类似于卖出一部分下行保护的代价,去换取继续持有现货和使用现金的权利。若 ETH 上涨,用户的抵押品增值,债务相对变轻;若 ETH 下跌,用户不是只亏损现货,还会面对健康度恶化和被强制出售的路径。
因此,借贷产品最不应该鼓励的,是把“最大可借”当作推荐额度。最大可借只是健康度刚好等于 1 的数学边界,不是正常使用的安全区。真正应该展示的是压力测试后的缓冲:ETH 下跌 10%、20%、30% 时健康度分别是多少;若利息按当前利用率持续累积 30 天,健康度会下降多少;若用户借入的稳定币又被换成相关风险资产,仓位的真实方向敞口是什么。
一个产品可以把这些信息做成很具体的选择,而不是风险教育文章:
- 借款输入框旁显示“建议保留 1.5 以上健康度”,并用资产历史波动推导建议借款区间;
- 开仓确认页显示清算价、距离现价的百分比、以及价格每下跌 5% 后的健康度;
- 价格接近警戒线时,先提醒可还款额和可补仓额,而不是只推送“你可能被清算”;
- 在高利用率或预言机异常时,明确说明为什么新增借款受限,而不是让用户以为按钮坏了;
- 对 e-mode、循环贷和相关资产抵押,使用不同的风险语言,而不是沿用普通现货借贷的说明。
这里的关键是承认:用户并不是真的在管理一个数字,而是在管理一条会被市场、预言机和其他参与者共同推动的清算路径。好的产品不是替用户隐藏这条路径,而是让用户在还有选择权时看见它。
协议的债权人是谁
资金池模型里,存款人把资产放进协议,得到一份可计息的凭证。表面上看,借款人欠的是协议;经济上看,借款人欠的是所有流动性提供者。清算机制之所以必须强制、自动、可被陌生人执行,正是因为存款人没有逐笔审查借款人的能力,也无法在价格下跌时逐个去催收。
这带来一个很朴素但常被忽略的治理问题:谁有权改变风险参数?提高某资产 LTV、降低清算 bonus、接入新的预言机、把长尾资产加入主池,看起来都是增长动作,实质上都在改变存款人的债权质量。治理投票若只由代币持有人决定,而代币持有人承担的损失与存款人不同,就会出现风险和决策权错配。
风险管理因此不能只是一张参数表。它需要明确每个动作的损失承担者、风险容量、变更延迟和紧急权限。比如新增抵押品可先放入隔离池,设置很低的 debt ceiling;观察预言机、流动性和用户行为后再扩大。又如高风险参数的变更应有 timelock,给存款人和市场参与者退出或重新定价的时间。紧急暂停权也要被精确限制:它能否暂停新增借款?能否暂停清算?能否暂停还款和提现?每个答案都影响系统在危机里的可信度。
结语:债的本质是对未来处置权的分配
加密金融把债务压缩成了几组公开参数和几段可执行代码。它因此更透明,也更残酷。传统金融可以在违约边缘通过展期、协商和关系缓冲;链上协议通常没有“再等等”。当健康度跌破 1,合约并不知道用户的长期判断是否正确,它只知道此刻谁有资格调用清算函数。
这不是去中心化借贷的缺陷,而是它获得无许可、可验证和自动结算能力所付出的代价。真正值得讨论的,不是链上借贷是否比银行先进,而是我们愿意把哪些判断交给市场,哪些判断交给代码,哪些风险必须由产品在用户开仓前说清楚。
借款把未来的流动性带到今天;抵押把今天仍然拥有的资产交给规则;清算则是在未来没有如愿到来时,提前写好的处置方式。理解这三件事,才真正理解了加密金融里的债。
下一篇:清算机器人如何报价,稳定币如何成为全系统的结算腿,以及为什么一套看似无须信任的规则,最终仍要依赖流动性、治理和用户对风险边界的共同理解。