Staking exposes getUserStake to read raw UserStake storage fields, but provides no external view function that computes how much a user can currently withdraw. The internal accrual logic lives entirely in _getAccrued, an internal pure function that is inaccessible to any external caller. Every off-chain consumer — frontend, monitoring bot, aggregator, or composing smart contract — must independently replicate the accrual formula to compute claimable amounts.
Vulnerability Details
The accrual formula is implemented once, in _getAccrued:
It is called exclusively inside _withdraw, which also applies the pre-conditions that gate a valid withdrawal:
IStakingV1 exposes only raw storage reads:
To derive the currently claimable amounts from these, an external caller must independently reconstruct the full computation — including the pre-conditions and the Math.mulDiv + Math.min formula:
This is non-trivial to replicate correctly — Math.mulDiv uses full 512-bit intermediate multiplication to avoid overflow — and there is no interface-level contract that these externally-replicated formulas will remain valid across contract upgrades.
Impact Details
Impact category: Architectural Decentralization and Composability
Add getClaimable and getClaimableAll to IStakingV1 and implement them in Staking. Storage fields are cached into locals to avoid repeated warm SLOADs. getClaimableAll delegates to getClaimable to avoid duplicating the per-stake logic:
This surfaces the authoritative computation through the interface with no additional logic risk, and ensures any future formula change is automatically reflected to all callers.