Root + Impact
Description
-
Describe the normal behavior:
When we use collectionId, it won't exceed 2^128-1 normally and encoding method won't make any problem.
-
Explain the specific issue or problem:
If collectionId
exceeds 128 bits, the encodeTokenId
function may lose the upper bits during the left shift operation. This causes the resulting tokenId
to be miscalculated—for example, a collectionId
of 2^128 - 1 + k
may be interpreted simply as k
.
function encodeTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
@> return (collectionId << COLLECTION_ID_SHIFT) + itemId;
}
function uri(uint256 tokenId) public view override returns (string memory) {
@> (uint256 collectionId, uint256 itemId) = decodeTokenId(tokenId);
}
function redeemMemorabilia(uint256 collectionId) external {
uint256 itemId = collection.currentItemId++;
@> uint256 tokenId = encodeTokenId(collectionId, itemId);
@> tokenIdToEdition[tokenId] = itemId;
@> _mint(msg.sender, tokenId, 1, "");
emit MemorabiliaRedeemed(msg.sender, tokenId, collectionId, itemId);
}
function getMemorabiliaDetails(uint256 tokenId) external view returns (
uint256 collectionId,
uint256 itemId,
string memory collectionName,
uint256 editionNumber,
uint256 maxSupply,
string memory tokenUri
) {
(collectionId, itemId) = decodeTokenId(tokenId);
@> MemorabiliaCollection memory collection = collections[collectionId];
require(collection.priceInBeat > 0, "Invalid token");
return (
collectionId,
itemId,
collection.name,
itemId,
collection.maxSupply,
uri(tokenId)
);
}
Risk
Likelihood:
-
This issue occurs when the collectionId
variable of type uint256
is used without proper validation, and the collectionId
exceeds 2^128 - 1, causing no exception or error handling.
-
Incorrect usage of bit shifting leads to misinterpretation of the collectionId
.
Resolution:
Impact:
-
Users may end up purchasing memorabilia items different from what they intended.
-
Functions like uri
, getMemorabiliaDetails
, and redeemMemorabilia
will use outdated or incorrect **collectionId
**s, resulting in the provision of wrong information.
Proof of Concept
pragma solidity ^0.8.25;
import "./FestivalPass.sol";
contract PoC_EncodeTokenIdOverflow {
FestivalPass public festivalPass;
constructor(address _festivalPass) {
festivalPass = FestivalPass(_festivalPass);
}
function testOverflowRedeem() external {
uint256 bigCollectionId = (1 << 128);
uint256 itemId = 1;
uint256 tokenId = festivalPass.encodeTokenId(bigCollectionId, itemId);
(uint256 decodedCollectionId, uint256 decodedItemId) = festivalPass.decodeTokenId(tokenId);
require(decodedCollectionId == 0, "decodedCollectionId should overflow to 0");
require(decodedItemId == itemId, "itemId should remain same");
}
function testUriMisbehavior() external view returns (string memory) {
uint256 bigCollectionId = (1 << 128) + 42;
uint256 itemId = 7;
uint256 tokenId = festivalPass.encodeTokenId(bigCollectionId, itemId);
(uint256 decodedCollectionId, uint256 decodedItemId) = festivalPass.decodeTokenId(tokenId);
require(decodedCollectionId != bigCollectionId, "No problems!");
return festivalPass.uri(tokenId);
}
}
Recommended Mitigation
- remove this code
// change all nextCollectionId type to uint128 or check if uint128 overflows
uint256 public nextCollectionId = 100;
function buyPass(uint256 collectionId) external payable
function encodeTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256)
function decodeTokenId(uint256 tokenId) public pure returns (uint256 collectionId, uint256 itemId)
function redeemMemorabilia(uint256 collectionId) external
function getUserMemorabiliaDetailed(address user) external view returns (
uint256[] memory tokenIds,
uint256[] memory collectionIds,
uint256[] memory itemIds
)
+ add this code
uint128 public nextCollectionId = 100;
function buyPass(uint128 collectionId) external payable
function encodeTokenId(uint128 collectionId, uint256 itemId) public pure returns (uint256) {
require(collectionId <= uint128.max(), "Need to choose another collectionId!")
}
function decodeTokenId(uint128 tokenId) public pure returns (uint256 collectionId, uint256 itemId)
function redeemMemorabilia(uint128 collectionId) external
function getUserMemorabiliaDetailed(address user) external view returns (
uint256[] memory tokenIds,
uint128[] memory collectionIds,
uint256[] memory itemIds
)