Beatland Festival

AI First Flight #4
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: low
Invalid

Lack of bounds check for 128-bit shift leads to bitwise overflow and non-reversible encoding

Lack of bounds check for 128-bit shift leads to bitwise overflow and non-reversible encoding

Description

The encodeTokenId function lacks bounds checks before performing a 128-bit bitwise shift. Since the input parameters are uint256, any collectionId or itemId exceeding 2^128 - 1 will cause high-bit data to overflow and be discarded.This makes the encoding logic non-reversible

@> function encodeTokenId(uint256 collectionId, uint256 itemId) public pure returns
(uint256) {
return (collectionId << COLLECTION_ID_SHIFT) + itemId;
}

Risk

Likelihood:Low

  • Under normal business scenarios, IDs rarely reach 2^128; however, it is inevitably triggered by extreme inputs or large-scale system expansion.

  • Missing Constraints: Since the contract lacks type restrictions or require checks, this risk is 100% present from a technical standpoint.

Impact:

  • Data Corruption: Original IDs cannot be correctly recovered, causing functions like uri() to return incorrect data.

  • Collisions: Different inputs can generate the same tokenId, destroying the uniqueness of the NFT

Proof of Concept

function test_encode_decode_fails_for_itemId_over_128bits() public view {
//assume collectionId is bigger than 2^128
uint256 collectionId = (uint256(1) << 128) +1 ;
uint256 itemId = 1;
// 2. Perform encoding: internally does (collectionId << 128)
uint256 decodedTokenId = festivalPass.encodeTokenId(collectionId, itemId);
// 3. Decode back to check integrity
(uint256 decodedCollectionId, uint256 decodedItemId) = festivalPass.decodeTokenId(decodedTokenId);
// The higher 128 bits are truncated during the left shift operation.
// This assertion passes, proving that data was corrupted/lost
assert(decodedCollectionId != collectionId);
// The itemId remains correct as it was not shifted out of bounds
assert(decodedItemId == itemId);
}

Recommended Mitigation

Add strict bounds checks using require statements in the encodeTokenId function. This ensures that both collectionId and itemId do not exceed the 2^128 - 1 limit, preventing any data loss or ID collisions before the bitwise shift occurs.

function encodeTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
+ // require collectionId & itemId not exceed 2^128
+ if (collectionId > type(uint128).max || itemId > type(uint128).max) {
+ revert IDOverflow();
+ }
return (collectionId << COLLECTION_ID_SHIFT) + itemId;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!