xanoutas/solidity-security-findings huggingface dataset:
Known edge case issue solution
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L232-L310


# Vulnerability details

## Description
As specified on the [`Contest page: Automated Findings / Publicly Known Issues`](https://github.com/code-423n4/2024-07-munchables?tab=readme-ov-file#automated-findings--publicly-known-issues) he following edge case occurs within the `_farmPlots()` function:
> Within the _farmPlots() function, the following edge case can happen: If the plot ID is lower than the max plot amount, the system uses the last updated plot metadata time and tracks this with a dirty boolean flag. The edge case occurs when a user hasn’t farmed in a while and the landlord updates the plots multiple times (ie unlocks/locks multiple times), causing the last updated time to reflect the latest lock update rather than the first. This is a known edge case that we are comfortable with. HOWEVER, we will accept alternative solutions to mitigate this issue. Pointing it out will not result in a valid finding.

To clarify, the edge case is as follows:

1. The landlord locks 100 plots.
2. The user places a munchable on plot 90.
3. The landlord unlocks 50 plots (making plot 90 invalid).
4. The landlord locks 100 plots again.
5. The user farms on plot 90.

As a result, the user can farm as though nothing was wrong, bypassing the invalid validation and farming when the plot becomes valid again.

```solidity 
File: src/managers/LandManager.sol

232:     function _farmPlots(address _sender) internal {
233:         (
234:             address mainAccount,
235:             MunchablesCommonLib.Player memory renterMetadata
236:         ) = _getMainAccountRequireRegistered(_sender);
237: 
                --- SNIPPED ---

247:         for (uint8 i = 0; i < staked.length; i++) {
                --- SNIPPED ---
                
258:@>           if (_getNumPlots(landlord) < _toiler.plotId) {
259:                 timestamp = plotMetadata[landlord].lastUpdated;
260:                 toilerState[tokenId].dirty = true;
261:             }
                --- SNIPPED ---
310:     }
```


## Tools Used
* Manual Review

## Recommended Mitigation Steps
Apply the checkpoint approach to keep track of the total available plots at each specific block timestamp.

* Checkpoints by OpenZeppelin: [Checkpoints](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6d18435098c797bc8c3cfbf51fe10d450a2f6595/contracts/utils/Checkpoints.sol)
* Example Implementation: [ERC20Votes](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6d18435098c797bc8c3cfbf51fe10d450a2f6595/contracts/token/ERC20/extensions/ERC20Votes.sol)

### Concept: 
1. Apply checkpoints whenever the landlord's `plotMetadata` updates.

```solidity
    struct Checkpoint {
        uint256 timestamp;
        uint256 numPlots;
    }

    mapping(address => Checkpoint[]) public landlordCheckpoints;

    function _writeCheckpoint(
        Checkpoint[] storage checkpoints,
        uint256 numPlots
Zero Tax Rate Exploit in stakeMunchable Function
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L104


# Vulnerability details

## **Vulnerability Details:**

The [stakeMunchable](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131) function allows users to stake their Munchable on an unoccupied valid plot of a landlord (users cannot stake on their own plot). Once staked, users earn points based on the Munchables' attributes as well as the landlord's. A tax is deducted and paid to the landlord for the use of their plot.

The LandManager contract has a [triggerPlotMetadata](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L104) function which allows users who had locked funds before the land manager contract was deployed to set their plot metadata. This sets their currentTaxRate to the DEFAULT_TAX_RATE and lastUpdated to block.timestamp. If users stake on their plot using the stakeMunchable function, the landlord will earn a tax rate.

```solidity
    // Only to be triggered by msg sender if they had locked before the land manager was deployed
    function triggerPlotMetadata() external override notPaused {
        (address mainAccount,) = _getMainAccountRequireRegistered(msg.sender);
        if (plotMetadata[mainAccount].lastUpdated != 0) {
            revert PlotMetadataTriggeredError();
        }
        plotMetadata[mainAccount] = PlotMetadata({lastUpdated: block.timestamp, currentTaxRate: DEFAULT_TAX_RATE});

        emit UpdatePlotsMeta(mainAccount);
    }
```

The problem is that the stakeMunchable function allows users to stake on a landlord’s plot who locked funds before the LandManager contract was deployed, even if they haven’t called the triggerPlotMetadata function yet. The only restriction is that the landlord has a valid plot (enough locked funds), which is determined by the _getNumPlots function.

```solidity
    function stakeMunchable(address landlord, uint256 tokenId, uint256 plotId)
        ...
    {
        (address mainAccount,) = _getMainAccountRequireRegistered(msg.sender);
        if (landlord == mainAccount) revert CantStakeToSelfError(); // Can't stake to self (landlord == mainAccount)
        if (plotOccupied[landlord][plotId].occupied) {
            revert OccupiedPlotError(landlord, plotId); // Can't stake to occupied plot
        }
        if (munchablesStaked[mainAccount].length > 10) {
            revert TooManyStakedMunchiesError(); // Can't stake more than 11 munchables
        if (munchNFT.ownerOf(tokenId) != mainAccount) {
            revert InvalidOwnerError();
        }

        uint256 totalPlotsAvail = _getNumPlots(landlord);
        if (plotId >= totalPlotsAvail) revert PlotTooHighError
Incorrect timestamp updating for invalid plots due to configured token becoming inactive
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L282
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L478
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L115-L127


# Vulnerability details

## Description
If the configuration `configuredTokens[configuredTokenContracts[i]].active` of the locked token becomes inactive, that token will not be accumulated in the `getLockedWeightedValue()`, and the landlord's `plotMetadata.timestamp` will not update to reflect the time that activates the invalid plots.

```solidity 
File: src/managers/LandManager.sol

344:     function _getNumPlots(address _account) internal view returns (uint256) {
345:@>       return lockManager.getLockedWeightedValue(_account) / PRICE_PER_PLOT;
346:     }
```

```solidity 
File: src/managers/LockManager.sol
On-chain deployed: 0xEA091311Fc07139d753A6BBfcA27aB0224854Bae (Blast)

469:     function getLockedWeightedValue(
470:         address _player
471:     ) external view returns (uint256 _lockedWeightedValue) {
472:         uint256 lockedWeighted = 0;
473:         uint256 configuredTokensLength = configuredTokenContracts.length;
474:         for (uint256 i; i < configuredTokensLength; i++) {
475:             if (
476:                 lockedTokens[_player][configuredTokenContracts[i]].quantity >
477:                 0 &&
478:@>               configuredTokens[configuredTokenContracts[i]].active
479:             ) {
480:                 // We are assuming all tokens have a maximum of 18 decimals and that USD Price is denoted in 1e18
481:                 uint256 deltaDecimal = 10 **
482:                     (18 -
483:                         configuredTokens[configuredTokenContracts[i]].decimals);
484:@>               lockedWeighted +=
485:                     (deltaDecimal *
486:                         lockedTokens[_player][configuredTokenContracts[i]]
487:                             .quantity *
488:                         configuredTokens[configuredTokenContracts[i]]
489:                             .usdPrice) /
490:                     1e18;
491:             }
492:         }
493: 
494:         _lockedWeightedValue = lockedWeighted;
495:     }
```

```solidity 
File: src/managers/LockManager.sol
On-chain deployed: 0xEA091311Fc07139d753A6BBfcA27aB0224854Bae (Blast)

115:     function configureToken(
116:         address _tokenContract,
117:         ConfiguredToken memory _tokenData
118:     ) external onlyAdmin {
119:         if (_tokenData.nftCost == 0) revert NFTCostInvalidError();
120:         if (configuredTokens[_tokenContract].nftCost == 0) {
121:             // new token
122:             configuredTokenContracts.push(_tokenContract);
123:         }
124:         configuredTokens[_tokenContract] = _tokenData;
125: 
126:         emit TokenConfigured(_tokenContract, _tokenData);
127:     }
```

As a result of not updating the landlord's `
Users can farm on zero-tax land if the landlord locked tokens before the LandManager deployment
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L131-L171


# Vulnerability details

## Description
The [`LandManager::stakeMunchable()`](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L131-L171) **does not verify whether the `plotMetadata[landlord]` is set or not**.

Since the `LockManager` contract is already deployed on-chain ([0xEA091311Fc07139d753A6BBfcA27aB0224854Bae (Blast)](https://blastscan.io/address/0xEA091311Fc07139d753A6BBfcA27aB0224854Bae)), accounts that have locked their tokens before the `LandManager` deployment will need to trigger `LandManager::triggerPlotMetadata()` to update their `LandManager.plotMetadata` after the `LandManager` is deployed.

* The current `AccountManager` interacts with the `LockManager` on-chain but does not introduce interaction with the `LandManager` during `AccountManager::forceHarvest()`.

    * On-chain: [`AccountManager::forceHarvest()`](https://blastscan.io/address/0xcee5fa48beb21dc9b3abefe414f5cb419bef940e#code#F1#L126)
    * Planned upgraded version: [`AccountManager::forceHarvest()`](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/AccountManager.sol#L154)

As a result, **users can stake their tokens on land with a 0% tax rate if the landlord has not updated their `plotMetadata`** and **farm schnibbles without paying tax to the landlord until the landlord’s plot metadata is initialized in the `LandManager` contract (via `triggerPlotMetadata()` and `updatePlotMetadata()`)**.

```solidity
File: src/managers/LandManager.sol

131:     function stakeMunchable(
132:         address landlord,
133:         uint256 tokenId,
134:         uint256 plotId
135:     ) external override forceFarmPlots(msg.sender) notPaused {
136:         (address mainAccount, ) = _getMainAccountRequireRegistered(msg.sender);
137:         if (landlord == mainAccount) revert CantStakeToSelfError();
138:         if (plotOccupied[landlord][plotId].occupied)
139:             revert OccupiedPlotError(landlord, plotId);
140:         if (munchablesStaked[mainAccount].length > 10)
141:             revert TooManyStakedMunchiesError();
142:         if (munchNFT.ownerOf(tokenId) != mainAccount)
143:             revert InvalidOwnerError();
144: 
145:         uint256 totalPlotsAvail = _getNumPlots(landlord);
146:         if (plotId >= totalPlotsAvail) revert PlotTooHighError();
147: 
148:         if (
149:             !munchNFT.isApprovedForAll(mainAccount, address(this)) &&
150:             munchNFT.getApproved(tokenId) != address(this)
151:         ) revert NotApprovedError();
152:         munchNFT.transferFrom(mainAccount, address(this), tokenId);
153: 
154:         plotOccupied[landlord][plotId] = Plot({
155:             occupied: true,
156:             tokenId: tokenId
157:         });
158: 
159:         munchablesStaked[mainAccount].push(tokenId);
160:         munchableOwner[tokenId] = mainAccount;
161: 
162:
In some cases, the staker can choose to Dos a plot of land landlord so that it cannot generate any revenue
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L251


# Vulnerability details

## Impact
If the landlord loses a plot due to a decrease in collateral price, the staker located on that plot call farmPlots and be marked as dirty.  

Even if the collateral price increases or the landlord chooses to add collateral to restore the plot afterwards, the staker's NFT will remain marked as dirty and will not be restored.  

If the staker is malicious, it can choose not to operate and permanently occupy a piece of land on the landlord, preventing it from receiving any profits  
This is a pessimistic attack: The staker may permanently prevent the landlord's piece of land from generating any profits
## Proof of Concept
Once toilerState[tokenId].dirty is marked as dirty, calling _farmPlots can never generate profits.And it will never recover false even if this plot is restored in the future.
The only option for the pledger is to release the pledge.  

github:[https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L251C1-L251C41](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L251C1-L251C41)

```solidity
            if (_toiler.dirty) continue;
            ...
            if (_getNumPlots(landlord) < _toiler.plotId) {
                timestamp = plotMetadata[landlord].lastUpdated;
                toilerState[tokenId].dirty = true;
            }
```

Consider the following situation:
1. A landlord has deposited collateral worth 1000 with a plot price of 900. He now holds a piece of plot, and Bob has chosen to pledge it on this plot.
2. As the collateral price decreases or the landlord withdraws some of the collateral, the current value of the collateral is 800, which is not enough to pay for a piece of plot. Therefore, Bob's continued operation on this plot will be marked as dirty and will not generate any income
3. Later, Landlord chose to increase the value of the collateral to 1800, and now he has two plots of land
4. Bob occupies the first plot of land on the landlord and is marked as dirty. He can choose to maliciously not release the pledge and permanently occupy one plot of land on the landlord, so that it will never generate any revenue

There is also a special case: plots with smaller DOS IDs have a greater impact than plots with larger DOS IDs. The staker located on plots with larger IDs is marked as dirty and can choose to monitor the landlord's plots. If there are vacant plots with smaller IDs, it can choose to call transferToUnoccupatiedPlot to transfer and cause plots with smaller IDs Dos.

Consider the following situation:
1. Landlord originally had 10 plots of land with 10 stakers on them  
2. Landlord has chosen to reduce the number of plots, and the stakers on plots with plot ID 9 have been marked as dirty
3.
Renters can stake NFTs on unsuspecting landlord's plots allowing them to earn schnibbles while the landlords do not
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L131-L171
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L298
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L292
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L166


# Vulnerability details

## Impact
Renters can stake NFTs on the plots of landlords who haven't set their tax rate yet while earning schnibles whereas the landlords do not.


## Proof of Concept
In the `stakeMunchable` function, you can stake your NFT on any landlord's plot as long as the plot is unoccupied but there is no verification if the specific landlord you're staking your NFT on their plot has set a tax rate. Thus, renters can then choose to stake munchables on unsuspecting landlord's plots while earning schnibles whereas such landlords earn nothing. Since anyone locking tokens (USDB, WETH, or any other configured tokens) in the `LockManager` essentially offers plots aka are landlords, these landlords can be caught off-guard while earning nothing in the process.

```solidity
function stakeMunchable(
        address landlord,
        uint256 tokenId,
        uint256 plotId
    ) external override forceFarmPlots(msg.sender) notPaused {
        (address mainAccount, ) = _getMainAccountRequireRegistered(msg.sender);
        if (landlord == mainAccount) revert CantStakeToSelfError();
        if (plotOccupied[landlord][plotId].occupied)
            revert OccupiedPlotError(landlord, plotId);
        if (munchablesStaked[mainAccount].length > 10)
            revert TooManyStakedMunchiesError();
        if (munchNFT.ownerOf(tokenId) != mainAccount)
            revert InvalidOwnerError();

        uint256 totalPlotsAvail = _getNumPlots(landlord);
        if (plotId >= totalPlotsAvail) revert PlotTooHighError();

        if (
            !munchNFT.isApprovedForAll(mainAccount, address(this)) &&
            munchNFT.getApproved(tokenId) != address(this)
        ) revert NotApprovedError();
        munchNFT.transferFrom(mainAccount, address(this), tokenId);

        plotOccupied[landlord][plotId] = Plot({
            occupied: true,
            tokenId: tokenId
        });

        munchablesStaked[mainAccount].push(tokenId);
        munchableOwner[tokenId] = mainAccount;

        toilerState[tokenId] = ToilerState({
            lastToilDate: block.timestamp,
            plotId: plotId,
            landlord: landlord,
 @>           latestTaxRate: plotMetadata[landlord].currentTaxRate, // @audit what if 0?
            dirty: false
        });

        emit FarmPlotTaken(toilerState[tokenId], tokenId);
    }
```

```solidity
toilerState[tokenId].latestTaxRate = plotMetadata[_toiler.landlord]
                .currentTaxRate;
```

```solidity
renterMetadata.unfedSchnibbles += (schnibblesTotal -
                schnibblesLandlord); // @audit renter earns snibbles
User can stake into plot before landlord updates tax rate
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131-L171
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L104-L114
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L287-L289


# Vulnerability details

## Summary

User can stake into plot before landlord sets up the tax rate. Landlord's plot will be occupied but he will not earn any `schnibbles`, because tax rate in users `toilerState` will be `0`.

## Root Cause

https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131-L171

```javascript
function stakeMunchable(
        address landlord,
        uint256 tokenId,
        uint256 plotId
    ) external override forceFarmPlots(msg.sender) notPaused {
        (address mainAccount, ) = _getMainAccountRequireRegistered(msg.sender);
        if (landlord == mainAccount) revert CantStakeToSelfError();
        if (plotOccupied[landlord][plotId].occupied)
            revert OccupiedPlotError(landlord, plotId);
        if (munchablesStaked[mainAccount].length > 10)
            revert TooManyStakedMunchiesError();
        if (munchNFT.ownerOf(tokenId) != mainAccount)
            revert InvalidOwnerError();

        uint256 totalPlotsAvail = _getNumPlots(landlord);
        if (plotId >= totalPlotsAvail) revert PlotTooHighError();

        if (
            !munchNFT.isApprovedForAll(mainAccount, address(this)) &&
            munchNFT.getApproved(tokenId) != address(this)
        ) revert NotApprovedError();
        munchNFT.transferFrom(mainAccount, address(this), tokenId);

        plotOccupied[landlord][plotId] = Plot({
            occupied: true,
            tokenId: tokenId
        });

        munchablesStaked[mainAccount].push(tokenId);
        munchableOwner[tokenId] = mainAccount;

        toilerState[tokenId] = ToilerState({
            lastToilDate: block.timestamp,
            plotId: plotId,
            landlord: landlord,
            latestTaxRate: plotMetadata[landlord].currentTaxRate,
            dirty: false
        });

        emit FarmPlotTaken(toilerState[tokenId], tokenId);
    }
```

## Vulnerability details

In `stakeMunchable` function there are checks to ensure valid staking. We can see that user can't stake to plot which is already occupied. User can only stake to available, unoccupied plots. However there is no check that plots were initialized by landlord. In other words there is no check that landlord had set up the tax rate for a plot. User can stake to plot that has `currentTaxRate` equal to `0`.

As a result landlord will not get any rewards because `_toiler.latestTaxRate` is used in `schnibblesLandlord` rewards calculation.

```javascript
schnibblesLandlord =
    (schnibblesTotal * _toiler.latestTaxRate) /
    1e18;
`