Closed c4-bot-2 closed 6 months ago
0xSorryNotSorry marked the issue as sufficient quality report
0xSorryNotSorry marked the issue as duplicate of #1211
Trumpero marked the issue as not a duplicate
Above is a applyGaugeLoss function for decerasing the weights of the users if a loss has occurred.The following function can be called by anyone.The following function should be called with the who = sgm(contract) whenever someone gets slashed so that the weight of the gauge is removed correctly, because if no one calls the following function the gaugeWeight remains same for the depreciated gauge and once the the gauge gets onboarded and if new users stake and increase the weight of the gauge then they would get less rewards because the weights of the previous users which sgm used on their behalf to increase the weights of the gauge wouldn't be reduced and gaugeProfitIndex[gauge] which is calculated as follows
I believe this issue is not a duplicate of #1194, and it should be invalid. The above statement is incorrect because the stake function will call GuildToken.incrementGauge()
for the SurplusGuildMinter contract, triggering the GuildToken._incrementGaugeWeight()
function. This function will revert if the address has an unapplied loss. Therefore, SurplusGuildMinter is unable to stake as long as it hasn't been applied loss.
Trumpero marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L114 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L216 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L392 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/governance/ProfitManager.sol#L409
Vulnerability details
Impact
Detailed description of the impact of this finding. If a credit token holder wants to stake the credit tokens so as to earn rewards in terms of credit tokens and Guild tokens,the user transfers the credit tokens to the SurplusGuildMinter contract and the guild tokens are minted to the SuprlusGuildMinter contract which on the behalf of the credit token holder increases the gauge weight of that gauge which user inputs. Now if the user is slashed then its stakes are reduced to zero whereas the gauge weight which the SurplusGuildMinter contract increased is not decreased immediately.Now a user can only be slashed if a gauge is depreciated i.e it is off boarded and it has suffered a loss.Now a depreciated can again be added using on boarding mechanism,so now again new users can stake the tokens and can earn rewards if that gauge earns profits.Issue is if the gauge weight contributed by the surplus minter is not decreased immediately after a user gets slashed it can cause the gauge weight to remain same instead it should decrease because some users were slashed so their wights should be decreased from the gauge weight, if it is not done than it can cause the rewards to the current stakers to be reduced than the actual value.And if the weights increased by the suprlusGuild minter contract are reduced after the gauge has been onbarded again it can cause the weights to reduce by more than the required value and cause more rewards been allocated to the users.
Proof of Concept
Lets start step by step 1.User calls the stake function
get rewards function is also used
IF you see the increment Gauge function it clear _incrementGaugeWeight is called with msg.sender as the sgm contract and weight equal to the guild amount minted
In the below function it is key to note getGaugeWeight is increased by weight(which is equal to the guildAmount minted) and getUserGaugeWeight[user][gauge] is also increased where user is sgm contract.
Now going back to our stake function userstake is now stored in the storage with msg.sender being the original usr who called the staked function you can see guild: userStake.guild + SafeCastLib.safeCastTo128(guildAmount) guildAmount = weight by which the gaugeweight increased and it also denotes how much of the totalVotes in the gauge a user has voted.
Now we will look how rewards are calculated
firstly the following is called ProfitManager(profitManager).claimRewards(address(this));
claim rewards function is as follows
from the above you can see it claims the credit earned from all the gauges where the sgm contract has voted i.e it has increased the weight.
claimGaugerewards function is as follows
rewards are caluclated mainly using the following
_gaugeProfitIndex = gaugeProfitIndex[gauge] is calculated as follows it happens when a profit occurs in a particular gauge
you can see from above that the gaugeProfitIndex uses _gaugeWeight = getGaugeWeight(gauge)
Now creditEarned = (_userGaugeWeight * deltaIndex) / 1e18 here _userGaugeWeight = uint256( GuildToken(guild).getUserGaugeWeight(user, gauge) i.e the weight contributed by the sgm contract in our context
if simplified creditEarned = (_userGaugeWeight ((amountForGuild 1e18) / _gaugeWeight;)) / 1e18 i.e percentage of reward for the sgm contract
Now coming back to how rewards are distributed to the stakers.
_profitIndex = ProfitManager(profitManager) .userGaugeProfitIndex(address(this), term); and userGaugeProfitIndex[user][gauge] = _gaugeProfitIndex here user is sgm contract and _gaugeProfitIndex = gaugeProfitIndex[gauge] and gaugeProfitIndex[gauge] is influenced by gaugeweight as follows (amountForGuild * 1e18) / _gaugeWeight;
now uint256 creditReward = (uint256(userStake.guild) deltaIndex) / 1e18; you can see here userStake.guild represent the weights or user which were contributed by the sgm contract on its behalf so credit rewards in other ways is calculated by how much of the total weights og gauge are contributed by this user. uint256 deltaIndex = _profitIndex - _userProfitIndex; = userGaugeProfitIndex[user][gauge]- _userProfitIndex = _gaugeProfitIndex - _userProfitIndex = (amountForGuild 1e18) / _gaugeWeight (equivalent)
so creditReward = (uint256(userStake.guild) ((amountForGuild 1e18) / _gaugeWeight)) / 1e18;
Now if a user gets slashed following is done in the getRewards function
The above updates the userStake .It can be seen that guild is reduced to zero for the user but there is no decrement of weights of that particular gauge , where sgm increased the weights on the behalf of user.
Above is a applyGaugeLoss function for decerasing the weights of the users if a loss has occurred.The following function can be called by anyone.The following function should be called with the who = sgm(contract) whenever someone gets slashed so that the weight of the gauge is removed correctly, because if no one calls the following function the gaugeWeight remains same for the depreciated gauge and once the the gauge gets onboarded and if new users stake and increase the weight of the gauge then they would get less rewards because the weights of the previous users which sgm used on their behalf to increase the weights of the gauge wouldn't be reduced and gaugeProfitIndex[gauge] which is calculated as follows gaugeProfitIndex[gauge] = _gaugeProfitIndex +(amountForGuild * 1e18)/_gaugeWeight; will be less because _gaugeWeight wouldn't be reduced this impacts the deltaIndex which is used while calculating the rewards for the users who staked as shown above when we calculated user rewards.All in all it reduces the rewards for users who staked.
I am labelling it as high because it would impact the rewards for the users who staked.
Tools Used
Manual Review
Recommended Mitigation Steps
Add the following in getRewards when a user is slashed
If applyGaugeLoss is applied on the sgm it gurantees the first rewards are claimed and then the weight is reduced,you can see that in teh applyGaugeLoss function.
Assessed type
Context