文章 · 质押
从退出者身上扣下的费用,并不会一次性交给留下的人,而是均匀发放——这个发放有一个窗口。看上去,连续几次退出应该把窗口一个接一个地串起来。合约里的做法并非如此,而这个差别正好解释了池子收益率为什么呈锯齿状。
2026 年 10 月 5 日 · 发布时已核验
窗口是从每一次退出单独计算的,永远不超过七天。连续三次大额退出并不会得到二十一天:每一次都把窗口清零,并从自己这一刻重新数七天。窗口不会串成一条链,而是互相重叠。
橙色刻度是退出发生的时刻,蓝色矩形是分配窗口,每个矩形里的数字就是它的天数。头两天里的三次退出会让分配在第九天结束,而不是第二十一天。结束时间原则上也不可能更晚:每个窗口都从它自己那次退出算起,正好七天。
质押池只有一个收入来源——向退出者扣下的费用。当一位质押者取回仓位时,合约会把整个仓位的 10% 留在池子里,连同已累积的收益一起。这一点可以直接在源码里核验:计数器 undistributedFees 只有一处会增长,就在退出函数内部。
但扣下的钱不会立刻发放。它会落进未分配费用这个「锅」里,并以恒定速度 rewardRate(单位是每秒多少 RNBW)均匀流出。在流出的过程中,份额价格上涨——也就是说,这笔增量会按仓位大小的比例,同时到达池子里的每一个人。没有人会因此拿到新的份额:是每一份既有份额变得更值钱。
所以质押收益率取决于有多少人退出,而不是有多少人进场。流入会把它压低:同样的币流要分给更大的池子。流出会把它抬高,而且是双重的——池子缩小了,同时还加进了离场者的十分之一。
新扣下的费用并不是简单地加到旧的上面。合约会重新计算现在全部流完需要多快,并在三个分支中选一个。选择只取决于一次比较:新速度是不是高于当前速度。
没有正在进行的分配——上一个窗口已经到期
速度等于锅里的量除以七天。一切从零开始。
窗口——正好 7 天
锅 ÷ 7 天不小于当前速度——大额退出
速度提高,窗口清零,并从这一秒重新开始计算。
窗口——正好 7 天
锅 ÷ 7 天小于当前速度——小额退出
速度不变,滴答还是原来那个。只是要滴得更久:窗口的剩余时间,加上新增量除以速度。
窗口——少于 7 天
这就是上限毫无例外地成立的原因。其中两个分支里,窗口按定义就等于七天。第三个分支里它短于七天——否则速度就会上升,走的会是第二个分支。代码里没有第四种可能。
禁止下调速度是有意为之,合约里用这句话作了注释:rewardRate can only stay flat or increase。没有它,任何人都可以按几分钱的份额反复退出,每退一次就把其他所有人的收益率往下拉一截。
三种情形放在同一个刻度上。橙色表示锅里有多少 RNBW:退出时跳升补充,然后均匀地向下流走。线的斜率就是分配速度——越陡,费用到达质押者的速度越快。
A——没有分配在进行时的一次退出。锅是空的,扣下 100,000 RNBW,于是每天发放 14,286。到第七天什么都不剩。
C——分配进行到一半时的大额退出。第三天又扣下 100,000。斜率变陡,速度升到每天 22,449,窗口清零——结束时间滑到了第十天。
D——连续三次大额退出。结束落在第九天,而不是第二十一天。真正增长的是速度:从每天 14,286 涨到 37,026 RNBW,分配快了 2.6 倍。
情形 C 里藏着视觉上的错觉。分配确实会比最初的七天更晚结束——在第十天。但这并不是「一个十天长的窗口」:从第三天那次退出到结束,正好是七天。窗口没有被拉长,而是被挪了位置。
第三个分支看着不起眼,却几乎每次都是它在起作用:纵观全部历史,大到足以抬高速度的退出屈指可数。我们把它放大来看——在总图上,所有关键的事情都发生在那里看不见的尺度上。
插图 ① 纵向放大了 28 倍,插图 ② 在时间上放大了 15 倍。两幅图里的虚线都表示:如果没有这次补充,事情会怎么走。
最关键的一点在插图 ① 里:台阶之前的线和之后的线是平行的。速度没有变化,滴答还是原来那个——上升的只是锅里的液位。所以期限延长的时间,正好等于滴答把新增量搬出去所需的时间:1,000 ÷ 每天 14,286 = 1 小时 41 分钟。这在插图 ② 里看得到。
分配结束从 7.00 挪到了 7.07 天,而从这次退出本身到结束还剩 4.07——比上限少了将近三天。但这三天的余量是这个例子的性质,不是这个分支的性质:它来自退出发生在窗口中间,而且金额极小。
那么同一个第三分支会把分配拉长将近七天。它的余量不仅取决于新增量的大小,还取决于窗口还剩多少:延长的时长等于窗口的剩余时间,加上新增量除以速度。
纵轴是从退出算起的窗口长度,单位是天。每条橙线对应一种扣费规模。点 B 是上一节的那个例子:第三天退出,新增 1,000。右上角那个点是第 6.9 天退出,新增 95,000。
这些线互相平行,并且全都顶在蓝色上限上。退出得越晚,剩下要流完的就越少——但新增量相对这点余量的分量就越重。窗口末尾的一次大额新增,能逼近七天:第 6.9 天退出、扣下 95,000 时,0.10 加 6.65 得到 6.75 天,而这仍然属于「期限延长」分支。
| 扣下 | 锅内 | 窗口,天 | 结束,天 | 分支 |
|---|---|---|---|---|
| 1,000 | 2,429 | 0.17 | 7.07 | 期限延长 |
| 25,000 | 26,429 | 1.85 | 8.75 | 期限延长 |
| 50,000 | 51,429 | 3.60 | 10.50 | 期限延长 |
| 95,000 | 96,429 | 6.75 | 13.65 | 期限延长 |
| 98,571 | 100,000 | 7.00 | 13.90 | 期限延长 |
| 100,000 | 101,429 | 7.00 | 13.90 | 速度上升 |
请看最后两行——那就是两个分支的边界。扣下 98,571 RNBW 仍然落在「期限延长」里,而 100,000 已经会抬高速度。两者给出的结果却一样:正好七天。它们之间没有跳变,边界是平滑的——「期限延长」分支从下方逼近七天,「速度上升」分支正好站在七天上。所以上限才会毫无例外地成立。
而分配在日历上的总长度,却不受任何限制。在「结束」这一列里,扣下 95,000 时是 13.90 天——将近两周。七天的上限约束的是每一次单独退出的窗口,而不是整体时长。落在窗口末尾的退出可以把它无限往后推,只要退出流持续不断,分配就根本不会停——实际发生的正是这样:锅从来没有见底。
这里描述的一切,都直接来自 _addFees 这个函数,它位于质押合约 0x288ea8Bcb51d53aD361D786EFa904C401904aF70 之中。源码已公开并通过验证,可以在区块浏览器里阅读。
合约会赋值 dripEndTime = block.timestamp + dripDuration,其中 dripDuration 等于 604800 秒,也就是从当前这一秒算起正好七天。上一个窗口就此不复存在。
这里是 dripEndTime = block.timestamp + undistributedFees / rewardRate。进入这个分支的条件本身就保证了这个商小于 dripDuration——否则走的会是上一个分支。
锅内余额和速度可以用 undistributedFees() 和 rewardRate() 这两个调用从合约里读出。它们连同全部退出的历史、以及每次退出触发了哪个分支的标注,都在质押页面上。
本文示例中的数字,是把 _syncPool 和 _addFees 从零状态开始逐步重放算出来的,而不是从网站上取的——这样它们就无法被凑成想要的结论。