Munchables - SovaSlava's results

A web3 point farming game in which Keepers nurture creatures to help them evolve, deploying strategies to earn them rewards in competition with other players.

General Information

Platform: Code4rena

Start Date: 22/05/2024

Pot Size: $20,000 USDC

Total HM: 6

Participants: 126

Period: 5 days

Judge: 0xsomeone

Total Solo HM: 1

Id: 379

League: ETH

Munchables

Findings Distribution

Researcher Performance

Rank: 15/126

Findings: 3

Award: $28.82

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L288-L290

Vulnerability details

Impact

A user can block another user's function call unlock() by calling the lockOnBehalf() even with a zero value, thereby prolonging the locking time.

Proof of Concept

There is 2 problems: user could not specify approved list of addresses, which allow call lockOnBehalf() and specify user's address. And second is that there is no minimum required amount of tokens/eth for lock. Attacker could frontrun user's call to unlock(), and just call function lockOnBehalf() and specify user's address and token.

Tools Used

Manual review

Add approve mechanism, where user could specify whitelisted address, which could lock tokens on behalf the user. And add check if _quantity > minQuantity.

Assessed type

DoS

#0 - c4-judge

2024-06-05T12:59:05Z

alex-ppg marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L256-L267

Vulnerability details

Impact

Due to incorrect verification of time duration, the user can reduce the locking time.

Proof of Concept

The unlockTime variable in setLockDuration() is updated as lastLockTime plus _duration, but _duration must be added to the current time, as in the check above. Example: User have: lastLockTime=100 and unlockTime = 1000. Block.timestamp is 800. User call setLockDuration(300)

if ( uint32(block.timestamp) + uint32(_duration) <
      lockedTokens[msg.sender][tokenContract].unlockTime
       //  800+300 > 1000. The verification will be passed  <---  
   ) {
      revert LockDurationReducedError();
     }

And new unlockTime will be updated as lastLockTime(100) + duration(300) = 100 + 300 = 400.

uint32 lastLockTime = lockedTokens[msg.sender][tokenContract] .lastLockTime;
lockedTokens[msg.sender][tokenContract].unlockTime =
                    lastLockTime +
                    uint32(_duration);

Previous value of unlockTime was 1000, now - 400.

Tools Used

Manual review

- uint32 lastLockTime = lockedTokens[msg.sender][tokenContract].lastLockTime;
  lockedTokens[msg.sender][tokenContract].unlockTime =
-                   lastLockTime +
+                   block.timestamp +
                    uint32(_duration);

Assessed type

Invalid Validation

#0 - c4-judge

2024-06-04T12:40:52Z

alex-ppg marked the issue as duplicate of #89

#1 - c4-judge

2024-06-05T12:53:50Z

alex-ppg marked the issue as partial-75

#2 - c4-judge

2024-06-05T12:54:34Z

alex-ppg changed the severity to 3 (High Risk)

Findings Information

Awards

28.7985 USDC - $28.80

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
:robot:_12_group
duplicate-73

External Links

Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L416

Vulnerability details

Impact

When user lock tokens, and execute calculation count of nfts, some value could be write into remainder value. But after unlockTime, user can call unlock() and get all tokens back, but remainder value does not delete, so in next call to lock() function, he could use less tokens, than it needed, because will be uses specified quantity and remainder value from previous lock.

Proof of Concept

Example: configuredToken.nftCost = 3; User call lock(10) -> quantity=10, remainder=1 (10%3=1)

function lock() {
   ....
   remainder = quantity % configuredToken.nftCost;
   numberNFTs = (quantity - remainder) / configuredToken.nftCost;
   ...
   lockedToken.remainder = remainder; // 1
   lockedToken.quantity += _quantity; // 3

When block.timestamp will be > unlockTime, user call unlock(10).

function unlock() {
   ...
   lockedToken.quantity -= _quantity;
   // But dont set 0 in remainder value.

User call lock(8)

function lock() {
   ...
   uint256 quantity = _quantity + lockedToken.remainder;  // 8 + 1= 9
   ...
   remainder = quantity % configuredToken.nftCost; // 9%3=0
   numberNFTs = (quantity - remainder) / configuredToken.nftCost; // (9-0) / 3=3

So, user use less tokens in second call of lock, than in first call.

Tools Used

Manual review

   function unlock() {
   ...
   lockedToken.quantity -= _quantity;
+  lockedToken.remainder = 0;

Assessed type

Other

#0 - c4-judge

2024-06-05T13:04:18Z

alex-ppg marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter