# StakingModule's stakedByAt() can report erroneous values

StakingModule's stakedByAt() returns only the first checkpoint of a block, so multiple stakes in one block yield understated historical balances, biasing downstream vault decisions.

- Payout: Contest reward
- Severity: medium
- Program: Telcoin
- Credited: hyh
- Disclosed: 2023-02-15
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-02-telcoin-judging/issues/36
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-02-telcoin%3AM-4
- Tags: domain:web3 impact:integrity kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** StakingModule.sol staking contract (Checkpoints-based accounting)
**Root cause:** _stake() lacks checkpointProtection, so multiple stakes in one block push checkpoints; Checkpoints.getAtBlock returns the first checkpoint of the block, not the final state.
**Steps:**
1. Call `stake()` twice for the same account within one block.
2. Each call pushes a checkpoint via `_stakes[account].push(stakedAfter)`.
3. Call `stakedByAt(account, block.number)` or `balanceOfAt(...)`; Checkpoints returns the first checkpoint of that block.
4. Observe the reported amount reflects only the first stake, understating actual balance.
**How found:** Reviewer noticed checkpointProtection was applied to exit functions but not _stake(), and reasoned that Checkpoints returns the first checkpoint of a block, so multiple same-block stakes produce stale readings.
**Exploitation:** A downstream vault using StakingModule as a strategy reads balanceOfAt()/stakedByAt() after multiple same-block deposits; the understated value biases vault decisions, potentially causing user losses.
**Insight:** When a contract uses Checkpoints for historical balances, audit every state-mutating path for same-block multi-write; a guard on exits but not entries still leaves historical reads stale.
