57813 sc critical transfer recipients will pay unwarranted emergency withdrawal penalties for share positions they legitimately own
Submitted on Oct 29th 2025 at 01:41:03 UTC by @Lavender88 for Audit Comp | Belong
Report ID: #57813
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol
Impacts: Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Missing stake-tracking updates on ERC20 transfers can force transfer recipients to use emergency withdrawal (10% penalty) even after waiting the full minStakePeriod.
Description
Brief / Intro
The missing stake tracking updates on ERC20 transfers will cause a 10% penalty loss for share recipients: they cannot use normal withdrawal even after waiting the full minStakePeriod because the stakes[] array remains empty for transferred shares.
Vulnerability Details
In Staking.sol, the ERC20 transfer() function inherited from Solady's base implementation does not update the stakes[] mapping. When shares are transferred:
balanceOf[]correctly updates (sender decreases, recipient increases)stakes[]does NOT update (recipient remains with empty array)
This desynchronization causes _consumeUnlockedSharesOrRevert() to fail for transfer recipients even after the lock period expires, forcing them into the emergency withdrawal path with its 10% penalty.
Preconditions / Reproduction Steps
Attack Path (example scenario)
Scenario: Innocent Transfer Recipient (Bob) Gets Griefed
Alice deposits 1000 LONG tokens via
deposit(), receiving 1000 sharesstakes[alice] = [{shares: 1000, timestamp: T}]balanceOf[alice] = 1000Alice transfers 1000 shares to Bob via standard ERC20
transfer()balanceOf[alice] = 0balanceOf[bob] = 1000stakes[alice] = unchanged [{shares: 1000, timestamp: T}]stakes[bob] = [](empty)Bob waits full lock period (1 day passes) and calls
redeem(1000, bob, bob)Execution reaches_consumeUnlockedSharesOrRevert(bob, 1000)which loops throughstakes[bob](empty) => remaining = 1000 => REVERTS withMinStakePeriodNotMet().Bob is forced to call
emergencyRedeem(1000, bob, bob)which uses_removeAnySharesFor()and does not checkstakes[]properly. Bob pays 10% penalty: 100 LONG sent to treasury, receives 900 LONG.
Result: Bob loses 100 LONG (10% of his position) despite legitimately holding shares for the required duration.
Impact Details
Transfer recipients suffer an unwarranted 10% loss when attempting to withdraw shares they legitimately own. Examples of affected recipients:
DEX purchasers
Gift/transfer recipients
Liquidity provision reward recipients
This is griefing: damage to users without profit motive for the attacker. Treasury receives unintended penalty funds.
Mitigation
Two suggested mitigations (as proposed by the reporter):
Option 1: Update
stakes[]on ERC20 transfers to keep stake metadata in sync withbalanceOf[].Option 2: Disable transfers entirely (preventing transfer-based desynchronization).
Proof of Concept
References
Target contract: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/periphery/Staking.sol
Was this helpful?