Open howlbot-integration[bot] opened 2 months ago
The Warden and its duplicates have outlined a significant issue in the way the finalBonus
is added or subtracted from the total as the order of operations is incorrect.
I believe a high-risk severity rating is appropriate given that rewards are significantly impacted and it may ultimately be possible for a Munchable to be permanently locked in the system.
alex-ppg marked the issue as selected for report
alex-ppg marked the issue as satisfactory
Lines of code
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L232
Vulnerability details
Vulnerability Details:
The function
_farmPlots()
is used to calculate rewards farming rewards. This function is used in different scenarios, including like a modifier in the functionunstakeMunchable()
function when a user unstakes their NFT. Let’s have a look at how finalBonus is calculated in the function:The
finalBonus
consists of bonus from the realm (REALM_BONUSES
), as well as a rarity bonus (RARITY_BONUSES
).REALM_BONUSES
can be either -10, -5, 0, 5 or 10, andRARITY_BONUSES
can be either 0, 10, 20, 30 or 50.finalBonus
is meant to incentivize staking Munchables in a plot in a suitable realm. It can either be a positive bonus or reduction in the amount ofschnibblesTotal
. An example of a negative scenario is whenREALM_BONUSES
is -10 andRARITY_BONUSES
is 0Let's look at the calculation of
schnibblesTotal
:schnibblesTotal
is typed asuint256
, therefore is meant as always positive. The current calculation, however, will result in a negative value ofschnibblesTotal
, whenfinalBonus
< 0, the conversion to uint256 will cause the value to evaluate to something neartype(uint256).max
. In that case the next calculation ofschnibblesLandlord
will revert due to overflow:This is due to the fact that
_toiler.latestTaxRate
> 1e16.Since
unstakeMunchable()
has a modifierforceFarmPlots()
, the unstaking will be blocked if_farmPlots()
reverts.Scenario
Proof of Concept
Add the following lines of code in
tests/managers/LandManager/unstakeMunchable.test.ts
:PoC
```diff . . . await registerPlayer({ account: alice, + realm: 1, testContracts, }); await registerPlayer({ account: bob, testContracts, }); await registerPlayer({ account: jirard, testContracts, }); . . . await mockNFTOverlord.write.addReveal([alice, 100], { account: alice }); await mockNFTOverlord.write.addReveal([bob, 100], { account: bob }); await mockNFTOverlord.write.addReveal([bob, 100], { account: bob }); await mockNFTOverlord.write.startReveal([bob], { account: bob }); // 1 - await mockNFTOverlord.write.reveal([bob, 4, 12], { account: bob }); // 1 + await mockNFTOverlord.write.reveal([bob, 1, 0], { account: bob }); // 1 await mockNFTOverlord.write.startReveal([bob], { account: bob }); // 2 await mockNFTOverlord.write.reveal([bob, 0, 13], { account: bob }); // 2 await mockNFTOverlord.write.startReveal([bob], { account: bob }); // 3 ```When we run the command
pnpm test:typescript
the logs from the tests show that thesuccessful path
test reverts.Impact
A user's Munchable NFT with specific attributes can be stuck in the protocol.
Taking into account the rarity distribution across the different NFT-s and assuming that the distribution across realms is equal, the chance of this problem occurring is around
26%
or 1 in every 4 staked munchables.Further, this finding blocks unstaking every munchable that has previously been staked.
Recommended Mitigation
finalBonus
is meant as a percentage change in the Schnibbles earned by a Munchable. Change the calculation ofschnibblesTotal
in_farmPlots()
to reflect that by removing the brackets:Assessed type
Math