57932 sc critical attacker can bypass stake lock

Submitted on Oct 29th 2025 at 14:06:41 UTC by @danial for Audit Comp | Belongarrow-up-right

  • Report ID: #57932

  • Report Type: Smart Contract

  • Report severity: Critical

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

  • Impacts:

    • Protocol insolvency

Description

Brief/Intro

An attacker can remove their assets while keeping large locked stakes untouched, effectively bypassing the minimum stake period and unlocking funds early. This can break internal accounting and cause protocol insolvency.

Vulnerability Details

Each deposit creates a new locked stake record:

stakes[to].push(Stake({shares: shares, timestamp: block.timestamp}));

Withdrawal uses _consumeUnlockedSharesOrRevert() to ensure minStakePeriod has passed. However, in emergencyWithdraw(), the contract calls:

_removeAnySharesFor(_owner, shares);
_burn(_owner, shares);

The function _removeAnySharesFor removes stake records:

The issue arises when the attacker holds multiple stakes and relies on transfers not creating stake entries. The sequence below demonstrates how an attacker can bypass the lock while keeping stake records intact.

1

Attacker setup & initial deposits

  • Wallet A deposits 100e18 → stakes[A] = {100, tA}.

  • Wallet B deposits 2e18 → stakes[B] = {2, tB}.

2

Transfer shares without creating stake record

  • Attacker transfers 100e18 shares from A → B (transfer does not create a stake entry).

  • State: stakes[A] still {100, tA}; stakes[B] has {2, tB} and balance includes transferred 100 shares.

3

Emergency withdraw consumes transferred shares

  • B calls emergencyWithdraw(102e18, to=B, owner=B)

  • _removeAnySharesFor removes {2,tB} and consumes transferred shares, paying ~90% (~92e18) to B. stakes[A] entry remains.

  • Attacker now holds ~92e18 tokens and can use these tokens near expiry while having the stake records.

4

Reuse stake records to withdraw locked stake + rewards later

  • Near original lock expiry, Wallet C deposits 100e18 → stakes[C] = {100, tC}.

  • Attacker transfers those 100 shares from C → A (transfer creates no stake entry).

  • Now A’s balance includes 100 shares, while stakes[A] still records the old {100, tA}.

  • Attacker withdraws from A after lock period and redeems 100e18 + rewards.

5

Result

  • Attacker extracts rewards while not actually locking the assets during the lock time, gaining staking benefits unfairly and breaking intended stake logic.

Impact Details

Attacker can transfer shares to get most of their tokens with minimal penalty while keeping stake records intact, then later get rewards without respecting the lock. This undermines the contract’s intended staking behavior and can lead to protocol insolvency.

Mitigation

Suggested mitigation (disable transfers of shares):

Proof of Concept

Note: for simplicity, this PoC is not using a proxy (the constructor in Staking.sol can be commented out for testing).

Run the test with: forge test -vv

Was this helpful?