Compound V3 奖励系统(COMP)详解

理解 MasterChef 式的追踪指数、每秒奖励速率、最低门槛保护与 CometRewards 领取机制

5 分钟阅读
Compound V3 奖励系统(COMP)详解

Compound V3 奖励系统(COMP)详解

目录


一、核心机制

Compound V3 按参与度成比例地给出借人和借款人分发 COMP 代币。系统紧密借鉴了 MasterChef 质押算法——追踪”自开天辟地以来每单位资产累积赚了多少奖励”。

这和上一篇的利率指数是同一类思想:用一个全局累加器代替逐账户更新,O(1) 省 Gas。

二、追踪指数(供应与借款)

协议维护 trackingSupplyIndextrackingBorrowIndex 作为奖励累加器,衡量”一个假想单位的 USDC 自开始以来赚了多少奖励”。

当总供应或总借款增加时,新赚的奖励要分摊到更多单位上,每单位收益下降——这正是 MasterChef 的精髓:奖励速率固定,参与的人越多,人均越少。

三、最低门槛保护 baseMinForRewards

Compound 在总借款或总出借低于某阈值时不发奖励

具体地,baseMinForRewards = 1,000,000 USDC(6 位小数下是 1e12)。

为什么?防止累加器溢出。当质押量很小时,“奖励速率 ÷ 质押量”会让累加器增长过快,有数值溢出风险。设最低门槛保证分母足够大。

四、速率变量(immutable)

两个固定参数控制奖励速度:

  • baseTrackingSupplySpeed:2.979166666666e-03(每单位时间)
  • baseTrackingBorrowSpeed:4.414467592592e-03(每单位时间)

都用 trackingIndexScale(1e15)做定点精度。它们代表”每单位时间的奖励”,乘以经过的时间得到参与资产累积的奖励。注意借款速率 > 供应速率——协议当时更想激励借款。

五、用户级奖励追踪

每个用户维护:

  • baseTrackingIndex:用户上次状态变更交易时记录的指数值;
  • baseTrackingAccrued:累积已赚奖励 = (当前指数 − 用户存的指数) × 用户余额。
新增累积奖励 = (当前指数 − 用户的指数) × 用户余额

AccrualDescaleFactor:归一化不同小数精度资产的缩放因子。ETH(18 位)的奖励除以 1e12 来匹配 USDC 的 6 位精度,实现统一追踪。

六、奖励累积公式 accrueInternal

核心更新逻辑:

若 totalSupplyBase ≥ baseMinForRewards:
    trackingSupplyIndex += (baseTrackingSupplySpeed × 经过时间) / totalSupplyBase

若 totalBorrowBase ≥ baseMinForRewards:
    trackingBorrowIndex += (baseTrackingBorrowSpeed × 经过时间) / totalBorrowBase

注意分母是 totalSupplyBase/totalBorrowBase——参与越多,指数涨得越慢,人均奖励越低。低于最低门槛时指数不增长(防溢出)。

七、领取机制 claim

用户在独立的 CometRewards 合约claim()(不在主 Comet 合约里)。函数流程:

  1. 检查 shouldAccrue 标志(若 true,先确保累积发生);
  2. 从用户的 Comet 结构体读当前 baseTrackingAccrued
  3. 减去 rewardsClaimed(追踪之前已领的,类似 MasterChef 的 reward debt);
  4. 把差额转给领取者。

为什么独立合约:COMP 总量固定 1000 万枚(已全部铸造),CometRewards 需要治理定期充值。历史提案显示大约每年补充一次。

八、COMP 代币的怪癖

COMP 代币偏离标准 ERC20:

COMP 不像大多数 ERC20 那样用 uint256 存余额,而是用 uint96

转账或授权超过 uint96 最大值(约 7.9e28)会 revert,给单个地址持有量设了上限。这是早期为投票打包优化设计的遗留。

