fix(wzrd-rails): M-03 emission remainder-carry + StakePool realloc migration#116
Merged
Conversation
…gration The MasterChef accrual floored per-slot emissions to zero once total_staked exceeded ~1M base units, permanently burning rewards and stranding the matching CCM in reward_vault. The live pool (pool_id=0, 6oQDChd9...) has 3.1M CCM staked with emissions ON, so this bug is actively truncating real rewards every slot. The fix carries the truncation remainder forward across accruals (numerator = slots*rate*SCALE + reward_remainder; increment = numerator/total; remainder = numerator%total), making accrual cadence-independent and conserving emissions. This requires a new reward_remainder:u128 field on StakePool (LEN 61 -> 77). Because Anchor deserializes a typed Account<StakePool> BEFORE applying a realloc constraint (verified against anchor-syn 0.32.1 codegen), the migration IX realloc_stake_pool takes the pool as a raw UncheckedAccount, validates owner + discriminator + PDA manually, tops up rent, and resizes 61->77 with zero-init (reward_remainder = 0 genesis). It is idempotent (already-77 -> Ok) and admin-gated via Config has_one. The realloc IX and the 77-byte struct ship in the SAME binary, as required: once deployed, the seven IXs that load Account<StakePool> cannot read the old 61-byte pool until it is migrated. DEPLOY RUNBOOK (mandatory order): 1. Deploy this binary. 2. Admin runs realloc_stake_pool(pool_id=0) against 6oQDChd9... BEFORE any stake/unstake/claim/update_pool/set_reward_rate/fund_reward_pool. Those six revert on the un-migrated pool until step 2 completes. Staking is briefly unavailable between step 1 and step 2 — this is expected. 3. Verify the pool account is 77 bytes on-chain; reward_remainder reads 0. Test proof: realloc_stake_pool_migrates_legacy_pool_full_path installs a raw 61-byte pool with the live values (3.1M CCM, rate 1000), asserts update_pool FAILS pre-migration, runs the migration, confirms 77 bytes + all legacy fields preserved bit-for-bit + reward_remainder=0 + accrual carries the dust. Plus idempotency and non-admin-rejection tests. Full suite: 92 passed, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
reward_remainder: u128toStakePool— captures sub-lamport CCM emission that was previously silently discarded each epochStakePool::LEN61 → 77 bytes (withLEGACY_LEN = 61constant for migration guard)realloc_stake_pooladmin instruction that safely migrates legacy 61-byte pools to 77 bytesUncheckedAccountfor the pool account (typedAccount<StakePool>fails deserialization against the old 61-byte layout before realloc applies)Audit Finding
M-03 from the wzrd-rails security audit. Remainder loss accumulates proportionally with staker count and reward rate.
Deploy Runbook (MANDATORY — funds-touching)
This is a two-step upgrade. Pool must be migrated before any staking operations resume.
realloc_stake_pool { pool_id: 0 }against pool6oQDChd9...reward_remainder = 0Do NOT stake/unstake/claim/fund between steps 1 and 2.
Test Coverage
realloc_stake_pool_migrates_legacy_pool_full_pathrealloc_stake_pool_is_idempotent_on_fresh_poolrealloc_stake_pool_rejects_non_adminTotal: 92 tests passing, 0 failures
Notes
claude/m03-pool-migrationonto current main (squash-merge desync — original branch based on pre-squash7cbf580)Co-Authored-By: twzrd-sol 33047129+twzrd-sol@users.noreply.github.com