xanoutas/solidity-security-findings huggingface dataset:
`transferToUnoccupiedPlot` is lacking updating `plotId field` which break farming accounting and might prevent player from earning when farming
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
`transferToUnoccupiedPlot` is lacking updating `plotId field` during his operation which is unexpected and make this `transfer incomplete` which seems to warrant `Medium` severity.

This is a separate finding from my other (lacking dirty field) as it is a `different root cause`, `different impact` and `require a different fix`, so those two issues while being in the same code area should be treated distinctively.

## Impact
Stake accounting is bricked when a player is transfering plots, as the new plotId is not updated inside the toiler struct, which means from the staking accounting perspective, the player NFT is still staked against the previous plotId which bring multiple side effects, one being: 
- If transfering from a non-dirty plot (last landlord plotId --> first landlord plotId) and later on the Lanlord unlock funds and have his plots number reduced, the player will not be earning during farming, as while the plot count reduction should not affect the player, it will as the toiler still point to the previous plotId (the last one which is now gone instead of the first plotId), so it will be marked as dirty and stop earning.

## PoC
Alice and Bob are the players. Alice is a landlord with 121 plots [0,120] as locked 1 ETH in LockManager.
1) Bob's stake NFT 1 on Alice's plotId 120. 
2) Bob decides to `transferToUnoccupiedPlot` his NFT 1 to plotId 100, it will work fine, but the `toiler` will still point to 120 internally even if `plotOccupied` will be ok.
3) After a week, Alice unlock 0.1 ETH, reducing his number of plot to 112 [0,111].
4) Bob decides to farm but will have a odd surprise, as his NFT will be earning points only for on the `first attempt but not afterwards`, as it will be `marked as dirty`, since toiler still see 120 and not 100 as plotId. 
```solidity
        ...

        for (uint8 i = 0; i < staked.length; i++) {
            timestamp = block.timestamp;
            tokenId = staked[i];
            _toiler = toilerState[tokenId];
            if (_toiler.dirty) continue;
            landlord = _toiler.landlord;
            // use last updated plot metadata time if the plot id doesn't fit
            // track a dirty bool to signify this was done once
            // the edge case where this doesnt work is if the user hasnt farmed in a while and the landlord
            // updates their plots multiple times. then the last updated time will be the last time they updated their plot details
            // instead of the first
            if (_getNumPlots(landlord) < _toiler.plotId) {    
                timestamp = plotMetadata[landlord].lastUpdated;
                toilerState[tokenId].dirty = true;                   //<---------------
            }
			
			...
```


## Tools Used
Manua
`_farmPlots` is not detecting properly the `plot's dirtiness` which might allow player to earn even on an invalid plot
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
`_farmPlots` is having a `wrong condition` regarding the `dirty detection mechanism` which will allow a player to `continue earning with an invalid plot` which seems to warrant `Medium` severity.


## Impact
Player can still earn while being on an invalid plot.

## PoC
Alice and Bob are the players. Alice is a landlord with 121 plots [0,120] as locked 1 ETH in LockManager.
1) Bob's stake NFT 1 on Alice's plotId 120. 
2) After a week, Alice unlock 0.01 ETH, reducing his number of plot to 120 [0,119], which should prevent Bob from earning point from now on, but it won't.
- 121 --> 120 plots. 
- [0,120] --> [0,119], so plotId 120 should become invalid (dirty == true)
3) Bob continue farming and earning points as usual, as his plot is not marked as dirty during farming.

By examining the condition, we can see that after the unlock, it will evaluate the following:
if (120 < 120) which will be false, so not marking the plot as dirty as it should.

```solidity
    ...

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

## Tools Used
Manual review

## Recommended Mitigation Steps
Apply the following patch to resolve this issue.

```diff
    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;
            // use last updated plot metadata time if the plot id doesn't fit
            // track a dirty bool to signify this was done once
            // the edge case where this doesnt work is if the user hasnt farmed in a while and the landlord
            // updates their plots multiple times. then the last updated time will be the last time they updated their plot details
