-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The ProfitManager can be drained of all its funds #472
Comments
0xSorryNotSorry marked the issue as sufficient quality report |
0xSorryNotSorry marked the issue as duplicate of #1211 |
Trumpero marked the issue as satisfactory |
Hi @Trumpero, I understand that labeling a report as Although the root cause was identified in both this report and report 1194, this report has identified and demonstrated how this root cause can be leveraged to drain all the funds from the
I believe this report has satisfied both of the requisites listed above by explicitly Thank you for taking the time to read my comment. |
@0xJCN I don't agree that this issue has a higher impact than #1194, since the funds in ProfitManager are not users' funds, they are specifically the rewards (profits from loans). This report provides more exploit vectors with long details, which are unnecessary to improve the quality of the report in this case. For this simple vulnerability, there are many attack vectors or scenarios to exploit, so clarity and insight in the report are more valuable in my evaluation. Therefore, for this issue, I consider #1194 to be better than this report for selection. |
Lines of code
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/tokens/GuildToken.sol#L242-L261
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/governance/ProfitManager.sol#L409-L426
Vulnerability details
Bug Description
User's are incentivized to stake into gauges by earning rewards when the gauges they staked into experience a profit, i.e. loans in the gauge/term are repaid. A user can stake into a gauge by calling
GuildToken::incrementGauge
. Given that the gauge is an active gauge, the execution flow will continue toGuildToken::_incrementGaugeWeight
:GuildToken::_incrementGaugeWeight#L247-L260
As we can see from the above code,
ProfitManager::claimGaugeRewards
will be called before the user's weight is incremented insuper._incrementGaugeWeight
. This should have the effect of claiming any pending rewards for the user every time they stake into a gauge. The ProfitManager distributes these rewards based on thegaugeProfitIndex
of the gauge and theuserGaugeProfitIndex
of the user:ProfitManager.sol#L413-L431
As shown above, the
ProfitManager::claimGaugeRewards
function call will return 0 (and thus perform no updates to any profit indexes) if the user's current weight is 0. If the user is staking into the gauge for the first time (or staking after previously unstaking all their weight) then this function call will not perform anything meaningful. However, this means that the user's profit index is not updated when they first stake into a gauge. The user's profit index will be first updated on line 41, which can only be reached when theclaimGaugeRewards
function is called once more after the user staked into the gauge for this first time (after their weight is increased). The user'suserGaugeProfitIndex
is thus first initialized to1e18
.The user will receive rewards if the gauge's
gaugeProfitIndex
is greater than the user'suserGaugeProfitIndex
, i.e. if thegaugeProfitIndex
is greater than1e18
. Therefore, a user is able to stake into a gauge with a large profit index (gaugeProfitIndex > 1e18
) and then immediately trigger theclaimGaugeRewards
function once more. Since the user's profit index will be less than the gauge's profit index (gaugeProfitIndex > 1e18 & userGaugeProfitIndex == 1e18
), then the user will be issued a reward despite the fact that the user staked into the gauge after the gauge's profit index was increased, i.e. the user can claim rewards on a gauge by staking into the gauge after it has experienced a profit.The magnitude of this exploit can be fully appreciated by observing the way the ProfitManager handles the rewards for Guild stakers:
ProfitManager.sol#L382-L400
The above code can be found in the
ProfitManager::notifyPnL
function, which is called when a term experiences a loss or a profit. It is important to note that when this function is used to notify a profit it is expected that the appropriate amount ofCredit
is forwarded to the ProfitManager. This is due to the fact that the ProfitManager will allocate this profit (Credit
) to the appropriate parties. Some percentage of theCredit
received will be sent to a designatedotherRecipient
(if such exists), a percentage will be burned and distributed toCredit rebasers
viaCredit::distribute
, a percentage of the profit will remain in the ProfitManager and will be internally allocated towards theglobal surplus buffer
, and the last percentage of profit, which is intended for the Guild stakers, will also remain in the ProfitManager.The above code demonstrates how the percentage for the Guild stakers is accounted for. Lines 397 - 399 shows that the ProfitManager updates the gauge's profit index according to the percentage of
Credit
allocated towards the stakers (amountForGuild
) and the current weight of the gauge (_gaugeWeight
).We have already established that a user is able to stake into a gauge after the gauge experiences a profit and be able to claim rewards from the ProfitManager. However, we now see that the amount of
Credit
for stakers is calculated based on the weight of the gauge, i.e. the cumulative weight of the stakers that were staked into this gauge at the time that the profit was generated. If a gauge had a total weight of100e18
at the time the profit was generated, then a user is able to stake100e18
into this gauge after the fact and will be eligible to claim all of the rewards. Additionally, the ProfitManager housesCredit
that belongs to surplus buffers. Therefore, If the user staked an amount greater than100e18
, then they will also be able to steal funds directly from the ProfitManager, i.e.Credit
allocated towards the surplus buffers.Impact
A user is able to stake a large amount of Guild into a gauge that has already experienced a profit and steal funds from the ProfitManager. Depending on the amount of Guild staked the user will be able to steal all of the funds from the ProfitManager. This includes rewards meant for Guild stakers and funds allocated towards the surplus buffers (either via direct donations or from user's who staked into gauges via the
SurplusGuildMinter
).Additionally, any stakers who are rightfully due rewards will also not be able to unstake from the gauge until more
Credit
flows into the ProfitManager. This is due to the fact that theclaimGaugeRewards
function is called for the user when they unstake viaGuild::decrementGauge
. When theclaimGaugeRewards
function is triggered the ProfitManager will attempt to transferCredit
to the user (for their reward), but the call will revert since the ProjectManager will no longer have aCredit
balance. Note that this can be mitigated by manually transferringCredit
to the ProfitManager for the stakersMoreover, this will also allow the following scenario to arise:
A gauge experiences profits and therefore its
gaugeProfitIndex
increases. The gauge is then offboarded. The gauge is re-onboarded in the future and still has the same increasedgaugeProfitIndex
. A user is therefore able to immediately stake into this gauge (for the first time) and then call theclaimGaugeRewards
function. This will allow the user to extractnon-existent
rewards for the gauge despite the fact that the gauge has not experienced a profit since being re-onboarded. The funds extracted would therefore be coming from funds allocated towards the surplus buffers in the ProfitManager.Proof of Concept
The following tests describe both of the situations outlined above:
Place the following tests inside of
test/unit/governance/ProfitManager.t.sol
:Tools Used
manual
Recommended Mitigation Steps
The profit index of a user who is staking into a gauge for the first time (i.e. their weight is going from
0 to > 0
) should be initialized to the gauge's profit index.Note that this is done properly in the
SurplusGuildMinter
:SurplusGuildMinter.sol#L136-L147
As seen above, when a user stakes into the SGM the user's profit index is initialized to the profit index of the SGM.
Assessed type
Other
The text was updated successfully, but these errors were encountered: