xanoutas/solidity-security-findings huggingface dataset:
Attacker can stake and earn rewards tax free due to missing plot metadata initialization check
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


# Vulnerability details

## Summary

**Context:**
Currently the LockManager.sol contract has already been deployed onchain. This means that there are already existing locks created on it. When [locks](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LockManager.sol#L341), [unlocks](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LockManager.sol#L420) and [duration changes](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LockManager.sol#L251) occur in the LockManager.sol contract, it calls the forceHarvest() function on the AccountManager.sol contract, which further calls the [updatePlotMetadata()](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/AccountManager.sol#L154) on the LandManager.sol contract, which just returns currently since the contract has not been deployed yet. Once deployed, the config for the land manager contract would be updated to the current contract being audited in this contest. 

Function [triggerPlotMetadata()](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L104) - This function would be used by users/landlords who have existing locks in order to initialize their plot metadata once.

Function [stakeMunchable()](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131) - This function is used by stakers to stake their munchables on a landlord's plot. The function initializes a ToilerState struct in the toilerState mapping [here](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L162C7-L168C12), which holds many details, one of them being the landlord's current tax rate that is charged on the schnibbles a staker earns.

**Root cause:**
The issue is that since the function [stakeMunchable()](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L131) does not check if the landlord's metadata was triggered, specifically the PlotMetadata.lastUpdated is not 0, it allows a malicious user/attacker to pre-stake and receive a 0 tax-rate since PlotMetadata.currentTaxRate would be 0.

The attack works on existing locks only since their locked weighted value and thus total plots available is positive, allowing the attacker to bypass [this check](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L145C2-L146C66).

**Impact:**
Due to this, even though the landlord had not trigger the metadata, the a
Users can start staking their munchables even though the landlord has not specified the 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#L131-L171


# Vulnerability details

## Impact
Users can start staking their munchables even though the landlord has not specified the taxrates, This happens when the landlord has enough tokens locked and hence plots are allocated but has not updated the farm.

## Proof of Concept

In the `stakeMunchable()` , there are no checks done to ensure `plotMetadata[landlord].lastUpdated` not equal to `0` for the landlord specified by the user.

Allows user stake at 0 tax rate since landlord has only locked the tokens to get allocated plots but has not updated the `plotMetadata` with `DEFAULT_TAX`

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

## Tools Used
Manual

## Recommended Mitigation Steps

Include  this check in `stakeMunchable()`
```solidity
if(plotMetadata[landlord].lastUpdated =0) revert;
```




## Assessed type

Context
No check for ```plotMetadata[landlord].lastUpdated == 0``` in ```stakeMunchable()``` function
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


# Vulnerability details

## Overview 
In the ```stakeMunchable(address landlord, uint256 tokenId, uint256 plotId)``` function, there is no check for ```plotMetadata[landlord].lastUpdated != 0```. This would lead to a staker use a landlord that does not exit.

## Proof of Concept
Below is a step-by-step PoC explaining the issue in detail.

Let us suppose a user X stakes one of his Munchables using:

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

```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,
            dirty: false
        });

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

Basically, X calls the ```stakeMunchable(address landlord, uint256 tokenId, uint256 plotId)``` function with the ```landlord``` as an address that has tokens in LockManager, but, is not a landowner, that is:

```solidity
plotMetadata[landlord].lastUpdated == 0
plotMetadata[landlord].currentTaxRate == 0
```

This is possible as there is no check, such as:

```solidity
        if (plotMetadata[landlord].lastUpdated == 0)
            revert LandlordDoesNotExistError();
```

in the ```stakeMunchable(address landlord, uint256 tokenId, uint256 plotId)``` function.

A user can have tokens in LockManager without being a landlord by:

1. Not updating his token balance on LockManager for sometime after the deployment of LandManager.
2. Not calling the ```triggerPlotMetadata()``` function:

https://github.com/code-423n4/2024-07-munchables/blob
Non-triggeredPlotMetadata can be used for zero tax available plots
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
This land manager contract will be deployed later than all the other contracts because the others are already deployed. Player should activate the tax rate using following function:
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L104C1-L115C1

The staking function doesn't check plotMetadata variable:

```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,
            dirty: false
        });

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

