xanoutas/solidity-security-findings huggingface dataset:
Invalid plots should not receive any rewards
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L258-L261
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L280-L282
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L125


# Vulnerability details

In [`LandManager::_farmPlots`](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L228-L230), a conditional statement [checks](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L258-L261) if the saved `plotId` in a `ToilerState` exceeds the number of currently available plots. If `_toiler.plotId` is greater than the available plots, it means the plot is no longer valid because the landlord has unlocked tokens, reducing the number of allocated plots. As a result, the local timestamp variable is set to `plotMetadata[landlord].lastUpdated`, which is expected to reflect the `block.timestamp` at the time of the token unlock. This allows the staker to receive [schnibbles](https://munchables.gitbook.io/munchables/explorers-guide/glossary/schnibbles) for the period during which the plot was still valid.

```solidity
function _farmPlots(address _sender) internal {
        (
            address mainAccount,
            MunchablesCommonLib.Player memory renterMetadata
        ) = _getMainAccountRequireRegistered(_sender);

        uint256[] memory staked = munchablesStaked[mainAccount];
        MunchablesCommonLib.NFTImmutableAttributes memory immutableAttributes;
        ToilerState memory _toiler;
        uint256 timestamp;
        address landlord;
        uint256 tokenId;
        int256 finalBonus;
        uint256 schnibblesTotal;
        uint256 schnibblesLandlord;
        for (uint8 i = 0; i < staked.length; i++) {
            timestamp = block.timestamp;
            tokenId = staked[i];
            _toiler = toilerState[tokenId];
            if (_toiler.dirty) continue;
            landlord = _toiler.landlord;
 
@>          if (_getNumPlots(landlord) < _toiler.plotId) {
@>              timestamp = plotMetadata[landlord].lastUpdated;
                toilerState[tokenId].dirty = true;
            }
            ...
@>          schnibblesTotal =
                (timestamp - _toiler.lastToilDate) *
                BASE_SCHNIBBLE_RATE;
```

However, this assumption doesn’t always hold true, as any further lock/unlock activity after the initial unlock would also update `plotMetadata[landlord].lastUpdated`, leading to inaccurate calculation of the valid staking period for that now-invalid `plotId` and as a consequence the schnibbles that are to be distributed.

The way the lock/unlock updates the `plotMetadata` of a landlord is as follows:

```solidity
/// @inheritdoc ILockManager
A malicious user can receive schnibbles rewards without having a locked amount throughout the full period
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

The main purpose of the `LandManager` is to allow munchable players to farm additional schnibbles. Two types of players are needed: landlords and toilers. Landlords lock tokens in the `LockManager` in exchange for plots in the `LandManager`. Toilers stake their `munchableNFT` on some of the landlords' plots. Once a plot is occupied by a munchable, it starts to farm, providing rewards to both the landlord and the toiler.

It's important to note that one address cannot be both a landlord and a toiler, but it can be a toiler on someone else's plots. However, it cannot be ensured that two addresses are not controlled by one person.

The reward formula can be simplified to this: the more the landlord has locked and the longer the toiler stakes at their plot, the more schnibbles they will receive. The rewards are distributed to both after [`LandManager.farmPlots()`](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L228-L230) is called. The goal of `LandManager` is to give fair rewards to pairs of landlords and toilers. So, if the landlord unlocks their funds, the reward that both should receive is up to that moment. Also, if the toiler unstakes, the reward should stop accumulating.

Here's the issue: it's possible for a landlord to lock funds, receive a plot, have someone stake on that plot, and then the landlord unstakes. At this moment, if the toiler calls [`LandManager.farmPlots()`](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L228-L230), they will receive the reward for the past period but will stop accumulating future rewards because the plot they are staked on is invalid.

If the toiler does not trigger [`LandManager.farmPlots()`](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L228-L230), they will not receive rewards, but the reward can continue accumulating. After some time, if the landlord re-locks the funds and the `plotId` becomes valid again, the toiler can call [`LandManager.farmPlots()`](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L228-L230). This way, both will receive the full reward for the period, even for the time when the funds were not staked.

## Impact

- The scenario gives an incentive for the landlord and toiler to perform that strategy and get free rewards.
- Someone can be a landlord from one account and a toiler from another, using this strategy to receive almost free (which are not backed by staked funds) schnibbles.
Inconsistent Staking Rewards Due to Dirty Flag Mismanagement
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

## Impact

The `LandManager.sol` contract in the Munchables Protocol contains a bug related to the management of the `toilerState[tokenId].dirty` flag and the calculation of staking rewards. This bug arises when the number of available plots (`totalPlotsAvail`) for a landlord decreases, causing the `toilerState[tokenId].dirty` flag to be set to `true`. This leads to the `lastToilDate` being updated to an earlier `lastUpdated` timestamp from the plot metadata instead of the current timestamp. As a result, users can lose a portion of their accrued rewards due to this retroactive adjustment.

When the `dirty` flag is set and the `lastToilDate` is adjusted to an earlier timestamp, users effectively lose out on the rewards they have accrued during the actual staking period. This is particularly unfair to users who have staked their Munchables for extended periods, as their rewards are reduced based on a past timestamp. The bug undermines the trust and satisfaction of users, affects a broad user base, and can harm the protocol's reputation for fairness and integrity.

## Proof of Concept

[`LandManager.sol#_farmPlots()`](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L232-L310) function:

```solidity
function _farmPlots(address _sender) internal {
    (address mainAccount, MunchablesCommonLib.Player memory renterMetadata) = _getMainAccountRequireRegistered(_sender);

    uint256[] memory staked = munchablesStaked[mainAccount];
    MunchablesCommonLib.NFTImmutableAttributes memory immutableAttributes;
    ToilerState memory _toiler;
    uint256 timestamp;
    address landlord;
    uint256 tokenId;
    int256 finalBonus;
    uint256 schnibblesTotal;
    uint256 schnibblesLandlord;
    uint256 dirtyTimestamp;

    for (uint8 i = 0; i < staked.length; i++) {
        timestamp = block.timestamp;
        tokenId = staked[i];
        _toiler = toilerState[tokenId];
        landlord = _toiler.landlord;

        uint256 totalPlotsAvail = _getNumPlots(landlord);
        if (_toiler.plotId >= totalPlotsAvail) {
            dirtyTimestamp = plotMetadata[landlord].lastUpdated;
            toilerState[tokenId].dirty = true;
        } else {
            dirtyTimestamp = timestamp;
            toilerState[tokenId].dirty = false;
        }

        (, MunchablesCommonLib.Player memory landlordMetadata) = _getMainAccountRequireRegistered(landlord);

        immutableAttributes = nftAttributesManager.getImmutableAttributes(tokenId);
        finalBonus = int16(REALM_BONUSES[(uint256(immutableAttributes.realm) * 5) + uint256(landlordMetadata.snuggeryRealm)]) +
                     int16(int8(RARITY_BONUSES[uint256(immutableAttributes.rarity)]));

        // Calculate rewards up to the dirty timestamp
        schnibblesTotal = (dirtyTimestamp - _toiler.lastToilDate) * BASE_SCHNIBBLE_RATE;
Users can avoid their plots getting `dirty` when the price of assets changes
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users can capture rewards even when their plots are `dirty`.

## Proof of Concept
1. [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L261) has the ability to make a plot `dirty` if the current landlord available plots are smaller than the ID of this plot:

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

When a plot is made dirty it's rewards are claimed for the last time, and it becomes basically useless. The only way to remove the `dirty` tag is to unstake and stake the NFT again. That implies that if a plot gets `dirty` even once, it's rewards are stopped. 

2. The number of available plots is calculates inside [_getNumPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L344-L346) and it represents the weight of our user divided by the price per plot:
```solidity
    function _getNumPlots(address _account) internal view returns (uint256) {
        // weight / price
        return lockManager.getLockedWeightedValue(_account) / PRICE_PER_PLOT;
    }
```
Where weight is calculated inside [getLockedWeightedValue](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L481-L491), as `quantity * price`:

```solidity
lockedWeighted += (
   deltaDecimal 
   * lockedTokens[_player][configuredTokenContracts[i]].quantity
   * configuredTokens[configuredTokenContracts[i]].usdPrice) 
   / 1e18;
```

3. And finally the `price` can be changed by oracle providers using [proposeUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L142).

---

When we take the above points into account we can see that a change in price can cause a change in available plots, which in tern my make some plots dirty.

However users can avoid such changes, by waiting them out and claiming the rewards for the whole period, even the time when their plot should have been considered `dirty`.

Example:
- Plot price is 100 weight, where each asset is priced as 1.
1. At T1 user has 200 weight and 2 plots.
2. At T2 price changes from 1 to 0.95, making his weight 195 and causing his plot to become dirty **if farmed**.
3. At T3 price goes up again to 1.
4. User farms his plot capturing rewards for T1 to T3, even thought his plot would have been dirty from T2 to T3.

In the above case our user captured rewards for a time where his plot should have been dirty. 

## Tools Used
Manual review

## Recommended Mitigation Steps
Given the current implementation I cannot provide a simple solution to this issue. My best suggestion is to enable anyone to farm plots inside the LandManager, but that may cause issues in other parts of
Invalid Stake Limits for Munchables in the LandManager Contract
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `stakeMunchable` function in the `LandManager` contract currently checks `if (munchablesStaked[mainAccount].length > 10)` during staking. However, according to the whitepaper, each account can have a maximum of 6 Munchables, as stated in [“You can input up to 6 Munchables at any given time but can obtain more slots in your Snuggery by converting MUNCH points to slots.”](https://github.com/code-423n4/2024-05-munchables/blob/main/guides/ARCHITECTURE.md). Therefore, it is recommended to modify this check to `if (munchablesStaked[mainAccount].length > 6)`.

## Proof of Concept
The `stakeMunchable` function in the `LandManager` contract checks `if (munchablesStaked[mainAccount].length > 10)` and throws an error if there are more than 10.
[src/managers/LandManager.sol:stakeMunchable_L140-L141](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L140C1-L141C49)
```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);
@audit=> 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
        });
