Currently the value of lockedToken.remainder will be updated if lock() gets called during a lockdrop. However, the lockedToken.remainder will be reset to zero if lock() gets called during a non lockdrop period.
lockedToken.remainder = 0 (first call of each scenario, A.1 and B.1)
Scenario A
Bob calls lock() during lockdrop_1, resulting in quantity = 7e18, remainder = 7e18 and numberNFTs = 0.
Bob calls lock() during lockdrop_2, resulting in quantity = 15e18, remainder = 7e18 and numberNFTs = 1.
Bob calls lock() during lockdrop_3, resulting in quantity = 15e18, remainder = 7e18 and numberNFTs = 1.
Scenario B
Same as 1.a above.
Bob calls lock() during no_lockdrop_2, resulting in quantity = 15e18, remainder = 0 (branch for lockdrop not called).
Bob calls lock() during lockdrop_3, resulting in quantity = 7e18, remainder = 7e18 and numberNFTs = 0.
Effectively, the issue is that if it's not a lockdrop period, the state variable lockedToken.remainder will have the value of the local remainder variable, which will still be zero since the branch for lockdrop will not be executed.
If the poc example, we can see the value of numberNFTs will be different during lockdrop_3 depending if he called lock() during lockdrop_2 or no_lockdrop_2.
Therefore, the nftOverloard.addReveal() can have a smaller number of NFTs if the user previously called lock() during a non lockdrop period, since the remainder is not carried over.
It's also possible for a mallicious user to call lockOnBehalf during a non lockdrop period for another user with 1 wei to forcefully reset his remainder.
Lines of code
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L344-L345 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L352-L355 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L379
Vulnerability details
Currently the value of
lockedToken.remainder
will be updated iflock()
gets called during a lockdrop. However, thelockedToken.remainder
will be reset to zero iflock()
gets called during a non lockdrop period.Proof of Concept
Assume a period of lockdrops and no lockdrops.
Also assume
_quantity = 7e18
(all calls)configuratedToken.nftCost = 8e18
(all calls)lockedToken.remainder = 0
(first call of each scenario, A.1 and B.1)Scenario A
lock()
duringlockdrop_1
, resulting inquantity = 7e18
,remainder = 7e18
andnumberNFTs = 0
.lock()
duringlockdrop_2
, resulting inquantity = 15e18
,remainder = 7e18
andnumberNFTs = 1
.lock()
duringlockdrop_3
, resulting inquantity = 15e18
,remainder = 7e18
andnumberNFTs = 1
.Scenario B
lock()
duringno_lockdrop_2
, resulting inquantity = 15e18
,remainder = 0
(branch for lockdrop not called).lock()
duringlockdrop_3
, resulting inquantity = 7e18
,remainder = 7e18
andnumberNFTs = 0
.Effectively, the issue is that if it's not a lockdrop period, the state variable
lockedToken.remainder
will have the value of the localremainder
variable, which will still be zero since the branch for lockdrop will not be executed.https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L344-L345
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L352-L355
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L379
Impact
If the poc example, we can see the value of
numberNFTs
will be different duringlockdrop_3
depending if he calledlock()
duringlockdrop_2
orno_lockdrop_2
.Therefore, the
nftOverloard.addReveal()
can have a smaller number of NFTs if the user previously calledlock()
during a non lockdrop period, since the remainder is not carried over.It's also possible for a mallicious user to call
lockOnBehalf
during a non lockdrop period for another user with 1 wei to forcefully reset his remainder.https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L369
Tools Used
Manual review
Recommended Mitigation Steps
One approach can be to move
lockedToken.remainder = remainder
inside the lockdrop conditional branch so it only gets updated during lockdrop periods.Assessed type
Timing