#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::accumulatedDeptRewardsYeetis 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::startUnstaketo start unstaking, their balance is still in the contract but subtracted from thetotalSupply, soStakingV2::accumulatedDeptRewardsYeetmistakes the unstake amount as undistributed rewards.
Vulnerability Details
When the manager would want to distribute rewards through
StakingV2::executeRewardDistributionYeet, he would first call theStakingV2::accumulatedDeptRewardsYeetto 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::unstaketo 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::startUnstakehis 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::staketo stake 100e18 Yeet.Bob(another staker) calls
StakingV2::staketo 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?