Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 20 additions & 3 deletions build/src/inject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,20 @@ impl Constant {
}
};

let comment_line = format!("# {}", comment);
assert!(
comment_line.len() <= max_width,
"comment exceeds {} chars ({} chars): {}",
max_width,
comment_line.len(),
comment_line,
);

let inline = format!(".equ {}, {} # {}", name, value_str, comment);
if inline.len() <= max_width {
inline
} else {
format!("# {}\n.equ {}, {}", comment, name, value_str)
format!("{}\n.equ {}, {}", comment_line, name, value_str)
}
}
}
Expand Down Expand Up @@ -148,9 +157,17 @@ fn render_group(group: &ConstantGroup) -> String {
if group.comment.is_empty() {
directives.join("\n")
} else {
let comment_line = format!("# {}", group.comment);
assert!(
comment_line.len() <= MAX_LINE_WIDTH,
"group comment exceeds {} chars ({} chars): {}",
MAX_LINE_WIDTH,
comment_line.len(),
comment_line,
);
format!(
"# {}\n{}\n{}\n{}",
group.comment,
"{}\n{}\n{}\n{}",
comment_line,
SEPARATOR,
directives.join("\n"),
SEPARATOR,
Expand Down
40 changes: 35 additions & 5 deletions docs/src/development/build-scaffolding.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ In frame-relative mode, generated constant names include a `_FM_` infix after
the prefix (e.g. `RM_FM_PDA_OFF` instead of `RM_PDA_OFF`) to distinguish
frame-relative constants from other offset constants.

::: tip
For frame structs, [`#[frame("mod")]`](#frame) with field attributes can
generate the constant group directly, making a separate `constant_group!`
invocation unnecessary. The `constant_group!` macro remains available for
non-frame groups (e.g. input buffer offsets, standalone immediates).
:::

<Include rs="interface::market#register_market_stack" collapsible/>

Each group generates:
Expand Down Expand Up @@ -152,6 +159,28 @@ mappings and the struct's doc comment in proc-macro shared state so that
[`constant_group!`](#constant_group) can auto-discover frame fields and
derive its header comment.

When called as `#[frame("module_name")]` with `#[inject("target")]` and
`#[prefix("PREFIX")]` on the struct, it also generates a constant group
module directly from field-level attributes, eliminating the need for a
separate `constant_group!` invocation. Supported field attributes:

- `#[offset]`: aligned frame-relative offset (`_OFF` suffix). Name is
auto-inferred from the field name via `SCREAMING_SNAKE_CASE`, or
overridden with `#[offset(CUSTOM_NAME)]`
- `#[unaligned_offset]`: frame-relative offset without alignment (`_UOFF`)
- `#[pubkey_offsets]`: base offset + four chunk offsets
- `#[signer_seeds]`: auto-expands seed offsets from
[`signer_seeds!`](#signer_seeds) shared state
- `#[cpi_accounts]`: auto-expands CPI account offsets from
[`cpi_accounts!`](#cpi_accounts) shared state
- `#[sol_instruction]`: base offset + per-field `SolInstruction` offsets

Sub-field access uses comma-separated form:
`#[unaligned_offset(NAME, subfield, "doc")]`.

Struct-level `#[relative_offset(NAME, from, to, "doc")]` attributes compute
the difference between two field offsets.

<Include rs="interface::market#frame_example" collapsible/>

### `#[svm_data]`
Expand All @@ -168,8 +197,9 @@ input buffer segments, tree nodes).
Function-like macro that defines a `#[repr(C)]` struct where every field is
typed as `SolSignerSeed`. Field names are registered in proc-macro shared
state so that `signer_seeds!(field)` inside a
[`constant_group!`](#constant_group) can auto-discover all seed fields by
looking up the parent field's type on the frame struct.
[`constant_group!`](#constant_group), or an `#[signer_seeds]` field attribute
on a [`#[frame]`](#frame) struct, can auto-discover all seed fields by
looking up the parent field's type.

<Include rs="interface::market#signer_seeds_example" collapsible/>

Expand All @@ -178,9 +208,9 @@ looking up the parent field's type on the frame struct.
Function-like macro that defines a `#[repr(C)]` struct with `SolAccountInfo`
fields first (contiguous), then `SolAccountMeta` fields (contiguous), for each
named account. Field names are registered in proc-macro shared state so that
`cpi_accounts!(field)` inside a [`constant_group!`](#constant_group) can
auto-discover all account fields by looking up the parent field's type on the
frame struct.
`cpi_accounts!(field)` inside a [`constant_group!`](#constant_group), or a
`#[cpi_accounts]` field attribute on a [`#[frame]`](#frame) struct, can
auto-discover all account fields by looking up the parent field's type.

### `size_of_group!`

Expand Down
200 changes: 106 additions & 94 deletions interface/src/market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub enum RegisterMarketAccounts {
}
// endregion: register_market_accounts

// region: register_market_stack
#[svm_data]
/// CPI instruction data for CreateAccount.
pub struct CreateAccountData {
Expand All @@ -116,8 +117,6 @@ pub struct CreateAccountData {
pub space: u64,
/// Zero-initialized on stack.
pub owner: Address,
/// Included for alignment on stack.
_pad: u32,
}

cpi_accounts! {
Expand All @@ -131,10 +130,9 @@ cpi_accounts! {
}
}

// region: register_market_stack
// region: signer_seeds_example
signer_seeds! {
PDASignerSeeds {
SignerSeeds {
/// Market PDA: base mint address. Vault: market PDA address.
idx_0,
/// Market PDA: quote mint address. Vault: vault index (0 = base, 1 = quote).
Expand All @@ -146,126 +144,140 @@ signer_seeds! {
// endregion: signer_seeds_example

// region: frame_example
#[frame]
#[frame("frame")]
#[prefix("RM")]
#[inject("market/register")]
#[relative_offset(
PDA_SEEDS_TO_SOL_INSN,
pda_seeds,
sol_instruction,
"From pda_seeds to sol_instruction."
)]
#[relative_offset(PDA_TO_SIGNERS_SEEDS, pda, signers_seeds, "From pda to signers_seeds.")]
#[relative_offset(
CREATE_ACCT_DATA_TO_CPI_ACCT_METAS,
create_account_data, cpi_accounts.idx_0_meta,
"From create_account_data to CPI account metas.")
]
/// Stack frame for REGISTER-MARKET.
pub struct RegisterMarketFrame {
/// Pointer to token program address.
#[offset]
pub token_program_id: *const Address,

/// Pointer to program ID in input buffer.
#[offset]
pub program_id: *const Address,

/// Saved input buffer pointer.
#[offset]
pub input: u64,

/// Saved input_shifted pointer.
#[offset]
pub input_shifted: u64,

/// From Rent sysvar.
#[offset]
pub lamports_per_byte: u64,
/// Return value from GetAccountDataSize CPI, to check token account data size at runtime.

/// Return value from spl_token_2022::GetAccountDataSize.
#[offset]
pub token_account_data_size: u64,

/// Pointer to mint account for vault initialization.
#[offset]
pub mint: *const RuntimeAccount,

/// Pointer to Rent sysvar account.
#[offset]
pub rent: *const RuntimeAccount,
/// Signer seeds for PDA derivation and CPI signing.
pub pda_seeds: PDASignerSeeds,
/// From `sol_try_find_program_address`.

/// PDA signer seeds.
#[signer_seeds]
pub pda_seeds: SignerSeeds,

/// PDA address.
#[pubkey_offsets]
pub pda: Address,
/// System Program pubkey, zero-initialized on stack

/// System Program pubkey.
#[pubkey_offsets]
pub system_program_pubkey: Address,
/// Pointer to System Program ID in input buffer.

/// System Program ID in input buffer.
#[offset]
pub system_program_id: *const Address,
/// Get return data program ID for CPI calls, zero-initialized on stack.

/// Get return data program ID for CPI calls.
#[offset]
pub get_return_data_program_id: Address,
/// CPI instruction data for CreateAccount.

/// CreateAccount instruction data.
#[offset(CREATE_ACCT_DATA)]
#[unaligned_offset(
CREATE_ACCT_LAMPORTS,
lamports,
"Lamports field within CreateAccount instruction data."
)]
#[unaligned_offset(
CREATE_ACCT_SPACE,
space,
"Space field within CreateAccount instruction data."
)]
#[unaligned_pubkey_offsets(
CREATE_ACCT_OWNER,
owner,
"Owner field within CreateAccount instruction data."
)]
pub create_account_data: CreateAccountData,
/// CPI instruction data for InitializeAccount2.
pub initialize_account_2_data: InitializeAccount2,

/// GetAccountDataSize CPI instruction data.
#[unaligned_offset]
pub get_account_data_size_data: u8,
/// CPI accounts for CreateAccount and InitializeAccount2.

/// Vault index for PDA derivation.
#[unaligned_offset]
pub vault_index: u8,

/// Whether the current token program is Token 2022.
#[unaligned_offset]
pub token_program_is_2022: u8,

/// Padding for 8-byte alignment after CreateAccountData.
_pad: u8,

/// InitializeAccount2 CPI instruction data.
#[offset(INIT_ACCT_2_DATA)]
#[unaligned_offset(
INIT_ACCT_2_DISC,
discriminant,
"Discriminant field within InitializeAccount2 instruction data."
)]
#[unaligned_pubkey_offsets(
INIT_ACCT_2_PROPRIETOR,
proprietor,
"Proprietor field within InitializeAccount2 instruction data."
)]
pub initialize_account_2_data: InitializeAccount2,

/// CPI accounts.
#[cpi_accounts(CPI)]
pub cpi_accounts: CPIAccounts,

/// Signers seeds for CPI.
#[unaligned_offset(SIGNERS_SEEDS_ADDR, addr, "Signers seeds address.")]
#[unaligned_offset(SIGNERS_SEEDS_LEN, len, "Signers seeds length.")]
pub signers_seeds: SolSignerSeeds,
/// Re-used across CPIs, zero-initialized on stack.

/// Solana instruction.
#[sol_instruction(SOL_INSN)]
pub sol_instruction: SolInstruction,
/// From `sol_try_find_program_address`.

/// Bump seed.
#[offset]
pub bump: u8,
/// Vault index for vault PDA derivation.
pub vault_index: u8,
/// Whether the current token program is Token 2022 (zero-initialized on stack).
pub token_program_is_2022: u8,
}
// endregion: frame_example

constant_group! {
#[prefix("RM")]
#[inject("market/register")]
#[frame(RegisterMarketFrame)]
frame {
/// Pointer to token program address.
TOKEN_PROGRAM_ID = offset!(token_program_id),
/// Pointer to program ID in input buffer.
PROGRAM_ID = offset!(program_id),
/// Saved input buffer pointer.
INPUT = offset!(input),
/// Saved input_shifted pointer.
INPUT_SHIFTED = offset!(input_shifted),
/// From Rent sysvar.
LAMPORTS_PER_BYTE = offset!(lamports_per_byte),
/// Return value from GetAccountDataSize CPI, to check token account data size at runtime.
TOKEN_ACCOUNT_DATA_SIZE = offset!(token_account_data_size),
/// Pointer to mint account for vault initialization.
MINT = offset!(mint),
/// Pointer to Rent sysvar account.
RENT = offset!(rent),
/// PDA signer seeds.
PDA_SEEDS = signer_seeds!(pda_seeds),
/// PDA address.
PDA = pubkey_offsets!(pda),
/// System Program pubkey.
SYSTEM_PROGRAM_PUBKEY = pubkey_offsets!(system_program_pubkey),
/// System Program ID in input buffer.
SYSTEM_PROGRAM_ID = offset!(system_program_id),
/// Get return data program ID for CPI calls.
GET_RETURN_DATA_PROGRAM_ID = offset!(get_return_data_program_id),
/// CreateAccount instruction data.
CREATE_ACCT_DATA = offset!(create_account_data),
/// Lamports field within CreateAccount instruction data.
CREATE_ACCT_LAMPORTS = unaligned_offset!(create_account_data.lamports),
/// Space field within CreateAccount instruction data.
CREATE_ACCT_SPACE = unaligned_offset!(create_account_data.space),
/// Owner field within CreateAccount instruction data.
CREATE_ACCT_OWNER = unaligned_pubkey_offsets!(create_account_data.owner),
/// InitializeAccount2 CPI instruction data.
INIT_ACCT_2_DATA = offset!(initialize_account_2_data),
/// Discriminant field within InitializeAccount2 instruction data.
INIT_ACCT_2_DISC = unaligned_offset!(initialize_account_2_data.discriminant),
/// Proprietor field within InitializeAccount2 instruction data.
INIT_ACCT_2_PROPRIETOR = unaligned_pubkey_offsets!(initialize_account_2_data.proprietor),
/// GetAccountDataSize CPI instruction data.
GET_ACCOUNT_DATA_SIZE_DATA = unaligned_offset!(get_account_data_size_data),
/// CPI accounts.
CPI = cpi_accounts!(cpi_accounts),
/// Signers seeds address.
SIGNERS_SEEDS_ADDR = unaligned_offset!(signers_seeds.addr),
/// Signers seeds length.
SIGNERS_SEEDS_LEN = unaligned_offset!(signers_seeds.len),
/// Solana instruction.
SOL_INSN = sol_instruction!(sol_instruction),
/// Bump seed.
BUMP = offset!(bump),
/// Vault index for PDA derivation.
VAULT_INDEX = unaligned_offset!(vault_index),
/// Whether the current token program is Token 2022.
TOKEN_PROGRAM_IS_2022 = unaligned_offset!(token_program_is_2022),
/// From pda_seeds to sol_instruction.
PDA_SEEDS_TO_SOL_INSN = relative_offset!(pda_seeds, sol_instruction),
/// From pda to signers_seeds.
PDA_TO_SIGNERS_SEEDS = relative_offset!(pda, signers_seeds),
/// From create_account_data to CPI account metas.
CREATE_ACCT_DATA_TO_CPI_ACCT_METAS = relative_offset!(
create_account_data, cpi_accounts.idx_0_meta
),
}
}

// endregion: register_market_stack
2 changes: 1 addition & 1 deletion macros/src/constant_group/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod expand;
mod parse;
pub(crate) mod parse;

use syn::{Expr, Ident};

Expand Down
Loading
Loading