So, basicly player can stake their NFT to non-triggered plot positions and they can bypass the tax rate. We also know that, minimum tax rate defined by the contract and it's definitely undesired behaviour:

```solidity
contract LandManager is BaseBlastManagerUpgradeable, ILandManager {
    uint256 MIN_TAX_RATE;
```


## Impact
- Players can bypass the tax rate and get higher reward than expected.

The another problem is a little bit interesting. If a player bypass this tax rate and use non-triggered plot positions, then if this position goes through edge case. Player can't farm the plot because ```timestamp = plotMetadata[landlord].lastUpdated;``` will be zero but last toil date will be higher than zero and following lines will give panic
Non-updated timestamp in `updateTaxRate` skews schnibble rewards for invalid plots
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

## Impact
`updateTaxRate` function  fails to update the `lastUpdated` timestamp when changing a landlord's tax rate. This can lead to incorrect Schnibble calculations in the `_farmPlots` function. The impact is particularly significant when dealing with plots that are no longer valid, as the function relies on the outdated `lastUpdated` timestamp for calculations.

## Proof of Concept
Take a look at `updateTaxRate` function: https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L92-L101

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

This function updates the `currentTaxRate` but doesn't update the `lastUpdated` timestamp in `plotMetadata`.

The problem manifests in the `_farmPlots` function: https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L258-L260

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

Here, for plots that are no longer valid, the function uses `plotMetadata[landlord].lastUpdated` as the timestamp. If tax rates have changed since the last update which is possible to `lastUpdated`, this could lead to incorrect Schnibble calculations.


## Tools used
Manual review

## Recommended Mitigation Steps
Update the `updateTaxRate` function to include the `lastUpdated` timestamp update:

```diff
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;
+   plotMetadata[landlord].lastUpdated = block.timestamp;
    emit TaxRateChanged(landlord, oldTaxRate, newTaxRate);
}
```


## Assessed type

Context
unfedSchnibbles are not being accounted for staked token with toiler state marked dirty 
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Under circumstances when toiler state is marked as dirty for a token, the `unfedSchnibbles` eligible for renter and landlord are not being account and hence not credited to the eligible parties.

So, when a token with toiler state marked as dirty is unstaked, the logic does not compute and credit the eligible unfedSchnibbles to renter and landlord. So, parties with such staked tokens will loose eligible unfedSchnibbles if they `unstake` the token while it is dirty.

As a result, the renter and landlord could potentially loose the `unfedSchnibbles` they were eligible for.

## Proof of Concept

```solidity
 function _farmPlots(address _sender) internal {
   ...
   for (uint8 i = 0; i < staked.length; i++) {
            timestamp = block.timestamp;
            tokenId = staked[i];
            _toiler = toilerState[tokenId];
   ==>     if (_toiler.dirty) continue;
   }
   ...
```
All the staked tokens for the user whose toiler state was marked dirty is being excluded from the computation of `unfedSchnibbles` for renter and landlord.


## Tools Used
Manual review

## Recommended Mitigation Steps
At the time of `unstaking`, if the toiler state for the token is dirty, then the unstaking logic should compute the `unfedSchnibbles` for renter and landlord. These computed `unfedSchnibbles` should be credited to both parties before competing the `unstaking` process.





## Assessed type

Other
`updateTaxRate` doesn't update time
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Not updating the time will cause [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L261) to farm more rewards to users than they should.

## Proof of Concept
[updateTaxRate](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L92-L101) doesn't update `plotMetadata[landlord].lastUpdated`. This cna be considered an issue as it breaks the implementation inside [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L261) made to calculate the new rewards based on `lastUpdated` if the plot is dirty.

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

Since the above concept is implemented inside [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L261), it's intentions are clearly to use the last tax updated timestamp --`plotMetadata[landlord].lastUpdated` -- as timestamp when the `toilerState` is marked as `dirty`.

However that won't be the case as multiple tax chnages can be made without updating `plotMetadata[landlord].lastUpdated`.

## Tools Used
Manual review

## Recommended Mitigation Steps
Update the `lastUpdated` time inside [updateTaxRate](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L98C9-L99):

