Under normal behavior, tokenId is expected to be uniquely preserved when deriving itemId, ensuring a 1:1 mapping between tokens and their associated items.
However, the contract downcasts a uint256 value to uint128 and then upcasts it back to uint256:
itemId = uint256(uint128(tokenId));
This operation truncates the upper 128 bits of tokenId, meaning the original value is not preserved. As a result, different tokenId values can map to the same itemId.
Likelihood:
tokenId values grow beyond the uint128 range during normal minting or system usage in long-running deployments or high-volume collections
External systems (ERC-721 / ERC-1155 patterns) naturally use full uint256 IDs, making large values realistic and expected
Impact:
Multiple distinct tokenIds can collapse into the same itemId, breaking uniqueness guarantees
Attackers can deliberately craft high tokenId values that collide with existing itemIds, leading to state manipulation or unauthorized interactions
tokenId A = 1
tokenId B = 2^128 + 1
itemId A = uint128(1) = 1
itemId B = uint128(2^128 + 1) = 1
Both different tokenIds map to the same itemId
Contract cannot distinguish between:
original token A
forged token B
If itemId is used in:
mappings → values get overwritten
ownership tracking → users share same entry
redemption logic → one token can affect another
uniqueness checks → become unreliable
If tokenId is already uint256, do not downcast:
itemId = tokenId;
Only allow values that fit in uint128:
require(tokenId <= type(uint128).max, "tokenId too large");````itemId = uint256(uint128(tokenId));
If the goal is to derive a different identifier:
itemId = uint256(keccak256(abi.encode(tokenId)));
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.