Stakes that should be invalidated are handled normally, so they can be exploited maliciously to get more reward for a relatively small lock.
Proof of Concept
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();
...
In stakeMunchable, plotId must be less than totalPlotsAvail. For example, if totalPlotsAvail is 2, only 0,1 plotIds are allowed.
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
// instead of the first
>> if (_getNumPlots(landlord) < _toiler.plotId) {
timestamp = plotMetadata[landlord].lastUpdated;
toilerState[tokenId].dirty = true;
}
...
}
totalPlotsAvail can fluctuate as the value of the locked asset changes. Therefore, the following scenarios may occur
Land lord stakes 1ETH. (ex. totalPlotsAvail = 2)
another user stakes 0,1 plot on the landlord's 0,1 plot
the price of ETH drops and totalPlotsAvail changes to 1
farmPlots is performed.
In these cases, the munchable staked on plotId 1 should be treated as invalid.
However, since the current condition is if(1 < 1) {...}, it does not enter the if statement, so it is treated as a valid stake.
Tools Used
VS Code
Recommended Mitigation Steps
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
// instead of the first
>> if (_getNumPlots(landlord) <= _toiler.plotId) {
timestamp = plotMetadata[landlord].lastUpdated;
toilerState[tokenId].dirty = true;
}
...
}
Lines of code
https://github.com/code-423n4/2024-07-munchables/blob/main/src/managers/LandManager.sol#L258
Vulnerability details
Impact
Stakes that should be invalidated are handled normally, so they can be exploited maliciously to get more reward for a relatively small lock.
Proof of Concept
In stakeMunchable, plotId must be less than totalPlotsAvail. For example, if totalPlotsAvail is 2, only 0,1 plotIds are allowed.
totalPlotsAvail can fluctuate as the value of the locked asset changes. Therefore, the following scenarios may occur
In these cases, the munchable staked on plotId 1 should be treated as invalid.
However, since the current condition is
if(1 < 1) {...}
, it does not enter the if statement, so it is treated as a valid stake.Tools Used
VS Code
Recommended Mitigation Steps
Assessed type
Other