xanoutas/solidity-security-findings huggingface dataset:
Munchable's `plotId` not updated during `transferToUnoccupiedPlot`
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When a Munchable is transferred to a new plot, its `plotId` in the `toilerState` mapping is not updated. This discrepancy can cause several issues:

1. The Munchable may be considered to be `dirty` and on an invalid plot in future `_farmPlots` calls, even though it was moved to a valid plot.
2. This can lead to unexpected loss of Schnibble generation if the landlord reduces their locked amount, making the old (non-updated) plot ID invalid.
3. It creates inconsistency between the `plotOccupied` mapping and the `toilerState` mapping.

## Proof of Concept

https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L213-L222
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L261

1. In the `transferToUnoccupiedPlot` function, the `plotId` in `toilerState` is not updated:

```javascript
function transferToUnoccupiedPlot(uint256 tokenId, uint256 plotId)
    external
    override
    forceFarmPlots(msg.sender)
    notPaused
{
    // ... existing checks ...

    toilerState[tokenId].latestTaxRate = plotMetadata[toiler.landlord].currentTaxRate;
    // @audit-issue plotId not updated in toilerState
    plotOccupied[toiler.landlord][oldPlotId] = Plot({occupied: false, tokenId: 0});
    plotOccupied[toiler.landlord][plotId] = Plot({occupied: true, tokenId: tokenId});

    // ... rest of the function ...
}
```

2. In the `_farmPlots` function, the old (incorrect) `plotId` is used to check for validity:

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

This check could incorrectly mark a Munchable as dirty, even if it was moved to a valid plot, because the `plotId` wasn't updated during the transfer.

## Tools Used

Manual review

## Recommended Mitigation Steps

Update the `transferToUnoccupiedPlot` function to properly update the `plotId` in the `toilerState` mapping:

```diff
function transferToUnoccupiedPlot(uint256 tokenId, uint256 plotId)
    external
    override
    forceFarmPlots(msg.sender)
    notPaused
{
    // ... existing code ...

+   toilerState[tokenId].plotId = plotId; // Update the plot ID in toilerState
    plotOccupied[_toiler.landlord][oldPlotId] = Plot({occupied: false, tokenId: 0});
    plotOccupied[_toiler.landlord][plotId] = Plot({occupied: true, tokenId: tokenId});

    // ... rest of the function ...
}
```


## Assessed type

Other
Incorrect Schnibble calculation in `_farmPlots` reduces total rewards instead of applying bonus
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

This bug severely impacts the reward mechanism of the protocol:

- Users receive far fewer schnibbles than they should, potentially by orders of magnitude.
- The intended bonus system is effectively nullified, as bonuses reduce rewards instead of increasing them.
- This affects both renters (who receive fewer schnibbles) and landlords (who receive less tax).

## Proof of Concept

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

The bug is in the `_farmPlots` function:

```javascript
schnibblesTotal = uint256(
  (int256(schnibblesTotal) + int256(schnibblesTotal) * finalBonus) / 100,
);
```

This calculation divides the entire sum by 100, effectively reducing the original `schnibblesTotal` to 1% of its intended value, plus a small bonus.

For example, if `schnibblesTotal` is 1000 and `finalBonus` is 10:

- Current, wrong calculation: `(1000 + (1000 * 10)) / 100 = 11`
- New, corrected calculation: `1000 + (1000 * 10 / 100) = 1100`

The current implementation reduces rewards by approximately 99% in this scenario.

## Tools Used
Manual review

## Recommended Mitigation Steps

Modify the calculation to apply the bonus correctly:

```diff
- schnibblesTotal = uint256(
-   (int256(schnibblesTotal) + int256(schnibblesTotal) * finalBonus) / 100,
- );

+ schnibblesTotal = schnibblesTotal + uint256((int256(schnibblesTotal) * finalBonus) / 100);
```


## Assessed type

Math
plotId not updated after transfer toUnoccupied plot
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
when the function `transferToUnoccupiedPlot` is called, the current `plotOccupied` of the old `plotId` is moved to unoccupied while the  `plotOccupied` of the new `plotId` becomes occupied which makes sense however the attribute `plotId` of `toilerState` isn't updated and keeps pointing to the old `plotId`, this raises many issues as both `_farmPlots` and `unstakeMunchable` use `toilerState[tokenId].plotId`, the impact is the following:
- Incorrect reward calculations:  the '_farmPlots' function will use the wrong tax rate when calculating rewards, leading to unfair reward distributions.
- Issues with unstaking: Since the contract relies on toilerState for unstaking logic, this means users attempting to unstake their nfts might leave plots incorrectly marked as `occupied` since only the old plot will be updated to unoccupied which is already the case, people will be unable to stake to unoccupied plots and also people will be able to stake multiple nfts in the same plot.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

