Skip to content

feat(x/distribution): auto-stake bond denom rewards#93

Draft
giunatale wants to merge 16 commits into
mainfrom
giunatale/feat/auto-stake-rewards
Draft

feat(x/distribution): auto-stake bond denom rewards#93
giunatale wants to merge 16 commits into
mainfrom
giunatale/feat/auto-stake-rewards

Conversation

@giunatale

Copy link
Copy Markdown
Collaborator

This PR adds auto-staking of bond denom rewards.

The feature is implemented editing the allocation logic to divert bond denom rewards directly to the bonded pool, essentially resulting in an increase of shares' exchange rate. This is achieved by adding a AddValidatorTokens to x/staking/keeper/validator.go.
Performance impact should be acceptable as it adds a handful of KV operations but overall reward distribution complexity remains unchanged wrt to pure F1 (O(validators))

giunatale added 6 commits May 7, 2026 15:07
Adds a new keeper method in x/staking that increases a bonded validator's token balance without issuing new delegator shares, raising the per-share exchange rate for all existing delegators proportionally.
Modifies the allocation logic in x/distribution to route the bond denom (minus validator commission) through the auto-staking path while commission and other coins go through F1.
@giunatale giunatale force-pushed the giunatale/feat/auto-stake-rewards branch from 67f756f to 5660536 Compare May 7, 2026 16:53
giunatale added 6 commits May 7, 2026 19:55
The tokens-based F1 algorithm computes rewards-per-token using
val.GetTokens() and applies slash events to scale stake by (1-fraction).
This works when sum(startingStakes) == val.Tokens, but auto-staking
breaks that invariant: AddValidatorTokens raises val.Tokens without
issuing new shares, so per-token ratio dilutes existing delegators.
A sole self-delegator who should receive 100% of non-bond rewards
instead got rewards × old_tokens / new_tokens, with the residual
accumulating as unclaimable F1 dust on the validator.

Switching the ratio to rewards-per-share (val.GetDelegatorShares())
and storing the delegator's share count in startingInfo.Stake makes
the calculation invariant to per-share exchange-rate changes from
both auto-staking and slashing. Slash event handling in
calculateDelegationRewardsBetween becomes unnecessary - slashes
naturally reduce future rewards via reduced voting power, and the
shares-based ratio distributes whatever was earned proportionally.
@giunatale

Copy link
Copy Markdown
Collaborator Author

A failing integration test raised an issue I initially missed (copying from commit comment):

The tokens-based F1 algorithm computes rewards-per-token using val.GetTokens() and applies slash events to scale stake by (1-fraction). This works when sum(startingStakes) == val.Tokens, but auto-staking breaks that invariant: AddValidatorTokens raises val.Tokens without issuing new shares, so per-token ratio dilutes existing delegators. A sole self-delegator who should receive 100% of non-bond rewards instead got rewards × old_tokens / new_tokens, with the residual accumulating as unclaimable F1 dust on the validator.

Switching the ratio to rewards-per-share (val.GetDelegatorShares()) and storing the delegator's share count in startingInfo.Stake makes the calculation invariant to per-share exchange-rate changes from both auto-staking and slashing. Slash event handling in calculateDelegationRewardsBetween becomes unnecessary - slashes naturally reduce future rewards via reduced voting power, and the shares-based ratio distributes whatever was earned proportionally.

Had to also update the migration code (which is now more invasive since it has to perform the token-based -> share-based migration) and the migration test is now importing testutil/integration as it's de facto an integration test (requires real keepers)

giunatale added 4 commits May 8, 2026 15:41
there was no actual need to force-claim the commission at migration,
therefore this part of the migration code is here removed.
@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions github-actions Bot added the Stale label Jun 10, 2026
@giunatale giunatale removed the Stale label Jun 10, 2026
@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions github-actions Bot added the Stale label Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant