57298 sc critical state sync omission in staking transfers forces transferred slong holders into penalized emergency exits

  • Submitted on: Oct 25th 2025 at 04:23:25 UTC by @InquisitorScythe for Audit Comp | Belongarrow-up-right

  • Report ID: #57298

  • Report Type: Smart Contract

  • Severity: Critical

  • Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol

  • Impact category: Griefing (no profit motive required; harms users/protocol)

Description

Brief / Intro

The staking vault mints and allows free transfer of sLONG (ERC4626-style shares), but it does not synchronize the per-account stake ledger on transfers. A recipient who obtains sLONG via ERC20 transfer receives shares (ERC20 balance) but has no corresponding unlocked stake entries in the contract's per-account ledger. Such recipients cannot pass the normal withdrawal checks and are forced to use the emergency withdrawal path, which burns shares and applies a permanent penalty routed to the treasury.

Root cause

The per-account stake ledger:

  • Is declared as mapping(address staker => Stake[] times) public stakes;

  • Is appended to only on deposits (via _deposit)

  • Is never reconciled or updated on ERC20 transfers (no transfer hooks or overrides)

Thus, transferred-in sLONG holders have token balances but zero stake entries, so they cannot consume "unlocked" stakes via the normal withdrawal flow and are blocked into emergency withdrawals with penalties.

Vulnerability Details (selected snippets)

  • File: contracts/v2/periphery/Staking.sol

  • stakes declaration:

  • _deposit — locks freshly minted shares on deposit:

  • _withdraw — requires consumption of unlocked stake entries:

  • _consumeUnlockedSharesOrRevert — iterates per-account stake entries and reverts if not enough unlocked shares:

  • _emergencyWithdraw — burns shares regardless of lock status and applies penalty:

Note: line ranges cited in the report correspond to the repository's current main branch at the time of reporting.

Impact (concise)

  • Transfers only move ERC20 balances and do not update stakes[...]. Recipients have shares but no stake entries.

  • Attempting a standard withdraw/redeem calls _consumeUnlockedSharesOrRevert and reverts with MinStakePeriodNotMet because the recipient has no unlocked stake entries.

  • Recipients are forced to use emergencyWithdraw/emergencyRedeem, which burns shares, applies the configured penalty (default 10%), and sends the penalty to treasury.

  • Monetary impact: transferred-in holders permanently lose a fraction of their assets proportional to penaltyPercentage. This can be arbitrarily large depending on transferred amount.

  • Attack surface: any integration or flow that transfers sLONG balances (e.g., auto-stake, rewards routing, third-party transfers) can inadvertently subject recipients to penalized exits.

References

  • Vulnerable file in related repo: https://github.com/belongnet/checkin-contracts/blob/main/contracts/v2/periphery/Staking.sol

Proof of Concept

Below is the Hardhat test PoC used to reproduce the issue: depositor stakes LONG, transfers the minted sLONG to a recipient, and the recipient cannot perform normal redeem — only emergency redeem succeeds (with penalty).

File: test/v2/platform/staking-transfer-poc.test.ts

Example run:

(Full console output and test artifacts omitted for brevity.)


If you want, I can:

  • Suggest minimal code fixes or design approaches to keep per-account stake consistency on transfers (e.g., hooks updating stakes on transfer, or migrating stakes on transfer/delegation).

  • Draft a patch/PR against the referenced repository demonstrating a fix.

Was this helpful?