```diff
    uint256 oldTaxRate = plotMetadata[landlord].currentTaxRate;
    plotMetadata[landlord].currentTaxRate = newTaxRate;
+   plotMetadata[landlord].lastUpdated = block.timestamp;
```


## Assessed type

Error
`proposeUSDPrice` can be manipulated into changing the price for different contracts
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Price updates can be triggered for different contracts with less (2) than the required threshold (3).

Change in the price will also change the weight on **each** landlord, as [_getNumPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L344-L346) relies on LockManager's weight, which in tern is [quantity * price](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L481-L491).

## Proof of Concept
When a new price for contracts is requested with [proposeUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L142) it needs to pass a threshold (currently 3) of approvals or disapproval in order to be accepted or deleted.

However currently the only validation, inside [approveUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L177) or [disapproveUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L212), that is done is on the price, missing a check if the proposed contracts are the same as the ones that are approved:

```solidity
    function approveUSDPrice(uint256 _price) external
        onlyOneOfRoles([Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5])
    {
        ...
        if (usdUpdateProposal.proposedPrice != _price) {
            revert ProposalPriceNotMatchedError();
        }
```

This would allow a corrupt oracle to front-run an approval with [disapproveUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L212), followed by [proposeUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L142) and make the approval for another set of contracts.

Example:
1. Price X is proposed for contracts 1,2,3.
2. 1 oracle votes for approval and 2 for disapproval.
3. Another oracle votes for approval.
4. However a front-run:
- calls [disapproveUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L212) to remove the current vote
- calls [proposeUSDPrice](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L142) on the same price - X, bit for contracts 4,5,6
5. The original approval for contracts 1,2,3 is now an approval for contract 4,5,6

The attacker has gained 1 extra vote for, making him 2/5, requiring one more in order to capture the proposal.


---


The above would enable anyone controlling 2/5 price feeds to control the prices.
Changing the threshold to 2 will make any singe price feed have control over the prices.

**This is breaking the core invariant of requiring 3 price feeds (majority
Landlord can lock their assets only for the time when harvesting
Audit: 2024-07-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Landlords can lock their assets for 1 block (only when harvesting), in order to avoid any risk exposures.

## Proof of Concept
In order for stakers to ear rewards, they need to stake to an empty active plot and that plot to be not `dirty` when claiming rewards. The plot is labeled `dirty`  when the current `plotId` is bigger than the available plots the landlord has:

https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258-L261
```solidity
   if (_getNumPlots(landlord) < _toiler.plotId) {
      timestamp = plotMetadata[landlord].lastUpdated;
      toilerState[tokenId].dirty = true;
   }
```

Plots are calculated with [_getNumPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L344-L346) doing `weight / price`, where the weight (as seen in [getLockedWeightedValue](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L481-L491)) is calculated as `quantity * price`, adjusted to 18 decimals. In short free plots are:
```markdown
number of free plots = token quantity * price per token / price per plot
```

The above means that in order for users to stake to a landlord he needs to lock his own tokens inside the market for a given duration, as seen inside [LockManager::_lock](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L349-L373)

```solidity
    if (_lockDuration == 0) {
       _lockDuration = lockdrop.minLockDuration;
    }
    if (lockdrop.start <= uint32(block.timestamp) && lockdrop.end >= uint32(block.timestamp)) {
        if ( _lockDuration < lockdrop.minLockDuration 
           || _lockDuration > uint32(configStorage.getUint(StorageKey.MaxLockDuration))
           ) revert InvalidLockDurationError();
```

However the issue that appears here is that lock duration is only checked if we are inside a `lockdrop` event. If outside one, only only restriction landlords have is:

```solidity
        if (_lockDuration == 0) {
            _lockDuration = lockdrop.minLockDuration;
        }
``` 

However that can also be avoided, by setting `lockDuration` to 1 using [setLockDuration](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LockManager.sol#L247), where there are no checks preventing users from setting it bellow the `lockdrop.minLockDuration`. 

Setting `lockDuration` to 1 second will allow landlords to lock their tokens, stake from anther account to their plots and unlock their tokens in the next block. When they decide to claim their rewards, they can again lock and call [farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L228-L230) to claim their rewards.

This will allow landlords to generate rewards while avoiding risk and not contributing to the system.

Example:
1. `lockdrop.