Flow

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

Static NFT Metadata Across All Streams Enables Value Spoofing and Breaks Marketplace Integration Security

Summary

FlowNFTDescriptor is responsible for generating metadata for NFTs that represent Sablier payment streams. The NFTs serve as on-chain representations of streaming payments.

The tokenURI function generates static metadata without including any stream-specific data, despite receiving sablierFlow and streamId parameters that could be used to fetch stream details.

Code

https://github.com/Cyfrin/2024-10-sablier/blob/main/src/FlowNFTDescriptor.sol#L13

function tokenURI(
IERC721Metadata, /* sablierFlow */
uint256 /* streamId */
)
external
pure // Prevents reading stream data
override
returns (string memory uri)
{
// Returns same static metadata for all streams
string memory json = string.concat(
'{"description": "This NFT represents a payment stream in Sablier Flow",',
'"external_url": "https://sablier.com",',
'"name": "Sablier Flow",',
'"image": "data:image/svg+xml;base64,',
Base64.encode(bytes(svg)),
'"}'
);
}

Impact

The identical metadata across Sablier stream NFTs creates a vulnerability in the protocol's usability and integration surface. When users interact with these streams through wallets or marketplaces, they're presented with identical, non-descriptive NFTs that fail to reflect the underlying stream parameters. This breaks a fundamental trust and verification mechanism - users cannot distinguish between a stream worth 1000 DAI and one worth 1 DAI by examining the NFT metadata.

The problem becomes particularly acute in marketplace scenarios where NFT metadata serves as the primary interface for purchase decisions. A malicious actor could exploit this limitation by listing a low-value stream NFT while advertising it as representing a high-value stream, since the NFT metadata provides no way to verify these claims without querying the contract directly.

The ripple effects extend beyond direct user interaction. Protocols attempting to build on top of Sablier streams cannot programmatically filter, sort, or validate streams based on their NFT representations. This severely limits composability - a cornerstone of DeFi protocols. For instance, a lending protocol that might want to accept Sablier stream NFTs as collateral has no way to assess the stream's value or parameters from the NFT metadata alone.

This limitation effectively forces all integrators and users to maintain direct contract-level interactions rather than leveraging the NFT standard's built-in metadata capabilities, negating much of the benefit of having an NFT representation in the first place. The static metadata essentially reduces these NFTs from being rich, self-describing financial instruments to mere identifiers, significantly undermining their utility in the broader DeFi ecosystem.

Test

function test_AllStreamsHaveIdenticalMetadata() external whenCallerAdmin {
// Create two streams with different parameters
uint256 streamId1 = createDefaultStream({
recipient: users.alice,
totalAmount: 1000e18
});
uint256 streamId2 = createDefaultStream({
recipient: users.bob,
totalAmount: 5000e18 // Different amount
});
// Get URIs for both streams
string memory uri1 = nftDescriptor.tokenURI(flow, streamId1);
string memory uri2 = nftDescriptor.tokenURI(flow, streamId2);
// Despite being different streams, URIs are identical
assertEq(
uri1,
uri2,
"Different streams should have different URIs but they are identical"
);
// Optionally verify metadata content is static
string memory expectedJson = string.concat(
'{"description": "This NFT represents a payment stream in Sablier Flow",',
'"external_url": "https://sablier.com",',
'"name": "Sablier Flow",',
'"image": "data:image/svg+xml;base64,',
// ... SVG base64 content would be here
'"}'
);
assertEq(
decode64(stripPrefix(uri1)),
expectedJson,
"Metadata should be static but isn't"
);
}

Fix

  1. Change function to view to allow reading stream data

  2. Add stream-specific data to metadata

  3. Generate dynamic SVG visualization

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 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.