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.
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).