1. Initial State:

Alice is a landlord with plots 120 and 10.
Bob owns a Munchable (TokenID 1) staked on Alice's plot 120.
The tax rate for plot 120 is set to 10% (1e17).


2. Bob transfers his Munchable:

Bob calls transferToUnoccupiedPlot to move his Munchable from plot 120 to plot 10.
The plotOccupied mapping is updated correctly:

plotOccupied[alice][120] is set to unoccupied.
plotOccupied[alice][10] is set to occupied with TokenID 1.


However, toilerState[1].plotId may still be 120 (the old plot).


3. Alice updates tax rates:

Alice sets the tax rate for plot 120 (the old plot) to 1% (1e16).
Alice sets the tax rate for plot 10 (the new plot) to 50% (5e17).


4. Time passes, and rewards accumulate.
5. Bob farms his plots:

Bob calls farmPlots().
The _farmPlots function uses toilerState[1].plotId to determine which plot's tax rate to use.
If toilerState[1].plotId is still 120, it will use the 1% tax rate instead of the correct 50% tax rate.

6. Result:

Bob receives more rewards than he should because the farming calculation uses the lower tax rate from the old plot.
Alice receives less tax than she should from Bob's farming.




Another issue is that: 

1. Initial State:

Alice is a landlord with plots 120 and 10.
Attacker owns a Munchable (TokenID 1) staked on Alice's plot 120.


2. Bob transfers his Munchable:

Bob calls transferToUnoccupiedPlot to move his Munchable from plot 120 to plot 10.
The plotOccupied mapping is updated correctly:
plotOccupied[alice][120] is set to unoccupied.
plotOccupied[alice][10] is set to occupied with TokenID 1.
However, toilerState[1].plotId may still be 120 (the old plot).
3. Attacker unstake his Munchable:
Underflow in _farmPlots leads to stuck munchables
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If a player staked a munchable in one of the last plots available of a landlord and the price of ETH/USD drops, the owner of the munchable will not be able to perform any staking related actions for any of their munchables. Staking, unstaking, transfer to another plot, farm plots will revert due to an underflow in the `_farmPlots` function. Leaving all the owner's munchables that were transferred to `LandManager` stuck.

Additionally, the landlord will be partially impacted because he will have at least one plot occupied that will not collect schnibbles from taxes.


## Proof of Concept
An underflow can happen inside the function `_farmPlots` which is executed before any staking related action.

The issue happens because when the price of ETH/USD drops according to the price reported by `LockManager`, the amount of plots available for the landlord decreases.

This can make the condition in the `if` statement on line [258](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258) true if one of the biggest `plotId` available for the landlord was in use. In that case, the variable `timestamp` gets assigned a value of `plotMetadata[landlord].lastUpdated` which corresponds to the timestamp of the last time the plot metadata was updated by the `AccountManager`.

The execution then follows until line [281](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L281), where a subtraction occurs that ends up in an underflow. In the expression `(timestamp - _toiler.lastToilDate)`, under the circumstances described, most of the time `_toiler.lastToilDate` will be greater than `plotMetadata[landlord].lastUpdated`.

The reason why `_toiler.lastToilDate` will most of the time be greater than `plotMetadata[landlord].lastUpdated` is because `lastUpdated` is only updated with a new timestamp on very special occasions, such as when the landlord locks or unlock funds, which will not happen as frequently as farming plots.

Additionally, since the munchables stuck are owned by the staker and not the landlord, the landlord might not be aware that the issue was triggered, so they might not have an incentive to lock / unlock to solve the issue.



The following test demonstrates how the scenario could happen by emulating a player staking a munchable in one of the landlords last plots and then making the price of ETH/USD drop 5%. After the price drops, staking, unstaking, transfer and farm plots calls revert.

To try this out you can add this test to the file `tests/managers/LandManager/stakeMunchable.test.ts`