```

According to the whitepaper, each account can have a maximum of 6 Munchables, as stated in [“You can input up to 6 Munchables at any given time but can obtain more slots in your Snuggery by converting MUNCH points to slots.”](https://github.com/code-423n4/2024-05-munchables/blob/main/guides/ARCHITECTURE.md). Therefore, a main account cannot stake more than 6 Munchables at any given time. As such, the current check `if (munchablesStaked[mainAccount].length > 10)` is invalid.

## Tools Used
Manual review

## Recommended Mitigation Steps
It is recommended to modify this check to `if (munchablesStaked[mainAccount].length > 6)`, as shown below:
```solidity
-        if (munchablesStaked[mainAccount].length > 10)
+       if (munchables
LandManager.sol should implement IERC721Receiver
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If the provided address is a smart contract and is not aware of the incoming ERC721 token, the sent token could be locked.

## Proof of Concept
In addition to this issue:https://github.com/code-423n4/2024-07-munchables/blob/main/4naly3er-report.md#M-1
As mentioned in the OpenZeppelin documentation (https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#IERC721Receiver), in order to use `IERC.safeTransferFrom`, `LandManager.sol` should implement `IERC721Receiver`. Otherwise, the `stakeMunchable` function will always revert.

Even though it's a known issue, the bot is not providing the correct mitigation steps. Replacing `transferFrom` with `safeTransferFrom` will further break the code as the contract won't be able to receive any NFTs.

## Tools Used
Manual

## Recommended Mitigation Steps
Add the IERC721Receiver interface.











## Assessed type

ERC721
`updateTaxRate` function does not update `lastUpdated` to current timestamp
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

`updateTaxRate` function does not update `lastUpdated` to current timestamp. It is used to change the currentTax rate of the landlord. It should also update the `lastUpdated` variable. But it does not. It will lead to invalid rewrads calculation.

## Root Cause

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

```javascript
function updateTaxRate(uint256 newTaxRate) external override notPaused {
        (address landlord, ) = _getMainAccountRequireRegistered(msg.sender);
        if (newTaxRate < MIN_TAX_RATE || newTaxRate > MAX_TAX_RATE)
            revert InvalidTaxRateError();
        if (plotMetadata[landlord].lastUpdated == 0)
            revert PlotMetadataNotUpdatedError();
        uint256 oldTaxRate = plotMetadata[landlord].currentTaxRate;
        plotMetadata[landlord].currentTaxRate = newTaxRate;
        emit TaxRateChanged(landlord, oldTaxRate, newTaxRate);
    }
```

## Vulnerability details

`updateTaxRate` is used to by landlord to update the current tax rate. When landlord calls this function it should also udpate the `plotMetadata[landlord].lastUpdated`. Fresh `lastUpdated` value is necessary for correct rewards calcualtion in `_farmPlots` function when landlords `plotId` out of available range.

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

If `lastUpdated` is out of sync it will calculate inaccurate amount of rewards for user and landlord.

```javascript
schnibblesTotal =
    (timestamp - _toiler.lastToilDate) *
    BASE_SCHNIBBLE_RATE;
```

We can see that `schnibblesTotal` is calculated based on `timestamp` which will be inaccurate in scenario where landlord updates his tax rate and his plotId will be out of range.

We can see that if `forceHarvest` is called in `AccountManager` the `updatePlotMetadata` function is called, which correctly updates landlord's `lastUpdated` variable.

```javascript
function updatePlotMetadata(
        address landlord
    ) external override onlyConfiguredContract(StorageKey.AccountManager) {
        if (plotMetadata[landlord].lastUpdated == 0) {
            plotMetadata[landlord] = PlotMetadata({
                lastUpdated: block.timestamp,
                currentTaxRate: DEFAULT_TAX_RATE
            });
        } else {
            plotMetadata[landlord].lastUpdated = block.timestamp;
        }

        emit UpdatePlotsMeta(landlord);
    }
```

## Impact

User and landlord will receive more rewards when `plotId` is out of available range (user's toiler is dirty) because `updateTaxRate` does not update `lastUpdated` value.

## Recommended Mitigation Steps

Update `lastUpdated` value in `updateTaxRate` function.

``
Missing validation in `stakeMunchable` allows stakers to not pay any taxes to landlords
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` contract has two main actors:

1. **Landlords**: Addresses that lock funds via `LockManager` and they own some plots, where stakers can stake their NFTs.
2. **Stakers**: Addresses that own NFTs and they stake them into the landlord's plots, paying a tax.


When the `LandManager` contract is deployed, the landlords that already have some funds locked, they have to call the function `triggerPlotMetadata`, which will set up the tax rate that stakers will pay if they stake into the landlord's plots.

```solidity
    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);
    }
```

However, the root cause of this issue is that if a staker calls `stakeMunchable` before the landlord has called `triggerPlotMetadata`, the staker won't have to pay any taxes at all. 

This will happen because the `stakeMunchable` function is missing a key validation step to ensure that the landlord has set up a tax for stakers. When this happens, the tax rate will be set to zero, allowing a staker to not pay any taxes for that plot:

```solidity
    function stakeMunchable(
        address landlord,
        uint256 tokenId,
        uint256 plotId
    ) external override forceFarmPlots(msg.sender) notPaused {
        // ...

        toilerState[tokenId] = ToilerState({
            lastToilDate: block.timestamp,
            plotId: plotId,
            landlord: landlord,
>>          latestTaxRate: plotMetadata[landlord].currentTaxRate,
            // @audit latestTaxRate will be set to zero if landlord hasn't called `triggerPlotMetadata`.
            dirty: false
        });

        // ...
    }
```

## Impact

An attacker can stake NFTs into plots from a landlord that still hasn't called `triggerPlotMetadata`. This will allow an attacker to pay any tax at all for those plots. 

The attacker will not pay any tax to the landlord, effectively stealing the yield that should go to the landlord. 

## Proof of Concept

An attacker can exploit this bug by following these steps:

1. Contract `LandManager` is deployed
2. Attacker sees the transaction and starts staking all his NFTs into the plots of landlords that have not yet called `triggerPlotMetadata`. 
    - This step will set the value of `latestTaxRate` to zero within the `toilerState` mapping,
3. Now, even if a landlord calls `triggerPlotMetadata` and sets a valid tax rate, the staker won't pay that rate until he calls `_farmPlots` again. 
4. After a whole year, the attacker