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: 52/127
Findings: 2
Award: $252.27
🌟 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
After a lending term (gauge) incurs a loss, the SurplusGuildMinter.getRewards()
function always returns slashed
= true, even when this term is deprecated and then onboarded again. This results in users losing their assets if they stake in this term afterward because they will always be slashed in this term.
In the SurplusGuildMinter.getRewards
function, userStake is a new memory struct with userStake.lastGaugeLoss
set to 0. Therefore, if this term incurred a loss in the past, GuildToken(guild).lastGaugeLoss(term)
always returns a non-zero value. Consequently, lastGaugeLoss
will be greater than userStake.lastGaugeLoss
, causing slashed to always be true.
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; } ...
Additionally, even when a term is deprecated, the lastGaugeLoss
of this term is still saved in the storage of the GuildToken contract. Therefore, it will always return a non-zero value, and users who stake in a re-added lending term will always be slashed.
Manual review
getRewards
function should load from _stakes[user][term]
before validation of slashed
variable.
lastGaugeLoss = GuildToken(guild).lastGaugeLoss(term); userStake = _stakes[user][term]; if (lastGaugeLoss > uint256(userStake.lastGaugeLoss)) { slashed = true; }
Context
#0 - c4-pre-sort
2023-12-29T14:46:09Z
0xSorryNotSorry marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-12-29T14:46:41Z
0xSorryNotSorry marked the issue as duplicate of #1164
#2 - c4-judge
2024-01-28T20:10:31Z
Trumpero marked the issue as satisfactory
🌟 Selected for report: critical-or-high
Also found by: 0xadrii, 0xpiken, Akali, Chinmay, Tendency, serial-coder
249.2197 USDC - $249.22
The SurplusGuildMinter.claimRewards
function loops and claims all rewards from active lending terms delegated by this contract. Consequently, this function can run out of gas and revert, potentially causing a DoS state on other functions of SurplusGuildMinter. This situation may occur when the market has numerous lending terms, and users stake in many of them.
In the SurplusGuildMinter contract, getRewards
function is used to claim and transfer rewards from a lending term (gauge) to the user. However, it calls the claimRewards
function from the ProfitManager contract, which loops and claims rewards from all delegated lending terms of this contract. That action is unnecessary because it only needs to claim the reward of a specific term, resulting in the risk of reverting due to running out of gas for this function.
function getRewards( address user, address term )... { ... ProfitManager(profitManager).claimRewards(address(this)); ... } function claimRewards( address user ) external returns (uint256 creditEarned) { address[] memory gauges = GuildToken(guild).userGauges(user); for (uint256 i = 0; i < gauges.length; ) { creditEarned += claimGaugeRewards(user, gauges[i]); unchecked { ++i; } } }
Manual review
getRewards
function should call claimGaugeRewards
function instead of claimRewards
:
ProfitManager(profitManager).claimGaugeRewards(address(this), term);
DoS
#0 - c4-pre-sort
2024-01-04T20:03:25Z
0xSorryNotSorry marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-01-04T20:03:38Z
0xSorryNotSorry marked the issue as duplicate of #1110
#2 - c4-judge
2024-01-28T20:23:04Z
Trumpero marked the issue as satisfactory