Skip to content

feat: IDL support for custom vector length discriminators#4148

Open
Otter-0x4ka5h wants to merge 10 commits into
otter-sec:masterfrom
Otter-0x4ka5h:anchor#4076
Open

feat: IDL support for custom vector length discriminators#4148
Otter-0x4ka5h wants to merge 10 commits into
otter-sec:masterfrom
Otter-0x4ka5h:anchor#4076

Conversation

@Otter-0x4ka5h
Copy link
Copy Markdown
Member

Anchor IDLs now support vectors with custom length discriminators (u8, u16, or u32), not just the default u32. This helps optimize account size for programs that use compact vectors.

Fixes #4076

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 22, 2025

Someone is attempting to deploy a commit to the Solana Foundation Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Collaborator

@acheroncrypto acheroncrypto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you seen #4076 (comment) ?

@acheroncrypto acheroncrypto added idl related to the IDL, either program or client side feature labels Dec 22, 2025
@0x4ka5h
Copy link
Copy Markdown
Collaborator

0x4ka5h commented Dec 22, 2025

@acheroncrypto sorry for the confusion here, will make changes. I think I pushed the wrong commit. was implemented with

impl IdlSerialization {
    /// Returns the number of bytes used for Vec length prefix in this serialization format.
    pub fn vec_length_bytes(&self) -> usize {
        match self {
            IdlSerialization::Borsh => 4,
            IdlSerialization::BorshU8 => 1,
            IdlSerialization::BorshU16 => 2,
            IdlSerialization::Bytemuck | IdlSerialization::BytemuckUnsafe => {
                8
            }
            IdlSerialization::Custom(_) => {
                4
            }
        }
    }
}

@0x4ka5h
Copy link
Copy Markdown
Collaborator

0x4ka5h commented Dec 22, 2025

Have you seen #4076 (comment) ?

re-pushed the correct changes

Copy link
Copy Markdown
Collaborator

@acheroncrypto acheroncrypto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think the approach is correct, but there are a few more things that need to get addressed:

  • The serialization should apply to all unsized types; otherwise, it would be very unintuitive (on-chain programs can't handle more than 2 bytes length borsh de/serialization anyway)

  • How to use the new serialization specs?

    • In programs, how to use them without having to manually implement the de/serialization?
    • How to include this information (the serialization field) in the IDL conveniently?

    Perhaps something like a generic #[serialization] attribute or a specific #[custom_borsh] similar to the #[zero_copy] attribute?

  • declare_program! also needs to support these new serialization methods, e.g. in:

    https://github.com/solana-foundation/anchor/blob/d7ace7a47d9720386d5ddc1690f358e2d1d33ff5/lang/attribute/program/src/declare_program/common.rs#L133-L138

    Depending on the resolution of the last point, something like:

    IdlSerialization::BorshU8 => quote!(#[custom_borsh(u8)]),
    IdlSerialization::BorshU16 => quote!(#[custom_borsh(u16)]),

Comment thread .github/workflows/reusable-tests.yaml Outdated
- run: cd tests/bpf-upgradeable-state
- run: cd tests/bpf-upgradeable-state && anchor build --skip-lint --ignore-keys
- run: cd tests/bpf-upgradeable-state && solana program deploy --program-id program_with_different_programdata.json target/deploy/bpf_upgradeable_state.so
- run: cd tests/bpf-upgradeable-state && solana program deploy --program-id program_with_different_programdata.json --use-rpc target/deploy/bpf_upgradeable_state.so
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need rebase since this is fixed in master (#4164)

Comment thread cli/src/lib.rs Outdated
.try_into()
.unwrap();
// Use serialization format from parent type definition, or default to Borsh (u32)
let serialization = serialization.unwrap_or(&IdlSerialization::Borsh);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use the actual default rather than manually specifying the default.

Suggested change
let serialization = serialization.unwrap_or(&IdlSerialization::Borsh);
let serialization = serialization.unwrap_or_default();

Comment thread idl/spec/Cargo.toml Outdated
[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.146" No newline at end of file
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this? The IDL spec itself doesn't care about JSON at all.

Suggested change
serde_json = "1.0.146"

Comment thread idl/spec/src/lib.rs Outdated

impl IdlSerialization {
/// Returns the number of bytes used for Vec length prefix in this serialization format.
pub fn vec_length_bytes(&self) -> usize {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be unintuitive and inconsistent if other unsized types still use 4 bytes when they specify BorshU8 or BorshU16 serialization. Therefore, it would be better if this was a generalized method like length_prefix_bytes.

The return type should be Option<usize> instead of usize because not all serialization specs support unsized types.

Suggested change
pub fn vec_length_bytes(&self) -> usize {
pub fn length_prefix_bytes(&self) -> Option<usize> {

Comment thread idl/spec/src/lib.rs Outdated
Comment on lines +273 to +275
IdlSerialization::Borsh => 4,
IdlSerialization::BorshU8 => 1,
IdlSerialization::BorshU16 => 2,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line with the previous comment:

Suggested change
IdlSerialization::Borsh => 4,
IdlSerialization::BorshU8 => 1,
IdlSerialization::BorshU16 => 2,
IdlSerialization::Borsh => Some(4),
IdlSerialization::BorshU8 => Some(1),
IdlSerialization::BorshU16 => Some(2),

{ ...field, type: genericArg.type },
types
types,
undefined,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: it's better to use null when you need to pass a value that you can't omit.

Suggested change
undefined,
null,

return IdlCoder.fieldLayout(
{ type: typeDef.type.alias, name },
types,
undefined,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here:

Suggested change
undefined,
null,

Comment on lines +472 to +478
const vecValue = type.vec;
const innerType =
typeof vecValue === "string" || !("type" in vecValue)
? typeof vecValue === "string"
? vecValue
: (vecValue as any)
: vecValue.type;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change achieve? It looks wrong. Serialization should not affect generic argument resolution.

Suggested change
const vecValue = type.vec;
const innerType =
typeof vecValue === "string" || !("type" in vecValue)
? typeof vecValue === "string"
? vecValue
: (vecValue as any)
: vecValue.type;

: vecValue.type;
const args = IdlCoder.resolveGenericArgs({
type: type.vec,
type: innerType,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line with the previous comment:

Suggested change
type: innerType,
type: type.vec,

Comment on lines +172 to +173
const innerType = idlType.vec;
return `Vec<${this.formatIdlType(innerType)}>`;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't seem to be doing anything.

Suggested change
const innerType = idlType.vec;
return `Vec<${this.formatIdlType(innerType)}>`;
return `Vec<${this.formatIdlType(idlType.vec)}>`;

@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 17, 2026

Deployment failed with the following error:

The provided GitHub repository does not contain the requested branch or commit reference. Please ensure the repository is not empty.

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

Labels

feature idl related to the IDL, either program or client side

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IDL Support for Custom Vector Length Discriminators

3 participants