-           if (_getNumPlots(landlord) < _toiler.plotId) {
+           if (_getNumPlots(landlord) <= _toiler.plotId) { // OR if (_toiler.plotId >= _getNumPlots(landlord)) to be more consistent with the whole implementation.
                timestamp = plotMetadata[landlord].lastUpdated;
                toilerState[tokenId].dirty = true;
            }
            (
                ,
Critical states are not updated when transferrnig tokens to new 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-L226


# Vulnerability details

## Impact

The [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L232-L310) function is executed during every toiling operation on a plot (staking, unstaking, and transferring a token to a new plot). The function checks if the plotId associated with the ToilerState exceeds the number of available plots for the landlord. If so, it sets the `dirty` status to true:
```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; // <------------ skip the reward if already dirty
        landlord = _toiler.landlord;
        
        if (_getNumPlots(landlord) < _toiler.plotId) { 
             timestamp = plotMetadata[landlord].lastUpdated;
            toilerState[tokenId].dirty = true; // <------------ set dirty to true if the plot becomes invalid
        }

        // ...
    }
}
```

If the `dirty` status is set to true, it means that the current plot is no longer valid (so, schnibbles will not be farmed) and the token should be moved to a new valid plot to start farming again. And to transfer the token to a new valid plot, [transferToUnoccupiedPlot](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L199-L226) should be called. The issue is that the function does not update the `dirty` and `lastToildDate` states:
```solidity
function transferToUnoccupiedPlot(
    uint256 tokenId,
    uint256 plotId
) external override forceFarmPlots(msg.sender) notPaused {
    (address mainAccount, ) = _getMainAccountRequireRegistered(msg.sender);
    ToilerState memory _toiler = toilerState[tokenId];
    uint256 oldPlotId = _toiler.plotId;
    uint256 totalPlotsAvail = _getNumPlots(_toiler.landlord);
    if (_toiler.landlord == address(0)) revert NotStakedError();
    if (munchableOwner[tokenId] != mainAccount) revert InvalidOwnerError();
    if (plotOccupied[_toiler.landlord][plotId].occupied)
        revert OccupiedPlotError(_toiler.landlord, plotId);
    if (plotId >= totalPlotsAvail) revert PlotTooHighError();

    toilerState[tokenId].latestTaxRate = plotMetadata[_toiler.landlord]
            .currentTaxRate;
     // @audit `toilerState[tokenId].lastToilDate` was not updated
    plotOccupied[_toiler.landlord][oldPlotId] = Plot({
        occupied: false,
        tokenId: 0
    });
    plotOccupied[_toiler.landlord][plotId] = Plot({
        occupied: true,
        tokenId: tokenId
    });

    // @audit `toilerState[tokenId].dirty` was not updated

    emit FarmPlotLeave(_toiler.landlord, tokenId, oldPlotId);
    emit FarmPlotTaken(toi
Incorrect check to verify if plotId exceeds available plots in `_farmPlots`
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L232-L310) function is executed during various operations on a plot, such as staking, unstaking, and transferring tokens to a new plot. This function attempts to verify whether the plotId exceeds the number of available plots for each staked token:
```solidity
function _farmPlots(address _sender) internal {
    // ...

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

    // ...
}
```

The issue is that the check is incorrect because `plotId` counting starts from zero, the highest valid plot id is `available plots - 1`, while the current check considers plot with id equal to `available plots` valid . If for example, the total available plots is *one*, the only valid plotId is *0*, or if the total available plots is `2` the only valid plots are `0` and `1`. This is confirmed in other instances of the code where the check was made correctly, such as in the [stakeMunchable](https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L146) function

As a result, the plot with an ID equal to the total number of plots will be considered a valid plot, which it should not be.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Update the mentioned check in `_farmPlots` to the following:
```diff
+ if (_getNumPlots(landlord) <= _toiler.plotId)
- if (_getNumPlots(landlord) < _toiler.plotId) {
    timestamp = plotMetadata[landlord].lastUpdated;
    toilerState[tokenId].dirty = true;
}
```


## Assessed type

Context
Integer overflow in Schnibbles Calculation Leading to Excessive Rewards
Audit: 2024-07-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## What and where is the bug
The bug is in the [_farmPlots()](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L232C2-L310C6) function, in the `schnibbles` calculation, differing to the edgecase mentioned:

```solidity
schnibblesTotal =
    (timestamp - _toiler.lastToilDate) *
    BASE_SCHNIBBLE_RATE;
schnibblesTotal = uint256(
    (int256(schnibblesTotal) +
        (int256(schnibblesTotal) * finalBonus)) / 100
);
```

### Vulnerability vector
This calculation is vulnerable to integer overflow, which could be exploited to generate an excessive amount of `schnibbles`. The issue arises from:

1. Initial calculation of `schnibblesTotal` could overflow if `(timestamp - _toiler.lastToilDate)` is large or if `BASE_SCHNIBBLE_RATE` is set to a high value.
2. Then the subsequent calculation involving `finalBonus` could also overflow, especially if `finalBonus` is a significant large positive number.
3. The conversion to `int256` and back to `uint256` could lead to negative patterns with very large numbers.

### Impact
A black hat could exploit the vulnerability to:
1. Generate an enormous amount of `schnibbles`, far far beyond what should be possible given the constraints intended.
2. Therefore disrupt the game's economic functions, by introducing a massive imbalance in the `schnibble` distribution.

## Proof of Concept
Add this named `snibble.test.ts` to [here](https://github.com/code-423n4/2024-07-munchables/tree/main/tests/managers/LandManager) and it will pass, showing the bug exists. 

```typescript
import assert from "node:assert";
import { after, afterEach, before, beforeEach, describe, it } from "node:test";
import { parseEther, zeroAddress } from "viem";
import { DeployedContractsType } from "../../../deployments/actions/deploy-contracts";
import { assertTxSuccess } from "../../utils/asserters";
import { testClient } from "../../utils/consts";
import { getTestContracts, getTestRoleAddresses } from "../../utils/contracts";
import { MockNFTOverlordContractType, deployMockNFTOverlord } from "../../utils/mock-contracts";
import { registerPlayer } from "../../utils/players";

describe("LandManager: SchnibblesOverflow", () => {
  let alice: `0x${string}`;
  let bob: `0x${string}`;
  let beforeSnapshot: `0x${string}`;
  let beforeEachSnapshot: `0x${string}`;
  let testContracts: DeployedContractsType;
  let mockNFTOverlord: MockNFTOverlordContractType;

  before(async () => {
    testContracts = await getTestContracts();
    beforeSnapshot = await testClient.snapshot();
    const testRoleAddresses = await getTestRoleAddresses();
    mockNFTOverlord = await deployMockNFTOverlord({ testContracts });
    [alice, bob] = testRoleAddresses.users;
    await testClient.setBalance({ address: alice, value: parseEther("10") });
    await testClient.setBalance({ address: bob, value: parseEthe
plotId in ToilerState is not being updated on transfer to Unoccupied 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-L226


# Vulnerability details

## Impact
While transferring Token to Unoccupied plot using the `transferToUnoccupiedPlot(...)` function, the logic is not properly update the new plot id in the Toiler state. This essentially breaks the logic check related to occupied plots vs unoccupied plots.

This will eventually lead to some plot to remain as occupied for the landlord while token was transferred to other plots.
 

## Proof of Concept
Lets say, for `tokenId` 15, the `plotId` assigned when staking was 1.
> toilerState.plotId = 1;
 plotOccupied[landlord][1] = {occupied, 15};

### First transfer:
At a later point, it was transferred to plotId 5.
> toilerState.plotId = 1; // due to bug, the plotId was not updated to 5
  plotOccupied[landlord][5] = {occupied, 15};

and 
> plotOccupied[landlord][1] = notoccupied, 0;  
  
### Second transfer:
At a later point, it was transferred to plotId 7.
> toilerState.plotId = 1; // due to bug, the plotId was not updated to 7
  plotOccupied[landlord][7] = {occupied, 15};

and
 
> plotOccupied[landlord][1] = notoccupied, 0; // incorrect plot is being marked as not occupied.

After executing these transfer for the landlord, plot number `5` and `7` are marked as occupied and will remain so. These plot cannot be reused by the landlord for other token ids.

Based on the total value of locked tokens, it is possible that some of the valid tokens are also marked dirty due to the plot number at which it was locked earlier. These tokens will remain locked for ever.

```solidity
   uint256 oldPlotId = _toiler.plotId;
   ...
 ==>if (plotOccupied[_toiler.landlord][plotId].occupied)
            revert OccupiedPlotError(_toiler.landlord, plotId);
    ...
 ==>   plotOccupied[_toiler.landlord][oldPlotId] = Plot({
            occupied: false,
            tokenId: 0
        });
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Release the previous `plotId` during the transfer process. Update the `ToilerState` to reflect the `plotId` to which the `token-id` was transferred to.

```solidity
    toilerState[tokenId].plotId = plotId;
```





## Assessed type

Other
`schnibblesTotal` uses wrong calculation to calculate the total
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
`schnibblesTotal` will be significantly lowered, reducing rewards for users.

## Proof of Concept
The current math used for farming schnibbles (inside [_farmPlots](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L283-L286))  has a `finalBonus` that will be applied to `schnibblesTotal`, however the calculation incorrectly encloses the total amount before the division:

$$
\frac{schnibblesTotal + schnibblesTotal \cdot finalBonus}{100}
$$

Where the real bonus calculation should be:

$$
schnibblesTotal + \left( \frac{schnibblesTotal \cdot finalBonus}{100} \right)
$$


This in tern will transform the bonus calculation into a penalty one. Bellow is the [code snippet](https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L283-L286) where that takes place.

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

## Tools Used
Manual review

## Recommended Mitigation Steps
Change the formula:

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

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


## Assessed type

Math
Stale Data After TransferToUnoccupiedPlot Call
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-L226
https://github.com/code-423n4/2024-07-munchables/blob/94cf468aaabf526b7a8319f7eba34014ccebe7b9/src/managers/LandManager.sol#L173-L197


# Vulnerability details

## Impact
The function `transferToUnoccupiedPlot` never changes the `plotId` in the `toilerState`. This leads to stale data in the contract.

```solidity
function transferToUnoccupiedPlot(
	uint256 tokenId,
	uint256 plotId
) external override forceFarmPlots(msg.sender) notPaused {
	(address mainAccount, ) = _getMainAccountRequireRegistered(msg.sender);
	ToilerState memory _toiler = toilerState[tokenId];
	uint256 oldPlotId = _toiler.plotId;
	uint256 totalPlotsAvail = _getNumPlots(_toiler.landlord);
	if (_toiler.landlord == address(0)) revert NotStakedError();
	if (munchableOwner[tokenId] != mainAccount) revert InvalidOwnerError();
	if (plotOccupied[_toiler.landlord][plotId].occupied)
		revert OccupiedPlotError(_toiler.landlord, plotId);
	if (plotId >= totalPlotsAvail) revert PlotTooHighError();

	toilerState[tokenId].latestTaxRate = plotMetadata[_toiler.landlord]
		.currentTaxRate;
	plotOccupied[_toiler.landlord][oldPlotId] = Plot({
		occupied: false,
		tokenId: 0
	});
	plotOccupied[_toiler.landlord][plotId] = Plot({
		occupied: true,
		tokenId: tokenId
	});

	emit FarmPlotLeave(_toiler.landlord, tokenId, oldPlotId);
	emit FarmPlotTaken(toilerState[tokenId], tokenId);
}
```

This issue leads to stale data in `toilerState` and also to `plotOccupied[_toiler.landlord][_toiler.plotId]` when the function `unstakeMunchable` is called after that.

Also, `_farmPlots` will not work correctly as this check:
```solidity
if (_getNumPlots(landlord) < _toiler.plotId) {
    timestamp = plotMetadata[landlord].lastUpdated;
    toilerState[tokenId].dirty = true;
}
```    
will be wrong.

## Proof of Concept
Attack vectors caused by this issue: 

**Manipulating Reward Calculations** 
By ensuring the `plotId` remains stale, an attacker can manipulate the `_farmPlots` function to use incorrect timestamps, leading to unfair reward calculations. 

Steps:

1. Stake a toiler and perform operations that prevent `plotId` from being updated.
2. The `_farmPlots` function will use incorrect timestamps, resulting in inflated or deflated `schnibblesTotal` and `schnibblesLandlord`.

**Monopolising All But One `plotIds`** 
Assume the following initial state:

- There are 5 plots available for staking (plot IDs 0 to 4).
- The user has one toiler (tokenId = 1) initially staked at `plotId` 0.
- The `toilerState` for `tokenId` 1 is as follows:
```solidity
toilerState[1] = ToilerState({
    lastToilDate: <timestamp>,
    plotId: 0,
    landlord: <landlord_address>,
    latestTaxRate: <tax_rate>,
    dirty: false
});
```
- The `plotOccupied` mapping is as follows:
```solidity
plotOccupied[<landlord_address>][0] = Plot({
    occupied: true,
    tokenId: 1
});
```

Steps:

1. First
`plotId` of toilerState is not updated in transferToUnoccupiedPlot()
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

## Vulnerability details 
When a user stake his munchable, plotId is set in toilerState.
```solidity
 toilerState[tokenId] = ToilerState({
            lastToilDate: block.timestamp,
      @>    plotId: plotId,
            landlord: landlord,
            latestTaxRate: plotMetadata[landlord].currentTaxRate,
            dirty: false
        });
```
But the problem is, when user `transfers` his munchable to `unoccupiedPlot` using transferToUnoccupiedPlot(), then the `new` plotId is `not` updated in toilerState.
```solidity
 function transferToUnoccupiedPlot(
        uint256 tokenId,
        uint256 plotId
    ) external override forceFarmPlots(msg.sender) notPaused {
....
        toilerState[tokenId].latestTaxRate = plotMetadata[_toiler.landlord]
            .currentTaxRate;
        plotOccupied[_toiler.landlord][oldPlotId] = Plot({
            occupied: false,
            tokenId: 0
        });
        plotOccupied[_toiler.landlord][plotId] = Plot({
            occupied: true,
            tokenId: tokenId
        });

        emit FarmPlotLeave(_toiler.landlord, tokenId, oldPlotId);
        emit FarmPlotTaken(toilerState[tokenId], tokenId);
    }
```

## Impact
Same plot will be occupied by two users because transfer to unoccupied plot leaves the previous plotId and occupie the new plotId but it doesn't update the toilerState

## Proof of Concept
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#L202C4-L226C6

## Tools Used
Manual Review

## Recommended Mitigation Steps
Update the plotId in toilerState
```solidity
toilerState[tokenId].plotId = plotId;
```





## Assessed type

Error