TUNDRA 借贷协议
本页讲的是借贷(TUNDRA)背后的机制。读取市场数据和构建交易,请看开发者指南;所有合约 ID 见合约参考。
TUNDRA 是运行在 alkanes 上的 Morpho Blue 分叉。凡是 alkanes 能支持的 Morpho Blue 行为,都原样复现,常量也保持一致;alkanes 不支持的地方,下文逐条说明差异。TUNDRA 最看重保留的是 Morpho Blue 的信任模型:任何人都可以创建市场,市场的风险参数永久固定,治理无法暂停、没收或重新调整任何已上线的东西。
架构
TUNDRA 工厂合约 4:881050
(登记表:市场 ID -> 资金池,启用列表,费用设置)
| |
资金池 2:98445 资金池 2:98619
DIESEL -> frUSD 市场 DIESEL -> frBTC 市场
只保管本市场的代币 只保管本市场的代币
| | | |
预言机 4:727967 利率模型 4:674184 预言机 4:727968
\______________ TUNDRACLE 4:9010 _____/
工厂合约
工厂合约(4:881050)负责四件事:
- 创建市场。
CreateMarket无需许可。它为新市场克隆资金池模板(4:668244),初始化并校验这个克隆,然后在登记表里记录市场 ID -> 资金池。 - 维护启用列表。 市场只能使用治理已启用的利率模型(IRM)和 LLTV。启用是单向的:一旦启用,就不能再停用。
- 保存费用设置。 新市场的默认费率(目前为 10%)和费用接收方。
- 回答市场发现查询。
GetPool(市场 ID)是判断哪个合约服务某个市场的唯一权威。
工厂合约不保管用户资金,也不提供用户操作。用户直接调用资金池。
每个市场一个资金池
Morpho Blue 原版是一个合约容纳所有市场。TUNDRA 则为每个市场克隆一个独立的资金池合约。资金池只保管本市场的代币,承载全部用户操作:存款、取款、借款、还款、追加抵押品、关闭仓位、清算和闪电贷。
这是与原版最大的不同,换来的是隔离:账本漏洞只会影响一个市场的代币,而不是所有市场;闪电贷也只能借出这一个资金池持有的代币。信任模型没有改变。
资金池份额就是存款凭证
每个资金池同时也是它自己的代币。存款时,资金池给存款人铸造自己的份额;取款时销毁份额。没有单独的份额代币合约,也没有外部可调用的铸造或销毁操作码。
利息让资金池的存款总额增加,而份额总数不变,所以每一份额能赎回的代币越来越多。持有份额就是索取权:存款端无需登记,份额可以像任何 alkane 一样转账或由其他合约持有。
新资金池以「每 1 个借出代币最小单位对应 1,000,000 份额」开局(即 Morpho Blue 的虚拟份额机制,用来防御针对空资金池的「通胀攻击」)。资金池名称为 Tundra Pool <市场 ID 前 8 位十六进制>,代号 TUN-P。
治理是一枚不记名代币
治理权是一枚不可分割的代币:工厂合约自身 ID 4:881050 的一个单位,在工厂部署时铸造。持有承载它的 UTXO 就是拥有治理权,转交它就是花费它。同一枚代币也用于授权所有资金池上的 SetFee。
治理能做的事:
- 启用利率模型;
- 启用 LLTV;
- 设置某个市场的费率,最高为利息的 25%;
- 设置之后新建市场的默认费率;
- 指定费用接收方。
治理不能做的事:暂停市场、没收或挪动仓位、修改已上线市场的预言机、利率模型、LLTV 或最低借款额、升级资金池、阻止清算。即使治理代币丢失,所有市场也会照常运行,只是启用列表和费用设置从此冻结。
目前启用的 LLTV 为 35% 和 40%,启用的利率模型是 4:674184 上的自适应曲线模型。
市场与市场 ID
一个市场由六个参数定义,编码为十个小端序 u128 字:
| 字 | 参数 |
|---|---|
| 1-2 | 借出代币(block、tx) |
| 3-4 | 抵押品代币 |
| 5-6 | 预言机 |
| 7-8 | 利率模型 |
| 9 | LLTV,以 1e18 为精度 |
| 10 | 最低借款额,以借出代币最小单位计(0 表示不限制) |
市场 ID 是这 160 字节的 SHA-256 哈希(Morpho Blue 用 keccak256,alkanes 只有 SHA-256)。只要参数一致,两个人得到的就是同一个市场,无需协调;任何参数一变,就是另一个市场。这也是参数无法修改的原因:「修改后」的市场其实就是另一个市场。
最低借款额是 TUNDRA 新增的参数,原版只有五个字段。见最低借款额。
市场发现规则
任何人都可以用编造的参数克隆资金池模板,假资金池可以按创建者的意愿回答任何查询。只有同时满足以下两个条件,资金池才是真正的 TUNDRA 市场:
pool.GetFactory() == 4:881050
factory.GetPool(pool.GetMarketId()) == pool
SUBFROST 应用和清算机器人都会拒绝没有通过这项检查的资金池。
账户与仓位
alkanes 合约无法直接向地址付款。它把代币放在返回结果里,由运行时转到交易中 pointer 所指定的输出。TUNDRA 的所有权模型就建立在这一点上。
- 存款人持有资金池份额,不需要账户。
- 借款人拥有一个仓位,以「所有者 + salt」为键。所有者可以是:
- 一个脚本(比特币输出脚本,比如 taproot 地址)。个人借款用的就是这种方式。salt 必须为 0,所以每个脚本在每个市场只有一个仓位。
- 一个 alkane(合约)。合约可以用 salt 维护多个仓位。
仓位键的计算方式:
key = sha256(owner_encoding || salt,16 字节小端序)
owner_encoding = 0x01 || 脚本长度(1 字节)|| 脚本 (脚本所有者)
= 0x00 || block(16 字节小端序)|| tx(16 字节小端序)(alkane 所有者)
脚本所有者的仓位通过证明付款落在该脚本上来授权:资金池读取自己所在的交易,找到自己的 protostone,检查其 pointer 是否指向一个真实输出,且该输出的脚本正是所有者的脚本。以下三条规则保证了它的安全:
- 只认顶层调用。 脚本所有者的授权只在顶层 protostone 中有效,因为只有顶层才会按 pointer 路由。合约不能代替脚本所有者操作;合约若要管理仓位,必须以 alkane 所有者身份持有。
- 授权消息不能带 edict。 运行时会在路由返回结果之后才执行该 protostone 的 edict,edict 可能在检查通过后把款项从所有者那里转走。所以资金池拒绝任何带 edict 的授权消息。实际操作中,代币由前一个只含 edict 的 protostone 转入调用。
- pointer 必须指向真实输出,而不是后面的某个 protostone。
各项操作所需的授权:
| 操作 | 对所有者的影响 | 授权方式 |
|---|---|---|
| 存款、追加抵押品、还款 | 资产流入,健康度提高 | 无需授权 |
| 取款 | 销毁调用者发来的份额 | 份额本身 |
| 开仓或追加借款 | 欠款增加 | 借出的代币必须付给所有者(pointer) |
| 关闭仓位(欠款为零) | 退回抵押品 | 抵押品必须付给所有者(pointer) |
| 清算 | 与原版一样无需许可 | 无需授权 |
| 闪电贷 | 原子操作,不产生仓位 | 无需授权 |
追加借款。 同一所有者的第二次借款会并入现有仓位,并且只有满足以下条件才会被接受:
- 不抬高仓位的欠款与抵押品之比。设现有欠款
D、抵押品C,追加欠款d、抵押品c,要求d × C <= D × c。这个条件不依赖价格,所以在任何价格下都成立; - 合并后的仓位在预言机价格下是健康的。
两者结合,使得替别人的仓位借款毫无意义:借出的钱归所有者,而所有者的比例不会变差。
不支持部分提取抵押品。 抵押品只能通过 ClosePosition(全部取回,且欠款必须为零)或清算离开仓位。想减少抵押品的借款人,需要先还款、关闭,再重新开仓。
利息
利息按区块累积,因为比特币上的合约能读到区块高度,却读不到时钟。所有利率都是每区块利率,一年按 52,560 个区块计。比特币出块通常略快于每十分钟一个,所以借款人实际支付的利息会比名义年利率略高一点,存款人也会多拿到同样的一点。
自适应曲线利率模型
4:674184 就是 Morpho Blue 的 AdaptiveCurveIRM,常量完全相同:
| 常量 | 数值 |
|---|---|
目标利用率 u* | 90% |
曲线陡度 k | 4 |
| 初始目标利率 | 年化 4% |
| 目标利率范围 | 年化 0.1% 到 200% |
| 调整速度 | 每年 50 |
设利用率为 u,归一化偏差为:
e = (u − u*) / u* 当 u <= u*
e = (u − u*) / (1 − u*) 当 u > u*
借款利率是当前目标利率 r_t 的倍数:
r = r_t × (1 + (1 − 1/k) × e) 当 e < 0 (u = 0 时为 r_t / 4)
r = r_t × (1 + (k − 1) × e) 当 e >= 0 (u = 1 时为 4 × r_t)
两次计息之间,目标利率按指数调整:
r_t(新)= r_t × exp(调整速度 × e × 经过的区块数 / 52,560)
并被限制在上述范围内。e = ±1 时,r_t 大约每五天翻倍或减半。一段时间内收取的利率是沿这条路径的平均值,所以两次计息隔得久,与多次短间隔计息的成本相同。利率模型为每个调用它的资金池分别保存目标利率。
计息与费用
存款、取款、借款、还款和清算都会先计息。以每区块利率 r 经过 n 个区块,利息为 借款总额 × (e^(r·n) − 1),用 Morpho Blue 的三阶泰勒展开计算。利息同时加到借款总额和存款总额上。
费用从每次计息中抽取一部分,以新铸造的资金池份额形式给费用接收方,份额价值恰好等于费用金额,因此不会稀释自身。接收方通过 ClaimFee 领取。两个已上线市场都以工厂默认的 10% 起步,上限为 25%。
存款利率 = 借款利率 × 利用率 × (1 − 费率)
健康度与清算
健康度
价格使用预言机的精度:每单位抵押品最小单位可换得的借出代币最小单位数,乘以 1e27。
最大可借 = 抵押品 × 价格 / 1e27 × LLTV
健康 ⇔ 欠款 <= 最大可借
欠款由仓位的借款份额换算,向上取整。没有欠款的仓位永远是健康的。
清算
任何人都可以清算不健康的仓位。清算人指定要拿走多少抵押品,或要偿还多少借款份额,另一个数值用清算激励系数算出:
LIF = min(1.15, 1 / (1 − 0.5 × (1 − LLTV)))
拿走的抵押品价值 = 偿还的欠款 × LIF
原版公式中的系数(cursor)是 0.3,TUNDRA 用的是 0.5。因为比特币上的清算人无法在一个区块内反应,每次尝试都要付真实的手续费,还可能在打包竞争中落败,所以奖励必须覆盖一笔固定成本,而不是一笔可以被竞争压低的成本。1.15 的上限沿用原版。
| LLTV | LIF | 清算奖励 |
|---|---|---|
| 40% | 1.15(触顶) | 15.00% |
| 77% | 1.130 | 12.99% |
| 86% | 1.075 | 7.53% |
两个已上线市场的 LLTV 都是 40%,奖励固定为 15%。
没有清算比例上限:一次调用就可以把一个仓位整笔清算。如果清算拿走了仓位最后的抵押品而仍有欠款,剩余欠款就是坏账,会在同一次调用中通过减少存款总额计入存款人。没有保险基金。
缓冲。 处在上限的仓位,在价格跌到「刚变为可清算时价格」的 LLTV × LIF 以下时,就无法被完全清偿。从那一刻起的安全跌幅为 1 − LLTV × LIF:LLTV 为 40% 时是 54%。
回调。 Liquidate 可以带一个可选的 callee。设置后,资金池先结算自身状态,把拿走的抵押品发给 callee,再从 callee 返回的代币中扣除还款;callee 返回的其他代币都归清算人。这样,手里没有借出代币的清算人也能在同一笔交易里卖掉抵押品并还款。4:999999 就是这样一个 callee:它通过 AMM 工厂 4:65522 卖出抵押品,如果卖出所得不足以还款,整笔清算会回滚。见运行清算机器人。
最低借款额
太小而不值得清算的仓位,会在暴跌中变成坏账。TUNDRA 增加了第六个市场参数:最低借款额,并在链上强制执行:
- 开仓和追加借款时,按仓位总欠款检查;
- 部分还款时,最多还到正好剩下最低借款额,多出的部分退回;除非这次还款清掉全部欠款;
- 部分清算时,剩余欠款不能低于最低借款额。整笔清算,或拿走全部抵押品的清算,始终允许。
已上线市场的最低借款额(3,000 frUSD 和 0.035 frBTC)是这样定的:15% 的奖励要能覆盖三次清算尝试,每次按暴跌时约 450 vB、300 sat/vB 的手续费计算。
LLTV 是怎么选的
上线第一个市场之前,团队用 347 天的 DIESEL / frBTC 逐小时价格(8,322 根 K 线),统计每个候选 LLTV 的缓冲在一到八小时内被击穿的次数。LLTV 为 40% 时,两小时内没有任何一次击穿;回放样本中最严重的一次暴跌(下跌 57.5%),清算也能干净完成。60% 及以上的 LLTV 在同一样本中多次被击穿。治理只启用了 35% 和 40%。
预言机
TUNDRA 沿用 Morpho Blue 的做法,每个市场只用一个预言机函数:price(),操作码 2,返回每单位抵押品可换得的借出代币数量,精度 1e27。每个市场指定自己的预言机适配合约,两个已上线的适配合约都读取 TUNDRACLE(4:9010):
| 适配合约 | 读取 | 价格 |
|---|---|---|
4:727967 | TUNDRACLE 市场 4 | 以美元计价的 DIESEL,用作每枚 DIESEL 的 frUSD 价格 |
4:727968 | TUNDRACLE 市场 5 | DIESEL / frBTC,两个价格源之比 |
TUNDRACLE 的精度是 1e8,所以每个适配合约乘以 1e19 换算到 1e27。适配合约的设置在部署时就已固定,因为它的 ID 是市场 ID 的一部分。
价格从哪里来
TUNDRACLE 订阅 SUBFROST 的预言机 DOOMACLE(4:9000)。价格由 DOOMACLE 签名组写入 DOOMACLE。这是一个 6-of-9 的 FROST 门限签名组,地址为 bc1pp9xj9ww24x5wenae96dpj3jtxnvrwehn4gqtam7xfc0dfwcmn2rqnms0du。九名签名者中必须有六名对同一笔交易达成一致,价格才会写入。
价格直接取自比特币链上的 AMM 资金池,所以每个签名者都能从相同的链上状态算出相同的数值:
- BTC 取自 frBTC / frUSD 资金池(
4:1778); - DIESEL 取自 DIESEL / frBTC 资金池(
2:77087),再乘以 BTC 价格; - frBTC 按托管锚定等于 BTC;
- frUSD 固定为 $1。
每个价格是 12 个样本的中位数,样本间隔 12 个区块,窗口共 144 个区块(约一天)。中位数会忽略单个被操纵的样本,所以只在一个区块里拉动资金池价格,无法改变最终价格。代价是滞后:真实的价格变动会逐步反映到预言机中。
新鲜度检查
TUNDRACLE 的每个价格源如果超过 144 个区块没有更新,就被视为过期。比值类市场(例如 DIESEL / frBTC)只有在两个价格源都新鲜时才算新鲜。
TUNDRACLE 会保留过期价格,而不是把它归零,因为价格归零会让所有仓位瞬间可被清算。所以 TUNDRA 的适配合约在价格不新鲜时会直接回滚。对资金池的影响如下:
| 操作 | 是否需要价格 | 价格过期时 |
|---|---|---|
| 开仓或追加借款 | 需要 | 拒绝 |
| 清算 | 需要 | 拒绝 |
| 存款、取款 | 不需要 | 正常 |
| 还款、追加抵押品、关闭仓位 | 不需要 | 正常 |
因此,价格源过期的市场会冻结新增风险,但始终可以平仓退出。
费用
| 费用 | 数值 | 由谁设定 |
|---|---|---|
| 利息上的协议费 | 两个市场均为 10% | 治理,每个市场最高 25% |
| 新市场默认费率 | 10% | 治理 |
| 存款、取款、借款、还款 | 除比特币网络手续费外没有其他费用 | |
| 闪电贷 | 免费 |
费用接收方登记在工厂合约上(GetFeeRecipient,操作码 107),具体地址见合约参考。
与 Morpho Blue 的对比
| Morpho Blue | TUNDRA | |
|---|---|---|
| 结构 | 单一合约 | 工厂合约 + 每个市场一个资金池 |
| 存款仓位 | 账本中的份额 | 资金池自身的代币(资金池份额) |
| 账户 | 以太坊地址 | 比特币脚本(以款项落点授权)或 alkane(以调用者授权) |
| 市场参数 | 5 个 | 6 个:新增最低借款额 |
| 市场 ID | keccak256 | SHA-256 |
| 时间 | 秒 | 区块(一年 52,560 个) |
| 预言机精度 | 1e36 | 1e27(1e36 放不进 128 位整数) |
| 清算系数(cursor) | 0.3 | 0.5 |
| 向某仓位追加借款 | 所有者或授权操作者 | 任何人都可以,但不能抬高欠款比例,且借款归所有者 |
| 部分提取抵押品 | 支持 | 不支持,需关闭后重开 |
| 治理 | 一个所有者地址 | 一枚不记名代币 |
| 新市场费率 | 设定前为 0 | 以默认费率起步 |
| 保持不变 | 25% 最高费率、1.15 最高激励、虚拟份额、取整方向、自适应利率模型及其常量、坏账即时计入、无需许可创建市场、参数永久固定 |
原版的 MetaMorpho 金库层不属于 TUNDRA。
接下来看什么
- 借贷(TUNDRA):用户指南。
- TUNDRA 开发者指南:读取数据、交易与清算。
- TUNDRA 合约参考。
- 预言机。