Flow

Sablier
FoundryDeFi
20,000 USDC
View results
Submission Details
Severity: low
Invalid

FlowNFTDescriptor Generates Identical Metadata for Different Streams

Summary

The FlowNFTDescriptor contract generates static and identical metadata for all Sablier stream NFTs, regardless of the underlying stream's parameters. This renders the NFTs ineffective at representing unique streams.

Expected Behavior: The tokenURI function should generate unique metadata for each NFT, reflecting the specific properties of the corresponding Sablier stream (e.g., recipient, amount, duration).

Actual Behavior: The tokenURI function returns the same hardcoded metadata for every NFT, including a fixed SVG image and description.

Vulnerability Details

Steps to Reproduce:

  1. Deploy the FlowNFTDescriptor contract.

  2. Call the tokenURI function with any ERC721 contract address and any streamId.

  3. Call the tokenURI function again with different ERC721 contract address and a different streamId.

  4. Observe that both calls return the identical token URI.

Impact

This bug significantly diminishes the value and utility of the stream NFTs. Users cannot distinguish between different streams based on their NFT metadata.

Tools Used

  • Manual Review

PoC:

To demonstrate the issue, the following PoC code can be run against Foundry. This will showcase the unexpected behavior:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import { Test, console } from "forge-std/src/Test.sol";
import { FlowNFTDescriptor } from "../src/FlowNFTDescriptor.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
// Create a concrete ERC721 contract
contract MockERC721 is ERC721 {
constructor(string memory name, string memory symbol) ERC721(name, symbol) { }
function _baseURI() internal pure override returns (string memory) {
return "";
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) {
return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId
|| super.supportsInterface(interfaceId);
}
}
contract FlowNFTDescriptorTest is Test {
FlowNFTDescriptor descriptor;
ERC721 mockNFT;
function setUp() public {
descriptor = new FlowNFTDescriptor();
mockNFT = new MockERC721("TestNFT", "TNFT");
}
function testStaticMetadata() public view {
string memory uri1 = descriptor.tokenURI(mockNFT, 1);
string memory uri2 = descriptor.tokenURI(mockNFT, 2); // Different streamId
string memory uri3 = descriptor.tokenURI(mockNFT, 1); // same Id
assertEq(uri1, uri2, "Token URIs should be different for different streamIds.");
assertEq(uri1, uri3, " Token URIs should be same for same StreamIds");
}
function test_generate_metadata() public view returns (string memory) {
return descriptor.tokenURI(mockNFT, 1);
}
}

Recommendations

  1. Refactor the tokenURI function to dynamically generate metadata based on the provided sablierFlow contract and streamId. The metadata should reflect the unique properties of each stream.

  2. Implement dynamic or customizable SVG generation to visually differentiate between NFTs.

  3. Consider decentralized metadata solutions for enhanced scalability and flexibility.

Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

[INVALID] Non-Unique Metadata

Appeal created

shabihethsec Submitter
8 months ago
inallhonesty Lead Judge
8 months ago
inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

[INVALID] Non-Unique Metadata

Support

FAQs

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