九、完整流程

  1. 用户在 Comet 存款/借款;
  2. accrueInternal() 更新追踪指数(若达最低门槛);
  3. updateBasePrincipal() 按指数差算用户新增累积奖励;
  4. 用户调 claim() 领取 COMP,从累积总额扣除已领部分;
  5. CometRewards 合约从治理充值的储备转出代币。

这套架构确保奖励随参与度反比缩放,同时用最低门槛闸门防止数值不稳定。

十、总结

  • COMP 奖励用 MasterChef 式追踪指数:每单位资产的累计奖励,参与越多人均越少;
  • 供应、借款各一个 trackingIndex,速率是 immutable,借款速率更高;
  • baseMinForRewards(100 万 USDC)防累加器溢出;
  • 用户存 baseTrackingIndex + baseTrackingAccrued,按指数差算奖励;
  • 在独立 CometRewards 合约 claim,治理定期充值 COMP;
  • COMP 用 uint96 存余额,是非标准遗留。

十一、动手练习项目:MasterChef 式奖励分发 RewardsDistributor

项目目标

亲手实现 MasterChef 式追踪指数奖励系统:固定速率、参与反比、最低门槛、独立合约 claim。部署到 Sepolia,验证两个用户按存款比例和时长分到奖励。

合约要求

1. StakingComet.sol(简化的存款合约,复用指数记账)

  • trackingSupplyIndexbaseTrackingSupplySpeed(immutable)、baseMinForRewardstrackingIndexScale = 1e15
  • mapping(address => uint) principalmapping(address => uint) baseTrackingIndexmapping(address => uint) baseTrackingAccrued
  • accrue():若 totalSupply >= baseMinForRewardstrackingSupplyIndex += speed * dt / totalSupply
  • _updateUser(address)baseTrackingAccrued[u] += (trackingSupplyIndex - baseTrackingIndex[u]) * principal[u] / SCALE,再把 baseTrackingIndex[u] = trackingSupplyIndex
  • supply/withdraw:进出前先 accrue + _updateUser。

2. RewardsClaimer.sol(独立领取合约)

  • 持有 RewardToken(你自部署的 COMP 替身)储备;
  • mapping(address => uint) rewardsClaimed
  • claim(address comet, address to):读 comet 的 baseTrackingAccrued[to],减 rewardsClaimed[to],转差额,更新 rewardsClaimed;
  • topUp(uint amount):治理充值储备。

测试要求(Foundry,重点 vm.warp)

  1. test_ProportionalRewards:A 存 100、B 存 300,推进时间后 B 的累积奖励是 A 的 3 倍;
  2. test_NoRewardsBelowMin:总供应 < baseMinForRewards 时,推进时间 trackingIndex 不增长、无奖励;
  3. test_TimeWeighted:A 先存、晚些 B 才存,A 累积更多(参与时间长);
  4. test_ClaimTransfers:claim 后 to 收到正确 COMP,rewardsClaimed 更新;
  5. test_DoubleClaimNoDouble:连续 claim 两次,第二次几乎为 0(reward debt 机制);
  6. test_ParticipationDilutes:新用户大额存入后,老用户的后续每秒奖励变少(指数增速下降);
  7. test_TopUpRequired:储备耗尽时 claim revert,topUp 后恢复。

Sepolia 部署与验证步骤

  1. 部署 RewardToken、StakingComet、RewardsClaimer(充值 RewardToken);
  2. 两个账户按不同金额 supply(总额超过 baseMinForRewards);
  3. 隔一段时间读各自 baseTrackingAccrued,验证比例;
  4. 在 RewardsClaimer 调 claim 领取,Etherscan 确认到账;
  5. 让一个大户加入,观察老用户后续奖励速率下降。

进阶挑战(可选)

  • 同时实现 borrow 侧的 trackingBorrowIndex,让借款人也赚奖励,验证供应/借款独立累积;
  • 用 uint96 存奖励余额复刻 COMP 怪癖,测试超过上限 revert;
  • 写注释回答:为什么需要 baseMinForRewards?用具体数字说明小质押量如何导致累加器快速增长甚至溢出。

💬 评论