Skip to content

feat: add cumulative reward/fee factors, delegator shares, and orchestrator commission tracking#231

Draft
rickstaa wants to merge 16 commits into
mainfrom
pr-217-suggestions
Draft

feat: add cumulative reward/fee factors, delegator shares, and orchestrator commission tracking#231
rickstaa wants to merge 16 commits into
mainfrom
pr-217-suggestions

Conversation

@rickstaa
Copy link
Copy Markdown
Member

Summary

Reconstructs the BondingManager's cumulative factor math and commission tracking in the subgraph, since the contract doesn't expose these values via events.

  • Cumulative Reward/Fee Factors on Pool — mirrors the contract's cumulativeRewardFactor and cumulativeFeeFactor per transcoder per round, with proper propagation for missed rounds and reactivation gaps (independent lastRewardRound/lastFeeRound fallbacks)
  • Delegator shares — precomputed bondedAmount * 10^27 / CRF for O(1) stake lookups at any round (shares * CRF[round] / 10^27)
  • DelegatorSnapshot — historical shares records on bond/unbond/rebond for time-series queries
  • Orchestrator commissionpendingRewardCommission, pendingFeeCommission (unclaimed, resets on claim) and lifetimeRewardCommission, lifetimeFeeCommission (never resets), including compounding on staked commission (transcoderRewardStakeRewards / transcoderRewardStakeFees)
  • activeCumulativeRewards — snapshotted at round start and re-snapshotted in the reward handler to match the contract's exact timing (line 1490). Fee handler uses pendingRewardCommission directly when reward() hasn't been called yet (mirrors contract line 339).

Edge cases handled

  • Cumulative factor propagation falls back independently to lastRewardRound (CRF) and lastFeeRound (CFF) when previous pool is missing (transcoder reactivation)
  • Fee handler uses current pool's propagated CRF as prevCRF fallback on reactivation
  • activeCumulativeRewards preserved on claim (matches contract), re-snapshotted by reward handler
  • Commission reset on earningsClaimed when delegator == delegate

Known limitations

Test plan

  • Run yarn prepare && yarn codegen && yarn build
  • Deploy to Subgraph Studio and verify sync completes without errors
  • Compare shares * CRF / 10^27 + pendingRewardCommission against contract's pendingStake() for sample transcoders
  • Verify commission resets to zero after an orchestrator claims earnings
  • Verify cumulative factors propagate correctly across rounds where reward() is not called

🤖 Generated with Claude Code

adamsoffer and others added 16 commits March 8, 2026 16:51
…cient historical stake computation

Store cumulativeRewardFactor and cumulativeFeeFactor on Pool entity
(matching on-chain PreciseMathUtils), propagate them forward each round,
and introduce a shares field on Delegator that enables O(1) stake lookups
via `stake = shares * crf[round] / 10^27`. DelegatorSnapshot entities
capture state at bond/unbond/rebond events for time-series chart support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ommission

Tracks the orchestrator's total rewardCut commission across all rounds,
incremented each time reward() is called. Never resets, so clients can
read lifetime earnings in a single field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ission

Resets Transcoder.cumulativeRewards to zero when the orchestrator claims
earnings, so the field represents pending/unclaimed commission rather
than lifetime total. This lets clients compute full orchestrator pending
stake as: shares * crf / 10^27 + cumulativeRewards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate never-reset counter alongside cumulativeRewards (which resets
on claim). Gives clients a single field for lifetime orchestrator
commission without summing historical claim events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename cumulativeRewards/lifetimeRewards to pendingRewardCommission/
lifetimeRewardCommission for clarity. Add pendingFeeCommission and
lifetimeFeeCommission on Transcoder, computed in WinningTicketRedeemed
handler. Both pending fields reset on claim.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous implementation did two divisions (num * 10^27 / denom, then
base * result / 10^27), causing intermediate truncation that compounded
each round in the CRF calculation. Solidity's version does one division
(base * num / denom). While the CRF ratio error cancelled out for
delegator stake, pendingRewardCommission accumulated ~0.23 LPT/round
drift. This fix ensures exact match with the contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The contract's cumulativeRewards includes two components: the rewardCut
commission plus rewards earned by the transcoder's own staked commission
(activeCumulativeRewards). The subgraph was only tracking the first.

Add activeCumulativeRewards field on Transcoder, snapshotted from
pendingRewardCommission at the start of each round in newRound. The
reward handler now computes both components matching the contract's
updateTranscoderWithRewards logic. Reset on claim.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…IVISOR

More readable and self-documenting than counting 27 zeros in a string literal.
graph-ts BigInt.pow(exp: u8) supports this cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Function was defined but never called in any mapping handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorder Transcoder commission fields in createOrLoadTranscoder to
match the schema.graphql field order for easier cross-referencing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a transcoder is reactivated after being inactive, the previous
round's pool entity doesn't exist (no pool is created for inactive
rounds). Previously this defaulted cumulative factors to zero, losing
the factor history. Now mirrors the contract's latestCumulativeFactorsPool()
by falling back to the lastRewardRound pool.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The orchestrator's staked commission earns its proportional share of
delegator fees, matching the contract's updateTranscoderWithFees which
adds both transcoderCommissionFees and transcoderRewardStakeFees to
cumulativeFees.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation

When a transcoder reactivates after being inactive, the previous round's
pool doesn't exist, causing prevCRF to default to PRECISE_PERC_DIVISOR.
The current pool's cumulativeRewardFactor was already correctly propagated
from lastRewardRound during newRound, so use it as the fallback.

Also adds lastFeeRound follow-up as #230 for full parity with the
contract's independent lastRewardRound/lastFeeRound lookups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deduplicate shares computation and snapshot creation that was repeated
across bond, unbond, and rebond handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eRound

Three fixes from protocol review:

1. Re-snapshot activeCumulativeRewards in the reward handler (mirrors
   contract's updateTranscoderWithRewards line 1490) instead of relying
   solely on the round-start snapshot. Fixes incorrect values when
   claims happen between round start and reward().

2. Fee handler uses pendingRewardCommission directly when reward()
   hasn't been called yet (mirrors contract line 339), and
   activeCumulativeRewards after reward() has been called.

3. Add lastFeeRound to Transcoder entity for independent CRF/CFF
   fallback on reactivation, matching the contract's separate
   lastRewardRound/lastFeeRound lookups in latestCumulativeFactorsPool.

Also renames transcoderCommission to transcoderCommissionRewards to
match contract naming.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These follow the existing patterns in helpers.ts (percOf/precisePercOf
for math, createOrLoad* for entity creation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

2 participants