```javascript
    it("munchables stuck by underflow when ETH/USD drops", async () => {
      let blockData = await testClient.getBlock();
      let initialTimestamp = blockData.timestamp;

      // test address taken from anvil to be used as test acco
Munchables can be staked with 0% 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#L166
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L287-L289


# Vulnerability details

## Impact
Players call [stakeMunchable](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131-L168) to stake their Munchables on a plot. The function updates the `toilerState` of the NFT, and more particularly sets the `latestTaxRate` of the NFT's toiler state which affects the schnibbles allocation. 

The issue is that the function does not check if the plot metadata was set for the landlord or not:
```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 No check if the metadata was set
        dirty: false
});
```
If the plot metadata for the landlord was not set, the tax rate of the toiler state will be set to the default zero-value. As consequence, when the next toiling happens (which might take too long by the users's intention), the `forceFarmPlots` modifier will calculate the schnibbles rewards of the users [with 0% tax](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L287-L289):
```solidity
function _farmPlots(address _sender) internal {
    (
        address mainAccount,
        MunchablesCommonLib.Player memory renterMetadata
    ) = _getMainAccountRequireRegistered(_sender);
    // ... 
        schnibblesTotal =
                (timestamp - _toiler.lastToilDate) *
                BASE_SCHNIBBLE_RATE;
        schnibblesTotal = uint256((int25
`stakeMunchable` is allowing to stake against `plot which doesn't have metadata` which will prevent the landlord from earning his tax
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
`stakeMunchable` is not considering if the plot metadata exist or not during staking, which allow a player to stake on a plot with `zero tax rate` for the landlord, which is unexpected and seems to warrant `Medium` severity.


## Impact
Player can stake on a plot with a zero tax rate which bypass the range [MIN_TAX_RATE, MAX_TAX_RATE]

## PoC
Alice and Bob are the players. Alice has some ETH locked (so a potential landlord) but somehow didn't generate his plot metadata yet.
1) Bob's stake NFT 1 on Alice's plotId 0. 

If we examine all the checks, nothing is verifying if the plot's metadata exist. `_getNumPlots(landlord)` is not generating nor doing any check either. So the `toilerState` will be created with a zero tax rate (plotMetadata[landlord].currentTaxRate == 0 as doesn't exist yet).

```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) //@audit: NC: remove this magic number
            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);
    }
```	
2) Bob will be able to farm as normally, but the landlord will not earn any points.

By examining the code below, we can see that `schnibblesLandlord` will always be zero (as _toiler.latestTaxRate == 0), which is unexpected.

```
    ...

            schnibblesTotal = (timestamp - _toiler.lastToilDate) * BASE_SCHNIBBLE_RATE;
            schnibblesTotal = uint256((int256(schnibblesTotal) + (int256(schnibblesTotal) *
Malicious user can take advantage of `unTriggeredPlotMetadata` to avoid taxRates
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
When a user stakes munchable then currentTaxRate of landlord is set to latestTaxRate of toilerState
```solidity
toilerState[tokenId] = ToilerState({
            lastToilDate: block.timestamp,
            plotId: plotId,
            landlord: landlord,
        @>  latestTaxRate: plotMetadata[landlord].currentTaxRate,
            dirty: false
        });
```
There can be situation where plotMetadata of landlord is not triggered because they had locked before the land manager was deployed, which means landlord has locked his position in LockManager but his currentTaxRate in plotMetadata is 0(default value) as its not triggered
```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);
    }
```
Now the problem is, a malicious user can take advantage of the above situation to stake munchable with a landlord whose plotMetadata is not triggered, which means latestTaxRate of user in toilerState will be 0 ie default value of plotMetadata[landlord].currentTaxRate

## Impact
User will be able avoid taxRate & landlord will lose `unfedSchnibbles`, which is calculated in _farmPlots() using toilerState.latestTaxRate
```solidity
schnibblesLandlord = (schnibblesTotal * _toiler.latestTaxRate) / 1e18;
landlordMetadata.unfedSchnibbles += schnibblesLandlord;
```

## Proof of Concept
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L103C2-L114C6
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L162C8-L168C12
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L287C12-L300C69

## Tools Used
Manual Review

## Recommended Mitigation Steps
Before staking munchables, check for unTriggeredPlotMetadata for landlord



## Assessed type

Invalid Validation
triggerPlotMetadata() can be frontrun to avoid taxRates
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
User can trigger his plotMetadata if he has deposited before landManager is deployed.
```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);
    }
```
A malicious user can frontrun above function & stake on landlord. As result taxRate for user will be 0 because currentTaxRate of landlord is 0.
```solidity
  toilerState[tokenId] = ToilerState({
            lastToilDate: block.timestamp,
            plotId: plotId,
            landlord: landlord,
     @>       latestTaxRate: plotMetadata[landlord].currentTaxRate,
            dirty: false
        });
```

## Proof of Concept
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L104C2-L114C6
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L162C7-L168C12

## Tools Used
VS code

## Recommended Mitigation Steps
Check for untriggered plotMetadata before staking


## Assessed type

Invalid Validation