Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lang-v2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ required-features = ["testing"]
[[test]]
name = "to_cpi_accounts_derive"
required-features = ["testing"]

[[test]]
name = "slab_idl_surface"
required-features = ["testing"]
3 changes: 3 additions & 0 deletions lang-v2/src/accounts/slab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,9 @@ impl<H, T> crate::IdlAccountType for Slab<H, T>
where
H: Pod + Zeroable + SlabSchema + crate::IdlAccountType,
{
// IDL currently exposes only the header shape. The dynamic tail cannot be
// represented faithfully in the existing spec because `Slab` stores
// `[len][pad][items..]`, not a plain `Vec<T>` encoding.
const __IDL_ACCOUNT_ENTRY: Option<&'static str> = H::__IDL_ACCOUNT_ENTRY;
const __IDL_TYPE_DEF: Option<&'static str> = H::__IDL_TYPE_DEF;
fn __register_idl_deps(
Expand Down
21 changes: 12 additions & 9 deletions lang-v2/src/idl_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
//!
//! Dispatches on the wrapper type: default returns `None` (elides
//! sysvar/signer/program/unchecked from IDL types). Data-bearing wrappers
//! (`Box<T>`, `Account<T>`, `BorshAccount<T>`, `Slab<H, T>`, `Nested<T>`)
//! delegate to the inner type. User `#[account]`/`#[event]`/`#[derive(IdlType)]`
//! structs get auto-generated impls with both `__IDL_ACCOUNT_ENTRY` and
//! `__IDL_TYPE_DEF` set at macro-expansion time.
//! (`Box<T>`, `Account<T>`, `BorshAccount<T>`, `Nested<T>`) delegate to the
//! inner type. `Slab<H, T>` is a special case: today it forwards only the
//! header `H`, because the current IDL has no faithful way to describe the
//! alignment-padded dynamic tail. User `#[account]`/`#[event]`/
//! `#[derive(IdlType)]` structs get auto-generated impls with both
//! `__IDL_ACCOUNT_ENTRY` and `__IDL_TYPE_DEF` set at macro-expansion time.
//!
//! The trait + helpers are unconditionally compiled — empty default-method
//! impls cost nothing in BPF. End-user crates opt into IDL emission via
Expand Down Expand Up @@ -50,11 +52,12 @@ pub trait IdlAccountType {
/// Push this type's accounts/types entries (if any) and recursively
/// register every user-defined type its fields reference. Default: no-op.
///
/// Wrappers (`Box<T>`, `BorshAccount<T>`, `Slab<H, T>`, `Nested<T>`)
/// forward to the inner type; collection impls (`Vec<T>`, `Option<T>`,
/// `[T; N]`, `[T]`, `&T`, `PodVec<T, N>`) forward to the element type.
/// Primitive impls (bool, u*, i*, f*, String, Address, etc.) use the
/// default no-op — they never appear in `types[]`.
/// Wrappers (`Box<T>`, `BorshAccount<T>`, `Nested<T>`) forward to the
/// inner type. `Slab<H, T>` currently forwards only the header `H`;
/// see [`crate::accounts::Slab`] for the limitation. Collection impls
/// (`Vec<T>`, `Option<T>`, `[T; N]`, `[T]`, `&T`, `PodVec<T, N>`) forward
/// to the element type. Primitive impls (bool, u*, i*, f*, String,
/// Address, etc.) use the default no-op — they never appear in `types[]`.
fn __register_idl_deps(
_accounts: &mut alloc::vec::Vec<&'static str>,
_types: &mut alloc::vec::Vec<&'static str>,
Expand Down
62 changes: 62 additions & 0 deletions lang-v2/tests/slab_idl_surface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use {
anchor_lang_v2::{accounts::Slab, Discriminator, IdlAccountType, Owner},
bytemuck::{Pod, Zeroable},
pinocchio::address::Address,
};

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Header {
count: u64,
}

impl Owner for Header {
const OWNER: Address = Address::new_from_array([0x11; 32]);
}

impl Discriminator for Header {
const DISCRIMINATOR: &'static [u8] = &[1, 2, 3, 4, 5, 6, 7, 8];
}

impl IdlAccountType for Header {
const __IDL_ACCOUNT_ENTRY: Option<&'static str> = Some("header-account");
const __IDL_TYPE_DEF: Option<&'static str> = Some("header-type");

fn __register_idl_deps(accounts: &mut Vec<&'static str>, types: &mut Vec<&'static str>) {
accounts.push("header-account");
types.push("header-type");
}
}

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Item {
value: u64,
}

impl IdlAccountType for Item {
const __IDL_TYPE_DEF: Option<&'static str> = Some("item-type");

fn __register_idl_deps(_accounts: &mut Vec<&'static str>, types: &mut Vec<&'static str>) {
types.push("item-type");
}
}

#[test]
fn slab_idl_surface_is_header_only() {
assert_eq!(
<Slab<Header, Item> as IdlAccountType>::__IDL_ACCOUNT_ENTRY,
Header::__IDL_ACCOUNT_ENTRY
);
assert_eq!(
<Slab<Header, Item> as IdlAccountType>::__IDL_TYPE_DEF,
Header::__IDL_TYPE_DEF
);

let mut accounts = Vec::new();
let mut types = Vec::new();
<Slab<Header, Item> as IdlAccountType>::__register_idl_deps(&mut accounts, &mut types);

assert_eq!(accounts, vec!["header-account"]);
assert_eq!(types, vec!["header-type"]);
}
1 change: 1 addition & 0 deletions tests-v2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ anyhow = "1"
anchor-lang-idl = { path = "../idl", version = "0.1.2", features = ["convert"] }
anchor-spl-v2 = { path = "../spl-v2" }
anchor-lang-idl-spec = { path = "../idl/spec", version = "0.1.0" }
bytemuck = { version = "1", features = ["derive"] }
proptest = "1"
solana-address = "2.0"
solana-loader-v3-interface.workspace = true
Expand Down
69 changes: 69 additions & 0 deletions tests-v2/tests/slab_idl_surface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use {
anchor_lang_v2::{
accounts::Slab,
bytemuck::{Pod, Zeroable},
Discriminator, IdlAccountType, Owner,
},
pinocchio::address::Address,
};

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Header {
count: u64,
}

impl Owner for Header {
const OWNER: Address = Address::new_from_array([0x11; 32]);
}

impl Discriminator for Header {
const DISCRIMINATOR: &'static [u8] = &[1, 2, 3, 4, 5, 6, 7, 8];
}

impl IdlAccountType for Header {
const __IDL_ACCOUNT_ENTRY: Option<&'static str> = Some("header-account");
const __IDL_TYPE_DEF: Option<&'static str> = Some("header-type");

fn __register_idl_deps(accounts: &mut Vec<&'static str>, types: &mut Vec<&'static str>) {
accounts.push("header-account");
types.push("header-type");
}
}

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Item {
value: u64,
}

impl IdlAccountType for Item {
const __IDL_TYPE_DEF: Option<&'static str> = Some("item-type");

fn __register_idl_deps(_accounts: &mut Vec<&'static str>, types: &mut Vec<&'static str>) {
types.push("item-type");
}
}

#[test]
fn slab_idl_surface_forwards_header_metadata() {
assert_eq!(
<Slab<Header, Item> as IdlAccountType>::__IDL_ACCOUNT_ENTRY,
Header::__IDL_ACCOUNT_ENTRY
);
assert_eq!(
<Slab<Header, Item> as IdlAccountType>::__IDL_TYPE_DEF,
Header::__IDL_TYPE_DEF
);
}

#[test]
fn slab_idl_surface_remains_header_only() {
let mut accounts = Vec::new();
let mut types = Vec::new();
<Slab<Header, Item> as IdlAccountType>::__register_idl_deps(&mut accounts, &mut types);

assert_eq!(accounts, vec!["header-account"]);
assert_eq!(types, vec!["header-type"]);
assert!(!types.iter().any(|entry| entry.contains("item-type")));
}
Loading