69245 sc insight no view function to compute current claimable amounts
Submitted on Mar 13th 2026 at 19:17:53 UTC by @ZenHunter for Audit Comp | Folks Finance: Staking Contracts
Report ID: #69245
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol
Impacts:
Description
Brief/Intro
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:
// src/Staking.sol#L332-L334
function _getAccrued(uint256 amount, uint256 duration, uint256 elapsed) internal pure returns (uint256) {
return Math.mulDiv(amount, Math.min(elapsed, duration), duration);
}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
References
Accrual logic:
src/Staking.sol#L332–L334Only call site:
src/Staking.sol#L315–L321Interface view functions:
src/interfaces/IStakingV1.sol#L111–L114
Recommendation
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.
Proof of Concept
File: test/NoClaimableView.t.sol
Command:
Output:
Was this helpful?