Skip to content

Serialize RemainderOptionBytes without a length prefix in the Rust client - #124

Open
ara-stock wants to merge 1 commit into
solana-program:mainfrom
ara-stock:fix-remainder-option-bytes
Open

ara-stock wants to merge 1 commit into
solana-program:mainfrom
ara-stock:fix-remainder-option-bytes

Conversation

@ara-stock

Copy link
Copy Markdown
Contributor

Problem

RemainderOptionBytes (the hooked type behind the data argument of write, initialize and set_data in the Rust client) serializes Some(bytes) with Vec::<u8>::serialize, which writes a 4-byte length prefix. The IDL types this argument as a remainderOptionTypeNode of bytesTypeNode: the bytes are the rest of the instruction data, with no prefix. The JS client encodes it with getOptionEncoder(getBytesEncoder(), { prefix: null }), and the program reads everything after the fixed fields as the data.

So a Rust client that builds a write with data: Some(vec![1, 2, 3]).into() and offset: 7 sends the args [7, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3] instead of [7, 0, 0, 0, 1, 2, 3], and the program writes [3, 0, 0, 0, 1, 2, 3] into the buffer. The same happens for initialize and set_data. Decoding has the opposite problem: deserialize_reader expects a length prefix and maps any error to None, so valid instruction data [1, 2, 3] decodes as None.

Fix

In clients/rust/src/hooked/remainder_options.rs, RemainderOptionBytes now writes the raw bytes for Some, and reads the rest of the input when deserializing: no bytes left is None, otherwise Some(bytes). This matches the JS option decoder with prefix: null. RemainderOptionSeed is unchanged, since Seed is a fixed [u8; 16] and has no prefix. The generated code is not touched.

This is a different problem from the ones listed in #86.

Test

I added unit tests to the same file: Some(vec![1, 2, 3]) serializes to [1, 2, 3], None to [], remaining bytes decode to Some and empty input to None, values round-trip, and WriteInstructionArgs { offset: 7, data: Some([1, 2, 3]) } serializes to [7, 0, 0, 0, 1, 2, 3]. Three of these fail without the fix. With the fix, cargo test on clients/rust passes, and cargo fmt --check and cargo clippy --all-targets --all-features -- -D warnings (nightly-2026-01-22) are clean.

This change was prepared with AI assistance (Claude) and reviewed by me before submitting.

🤖 Generated with Claude Code

…ient

The data argument of write, initialize and set_data is a remainder
option of bytes with no prefix, as the IDL, the JS client and the
program define it. Write the raw bytes and read the rest of the input
instead of using the Vec<u8> borsh encoding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant