For the complete documentation index, see llms.txt. This page is also available as Markdown.

68880 sc insight missing reward parameter in staked event breaks off chain accounting

Submitted on Mar 11th 2026 at 19:35:35 UTC by @Oxb4b for Audit Comp | Folks Finance: Staking Contracts

  • Report ID: #68880

  • 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

The Staked event emits the deposited amount but omits the calculated reward value. This creates an observability gap where off-chain systems (indexers, dashboards, analytics) cannot accurately reconstruct user rewards from event logs alone. When staking period parameters are updated between stake execution and off-chain processing, the reconstructed reward will differ from the actual on-chain value, leading to incorrect accounting and user-facing data.

Vulnerability Details

Affected Code: Staking.sol, _stake() function

Current Implementation:

// Reward calculated using CURRENT period parameters
uint256 reward = (amount * stakingPeriod.aprBps * stakingPeriod.stakingDurationSeconds) / rewardBpsDenominator;

// Reward stored immutably in UserStake
userStakes[msg.sender].push(
    UserStake({
        amount: amount,
        reward: reward,  // ← Stored on-chain
        claimedAmount: 0,
        claimedReward: 0,
        aprBps: stakingPeriod.aprBps,
        stakeTime: uint64(block.timestamp),
        unlockTime: uint64(block.timestamp) + stakingPeriod.stakingDurationSeconds,
        unlockDuration: stakingPeriod.unlockDurationSeconds
    })
);

// Event emission WITHOUT reward
emit Staked(msg.sender, periodIndex, params.referrer, stakeIndex, amount);
//                                                                 ^^^^^^
//                                                                 Missing: reward

Root Cause: The Staked event signature (defined in IStakingV1.sol) is:

Attack Path / Observability Failure Scenario:

  1. T₀: User stakes 10 ETH with Period 0 (APR = 50%, duration = 30 days)

    • On-chain: reward = 10 * 5000 * 30 days / (1e4 * 365 days) ≈ 0.4109 ETH stored in UserStake

    • Event emitted: Staked(user, 0, referrer, 0, 10 ether) — no reward field

  2. T₁: Manager updates Period 0 parameters (legitimate governance action)

    • Period 0 APR changed from 50% → 10%

    • StakingPeriodUpdated event emitted

  3. T₂: Off-chain indexer processes Staked event from T₀

    • Indexer reads current Period 0 parameters via RPC: aprBps = 1000 (10%)

    • Indexer reconstructs: reward = 10 * 1000 * 30 days / (1e4 * 365 days) ≈ 0.0821 ETH

    • Discrepancy: 0.0821 ETH (reconstructed) vs 0.4109 ETH (actual) — 80% error

  4. Impact: User dashboard shows incorrect reward, audit trails fail, accounting mismatch

Impact and Likelihood Details

Impact: Low — No on-chain funds at risk, no user loss, no exploitable attack vector

Affected Systems:

  • Off-chain indexers (TheGraph subgraphs, custom backends)

  • User-facing dashboards showing staking positions

  • Analytics platforms tracking protocol metrics

  • Accounting/audit systems reconciling rewards

  • Mobile/light clients unable to make RPC calls

Likelihood: High — Occurs whenever:

  • Staking periods are updated (routine governance operation)

  • Network congestion delays event processing

  • Indexer catches up on historical events after period updates

https://gist.github.com/tharunbethina/497b3263c659393a585118e2dbf2c74c

Proof of Concept

  1. User stakes 10 ETH in Period 0 (50% APR, 30-day duration)

  2. On-chain reward calculated and stored: 0.4109 ETH

  3. Manager updates Period 0 APR to 10% (legitimate governance)

  4. Off-chain indexer reconstructs reward from event data: 0.0821 ETH (uses updated APR)

  5. Assertion: reconstructedReward ≠ actualReward → 80% discrepancy

Foundry Testcase gist:

https://gist.github.com/tharunbethina/497b3263c659393a585118e2dbf2c74c

Was this helpful?