Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

FestivalPass::encodeTokenId function will always revert when minting unique NFT, if parameters collectionId or itemId exceeds max limit of uint128

Root + Impact

Description

  • In FestivalPass contract which follows the ERC1155 standard of handling tokens, has a function to encodeTokenId FestivalPass::encodeTokenId , which tries to fit collectionId and itemId into a uint256 variable, it fails provide a solution in cased where collectionId or itemId would exceed the limit of type(uint128) for the Ids, when users want to redeem their unique NFT it would revert when both or one of the IDs are bigger than uint128 max, this would prevent users from minting Memorabilia NFTs


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

Risk

Likelihood:

  • When Collection IDs or Items Ids exceed the maximum of uint128 , Users who tries to redeem their unique NFT would get back failed transactions

Impact:

Users redeeming their NFTs at a period when collection IDs are bigger than uint128 max would most definitely see recieve a failed transaction call

Proof of Concept

A user randomly calls encodeTokenId with numbers greater than uint128 max and gets a revert

function testEncodeOverflowReverts() public {
uint256 tooBig = type(uint128).max + 1;
uint256 itemId = type(uint128).max;
festivalPass.encodeTokenId(tooBig, itemId);
}
Results:
Failing tests:
Encountered 1 failing test in test/FestivalPass.t.sol:FestivalPassTest
[FAIL: panic: arithmetic underflow or overflow (0x11)] testEncodeOverflowReverts() (gas: 466)

Recommended Mitigation

The Protocol could add a measure that increases the limit for encoding collectionId and itemId or they could add a cap to minting unique NFT tokens

Option 1:
Add these variables at the top level of FestivalPass Contract
uint256 constant ITEM_ID_BITS = 96;
uint256 constant ITEM_ID_MASK = (1 << ITEM_ID_BITS) - 1;
function encodeTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
-- return (collectionId << COLLECTION_ID_SHIFT) + itemId;
++ require(itemId <= ITEM_ID_MASK, "itemId too big");
++ return (collectionId << ITEM_ID_BITS) | itemId;
}
function decodeTokenId(uint256 tokenId) public pure returns (uint256 collectionId, uint256 itemId) {
+ itemId = tokenId & ITEM_ID_MASK;
+ collectionId = tokenId >> ITEM_ID_BITS;
}
Option 2:
add a require statement at the top of redeemMemorabilia function
function redeemMemorabilia(uint256 collectionId) external {
++ require(collectionId <= type(uint128).max, "collectionId too large");
MemorabiliaCollection storage collection = collections[collectionId];
require(collection.priceInBeat > 0, "Collection does not exist");
require(collection.isActive, "Collection not active");
require(collection.currentItemId < collection.maxSupply, "Collection sold out");
// Burn BEAT tokens
BeatToken(beatToken).burnFrom(msg.sender, collection.priceInBeat);
// Generate unique token ID
uint256 itemId = collection.currentItemId++;
++ require(itemId <= type(uint128).max, "itemId too large");
uint256 tokenId = encodeTokenId(collectionId, itemId);
// Store edition number
tokenIdToEdition[tokenId] = itemId;
// Mint the unique NFT
_mint(msg.sender, tokenId, 1, "");
emit MemorabiliaRedeemed(msg.sender, tokenId, collectionId, itemId);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 26 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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