Platform: Code4rena
Start Date: 11/12/2023
Pot Size: $90,500 USDC
Total HM: 29
Participants: 127
Period: 17 days
Judge: TrungOre
Total Solo HM: 4
Id: 310
League: ETH
Rank: 126/127
Findings: 1
Award: $3.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0xadrii, 0xaltego, 0xdice91, 0xivas, 0xpiken, Akali, AlexCzm, Chinmay, DanielArmstrong, HighDuty, Infect3d, Inference, KupiaSec, PENGUN, SECURITISE, Stormreckson, SweetDream, TheSchnilch, Timeless, Varun_05, XDZIBECX, alexzoid, asui, beber89, btk, carrotsmuggler, cats, cccz, developerjordy, ether_sky, grearlake, imare, jasonxiale, kaden, klau5, santipu_, serial-coder, sl1, smiling_heretic, stackachu, wangxx2026, whitehat-boys
3.0466 USDC - $3.05
in the the getRewards function user slashing logic is executed based on an uninitialized UserStake struct. As a result, users may be falsely considered slashed before any actual loss, erroneously affecting staking rewards and other related functionalities. and that cause the incorrect variable initialization , the The variable userStake is used to compare with lastGaugeLoss before actually fetching the UserStake struct from _stakes[user][term] and This causes the comparison if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) to use a default-initialized UserStake object, which has the lastGaugeLoss field default to 0 and this problem can result in all users being considered slashed (slashed = true), as lastGaugeLoss is usually non-zero (once any gauge loss occurs) and will always be greater than the default 0 value of an uninitialized userStake.lastGaugeLoss. This could lead to incorrect logic where users are considered slashed incorrectly, affecting the functionality of reward distribution or other contract behaviors. here is the vulenrbale part :
function getRewards( address user, address term ) public returns ( uint256 lastGaugeLoss, // GuildToken.lastGaugeLoss(term) UserStake memory userStake, // stake state after execution of getRewards() bool slashed // true if the user has been slashed ) { bool updateState; lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term); if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) { slashed = true; } // if the user is not staking, do nothing userStake = _stakes[user][term]; if (userStake.stakeTime == 0) return (lastGaugeLoss, userStake, slashed); // compute CREDIT rewards ProfitManager(profitManager).claimRewards(address(this)); // this will update profit indexes uint256 _profitIndex = ProfitManager(profitManager) .userGaugeProfitIndex(address(this), term); uint256 _userProfitIndex = uint256(userStake.profitIndex); if (_profitIndex == 0) _profitIndex = 1e18; if (_userProfitIndex == 0) _userProfitIndex = 1e18; uint256 deltaIndex = _profitIndex - _userProfitIndex; if (deltaIndex != 0) { uint256 creditReward = (uint256(userStake.guild) * deltaIndex) / 1e18; uint256 guildReward = (creditReward * rewardRatio) / 1e18; if (slashed) { guildReward = 0; } // forward rewards to user if (guildReward != 0) { RateLimitedMinter(rlgm).mint(user, guildReward); emit GuildReward(block.timestamp, user, guildReward); } if (creditReward != 0) { CreditToken(credit).transfer(user, creditReward); } // save the updated profitIndex userStake.profitIndex = SafeCastLib.safeCastTo160(_profitIndex); updateState = true; } // if a loss occurred while the user was staking, the GuildToken.applyGaugeLoss(address(this)) // can be called by anyone to slash address(this) and decrement gauge weight etc. // The contribution to the surplus buffer is also forfeited. if (slashed) { emit Unstake(block.timestamp, term, uint256(userStake.credit)); userStake = UserStake({ stakeTime: uint48(0), lastGaugeLoss: uint48(0), profitIndex: uint160(0), credit: uint128(0), guild: uint128(0) }); updateState = true; } // store the updated stake, if needed if (updateState) { _stakes[user][term] = userStake; } }
need to Retrieve the userStake data from contract storage before performing any logic comparisons as :
userStake = _stakes[user][term]; lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term); if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) { slashed = true; }
Other
#0 - c4-pre-sort
2023-12-29T15:08:31Z
0xSorryNotSorry marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-12-29T15:08:45Z
0xSorryNotSorry marked the issue as duplicate of #1164
#2 - c4-judge
2024-01-28T20:17:34Z
Trumpero marked the issue as satisfactory