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–L334

  • Only call site: src/Staking.sol#L315–L321

  • Interface 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?