Skip to content

Serialize.tryToJSONString does not escape JSON special characters, producing invalid metadata JSON #213

Description

@janezpodhostnik

Problem

Serialize.tryToJSONString wraps string values in double quotes without escaping the content:

// cadence/contracts/utils/Serialize.cdc:30
case Type<String>():
    return String.join(["\"", value as! String, "\"" ], separator: "")

", \, and control characters (U+0000–U+001F) pass through unescaped, which violates RFC 8259 §7. The same gap applies to the Type<String?>() (line 32) and Type<Character>() (line 34) cases, and to dictionary keys in dictToJSONString (line 120), which are serialized through the same helper.

Impact

  • SerializeMetadata.serializeNFTMetadataAsURI is the fallback tokenURI path for every NFT that does not resolve MetadataViews.EVMBridgedMetadata (see handleDefaultNFTToEVM in FlowEVMBridge.cdc). A single newline, quote, or backslash in a Display description or trait value makes the entire metadata document unparseable — block explorers, wallets, and marketplaces silently render no metadata at all.
  • The same serializer derives contractURI during onboarding (FlowEVMBridgeUtils.cdc:414,430). The bridged ERC721 template exposes no setContractURI, so a corrupted contractURI is permanent for that collection. Every newly onboarded collection with such characters in its NFTCollectionDisplay is at risk until this is fixed.
  • The unescaped code is present in the currently deployed mainnet Serialize contract.

Example — an NFT description containing a raw line feed serializes to:

data:application/json;utf8,{"name": "Example NFT #42", "description": "Line one
Line two", "image": "https://example.com/42.png"}

A strict parser rejects this: json.JSONDecodeError: Invalid control character at: line 1 column 53 (char 52).

Proposed fix

  • Add an escapeJSONString(_ s: String): String helper to Serialize.cdc that escapes \, ", and control characters (\n, \r, \t, \b, \f, and \u00XX for the remaining U+0000–U+001F range).
  • Apply it in the String, String?, and Character cases of tryToJSONString and to keys in dictToJSONString.

Because the bridge rewrites tokenURI on every Cadence→EVM bridge, the fix retroactively repairs per-NFT metadata for affected collections on their next bridge round-trip — no project-side changes required. Already-onboarded collections with a corrupted contractURI remain unrecoverable, which is why this should be fixed promptly.

Acceptance criteria

  • Escaping helper implemented and applied to all string-producing cases and dictionary keys
  • Unit tests in cadence/tests/serialize_tests.cdc covering quotes, backslashes, newlines/CR/tab, and other control characters, validating output against a strict JSON parser
  • Regression test in cadence/tests/serialize_metadata_tests.cdc: an NFT with a multi-line description produces a parseable JSON data URI
  • Staged contract update of Serialize on testnet and mainnet (tracked separately if preferred)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
✅ Done
Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions