#41894 [SC-Critical] Incorrect calculation of deposited rewards yeet leads to Staker's not being able to get their staked amount back
Submitted on Mar 19th 2025 at 08:41:12 UTC by @Oxgritty for Audit Comp | Yeet
Report ID: #41894
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol
Impacts:
Protocol insolvency
Description
Brief/Intro
StakingV2::accumulatedDeptRewardsYeet
is used to calculate the amount of accumulated rewards in yeet that are not distributed yet.The problem here is that, this function doesn't account for the fact that when a staker calls
StakingV2::startUnstake
to start unstaking, their balance is still in the contract but subtracted from thetotalSupply
, soStakingV2::accumulatedDeptRewardsYeet
mistakes the unstake amount as undistributed rewards.
Vulnerability Details
When the manager would want to distribute rewards through
StakingV2::executeRewardDistributionYeet
, he would first call theStakingV2::accumulatedDeptRewardsYeet
to know the amount available for distribution. This function would return an amount which includes the unStakeAmount, which staker unstaked by callingStakingV2::startUnstake
.When the manager will call
StakingV2::executeRewardDistributionYeet
, it will convert (undistributed rewards + unStakeAmount) intovaultShares
, meaning when the staker would callStakingV2::unstake
to get his unStakeAmount amount back, this contract won't have enough unStakeAmount of stakingToken and function will revert.
Impact Details
Stakers not being able to withdraw their staked amount as the contract will not have enough funds.
References
When a staker calls
StakingV2::startUnstake
his unstake amount will be deducted from thetotalSupply
.This unstake amount is not deducted in
StakingV2::accumulatedDeptRewardsYeet
.
Proof of Concept
POC: (NOTE: StakingToken is YEET)
Step 0: Checking Current Balances
totalSupply = 0
stakingToken.balanceOf(address(this)) = 0
Step 1: Staking
Alice(a staker) calls
StakingV2::stake
to stake 100e18 Yeet.Bob(another staker) calls
StakingV2::stake
to stake 50e18 Yeet.
Current Balances:
totalSupply = 150e18
stakingToken.balanceOf(address(this)) = 150e18
Step 2: Initiating Unstaking process
Alice initiates unstaking process by calling StakingV2::startUnstake
where unStakeAmount=100e18
Current Balances:
totalSupply = 50e18
stakingToken.balanceOf(address(this)) = 150e18
Step 3: Manager checks amount of stakingToken available for distribution by calling StakingV2::accumulatedDeptRewardsYeet
. This function will return 100e18(150e18 - 50e18).
StakingV2::accumulatedDeptRewardsYeet
. This function will return 100e18(150e18 - 50e18).Step 4: Manager calls StakingV2::executeRewardDistributionYeet
to distribute yeet rewards.
StakingV2::executeRewardDistributionYeet
to distribute yeet rewards.Here the value of (stakingParams.amount0Max + swapData.inputAmount would be equal to 100e18). After the execution of this function, the contract would have 100e18 yeet worth of vault Shares and 50e18 Yeet.
Current Balance:
totalSupply = 50e18
stakingToken.balanceOf(address(this)) = 50e18
Step 5: Finalize unstaking and get unStakeAmount back
Alice calls StakingV2::unstake
to get her unStakeAmount(100e18) back.
This function will fail because StakingV2.sol has only 50e18 Yeet.
Was this helpful?