Fix: Solana memo toBytes() corrupts hex-looking memos#33
Merged
Conversation
StringUtils.toBytes sniffs content and hex-decodes memos matching
^(0x|0X)?([0-9A-Fa-f]{2})+$, breaking the round-trip with fromBuffer
(UTF-8) and producing invalid UTF-8 the SPL Memo program rejects.
Switch to StringUtils.encode (UTF-8) so the write path is symmetric
with the read path and matches Memo program semantics.
Add layout tests covering hex-looking memos, byte-level UTF-8,
multi-byte characters, empty memo, and toJson consistency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
|
@KabaDH Hi, I think we should change the |
MemoLayout kept the memo as a String and converted on both sides, which was lossy: toBytes() hex-sniffed via StringUtils.toBytes (corrupting hex-looking memos) and fromBuffer decoded with allowInvalidOrMalformed (replacing non-UTF-8 bytes with U+FFFD). Either way the exact on-chain byte representation was not preserved, which is unacceptable for Web3 signing/hashing. Make raw bytes the source of truth: - store List<int> memoBytes (asImmutableBytes, the codebase convention), so neither the input list nor the toBytes() result can mutate it - MemoLayout.fromString for UTF-8 text, memo getter for best-effort read - fromBuffer keeps bytes as-is; fromBuffer(toBytes(x)) == x is lossless - toJson emits best-effort UTF-8 text with a 0x hex fallback, matching the ed25519 layout convention, so the view never disagrees with bytes Add layout tests: string round-trip, byte-level UTF-8, multi-byte chars, lossless non-UTF-8 round-trip, immutability, memo getter, empty memo, and toJson text/hex behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
@mrtnetwork Done. |
This was referenced Jun 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix: Solana memo
toBytes()corrupts hex-looking memosSummary
MemoLayout.toBytes()encoded the memo with the hybridStringUtils.toBytes()(hex-or-UTF-8 by content sniffing), while
fromBufferandtoJsontreat a memoas a plain UTF-8 string. Memos whose text happens to look like hex were silently
corrupted, and some produced invalid UTF-8 that the SPL Memo program rejects.
This PR switches the write path to a plain UTF-8 encode, making it symmetric with
the read path and consistent with the Memo program semantics.
Changes
lib/solana/src/instructions/memo/layouts/memo.dart@override List<int> toBytes() { - return StringUtils.toBytes(memo); + return StringUtils.encode(memo); // UTF-8, symmetric with fromBuffer }Impact
are unaffected — they already used the UTF-8 branch.
^(0x|0X)?([0-9A-Fa-f]{2})+$: theliteral text is now preserved instead of being reinterpreted as raw bytes.
decoded bytes explicitly; that was undocumented behavior that could yield
invalid UTF-8.
Testing
Added
test/solana/tests/memo/layout_test.dartcovering round-trip, byte-levelUTF-8, multi-byte characters, empty memo, and
toJsonconsistency. All pass; thesuite fails on the pre-fix code.