sherlock-audit / 2024-06-velocimeter-judging

11 stars 7 forks source link

Chinmay - disabling max lock does not correctly reset maxLockIdToIndex leading to some tokenIDs permanently stuck #610

Closed sherlock-admin2 closed 3 months ago

sherlock-admin2 commented 3 months ago

Chinmay

High

disabling max lock does not correctly reset maxLockIdToIndex leading to some tokenIDs permanently stuck

Summary

The disable_max_lock is meant to reset the maxLockIdToIndex for an NFT to zero, so that it will not be max locked on any interaction going forward.

But for some tokens, this maxLockIdToIndex is never reset causing critical problems.

Vulnerability Detail

Once an NFT is enabled for max lock it gets a non-zero index stored against the tokenID in maxLockIdToIndex, which behaves as an identifier of if the nft has been max locked. This is disable_max_lock function :


    function disable_max_lock(uint _tokenId) external {
        assert(_isApprovedOrOwner(msg.sender, _tokenId));
        require(maxLockIdToIndex[_tokenId] != 0,"disabled");

        uint index =  maxLockIdToIndex[_tokenId] - 1;
        maxLockIdToIndex[_tokenId] = 0;

         // Move the last element into the place to delete
        max_locked_nfts[index] = max_locked_nfts[max_locked_nfts.length - 1];

        // update the index 
        maxLockIdToIndex[max_locked_nfts[index]] = index + 1;

        // Remove the last element
        max_locked_nfts.pop();
    }

This logic is swapping the relevant tokenID and replacing it with the last element in the max_locked_nfts array, then popping the last element.

This handles normal caes properly by resetting the disabled tokenID's maxLockIdToIndex to 0, and that is important so that the tokenID doesn't get re-locked automatically hereafter (see max_lock logic here)

But if the last element from the max_locked_nfts array is being disabled, the current logic does not handle that case correctly.

Lets go through an example :

Boom, now we have a non-zero maxLockIdToIndex for a tokenID which has actually been popped from the max_locked_nfts and the user wanted to disable its max lock feature.

This will cause many critical problems because max_Lock logic now considers it as still max lock enabled due to maxLockIdToIndex being non-zero.

Impact

Disable_max_lock does not correctly reset the maxLockIdToIndex for some tokens, which allows anyone to max_lock the nft even after the user tried to disable it. This will prevent users from accessing their locked tokens.

High severtiy because this can happen under normal operations without any external conditions, and can happen repeatedly for many users causing critical problems.

Code Snippet

https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L893-L908

Tool used

Manual Review

Recommendation

Refactor the logic to accomodate the special case of when the array's last element is being removed, similar to how its done in removeTokenFromOwnerList

Duplicate of #257