#41521 [SC-Critical] Unstaked tokens incorrectly counted as rewards during vesting period
Was this helpful?
Was this helpful?
Submitted on Mar 16th 2025 at 07:55:16 UTC by @merlinboii for
Report ID: #41521
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
An accounting error in StakeV2
causes unstaked tokens in vesting to be incorrectly counted as rewards. This could lead to unintended loss of funds for stakers and potential insolvency for the protocol.
The accumulatedDeptRewardsYeet()
function does not account for unstaked tokens during the vesting period:
Since totalSupply
is reduced immediately for the unstakeAmount
at startUnstake()
, while the unstaked amount remains in the contract during vesting, accumulatedDeptRewardsYeet()
mistakenly includes this unstaked amount as part of the rewards. Consequently, when executeRewardDistributionYeet()
is called, these mistakenly counted rewards are distributed to stakers, leading to an unintended loss of funds.
Consider the following scenario:
0. Users A and B each stake 100e18 YEET
- Contract State:
- YEET.balance(StakeV2)
: 200e18
- totalSupply
: 200e18
- Real rewards: 0
B starts unstaking 100e18 YEET
Contract State:
YEET.balance(StakeV2)
: 200e18 (unchanged)
totalSupply
: 100e18 (reduced by B's unstake)
Tokens in vesting: 100e18 (B's unstaking amount)
Manager Distributes "Rewards":
accumulatedDeptRewardsYeet()
returns: 200e18 - 100e18 = 100e18
These rewards (actually B's vesting tokens) are distributed
Contract State:
YEET.balance(StakeV2)
: 100e18
totalSupply
: 100e18
Tokens distributed as "rewards": 100e18
B finalizes unstake at vesting period ends and withdraws their 100e18 YEET
Final Contract State:
YEET.balance(StakeV2)
: 0
totalSupply
: 100e18 (A's stake)
Result: A's stake becomes unbacked by tokens
This scenario demonstrates the following impacts:
User A suffers a complete loss of their 100e18 YEET stake as they cannot unstake due to insufficient contract balance
The protocol becomes technically insolvent as it owes User A 100e18 YEET but has 0 balance
The lack of vesting token tracking allows the manager to unknowingly distribute vesting tokens as rewards through accumulatedDeptRewardsYeet()
, creating a HIGH likelihood.
This issue qualifies as Direct theft of any user funds
because:
Stakers lose their entire principal investment
The loss occurs while funds are at-rest in the staking contract
The vulnerability allows one user (B) to profit at the expense of another (A)
https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol#L247-L262 https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol#L148-L150
Users A and B each stake 100e18 YEET
Contract State:
YEET.balance(StakeV2)
: 200e18
totalSupply
: 200e18
Real rewards: 0
B starts unstaking 100e18 YEET
Contract State:
YEET.balance(StakeV2)
: 200e18 (unchanged)
totalSupply
: 100e18 (reduced by B's unstake)
Tokens in vesting: 100e18 (B's unstaking amount)
Manager Distributes "Rewards":
accumulatedDeptRewardsYeet()
returns: 200e18 - 100e18 = 100e18
These rewards (actually B's vesting tokens) are distributed
Contract State:
YEET.balance(StakeV2)
: 100e18
totalSupply
: 100e18
Tokens distributed as "rewards": 100e18
B finalizes unstake at vesting period ends and withdraws their 100e18 YEET
Final Contract State:
YEET.balance(StakeV2)
: 0
totalSupply
: 100e18 (A's stake)
Result: A's stake becomes unbacked by tokens