Closed code423n4 closed 1 year ago
trust1995 marked the issue as duplicate of #37
trust1995 marked the issue as satisfactory
trust1995 marked the issue as selected for report
trust1995 changed the severity to QA (Quality Assurance)
trust1995 marked the issue as not selected for report
Lines of code
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L341-L344 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L312-L314 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L323-L330 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L302-L304 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L175-L187 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L190-L195 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L198-L200 https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L203-L227
Vulnerability details
Proof of Concept
getUserBoost
computes the max amount of boost that a user had for all the gauges.https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L31
This value will be used on every transfer and burn to check the free gauge boost.
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L341-L344
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L312-L314
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L323-L330
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L302-L304
Currently
getUserBoost
only gets incremented/decremented when callingupdateUserBoost()
and it gets reset when callingdecrementAllGaugesAllBoost()
. This is the behavior described in this comment.https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L150-L172
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L249
However, this will result in the value for
getUserBoost
getting out-of-sync when calling other functions that decrement gauges, e.g.decrementGaugeBoost()
,decrementGaugeAllBoost()
,decrementAllGaugesBoost()
anddecrementGaugesBoostIndexed()
. If the removed gauge boost is from the max boost previously computed to the user, the user gauge value will be incorrect when calling thetransfer
function.https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L175-L187
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L190-L195
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L198-L200
https://github.com/code-423n4/2023-05-maia/blob/main/src/erc-20/ERC20Boost.sol#L203-L227
Even if only decrementing
getUserBoost
when callingdecrementAlGaugesAllBoost()
is expected, there's still a possible edge case when callingdecrementAllGaugesBoost()
with a value that resets all the boost for the gauge with the max value.The following POC demonstrates that if
decrementAllGaugeBoost()
is called with a value that resets thefreeGaugeBoost
to zero, the transfer function will revert.To demonstrate that the transfer function should work, we can take the existing test testDecrementAllGaugesAllBoost, and add two lines on the bottom to make the transfer.
Impact
The value for
freeGaugeBoost()
will be incorrect on every transfer until the functionupdateUserBoost()
gets called. It is possible that this function doesn't get called andfreeGaugeBoost()
is out-of-sync for days/weeks.getUserBoost
will get a corrupted value, thetransfer
andburn
functions will stop working/work unexpectedly forERC20Boost
, potentially resulting in DOS.Tools Used
Manual review, foundry/forge.
Recommended Mitigation Steps
My recommendation is to update
getUserBoost
every timedecrementGaugeBoost()
,decrementGaugeAllBoost()
,decrementAllGaugesBoost()
anddecrementGaugesBoostIndexed()
are called, since the max boost for a gauge can potentially be updated on any of these functions. This could be done by convertingupdateUserBoost()
from external to public and calling it insidedecrementGaugeBoost()
,decrementGaugeAllBoost()
,decrementGaugesBoostIndexed()
(sincedecrementAllGaugesBoost()
callsdecrementGaugesBoostIndexed()
).However, if updating
getUserBoost
only when resetting all boost and gauges throughdecrementAllGaugesAllBoost()
is still desired, I would still recommend to preventdecrementAllGaugesBoost()
being called with a value that resets all the boost value for a gauge that contains the max boost.Assessed type
Other