#41938 [SC-Critical] Unstake process manipulation and reward distribution vulnerability
Was this helpful?
Was this helpful?
Submitted on Mar 19th 2025 at 14:07:25 UTC by @pontifex for
Report ID: #41938
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
The StakeV2.accumulatedDeptRewardsYeet
function wrongly considers tokens locked for the VESTING_PERIOD
as the accumulated rewards. So locked tokens can be distributed to the vault and then claimed as rewards. This causes that part of users can't unstake the full amount of their staked tokens.
The StakeV2
tracks the virtual amount of staked tokens in the totalSupply
variable:
The contract owner can distribute excess rewards to the vault via the executeRewardDistributionYeet
function. This function calculates the distributed amount as a difference between the contract balance an the totalSupply
variable:
The problem is that when users start the unstake process the totalSupply
variable is also decreased by the unStakeAmount
, but the contract balance is not changed.
This way the unStakeAmount
value can be distributed via the executeRewardDistributionYeet
function.
When users finalize the unstake process the contract balance becomes less than the totalSupply
and users' stakes becomes undercollateralized.
I suggest tracking locked amounts in a separate variable and taking it into account in the accumulatedDeptRewardsYeet
function.
Loss of user funds: Users may not be able to retrieve their staked tokens, resulting in a loss of funds. Undercollateralization: The contract's balance becomes less than the totalSupply, causing users' stakes to become undercollateralized.
https://github.com/immunefi-team/audit-comp-yeet/blob/da15231cdefd8f385fcdb85c27258b5f0d0cc270/src/StakeV2.sol#L149 https://github.com/immunefi-team/audit-comp-yeet/blob/da15231cdefd8f385fcdb85c27258b5f0d0cc270/src/StakeV2.sol#L255
Suppose the totalSupply
is 1000 tokens stacked by innocent users.
Suppose there are 100 tokens as accumulated rewards. So the stakingToken.balanceOf(address(this))
is 1100.
An attacker stakes another 1000 tokens.
Now the totalSupply
is 2000 tokens and the stakingToken.balanceOf(address(this))
is 2100.
The attacker starts the unstake process for 500 tokens.
Now the totalSupply
is 1500 tokens and the stakingToken.balanceOf(address(this))
is still 2100.
The attacker waits for the moment when the contract owner distributes excess rewards (100) and locked (500) to the vault.
Now the totalSupply
is 1500 tokens and the stakingToken.balanceOf(address(this))
is 1500.
Then the attacker finalizes the unstake process for the first 500 tokens.
Now the totalSupply
is 1500 tokens and the stakingToken.balanceOf(address(this))
is 1000.
Then the attacker claims rewards and receives about 500 / 1500 part of distributed rewards (600), i.e. ~200 tokens.
Then the attacker starts the unstake process for another 500 tokens and finalizes it after the VESTING_PERIOD
.
Now the totalSupply
is 1000 tokens and the stakingToken.balanceOf(address(this))
is 500.
The attacker has profit and the totalSupply
is undercollateralized.