StockLend 怎么运转
用 USDG 押着借出代币化股票,卖进周末溢价,等喂价回归再买回来。为什么债务要按两个价而不是一个价计,以及买卖双方还有哪些 赔钱的方式。
一笔空头,按买回来的真实成本计价
Chainlink 的股票喂价是 24/5。从周五收盘到周日晚上,它停在上一个收盘价,而 token 照常交易,所以溢价可以挂两天没人套。 StockLend 把这件事变成一个仓位:押 USDG,借出股票 token,卖掉,等喂价重开再买回来。
- 这是我们自己的合约,不是 Morpho。债务按 max(预言机, 池 TWAP) 计,不是按预言机。
- 它是公开、无许可的。没有 keeper:清算和买回对任何人开放,并且给奖励。
- owner 手上唯一的旋钮是借出上限。其他每个参数都被合约自己限住。
:::note 两边分别是谁
出借方存入股票 token,赚 USDG 计价的借券费。借款人押 USDG,欠股票。两边其实在承保同一件事的两端:在喂价回来之前,池价能 跑出去多远。
:::
为什么一个价不够
按预言机计债的借贷市场,是用一个已经停住的数给贷款定价。整个周末这等于白送给借款人一个看涨期权:只要 token 价格越过 「预言机 ÷ 质押率」,不还款就严格优于还款,而清算按冻结价用 USDG 结算给出借方。出借方永远拿不回股票。
这不是假设,这就是我们有数据的上一个周末的样子。
那个周末 $289 万成交里有 $193 万发生在 60% 以上的溢价。Chainlink 全程停在 $127.52,周一 02:00 UTC token 已经回到 $129.68。
按 Morpho 那套建的市场会被按设计掏空,不是被 bug 掏空——这就是为什么 StockLend 是我们自己写的合约,而不是在别人合约上建的 一个市场。
两个价,和一道钳制
预言机是周一会回归的那个价,也是收费的基准。池子的 TWAP 是把股票买回来实际要花的钱。债务按两者更高的那个计价,所以挤兑 发生的时候,仓位跟着挤兑走。
但直接信池价会犯另一个更糟的错。集中流动性仓位的尾巴是空的:把窄区间买穿之后,把 tick 顶到任何地方都几乎不花钱,测试里 30 分钟 TWAP 在五分钟内读到了 53 倍。所以计债价被钳住。
计债价 = min( max(预言机, 池 TWAP), 预言机 × (1 + 带宽) )
光有钳制还不够
如果初始保证金低于 维持线 × (1 + 带宽),就有人能把池子推到钳制上限、在上限处清算掉健康的贷款。合约不允许这么配:两个档位 都强制 IM ≥ MM × (1 + 带宽),这就是为什么你在应用里看到的保证金是那种不整的数字,而不是更整的数。
带宽冻进每笔贷款
一次评审找到了这个缺口:如果带宽按当前档位读,那么在开市档、按较窄带宽开的贷款,每天晚上喂价停更时就会落到闭市带宽下,被 一次它从没为之交过保证金的推价清算掉。带宽、维持线、清算奖励都在开仓时抄进贷款里,owner 改参数够不到已经存在的贷款。
借款人付什么
费在贷款开出的那一刻用 USDG 收,而且它不是利率,是溢价的一个分成:α × 溢价 × 股数 × 预言机价。理由正是自适应利率做不到的 那件事——两个小时的周末挤兑,早在利用率曲线反应过来之前就结束了。
| 什么 | 为什么是这样 |
|---|---|
| 开仓费(α) | 溢价的一个分成,一次性预收。溢价按 max(TWAP, spot) 对预言机取,借款人自带 maxFeeOpen,报价不会在他们脚下变。 |
| 初始保证金 | 开市一个值,闭市一个更高的值。按开仓时的档位收,并且是对计债价收,不是对预言机——这正是关键。 |
| 期限 | 先由链上交易日历定,配置的期限是上限。开市时借的贷款在当天收盘到期,所以没人能周四晚上借一笔零溢价的贷款横跨整个周末。闭市时借的在下次开市后一段固定时间到期,另有宽限。 |
| 买回上限 | 到期之后任何人都可以用借款人的抵押品把股票买回来,但只能在结算价加上一个余量内成交。它从很紧开始、每小时放宽一个点,所以拉盘的那个区块只会让买回失败,而不是掏空借款人——真实的溢价照样一天内清掉。 |
:::caution 借款人的尾部
如果到期落在喂价仍然冻结的窗口里,结算价就是预言机价,上限从那里往外长。最坏情况下借款人会按预言机价加上完整的滑点上限被 买回。这是「上限不能被守尾巴的人玩弄」所要付的代价。
:::
出借方赚什么、承保什么
存入股票拿份额,费按份额以 USDG 累计,跟所有 staking 合约一个做法。本金只在核销时变动。没有利率曲线要盯:收入就是每笔借款 溢价的 α 分成加上时间费,两样都由自己选择进来的借款人支付。
保护到带宽为止
在钳制上限以下,池价就是出借方的保护——债务跟着挤兑一起涨。上限之上就不是了。超过带宽的那部分溢价在屏幕上有、在债务里没 有,这时候站在出借方身后的是抵押品,以及喂价会重开这件事。如果一个标的真的重估超过带宽并且不回来,那笔抵押品就不够。
所以带宽不是一个安全开关,它是一个产品决定:它是这个市场承诺替出借方扛住的最大溢价,按每个标的自己的历史一只一只定。上面 那个 MSTR 周末,要覆盖完整的 2.7 倍需要 310% 的初始保证金。
贷款怎么结束
四个出口,没有一个需要是我们。repay 是借款人的。健康度跌破维持线之后任何人都可以 liquidate,有奖励。贷款到期之后任何人都 可以 buyIn,有奖励。writeOff 是最后一个,也是出借方本金唯一会变的那条路。
两个细节来自评审而不是设计。抵押品退款是拉取式的,所以某个 USDG 账户被冻结,卡不死别人的平仓。以及在 token 的
oraclePaused() 因为公司行动被置位期间,完全不做健康度清算、也不做核销——喂价在乘数变更期间冻在最后一个好值上,按它行动
就是按一个已知错误的数行动。
哪些是证明,哪些只是测出来的
推价推不掉一笔有抵押的贷款
这条是算术,不是判断。计债价不可能超过 预言机 × (1 + 带宽);初始保证金不低于 维持线 × (1 + 带宽);带宽在开仓时定死。三者 合起来,一笔新贷款的健康度不可能被池价单独推到维持线以下——只有喂价真的动才行。
对已部署合约跑了三个攻击场景:闭市档按闭市初始保证金开的贷款、跨过喂价夜间空档的开市档贷款、以及池子真涨 22% 时一笔到期的 健康贷款。三次全被拒,攻击者每次净收益都是负的——那是把窄区间买穿再买回来的往返成本。
买回不会被无限期卡住
确实有人可以在每次 buyIn 之前把池子推上去、让它 revert。上限每小时长一个点直到封顶,所以这个每次都要付一趟区间往返成本的 拖延,上界大约是一天——而这段时间的时间费归出借方,不归捣乱的人。
不能证明的:借出去比拿着强
我们只有一个真实挤兑周末的数据,一个标的,而且参数就是照着这个事件校准的。用你唯一的样本做校准,不能证明校准会泛化。诚实 的说法是:机制在结构上扛得住那个事件;至于手续费在很多个周末上是否付得起那条尾巴,这是一个还没有人有的测量。
会出什么事
带宽之上,池价不再保护出借方
再说一次,因为这是真会亏钱的那条:超过带宽之后债务不再跟着价格走,出借方靠的是押进来的抵押品,以及喂价重开时这个标的会 回来。
发行方可以暂停 token
股票 token 是带全局 pause 的 beacon 代理。转账一停,市场里的一切跟着停——在恢复之前没人能还款、清算或买回。没有黑名单,但 pause 已经够了。
一个市场,一个上限,一次记录在案的事件
第一个市场的上限是我们愿意犯错的规模,不是对需求的判断。「借出股票 token 用来卖」的法律定性还没有评估过。
:::info 一句话
债务按「把股票买回来实际要花多少」计价,上限是这个市场承诺替你扛的那个溢价——超过上限,剩下的就只有抵押品和喂价会重开 这件事。
:::
参考
| 角色 | 它决定什么 |
|---|---|
StockLend | 一只股票一个市场:存取、借贷、清算、买回、核销 |
TwoPriceOracle | 喂价、池 TWAP、钳制后的计债价、当前档位 |
TradingCalendar | 24/5 交易时段、夏令时、NYSE 假日,全部从时间戳算出——不需要任何人喂 |
| 池子 | TWAP 从这里读,买回也在这里成交 |
| Owner | 一个 Safe:在合约自身边界内改参数,以及上限。不存在 keeper 角色。 |
地址和上面每个参数的实时值,在应用里各个市场自己的页面上:hedgehood.app/lending。另见 三个价和术语。