Skip to content

fix(borsh): encode layouts larger than 1000 bytes without error - #4961

Open
latent-9 wants to merge 1 commit into
otter-sec:masterfrom
latent-9:fix/borsh-encoder-truncation
Open

fix(borsh): encode layouts larger than 1000 bytes without error#4961
latent-9 wants to merge 1 commit into
otter-sec:masterfrom
latent-9:fix/borsh-encoder-truncation

Conversation

@latent-9

Copy link
Copy Markdown

Summary

The Borsh instruction, account, and type coders in the TS package allocate a
fixed Buffer.alloc(1000) scratch buffer for every encode. Any payload that
serializes to more than 1000 bytes fails hard: buffer-layout throws
RangeError: encoding overruns Buffer (or the borsh length guards throw
Invalid bytes: need N bytes, only M remaining), so the data can never be
encoded at all.

This is not an exotic case. A single instruction taking Vec<PublicKey> with
32 entries serializes to 4 + 32*32 = 1028 bytes, and Solana transactions allow
instruction data well past that (the tx size cap is 1232 bytes, and account /
type payloads have no such limit). Multi-KB Vec<u8> arguments, large account
structs, and batch operations all hit this wall with a confusing error instead
of working.

Change

Add encodeLayout() to @anchor-lang/borsh:

  • Fixed-size layouts (layout.span > 0) allocate exactly layout.span bytes.
  • Variable-size layouts (Vec, String, Option, ...) start with a 1000-byte
    buffer and grow on overflow, so values larger than the initial allocation
    are never dropped. Genuine input errors are rethrown, not retried.

Route BorshInstructionCoder.encode, BorshAccountsCoder.encode, and
BorshTypesCoder.encode through it, removing the duplicated
Buffer.alloc(1000) / slice pattern.

Tests

Add a regression test in coder-instructions.spec.ts that encodes an
instruction with 32 public keys (1028 bytes) and asserts the round-trip is
intact. Without the fix it throws RangeError: encoding overruns Buffer;
with the fix it passes. The full coder suite (instruction/account/type/malformed
lengths) passes: 33/33.

The instruction, account, and type coders allocated a fixed 1000-byte
scratch buffer for every encode. Anything that serialized larger than
that (a Vec<PublicKey> with 32 entries, a multi-KB Vec<u8>, a large
account struct) threw a confusing RangeError from buffer-layout
("encoding overruns Buffer") or the borsh length guards, so valid data
could not be encoded at all.

Add encodeLayout() in @anchor-lang/borsh that allocates exactly
layout.span when the size is fixed and grows the scratch buffer on
overflow for variable-size layouts. Route all three coders through it.
@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

@latent-9 is attempting to deploy a commit to the OtterSec Team on Vercel.

A member of the Team first needs to authorize it.

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