Skip to content

fix(staking): account for unstakes pro-rata in stake position rewards - #1067

Open
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1784934718-strsr-prorata-rewards
Open

fix(staking): account for unstakes pro-rata in stake position rewards#1067
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1784934718-strsr-prorata-rewards

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two bugs in the "Rewards" line of Your stake position.

1. Unstaking wiped rewards it should have kept. accountStakeHistoryAtom kept every stake as its own lot and matched an unstake against whole lots oldest-first, closing them outright — the rewards accrued on a closed lot were tallied into a totalRewardBalance that accountCurrentPositionAtom never read, so they disappeared. Stake 100 RSR at rate 1.0, later stake 100 more at 1.2, then unstake half your stRSR at 1.2: the oldest lot carries all 20 RSR of gains, is consumed whole, and the section shows 0 despite half the position still being staked.

stRSR is fungible, so there are no lots on chain: an unstake burns a proportional slice of the whole position. The matching loop is replaced with that, in a new stake-accounting.ts:

const remainingShare = Math.max(1 - unstakedAmount / totalStaked, 0)
lots = lots.map(({ amount, rsrAmount }) => ({
  amount: amount * remainingShare,
  rsrAmount: rsrAmount * remainingShare,
}))

Rewards stay Σ(amount * exchangeRate - rsrAmount), so unstaking x% of the position always leaves (100 - x)% of its rewards — what a single-stake account already did, now true regardless of how many times the user staked. The example above reports 10 instead of 0. Replaying all 4,760 mainnet accountStakeRecords, 37 of the 684 accounts with an open position change; the largest correction is 0xcfc0805e on eUSD, 0.78M → 1.87M RSR.

2. Rewards survived a full unstake. The subgraph drops unstake records: 11 mainnet accounts hold no stRSR on chain yet still have open stake according to their records — 0x5a1b2acd on eUSD has a single stake record and no unstake at all, and balanceOf returns 0. stake-position.tsx papered over this with if (!balance.value && rewards) rewards = 0, which only fires once the balance query resolves and does nothing for an account whose records over-report a partial amount. Instead calculateStakeRewards now reconciles against the balance:

const heldShare = Math.min(stRsrBalance / staked, 1)
return sumOfLotRewards * heldShare

This also covers the queued-unstake and cancel-unstake paths: stRSR is burnt when an unstake is queued, so RSR waiting out the cooldown is already out of both the balance and the records; and a cancel mints stRSR back, which — until the records catch up — is capped by Math.min(…, 1) rather than being credited rewards it has no cost basis for.

Also on the query: it had no first:, so it silently capped at the subgraph's default 100 records (the busiest mainnet account has 33 today), and no explicit orderDirection.

stake-calculation.test.ts tested a copy of the old algorithm pasted into the test file rather than the shipped code, so it could not catch either bug. It is replaced by stake-accounting.test.ts, which exercises the real exports against accountStakeRecords fixtures pulled from the mainnet subgraph — including the account with unrecorded unstakes — and checks the reconstructed stRSR balances against the on-chain balances of those accounts.

Link to Devin session: https://app.devin.ai/sessions/a1aa27c2297449498d9948df333d4a1b

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploying register-app with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2b60bb1
Status: ✅  Deploy successful!
Preview URL: https://a8356f71.register-app.pages.dev
Branch Preview URL: https://devin-1784934718-strsr-prora.register-app.pages.dev

View logs

taylor.brent and others added 2 commits July 24, 2026 23:18
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants