code-423n4 / 2024-07-munchables-findings

6 stars 1 forks source link

Failure to Update Dirty Flag in transferToUnoccupiedPlot Prevents Reward Accumulation On Valid Plot #42

Open c4-bot-1 opened 3 months ago

c4-bot-1 commented 3 months ago

Lines of code

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

Vulnerability details

Vulnerability Details:

The transferToUnoccupiedPlot function allows a user to transfer their Munchable to another unoccupied plot from the same landlord. This can be used for example by users currently staked in an invalid plot marked as “dirty”, meaning they will not be earning any rewards, to transfer to a valid plot so they can start earning rewards again.

Since the function checks that the transfer is to a valid plot, it should mean users are now eligible to start earning rewards again. However, it doesn’t update the user’s dirty flag back to false, meaning the user will still not be earning any rewards even after they have moved to a valid plot.

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

The next time _farmPlots is called, since dirty is still true, it’ll be skipped, accumulating no rewards.

    function _farmPlots(address _sender) internal {
        ...
        for (uint8 i = 0; i < staked.length; i++) {
            ...
            if (_toiler.dirty) continue;
            ...
            );
        }
        accountManager.updatePlayer(mainAccount, renterMetadata);
    }

Impact:

Since the function does not update the user's dirty flag back to valid, users who transfer their Munchable to a valid plot and the landlord will still not earn any rewards. This results in a situation where users remain unfairly penalized even after moving to a valid plot

Tools Used:

Manual analysis

Recommendation:

Update the transferToUnoccupiedPlot function to reset the dirty flag to false and update the lastToilDate if it was previously marked as dirty when a user successfully transfers to a valid plot. This will ensure that users start earning rewards again once they are on a valid plot.

    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();
    // ADD HERE
        if (_toiler.dirty) {
            toilerState[tokenId].lastToilDate = block.timestamp;
            toilerState[tokenId].dirty = false;
        }
    //
        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);
    }

Assessed type

Other

c4-judge commented 2 months ago

alex-ppg marked the issue as satisfactory

c4-judge commented 2 months ago

alex-ppg marked the issue as selected for report

0xinsanity commented 2 months ago

It is on-purpose that is set to dirty permanently. I have said this multiple times.

0xinsanity commented 2 months ago

this should fix it: https://github.com/munchablesorg/munchables-common-core-latest/pull/61/files#diff-3c0d7422f89a5582ebb8738fab0332b6d8e60f17c93d290f2e448d88f49fb189L247