diff --git a/docs/algorithms/ENTRYPOINT.tex b/docs/algorithms/ENTRYPOINT.tex index df2d1cb..5c20040 100644 --- a/docs/algorithms/ENTRYPOINT.tex +++ b/docs/algorithms/ENTRYPOINT.tex @@ -2,15 +2,15 @@ \caption{ENTRYPOINT} \begin{algorithmic} \COMMENT{Pointer to input buffer.} - \INPUT $r_1 = input$ + \INPUT $r_1$ = input \COMMENT{Pointer to instruction data.} - \INPUT $r_2 = insn$ -\PROCEDURE{ENTRYPOINT}{$input, insn$} - \STATE $n\_accounts = input.n\_accounts$ - \STATE $insn\_len = insn.length$ - \STATE $insn\_disc = insn.discriminant$ - \IF{$insn\_disc ==$ \texttt{Discriminant::RegisterMarket}} - \RETURN \CALL{REGISTER-MARKET}{$input$, $insn$, $n\_accounts$, $insn\_len$} + \INPUT $r_2$ = insn +\PROCEDURE{ENTRYPOINT}{input, insn} + \STATE n\_accounts = input.n\_accounts + \STATE insn\_len = insn.length + \STATE insn\_disc = insn.discriminant + \IF{insn\_disc == \texttt{Discriminant::RegisterMarket}} + \RETURN \CALL{REGISTER-MARKET}{input, insn, n\_accounts, insn\_len} \ENDIF \RETURN \texttt{ErrorCode::InvalidDiscriminant} \ENDPROCEDURE diff --git a/docs/algorithms/REGISTER-MARKET.tex b/docs/algorithms/REGISTER-MARKET.tex index 0de32e3..e315fad 100644 --- a/docs/algorithms/REGISTER-MARKET.tex +++ b/docs/algorithms/REGISTER-MARKET.tex @@ -2,82 +2,127 @@ \caption{REGISTER-MARKET} \begin{algorithmic} \COMMENT{Pointer to input buffer.} - \INPUT $r_1 = input$ + \INPUT $r_1$ = input \COMMENT{Pointer to instruction data.} - \INPUT $r_2 = insn$ + \INPUT $r_2$ = insn \COMMENT{Number of accounts.} - \INPUT $r_3 = n\_accounts$ + \INPUT $r_3$ = n\_accounts \COMMENT{Instruction data length.} - \INPUT $r_4 = insn\_len$ - \REQUIRE $insn.discriminant ==$ \texttt{Discriminant::RegisterMarket} -\PROCEDURE{REGISTER-MARKET}{$input, insn, n\_accounts, insn\_len$} - \IF{$n\_accounts <$ \texttt{RegisterMarketAccounts.LEN}} + \INPUT $r_4$ = insn\_len + \REQUIRE insn.discriminant == \texttt{Discriminant::RegisterMarket} +\PROCEDURE{REGISTER-MARKET}{input, insn, n\_accounts, insn\_len} + \IF{n\_accounts $<$ \texttt{RegisterMarketAccounts.LEN}} \RETURN \texttt{ErrorCode::InvalidNumberOfAccounts} \ENDIF - \IF{$insn\_len \neq$ \texttt{RegisterMarketData.LEN}} + \IF{insn\_len $\neq$ \texttt{RegisterMarketData.LEN}} \RETURN \texttt{ErrorCode::InvalidInstructionLength} \ENDIF \COMMENT{Check user, market accounts.} - \IF{$input.user.data\_len \neq 0$} + \IF{input.user.data\_len $\neq$ 0} \RETURN \texttt{ErrorCode::UserHasData} \ENDIF - \IF{$input.market.duplicate \neq$ \texttt{account.NON\_DUP\_MARKER}} + \IF{input.market.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} \RETURN \texttt{ErrorCode::MarketAccountIsDuplicate} \ENDIF - \IF{$input.market.data\_len \neq 0$} + \IF{input.market.data\_len $\neq$ 0} \RETURN \texttt{ErrorCode::MarketHasData} \ENDIF \COMMENT{Check base mint account, create signer seed.} - \IF{$input.base\_mint.duplicate \neq$ \texttt{account.NON\_DUP\_MARKER}} + \IF{input.base\_mint.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} \RETURN \texttt{ErrorCode::BaseMintIsDuplicate} \ENDIF - \STATE $frame.pda\_seeds.base.addr = input.base\_mint.pubkey$ - \STATE $frame.pda\_seeds.base.len =$ \texttt{Address.size} + \STATE frame.pda\_seeds.base.addr = input.base\_mint.pubkey + \STATE frame.pda\_seeds.base.len = \texttt{Address.size} \COMMENT{Increment input buffer by base mint padded data length for quote offsets.} - \STATE $input\_shifted = input + input.base\_mint.padded\_data\_len$ + \STATE input\_shifted = input + input.base\_mint.padded\_data\_len \COMMENT{Check quote mint account, create signer seed.} - \IF{$input\_shifted.quote\_mint.duplicate \neq$ - \texttt{account.NON\_DUP\_MARKER} - } + \IF{input\_shifted.quote\_mint.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} \RETURN \texttt{ErrorCode::QuoteMintIsDuplicate} \ENDIF - \STATE $frame.pda\_seeds.quote.addr = input\_shifted.quote\_mint.pubkey$ - \STATE $frame.pda\_seeds.quote.len =$ \texttt{Address.size} + \STATE frame.pda\_seeds.quote.addr = input\_shifted.quote\_mint.pubkey + \STATE frame.pda\_seeds.quote.len = \texttt{Address.size} \COMMENT{Advance to System Program account.} - \STATE $quote\_mint\_padded\_data\_len = input\_shifted.quote\_mint.padded\_data\_len$ - \STATE $acct = \&input\_shifted.quote\_mint$ - \STATE $acct \mathrel{{+}{=}} quote\_mint\_padded\_data\_len$ + - \texttt{EmptyAccount.size} + \STATE quote\_mint\_padded\_data\_len = input\_shifted.quote\_mint.padded\_data\_len + \STATE acct = \&input\_shifted.quote\_mint + \STATE acct += quote\_mint\_padded\_data\_len + \texttt{EmptyAccount.size} \COMMENT{Derive market PDA.} - \STATE \CALL{Store}{$input$} - \STATE $syscall.seeds = \&frame.pda\_seeds$ - \STATE $syscall.program\_id = \&insn.program\_id$ - \STATE $syscall.seeds\_len =$ \texttt{register\_misc.TRY\_FIND\_PDA\_SEEDS\_LEN} - \STATE $syscall.program\_address = frame.pda$ - \STATE $syscall.bump\_seed = frame.bump$ + \STATE \CALL{Store}{input} + \STATE syscall.seeds = \&frame.pda\_seeds + \STATE syscall.program\_id = \&insn.program\_id + \STATE syscall.seeds\_len = \texttt{register\_misc.TRY\_FIND\_PDA\_SEEDS\_LEN} + \STATE syscall.program\_address = \&frame.pda + \STATE syscall.bump\_seed = \&frame.bump \STATE \CALL{sol-try-find-program-address}{} \COMMENT{Verify derived market PDA matches market account pubkey.} - \IF{$input.market.pubkey \neq frame.market\_pda$} + \IF{input.market.pubkey $\neq$ frame.market\_pda} \RETURN \texttt{ErrorCode::InvalidMarketPubkey} \ENDIF + \COMMENT{Populate bump signer seed from derived bump.} + \STATE frame.pda\_seeds.bump.addr = \&syscall.bump\_seed + \STATE frame.pda\_seeds.bump.len = \texttt{u8.size} + \COMMENT{Populate CreateAccount CPI instruction data owner field.} + \STATE frame.create\_account\_data.owner = syscall.program\_id \COMMENT{Check System Program account.} - \IF{$acct.duplicate \neq$ \texttt{account.NON\_DUP\_MARKER}} + \IF{acct.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} \RETURN \texttt{ErrorCode::SystemProgramIsDuplicate} \ENDIF - \IF{$acct.pubkey \neq frame.system\_program\_pubkey$} + \IF{acct.pubkey $\neq$ frame.system\_program\_pubkey} \RETURN \texttt{ErrorCode::InvalidSystemProgramPubkey} \ENDIF + \COMMENT{Populate CPI program ID field.} + \STATE frame.sol\_instruction.program\_id = \&acct.address \COMMENT{Advance to Rent sysvar account.} - \STATE $system\_program\_padded\_data\_len = acct.padded\_data\_len$ - \STATE $acct \mathrel{{+}{=}} system\_program\_padded\_data\_len$ + - \texttt{EmptyAccount.size} + \STATE system\_program\_padded\_data\_len = acct.padded\_data\_len + \STATE acct += system\_program\_padded\_data\_len + \texttt{EmptyAccount.size} \COMMENT{Check Rent sysvar account.} - \IF{$acct.duplicate \neq$ \texttt{account.NON\_DUP\_MARKER}} + \IF{acct.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} \RETURN \texttt{ErrorCode::RentSysvarIsDuplicate} \ENDIF - \IF{$acct.pubkey \neq$ \texttt{pubkey.RENT}} + \IF{acct.pubkey $\neq$ \texttt{pubkey.RENT}} \RETURN \texttt{ErrorCode::InvalidRentSysvarPubkey} \ENDIF + \COMMENT{Prepare CreateAccount instruction lamports, space fields.} + \STATE frame.create\_account\_data.space = \texttt{MarketHeader.size} + \STATE acct\_size = \texttt{MarketHeader.size} + \texttt{account.STORAGE\_OVERHEAD} + \STATE lamports\_per\_byte = acct.data.lamports\_per\_byte + \STATE frame.create\_account\_data.lamports = acct\_size $\times$ lamports\_per\_byte + \COMMENT{Assign CPI account fields via immediates.} + \STATE frame.cpi.user\_info.is\_signer = \texttt{true} + \STATE frame.cpi.user\_info.is\_writable = \texttt{true} + \STATE frame.cpi.user\_meta.is\_signer = \texttt{true} + \STATE frame.cpi.user\_meta.is\_writable = \texttt{true} + \STATE frame.cpi.target\_info.is\_signer = \texttt{true} + \STATE frame.cpi.target\_info.is\_writable = \texttt{true} + \STATE frame.cpi.target\_meta.is\_signer = \texttt{true} + \STATE frame.cpi.target\_meta.is\_writable = \texttt{true} + \COMMENT{Assign CPI account fields via pointers.} + \STATE frame.cpi.user\_meta.pubkey = \&input.user.address + \STATE frame.cpi.user\_info.key = \&input.user.address + \STATE frame.cpi.user\_info.owner = \&input.user.owner + \STATE frame.cpi.user\_info.lamports = \&input.user.lamports + \STATE frame.cpi.user\_info.data = \&input.user.data + \STATE frame.cpi.target\_meta.pubkey = \&input.market.address + \STATE frame.cpi.target\_info.key = \&input.market.address + \STATE frame.cpi.target\_info.owner = \&input.market.owner + \STATE frame.cpi.target\_info.lamports = \&input.market.lamports + \STATE frame.cpi.target\_info.data = \&input.market.data + \COMMENT{Populate signers seeds for CPI.} + \STATE frame.signers\_seeds.addr = \&frame.pda\_seeds + \STATE frame.signers\_seeds.len = \texttt{RegisterMarketFrame.PDA\_SEEDS\_N\_SEEDS} + \COMMENT{Populate SolInstruction for CreateAccount CPI.} + \STATE frame.sol\_instruction.accounts = \&frame.cpi.account\_metas + \STATE frame.sol\_instruction.account\_len = + \texttt{register\_misc.CREATE\_ACCOUNT\_N\_ACCOUNTS} + \STATE frame.sol\_instruction.data = \&frame.create\_account\_data + \STATE frame.sol\_instruction.data\_len = \texttt{CreateAccountData.size} + \COMMENT{Invoke CreateAccount CPI.} + \STATE syscall.instruction = \&frame.sol\_instruction + \STATE syscall.account\_infos = \&frame.cpi.account\_infos + \STATE syscall.account\_infos\_len = + \texttt{register\_misc.CREATE\_ACCOUNT\_N\_ACCOUNTS} + \STATE syscall.seeds = \&frame.signers\_seeds + \STATE syscall.seeds\_len = \texttt{register\_misc.N\_PDA\_SIGNERS} + \STATE \CALL{sol-invoke-signed-c}{} \ENDPROCEDURE \end{algorithmic} \end{algorithm} diff --git a/docs/algorithms/syscalls.json b/docs/algorithms/syscalls.json index 1ac3ad1..9d8a303 100644 --- a/docs/algorithms/syscalls.json +++ b/docs/algorithms/syscalls.json @@ -1,3 +1,4 @@ { - "sol_try_find_program_address": "https://github.com/anza-xyz/agave/blob/v3.1.6/platform-tools-sdk/sbf/c/inc/sol/inc/pubkey.inc#L74-L83" + "sol_invoke_signed_c": "https://github.com/anza-xyz/agave/blob/v4.0.0-beta.5/platform-tools-sdk/sbf/c/inc/sol/inc/cpi.inc#L56-L90", + "sol_try_find_program_address": "https://github.com/anza-xyz/agave/blob/v4.0.0-beta.5/platform-tools-sdk/sbf/c/inc/sol/inc/pubkey.inc#L74-L83" } diff --git a/docs/src/development/build-scaffolding.md b/docs/src/development/build-scaffolding.md index 0b4b050..0fff373 100644 --- a/docs/src/development/build-scaffolding.md +++ b/docs/src/development/build-scaffolding.md @@ -41,7 +41,7 @@ Defines a group of named assembly constants with an injection target. The constants. An optional `#[prefix("...")]` attribute prepends a prefix to all generated constant names. An optional `///` doc comment on the group itself adds a header comment and separator lines around the group in the output -assembly file. Each constant is assigned a value using one of nine custom +assembly file. Each constant is assigned a value using one of the following syntax forms (parsed within the proc macro, not standalone macros): - `offset!(expr)`: an `i16` memory offset, the generated name is suffixed with @@ -69,6 +69,10 @@ syntax forms (parsed within the proc macro, not standalone macros): offsets for each `SolAccountInfo` and `SolAccountMeta` field (requires `#[frame(Type)]`, field type must be defined with [`cpi_accounts!`](#cpi_accounts)) +- `relative_offset!(Struct, from_field, to_field)`: computes the difference + between two field offsets within the same struct, emitted as an `i32` + immediate with `_REL_OFF_IMM` suffix. In `#[frame(Type)]` context the + struct is inferred and only the two field paths are required diff --git a/interface/src/lib.rs b/interface/src/lib.rs index b85e73b..419e7c1 100644 --- a/interface/src/lib.rs +++ b/interface/src/lib.rs @@ -65,9 +65,10 @@ pub const INJECTION_GROUPS: &[&dropset_build::ConstantGroup] = &[ &error_code::GROUP, &market::register_market_data::GROUP, &market::register_market_accounts::GROUP, - &market::register_market_frame::GROUP, + &market::frame::GROUP, &market::register_misc::GROUP, &memory::account::GROUP, + &memory::cpi::GROUP, &memory::data::GROUP, &memory::input_buffer::GROUP, &memory::size_of::GROUP, diff --git a/interface/src/market.rs b/interface/src/market.rs index cd2dea9..b875964 100644 --- a/interface/src/market.rs +++ b/interface/src/market.rs @@ -1,4 +1,6 @@ -use crate::cpi_bindings::{SolAccountInfo, SolAccountMeta, SolInstruction, SolSignerSeed}; +use crate::cpi_bindings::{ + SolAccountInfo, SolAccountMeta, SolInstruction, SolSignerSeed, SolSignerSeeds, +}; use crate::memory::EmptyAccount; use crate::memory::StackNode; use crate::order::Order; @@ -64,6 +66,10 @@ constant_group! { QUOTE_DATA_LEN = offset!(RegisterMarketInputBuffer.quote_mint.header.data_len), /// Number of seeds for market PDA derivation (base, quote). TRY_FIND_PDA_SEEDS_LEN = immediate!(2), + /// Number of accounts for CreateAccount CPI (user, target). + CREATE_ACCOUNT_N_ACCOUNTS = immediate!(2), + /// Number of PDA signers for CPI. + N_PDA_SIGNERS = immediate!(1), } } @@ -90,6 +96,7 @@ pub struct CreateAccountData { pub discriminator: u32, pub lamports: u64, pub space: u64, + /// Zero-initialized on stack. pub owner: Address, /// Included for alignment on stack. _pad: u32, @@ -99,10 +106,10 @@ cpi_accounts! { /// CPI accounts for CreateAccount and ATA creation. /// /// CreateAccount uses the first two accounts (user, target). ATA creation requires all six. - pub struct CPIAccounts { + CPIAccounts { /// User account. user, - /// Target account. + /// Target account (the account to create, either market account or ATA). target, /// Proprietor account. proprietor, @@ -118,7 +125,7 @@ cpi_accounts! { // region: register_market_stack // region: signer_seeds_example signer_seeds! { - pub struct PDASignerSeeds { + PDASignerSeeds { /// Base mint seed. base, /// Quote mint seed. @@ -143,8 +150,10 @@ pub struct RegisterMarketFrame { pub create_account_data: CreateAccountData, /// CPI accounts for CreateAccount and ATA creation. pub cpi_accounts: CPIAccounts, + /// Signers seeds for CPI. + pub signers_seeds: SolSignerSeeds, /// Re-used across CPIs, zero-initialized on stack. - pub cpi_instruction: SolInstruction, + pub sol_instruction: SolInstruction, /// From `sol_try_find_program_address`. pub bump: u8, } @@ -154,15 +163,13 @@ constant_group! { #[prefix("RM")] #[inject("market/register")] #[frame(RegisterMarketFrame)] - register_market_frame { + frame { /// 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), - /// Bump seed. - BUMP = offset!(bump), /// CreateAccount instruction data. CREATE_ACCT_DATA = offset!(create_account_data), /// Lamports field within CreateAccount instruction data. @@ -173,8 +180,22 @@ constant_group! { CREATE_ACCT_OWNER = unaligned_pubkey_offsets!(create_account_data.owner), /// 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!(cpi_instruction), + SOL_INSN = sol_instruction!(sol_instruction), + /// Bump seed. + BUMP = offset!(bump), + /// 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.user_meta + ), } } diff --git a/interface/src/memory.rs b/interface/src/memory.rs index cce7df9..3a5f803 100644 --- a/interface/src/memory.rs +++ b/interface/src/memory.rs @@ -1,7 +1,9 @@ +use crate::market::{CreateAccountData, MarketHeader}; use dropset_macros::{constant_group, size_of_group, svm_data}; use pinocchio::Address; use pinocchio::account::{MAX_PERMITTED_DATA_INCREASE, RuntimeAccount}; use pinocchio::entrypoint::NON_DUP_MARKER; +use pinocchio::sysvars::rent::ACCOUNT_STORAGE_OVERHEAD; #[svm_data] pub struct StackNode { @@ -41,7 +43,7 @@ pub type EmptyAccount = FullRuntimeAccount<{ runtime_data_size(data::LEN_ZERO) } constant_group! { #[prefix("ACCT")] #[inject("common/memory")] - /// Field offsets within a runtime account. + /// Assorted runtime account constants. account { /// Borrow state / duplicate marker. DUPLICATE = offset!(EmptyAccount.header.borrow_state), @@ -59,8 +61,22 @@ constant_group! { OWNER = pubkey_offsets!(EmptyAccount.header.owner), /// Account data length. DATA_LEN = offset!(EmptyAccount.header.data_len), + /// Account data start. + DATA = offset!(EmptyAccount.data), /// Non-dup marker for accounts. NON_DUP_MARKER = immediate!(NON_DUP_MARKER as i32), + /// Account storage overhead for rent calculation. + STORAGE_OVERHEAD = immediate!(ACCOUNT_STORAGE_OVERHEAD as i32), + } +} + +constant_group! { + #[prefix("CPI")] + #[inject("common/memory")] + /// CPI-related constants. + cpi { + /// Mask for writable signer (is_writable | is_signer). + WRITABLE_SIGNER = immediate!(0x0101), } } @@ -82,12 +98,24 @@ constant_group! { input_buffer { /// From input buffer to user data length. USER_DATA_LEN = offset!(InputBufferHeader.user.header.data_len), + /// From input buffer to user pubkey. + USER_PUBKEY = pubkey_offsets!(InputBufferHeader.user.header.address), /// From input buffer to market duplicate flag. MARKET_DUPLICATE = offset!(InputBufferHeader.market.borrow_state), /// From input buffer to market data length. MARKET_DATA_LEN = offset!(InputBufferHeader.market.data_len), /// From input buffer to market address. MARKET_PUBKEY = pubkey_offsets!(InputBufferHeader.market.address), + /// From address to owner in a runtime account. + ADDRESS_TO_OWNER = relative_offset!(RuntimeAccount, address, owner), + /// From owner to lamports in a runtime account. + OWNER_TO_LAMPORTS = relative_offset!(RuntimeAccount, owner, lamports), + /// From lamports to data start in a runtime account. + LAMPORTS_TO_DATA = relative_offset!(EmptyAccount, header.lamports, data), + /// From user data to market address in the input buffer. + USER_DATA_TO_MARKET_ADDRESS = relative_offset!( + InputBufferHeader, user.data, market.address + ), } } // endregion: constant_group_example @@ -95,7 +123,7 @@ constant_group! { // region: size_of_group_example size_of_group! { #[inject("common/memory")] - [Address, EmptyAccount] + [u8, Address, EmptyAccount, MarketHeader, CreateAccountData] } // endregion: size_of_group_example diff --git a/macros/src/constant_group/expand/mod.rs b/macros/src/constant_group/expand/mod.rs index 2e42d07..b1c6dd1 100644 --- a/macros/src/constant_group/expand/mod.rs +++ b/macros/src/constant_group/expand/mod.rs @@ -130,6 +130,28 @@ pub fn expand(input: &ConstantGroupInput) -> proc_macro2::TokenStream { &mut meta_idents, ); } + ConstantKind::RelativeOffset { + ty, + from_fields, + to_fields, + } => { + let resolved_ty = ty.as_ref().unwrap_or_else(|| { + input + .frame_type + .as_ref() + .expect("frame_type must be set for RelativeOffset without explicit type") + }); + let (def, meta) = offset::expand_relative_offset( + base_name, + &asm_name, + doc, + resolved_ty, + from_fields, + to_fields, + ); + const_defs.push(def); + meta_idents.push(meta); + } ConstantKind::UnalignedFramePubkeyOffsets { fields } => { let frame_ty = input .frame_type diff --git a/macros/src/constant_group/expand/offset.rs b/macros/src/constant_group/expand/offset.rs index cacb502..42e2efd 100644 --- a/macros/src/constant_group/expand/offset.rs +++ b/macros/src/constant_group/expand/offset.rs @@ -334,6 +334,35 @@ pub fn expand_unaligned_frame_offset( (def, meta_ident) } +/// Expand `relative_offset!(...)` into an i32 immediate with `_REL_OFF_IMM` +/// suffix. Computes `offset_of!(Struct, to) - offset_of!(Struct, from)`. +pub fn expand_relative_offset( + base_name: &Ident, + asm_name: &str, + doc: &str, + ty: &syn::Path, + from_fields: &[syn::Member], + to_fields: &[syn::Member], +) -> (proc_macro2::TokenStream, Ident) { + let rust_name = Ident::new(&format!("{}_REL_OFF_IMM", base_name), base_name.span()); + let asm_name = format!("{}_REL_OFF_IMM", asm_name); + let meta_ident = codegen::meta_ident(&asm_name, base_name.span()); + let meta = codegen::immediate_meta(&meta_ident, &asm_name, doc, quote! { #rust_name }); + + let def = quote! { + #[doc = #doc] + pub const #rust_name: i32 = { + use super::*; + (core::mem::offset_of!(#ty, #(#to_fields).*) + - core::mem::offset_of!(#ty, #(#from_fields).*)) as i32 + }; + + #meta + }; + + (def, meta_ident) +} + /// Expand `offset!(field)` inside a `#[frame(Type)]` group. pub fn expand_frame_offset( base_name: &Ident, diff --git a/macros/src/constant_group/mod.rs b/macros/src/constant_group/mod.rs index 7a16eee..d588273 100644 --- a/macros/src/constant_group/mod.rs +++ b/macros/src/constant_group/mod.rs @@ -52,6 +52,18 @@ pub(crate) enum ConstantKind { /// like `FramePubkeyOffsets` but without the alignment assertion. Names /// get `_UOFF` suffix. UnalignedFramePubkeyOffsets { fields: Vec }, + /// `relative_offset!(Struct, from_field, to_field)`: difference between + /// two field offsets within the same struct, emitted as an i32 immediate + /// with `_REL_OFF_IMM` suffix. In `#[frame(Type)]` context the struct + /// is inferred and both paths are bare field chains. + RelativeOffset { + /// Explicit struct type (non-frame context). + ty: Option, + /// Field chain for the "from" position. + from_fields: Vec, + /// Field chain for the "to" position. + to_fields: Vec, + }, } impl ConstantKind { diff --git a/macros/src/constant_group/parse/mod.rs b/macros/src/constant_group/parse/mod.rs index f7ecb02..5cecd43 100644 --- a/macros/src/constant_group/parse/mod.rs +++ b/macros/src/constant_group/parse/mod.rs @@ -140,6 +140,11 @@ impl Parse for ConstantGroupInput { .into_unaligned_pubkey_offsets() .map_err(|msg| syn::Error::new(kind_ident.span(), msg))? } + "relative_offset" => { + let inner; + syn::parenthesized!(inner in content); + offset::parse_relative_offset(&inner, &frame_type, kind_ident.span())? + } "pubkey_offsets" => { let inner; syn::parenthesized!(inner in content); diff --git a/macros/src/constant_group/parse/offset.rs b/macros/src/constant_group/parse/offset.rs index 55e7231..1adeae2 100644 --- a/macros/src/constant_group/parse/offset.rs +++ b/macros/src/constant_group/parse/offset.rs @@ -2,6 +2,52 @@ use syn::{Expr, Ident, Token, parse::ParseStream}; use super::super::ConstantKind; +/// Parse a bare field chain: `field.subfield.nested`. +fn parse_field_chain(input: ParseStream) -> syn::Result> { + let mut fields = Vec::new(); + let ident: Ident = input.parse()?; + fields.push(syn::Member::Named(ident)); + while input.peek(Token![.]) { + input.parse::()?; + let member: Ident = input.parse()?; + fields.push(syn::Member::Named(member)); + } + Ok(fields) +} + +/// Parse `relative_offset!(Struct, from, to)` or `relative_offset!(from, to)` +/// (frame context). +pub fn parse_relative_offset( + inner: ParseStream, + frame_type: &Option, + _span: proc_macro2::Span, +) -> syn::Result { + if let Some(frame_path) = frame_type { + // Frame context: relative_offset!(from_field.sub, to_field.sub) + let _ = frame_path; // struct inferred from frame + let from_fields = parse_field_chain(inner)?; + inner.parse::()?; + let to_fields = parse_field_chain(inner)?; + Ok(ConstantKind::RelativeOffset { + ty: None, + from_fields, + to_fields, + }) + } else { + // Non-frame: relative_offset!(Struct, from_field.sub, to_field.sub) + let ty: syn::Path = inner.parse()?; + inner.parse::()?; + let from_fields = parse_field_chain(inner)?; + inner.parse::()?; + let to_fields = parse_field_chain(inner)?; + Ok(ConstantKind::RelativeOffset { + ty: Some(ty), + from_fields, + to_fields, + }) + } +} + /// Parse the inside of `offset!(...)`. /// /// When a `#[frame(Type)]` is present, a bare identifier like `offset!(bump)` diff --git a/macros/src/cpi_accounts.rs b/macros/src/cpi_accounts.rs index c780350..2cc9e09 100644 --- a/macros/src/cpi_accounts.rs +++ b/macros/src/cpi_accounts.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ - Ident, Token, Visibility, braced, + Ident, Token, braced, parse::{Parse, ParseStream}, }; @@ -16,17 +16,14 @@ struct CpiAccountField { /// Parsed input for the `cpi_accounts!` macro. pub struct CpiAccountsInput { - vis: Visibility, name: Ident, fields: Vec, } impl Parse for CpiAccountsInput { fn parse(input: ParseStream) -> syn::Result { - // Consume any outer attributes (e.g. doc comments) before the struct. + // Consume any outer attributes (e.g. doc comments) before the name. let _attrs = input.call(syn::Attribute::parse_outer)?; - let vis: Visibility = input.parse()?; - input.parse::()?; let name: Ident = input.parse()?; let content; @@ -48,7 +45,7 @@ impl Parse for CpiAccountsInput { return Err(input.error("cpi_accounts! must have at least one field")); } - Ok(CpiAccountsInput { vis, name, fields }) + Ok(CpiAccountsInput { name, fields }) } } @@ -56,7 +53,6 @@ impl Parse for CpiAccountsInput { /// `SolAccountInfo` fields first (contiguous), then `SolAccountMeta` fields /// (contiguous). Registers field names in shared state. pub fn expand(input: &CpiAccountsInput) -> TokenStream { - let vis = &input.vis; let name = &input.name; // SolAccountInfo fields first, then SolAccountMeta fields. @@ -102,7 +98,7 @@ pub fn expand(input: &CpiAccountsInput) -> TokenStream { quote! { #[repr(C)] - #vis struct #name { + pub struct #name { #(#info_fields),*, #(#meta_fields),* } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 4265056..5bd9aa5 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -51,7 +51,7 @@ pub fn constant_group(input: TokenStream) -> TokenStream { /// /// ```ignore /// signer_seeds! { -/// pub struct PdaSignerSeeds { +/// PdaSignerSeeds { /// /// Base mint seed. /// base, /// /// Quote mint seed. @@ -77,7 +77,7 @@ pub fn signer_seeds(input: TokenStream) -> TokenStream { /// /// ```ignore /// cpi_accounts! { -/// pub struct CreateAccountCPIAccounts { +/// CreateAccountCPIAccounts { /// /// User account. /// user, /// /// Market account. diff --git a/macros/src/signer_seeds.rs b/macros/src/signer_seeds.rs index 7fd75aa..d0548d6 100644 --- a/macros/src/signer_seeds.rs +++ b/macros/src/signer_seeds.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ - Ident, Token, Visibility, braced, + Ident, Token, braced, parse::{Parse, ParseStream}, }; @@ -16,15 +16,12 @@ struct SignerSeedField { /// Parsed input for the `signer_seeds!` macro. pub struct SignerSeedsInput { - vis: Visibility, name: Ident, fields: Vec, } impl Parse for SignerSeedsInput { fn parse(input: ParseStream) -> syn::Result { - let vis: Visibility = input.parse()?; - input.parse::()?; let name: Ident = input.parse()?; let content; @@ -46,14 +43,13 @@ impl Parse for SignerSeedsInput { return Err(input.error("signer_seeds! must have at least one field")); } - Ok(SignerSeedsInput { vis, name, fields }) + Ok(SignerSeedsInput { name, fields }) } } /// Expand a `signer_seeds!` invocation into a `#[repr(C)]` struct with all /// fields typed as `SolSignerSeed`, and register field names in shared state. pub fn expand(input: &SignerSeedsInput) -> TokenStream { - let vis = &input.vis; let name = &input.name; let field_defs: Vec<_> = input @@ -79,7 +75,7 @@ pub fn expand(input: &SignerSeedsInput) -> TokenStream { quote! { #[repr(C)] - #vis struct #name { + pub struct #name { #(#field_defs),* } } diff --git a/macros/src/size_of_group.rs b/macros/src/size_of_group.rs index 0a61fba..76a57bb 100644 --- a/macros/src/size_of_group.rs +++ b/macros/src/size_of_group.rs @@ -44,11 +44,33 @@ pub fn expand(input: &SizeOfGroupInput) -> proc_macro2::TokenStream { let doc = format!("Size of {} in bytes.", type_str); let meta_ident = codegen::meta_ident(&asm_name, Span::call_site()); + // Primitives (e.g. u8, u64) live in the prelude, not in `super`. + let is_primitive = matches!( + type_str.as_str(), + "u8" | "u16" + | "u32" + | "u64" + | "u128" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + | "usize" + | "isize" + | "bool" + ); + let qualified_ty = if is_primitive { + quote! { #ty } + } else { + quote! { super::#ty } + }; + let meta = codegen::immediate_meta(&meta_ident, &asm_name, &doc, quote! { #rust_name }); let def = quote! { #[doc = #doc] - pub const #rust_name: i32 = std::mem::size_of::() as i32; + pub const #rust_name: i32 = std::mem::size_of::<#qualified_ty>() as i32; #meta }; diff --git a/program/src/dropset/common/memory.s b/program/src/dropset/common/memory.s index 2e1a4cd..0811054 100644 --- a/program/src/dropset/common/memory.s +++ b/program/src/dropset/common/memory.s @@ -1,4 +1,4 @@ -# Field offsets within a runtime account. +# Assorted runtime account constants. # ------------------------------------------------------------------------- .equ ACCT_DUPLICATE_OFF, 0 # Borrow state / duplicate marker. .equ ACCT_IS_SIGNER_OFF, 1 # Whether the account is a signer. @@ -16,7 +16,16 @@ .equ ACCT_OWNER_CHUNK_2_OFF, 56 # Account owner (chunk 2). .equ ACCT_OWNER_CHUNK_3_OFF, 64 # Account owner (chunk 3). .equ ACCT_DATA_LEN_OFF, 80 # Account data length. +.equ ACCT_DATA_OFF, 88 # Account data start. .equ ACCT_NON_DUP_MARKER, 255 # Non-dup marker for accounts. +# Account storage overhead for rent calculation. +.equ ACCT_STORAGE_OVERHEAD, 128 +# ------------------------------------------------------------------------- + +# CPI-related constants. +# ------------------------------------------------------------------------- +# Mask for writable signer (is_writable | is_signer). +.equ CPI_WRITABLE_SIGNER, 257 # ------------------------------------------------------------------------- # Common data-related constants. @@ -31,6 +40,15 @@ # Input buffer constants for static header. # ------------------------------------------------------------------------- .equ IB_USER_DATA_LEN_OFF, 88 # From input buffer to user data length. +.equ IB_USER_PUBKEY_OFF, 16 # From input buffer to user pubkey. +# From input buffer to user pubkey (chunk 0). +.equ IB_USER_PUBKEY_CHUNK_0_OFF, 16 +# From input buffer to user pubkey (chunk 1). +.equ IB_USER_PUBKEY_CHUNK_1_OFF, 24 +# From input buffer to user pubkey (chunk 2). +.equ IB_USER_PUBKEY_CHUNK_2_OFF, 32 +# From input buffer to user pubkey (chunk 3). +.equ IB_USER_PUBKEY_CHUNK_3_OFF, 40 # From input buffer to market duplicate flag. .equ IB_MARKET_DUPLICATE_OFF, 10344 # From input buffer to market data length. @@ -44,7 +62,18 @@ .equ IB_MARKET_PUBKEY_CHUNK_2_OFF, 10368 # From input buffer to market address (chunk 3). .equ IB_MARKET_PUBKEY_CHUNK_3_OFF, 10376 +# From address to owner in a runtime account. +.equ IB_ADDRESS_TO_OWNER_REL_OFF_IMM, 32 +# From owner to lamports in a runtime account. +.equ IB_OWNER_TO_LAMPORTS_REL_OFF_IMM, 32 +# From lamports to data start in a runtime account. +.equ IB_LAMPORTS_TO_DATA_REL_OFF_IMM, 16 +# From user data to market address in the input buffer. +.equ IB_USER_DATA_TO_MARKET_ADDRESS_REL_OFF_IMM, 10256 # ------------------------------------------------------------------------- +.equ SIZE_OF_U8, 1 # Size of u8 in bytes. .equ SIZE_OF_ADDRESS, 32 # Size of Address in bytes. .equ SIZE_OF_EMPTY_ACCOUNT, 10336 # Size of EmptyAccount in bytes. +.equ SIZE_OF_MARKET_HEADER, 40 # Size of MarketHeader in bytes. +.equ SIZE_OF_CREATE_ACCOUNT_DATA, 56 # Size of CreateAccountData in bytes. diff --git a/program/src/dropset/market/register.s b/program/src/dropset/market/register.s index e1fe465..ce498bb 100644 --- a/program/src/dropset/market/register.s +++ b/program/src/dropset/market/register.s @@ -6,187 +6,195 @@ # Stack frame for REGISTER-MARKET. # ------------------------------------------------------------------------- -.equ RM_FM_PDA_SEEDS_OFF, -648 # Signer seeds offset. +.equ RM_FM_PDA_SEEDS_OFF, -664 # Signer seeds offset. .equ RM_FM_PDA_SEEDS_N_SEEDS, 3 # Number of signer seeds. -.equ RM_FM_PDA_SEEDS_BASE_ADDR_OFF, -648 # Base signer seed address. -.equ RM_FM_PDA_SEEDS_BASE_LEN_OFF, -640 # Base signer seed length. -.equ RM_FM_PDA_SEEDS_QUOTE_ADDR_OFF, -632 # Quote signer seed address. -.equ RM_FM_PDA_SEEDS_QUOTE_LEN_OFF, -624 # Quote signer seed length. -.equ RM_FM_PDA_SEEDS_BUMP_ADDR_OFF, -616 # Bump signer seed address. -.equ RM_FM_PDA_SEEDS_BUMP_LEN_OFF, -608 # Bump signer seed length. -.equ RM_FM_PDA_OFF, -600 # PDA address. -.equ RM_FM_PDA_CHUNK_0_OFF, -600 # PDA address (chunk 0). -.equ RM_FM_PDA_CHUNK_1_OFF, -592 # PDA address (chunk 1). -.equ RM_FM_PDA_CHUNK_2_OFF, -584 # PDA address (chunk 2). -.equ RM_FM_PDA_CHUNK_3_OFF, -576 # PDA address (chunk 3). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_OFF, -568 # System Program pubkey. +.equ RM_FM_PDA_SEEDS_BASE_ADDR_OFF, -664 # Base signer seed address. +.equ RM_FM_PDA_SEEDS_BASE_LEN_OFF, -656 # Base signer seed length. +.equ RM_FM_PDA_SEEDS_QUOTE_ADDR_OFF, -648 # Quote signer seed address. +.equ RM_FM_PDA_SEEDS_QUOTE_LEN_OFF, -640 # Quote signer seed length. +.equ RM_FM_PDA_SEEDS_BUMP_ADDR_OFF, -632 # Bump signer seed address. +.equ RM_FM_PDA_SEEDS_BUMP_LEN_OFF, -624 # Bump signer seed length. +.equ RM_FM_PDA_OFF, -616 # PDA address. +.equ RM_FM_PDA_CHUNK_0_OFF, -616 # PDA address (chunk 0). +.equ RM_FM_PDA_CHUNK_1_OFF, -608 # PDA address (chunk 1). +.equ RM_FM_PDA_CHUNK_2_OFF, -600 # PDA address (chunk 2). +.equ RM_FM_PDA_CHUNK_3_OFF, -592 # PDA address (chunk 3). +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_OFF, -584 # System Program pubkey. # System Program pubkey (chunk 0). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_0_OFF, -568 +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_0_OFF, -584 # System Program pubkey (chunk 1). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_1_OFF, -560 +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_1_OFF, -576 # System Program pubkey (chunk 2). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_2_OFF, -552 +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_2_OFF, -568 # System Program pubkey (chunk 3). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_3_OFF, -544 -.equ RM_FM_BUMP_OFF, -8 # Bump seed. -.equ RM_FM_CREATE_ACCT_DATA_OFF, -536 # CreateAccount instruction data. +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_3_OFF, -560 +.equ RM_FM_CREATE_ACCT_DATA_OFF, -552 # CreateAccount instruction data. # Lamports field within CreateAccount instruction data. -.equ RM_FM_CREATE_ACCT_LAMPORTS_UOFF, -532 +.equ RM_FM_CREATE_ACCT_LAMPORTS_UOFF, -548 # Space field within CreateAccount instruction data. -.equ RM_FM_CREATE_ACCT_SPACE_UOFF, -524 +.equ RM_FM_CREATE_ACCT_SPACE_UOFF, -540 # Owner field within CreateAccount instruction data. -.equ RM_FM_CREATE_ACCT_OWNER_UOFF, -516 +.equ RM_FM_CREATE_ACCT_OWNER_UOFF, -532 # Owner field within CreateAccount instruction data (chunk 0). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_0_UOFF, -516 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_0_UOFF, -532 # Owner field within CreateAccount instruction data (chunk 1). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_1_UOFF, -508 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_1_UOFF, -524 # Owner field within CreateAccount instruction data (chunk 2). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_2_UOFF, -500 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_2_UOFF, -516 # Owner field within CreateAccount instruction data (chunk 3). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_3_UOFF, -492 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_3_UOFF, -508 .equ RM_FM_CPI_N_ACCOUNTS, 6 # Number of CPI accounts. -.equ RM_FM_CPI_SOL_ACCT_INFO_OFF, -480 # Start of SolAccountInfo vector. -.equ RM_FM_CPI_SOL_ACCT_META_OFF, -144 # Start of SolAccountMeta vector. -.equ RM_FM_CPI_USER_ACCT_INFO_KEY_UOFF, -480 # User account info key. +.equ RM_FM_CPI_SOL_ACCT_INFO_OFF, -496 # Start of SolAccountInfo vector. +.equ RM_FM_CPI_SOL_ACCT_META_OFF, -160 # Start of SolAccountMeta vector. +.equ RM_FM_CPI_USER_ACCT_INFO_KEY_UOFF, -496 # User account info key. # User account info lamports. -.equ RM_FM_CPI_USER_ACCT_INFO_LAMPORTS_UOFF, -472 +.equ RM_FM_CPI_USER_ACCT_INFO_LAMPORTS_UOFF, -488 # User account info data length. -.equ RM_FM_CPI_USER_ACCT_INFO_DATA_LEN_UOFF, -464 -.equ RM_FM_CPI_USER_ACCT_INFO_DATA_UOFF, -456 # User account info data. -.equ RM_FM_CPI_USER_ACCT_INFO_OWNER_UOFF, -448 # User account info owner. +.equ RM_FM_CPI_USER_ACCT_INFO_DATA_LEN_UOFF, -480 +.equ RM_FM_CPI_USER_ACCT_INFO_DATA_UOFF, -472 # User account info data. +.equ RM_FM_CPI_USER_ACCT_INFO_OWNER_UOFF, -464 # User account info owner. # User account info rent epoch. -.equ RM_FM_CPI_USER_ACCT_INFO_RENT_EPOCH_UOFF, -440 +.equ RM_FM_CPI_USER_ACCT_INFO_RENT_EPOCH_UOFF, -456 # User account info is signer. -.equ RM_FM_CPI_USER_ACCT_INFO_IS_SIGNER_UOFF, -432 +.equ RM_FM_CPI_USER_ACCT_INFO_IS_SIGNER_UOFF, -448 # User account info is writable. -.equ RM_FM_CPI_USER_ACCT_INFO_IS_WRITABLE_UOFF, -431 +.equ RM_FM_CPI_USER_ACCT_INFO_IS_WRITABLE_UOFF, -447 # User account info executable. -.equ RM_FM_CPI_USER_ACCT_INFO_EXECUTABLE_UOFF, -430 -.equ RM_FM_CPI_USER_ACCT_META_PUBKEY_UOFF, -144 # User account meta pubkey. +.equ RM_FM_CPI_USER_ACCT_INFO_EXECUTABLE_UOFF, -446 +.equ RM_FM_CPI_USER_ACCT_META_PUBKEY_UOFF, -160 # User account meta pubkey. # User account meta is writable. -.equ RM_FM_CPI_USER_ACCT_META_IS_WRITABLE_UOFF, -136 +.equ RM_FM_CPI_USER_ACCT_META_IS_WRITABLE_UOFF, -152 # User account meta is signer. -.equ RM_FM_CPI_USER_ACCT_META_IS_SIGNER_UOFF, -135 -.equ RM_FM_CPI_TARGET_ACCT_INFO_KEY_UOFF, -424 # Target account info key. +.equ RM_FM_CPI_USER_ACCT_META_IS_SIGNER_UOFF, -151 +.equ RM_FM_CPI_TARGET_ACCT_INFO_KEY_UOFF, -440 # Target account info key. # Target account info lamports. -.equ RM_FM_CPI_TARGET_ACCT_INFO_LAMPORTS_UOFF, -416 +.equ RM_FM_CPI_TARGET_ACCT_INFO_LAMPORTS_UOFF, -432 # Target account info data length. -.equ RM_FM_CPI_TARGET_ACCT_INFO_DATA_LEN_UOFF, -408 -.equ RM_FM_CPI_TARGET_ACCT_INFO_DATA_UOFF, -400 # Target account info data. +.equ RM_FM_CPI_TARGET_ACCT_INFO_DATA_LEN_UOFF, -424 +.equ RM_FM_CPI_TARGET_ACCT_INFO_DATA_UOFF, -416 # Target account info data. # Target account info owner. -.equ RM_FM_CPI_TARGET_ACCT_INFO_OWNER_UOFF, -392 +.equ RM_FM_CPI_TARGET_ACCT_INFO_OWNER_UOFF, -408 # Target account info rent epoch. -.equ RM_FM_CPI_TARGET_ACCT_INFO_RENT_EPOCH_UOFF, -384 +.equ RM_FM_CPI_TARGET_ACCT_INFO_RENT_EPOCH_UOFF, -400 # Target account info is signer. -.equ RM_FM_CPI_TARGET_ACCT_INFO_IS_SIGNER_UOFF, -376 +.equ RM_FM_CPI_TARGET_ACCT_INFO_IS_SIGNER_UOFF, -392 # Target account info is writable. -.equ RM_FM_CPI_TARGET_ACCT_INFO_IS_WRITABLE_UOFF, -375 +.equ RM_FM_CPI_TARGET_ACCT_INFO_IS_WRITABLE_UOFF, -391 # Target account info executable. -.equ RM_FM_CPI_TARGET_ACCT_INFO_EXECUTABLE_UOFF, -374 +.equ RM_FM_CPI_TARGET_ACCT_INFO_EXECUTABLE_UOFF, -390 # Target account meta pubkey. -.equ RM_FM_CPI_TARGET_ACCT_META_PUBKEY_UOFF, -128 +.equ RM_FM_CPI_TARGET_ACCT_META_PUBKEY_UOFF, -144 # Target account meta is writable. -.equ RM_FM_CPI_TARGET_ACCT_META_IS_WRITABLE_UOFF, -120 +.equ RM_FM_CPI_TARGET_ACCT_META_IS_WRITABLE_UOFF, -136 # Target account meta is signer. -.equ RM_FM_CPI_TARGET_ACCT_META_IS_SIGNER_UOFF, -119 +.equ RM_FM_CPI_TARGET_ACCT_META_IS_SIGNER_UOFF, -135 # Proprietor account info key. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_KEY_UOFF, -368 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_KEY_UOFF, -384 # Proprietor account info lamports. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_LAMPORTS_UOFF, -360 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_LAMPORTS_UOFF, -376 # Proprietor account info data length. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_DATA_LEN_UOFF, -352 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_DATA_LEN_UOFF, -368 # Proprietor account info data. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_DATA_UOFF, -344 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_DATA_UOFF, -360 # Proprietor account info owner. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_OWNER_UOFF, -336 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_OWNER_UOFF, -352 # Proprietor account info rent epoch. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_RENT_EPOCH_UOFF, -328 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_RENT_EPOCH_UOFF, -344 # Proprietor account info is signer. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_IS_SIGNER_UOFF, -320 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_IS_SIGNER_UOFF, -336 # Proprietor account info is writable. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_IS_WRITABLE_UOFF, -319 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_IS_WRITABLE_UOFF, -335 # Proprietor account info executable. -.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_EXECUTABLE_UOFF, -318 +.equ RM_FM_CPI_PROPRIETOR_ACCT_INFO_EXECUTABLE_UOFF, -334 # Proprietor account meta pubkey. -.equ RM_FM_CPI_PROPRIETOR_ACCT_META_PUBKEY_UOFF, -112 +.equ RM_FM_CPI_PROPRIETOR_ACCT_META_PUBKEY_UOFF, -128 # Proprietor account meta is writable. -.equ RM_FM_CPI_PROPRIETOR_ACCT_META_IS_WRITABLE_UOFF, -104 +.equ RM_FM_CPI_PROPRIETOR_ACCT_META_IS_WRITABLE_UOFF, -120 # Proprietor account meta is signer. -.equ RM_FM_CPI_PROPRIETOR_ACCT_META_IS_SIGNER_UOFF, -103 -.equ RM_FM_CPI_MINT_ACCT_INFO_KEY_UOFF, -312 # Mint account info key. +.equ RM_FM_CPI_PROPRIETOR_ACCT_META_IS_SIGNER_UOFF, -119 +.equ RM_FM_CPI_MINT_ACCT_INFO_KEY_UOFF, -328 # Mint account info key. # Mint account info lamports. -.equ RM_FM_CPI_MINT_ACCT_INFO_LAMPORTS_UOFF, -304 +.equ RM_FM_CPI_MINT_ACCT_INFO_LAMPORTS_UOFF, -320 # Mint account info data length. -.equ RM_FM_CPI_MINT_ACCT_INFO_DATA_LEN_UOFF, -296 -.equ RM_FM_CPI_MINT_ACCT_INFO_DATA_UOFF, -288 # Mint account info data. -.equ RM_FM_CPI_MINT_ACCT_INFO_OWNER_UOFF, -280 # Mint account info owner. +.equ RM_FM_CPI_MINT_ACCT_INFO_DATA_LEN_UOFF, -312 +.equ RM_FM_CPI_MINT_ACCT_INFO_DATA_UOFF, -304 # Mint account info data. +.equ RM_FM_CPI_MINT_ACCT_INFO_OWNER_UOFF, -296 # Mint account info owner. # Mint account info rent epoch. -.equ RM_FM_CPI_MINT_ACCT_INFO_RENT_EPOCH_UOFF, -272 +.equ RM_FM_CPI_MINT_ACCT_INFO_RENT_EPOCH_UOFF, -288 # Mint account info is signer. -.equ RM_FM_CPI_MINT_ACCT_INFO_IS_SIGNER_UOFF, -264 +.equ RM_FM_CPI_MINT_ACCT_INFO_IS_SIGNER_UOFF, -280 # Mint account info is writable. -.equ RM_FM_CPI_MINT_ACCT_INFO_IS_WRITABLE_UOFF, -263 +.equ RM_FM_CPI_MINT_ACCT_INFO_IS_WRITABLE_UOFF, -279 # Mint account info executable. -.equ RM_FM_CPI_MINT_ACCT_INFO_EXECUTABLE_UOFF, -262 -.equ RM_FM_CPI_MINT_ACCT_META_PUBKEY_UOFF, -96 # Mint account meta pubkey. +.equ RM_FM_CPI_MINT_ACCT_INFO_EXECUTABLE_UOFF, -278 +.equ RM_FM_CPI_MINT_ACCT_META_PUBKEY_UOFF, -112 # Mint account meta pubkey. # Mint account meta is writable. -.equ RM_FM_CPI_MINT_ACCT_META_IS_WRITABLE_UOFF, -88 +.equ RM_FM_CPI_MINT_ACCT_META_IS_WRITABLE_UOFF, -104 # Mint account meta is signer. -.equ RM_FM_CPI_MINT_ACCT_META_IS_SIGNER_UOFF, -87 +.equ RM_FM_CPI_MINT_ACCT_META_IS_SIGNER_UOFF, -103 # System Program account info key. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_KEY_UOFF, -256 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_KEY_UOFF, -272 # System Program account info lamports. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_LAMPORTS_UOFF, -248 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_LAMPORTS_UOFF, -264 # System Program account info data length. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_DATA_LEN_UOFF, -240 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_DATA_LEN_UOFF, -256 # System Program account info data. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_DATA_UOFF, -232 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_DATA_UOFF, -248 # System Program account info owner. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_OWNER_UOFF, -224 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_OWNER_UOFF, -240 # System Program account info rent epoch. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_RENT_EPOCH_UOFF, -216 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_RENT_EPOCH_UOFF, -232 # System Program account info is signer. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_IS_SIGNER_UOFF, -208 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_IS_SIGNER_UOFF, -224 # System Program account info is writable. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_IS_WRITABLE_UOFF, -207 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_IS_WRITABLE_UOFF, -223 # System Program account info executable. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_EXECUTABLE_UOFF, -206 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_INFO_EXECUTABLE_UOFF, -222 # System Program account meta pubkey. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_META_PUBKEY_UOFF, -80 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_META_PUBKEY_UOFF, -96 # System Program account meta is writable. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_META_IS_WRITABLE_UOFF, -72 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_META_IS_WRITABLE_UOFF, -88 # System Program account meta is signer. -.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_META_IS_SIGNER_UOFF, -71 +.equ RM_FM_CPI_SYSTEM_PROGRAM_ACCT_META_IS_SIGNER_UOFF, -87 # Token Program account info key. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_KEY_UOFF, -200 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_KEY_UOFF, -216 # Token Program account info lamports. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_LAMPORTS_UOFF, -192 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_LAMPORTS_UOFF, -208 # Token Program account info data length. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_DATA_LEN_UOFF, -184 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_DATA_LEN_UOFF, -200 # Token Program account info data. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_DATA_UOFF, -176 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_DATA_UOFF, -192 # Token Program account info owner. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_OWNER_UOFF, -168 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_OWNER_UOFF, -184 # Token Program account info rent epoch. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_RENT_EPOCH_UOFF, -160 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_RENT_EPOCH_UOFF, -176 # Token Program account info is signer. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_IS_SIGNER_UOFF, -152 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_IS_SIGNER_UOFF, -168 # Token Program account info is writable. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_IS_WRITABLE_UOFF, -151 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_IS_WRITABLE_UOFF, -167 # Token Program account info executable. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_EXECUTABLE_UOFF, -150 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_INFO_EXECUTABLE_UOFF, -166 # Token Program account meta pubkey. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_META_PUBKEY_UOFF, -64 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_META_PUBKEY_UOFF, -80 # Token Program account meta is writable. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_META_IS_WRITABLE_UOFF, -56 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_META_IS_WRITABLE_UOFF, -72 # Token Program account meta is signer. -.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_META_IS_SIGNER_UOFF, -55 +.equ RM_FM_CPI_TOKEN_PROGRAM_ACCT_META_IS_SIGNER_UOFF, -71 +.equ RM_FM_SIGNERS_SEEDS_ADDR_UOFF, -64 # Signers seeds address. +.equ RM_FM_SIGNERS_SEEDS_LEN_UOFF, -56 # Signers seeds length. .equ RM_FM_SOL_INSN_OFF, -48 # SolInstruction offset. .equ RM_FM_SOL_INSN_PROGRAM_ID_UOFF, -48 # SolInstruction program ID. .equ RM_FM_SOL_INSN_ACCOUNTS_UOFF, -40 # SolInstruction accounts pointer. .equ RM_FM_SOL_INSN_ACCOUNT_LEN_UOFF, -32 # SolInstruction account length. .equ RM_FM_SOL_INSN_DATA_UOFF, -24 # SolInstruction data pointer. .equ RM_FM_SOL_INSN_DATA_LEN_UOFF, -16 # SolInstruction data length. +.equ RM_FM_BUMP_OFF, -8 # Bump seed. +# From pda_seeds to sol_instruction. +.equ RM_FM_PDA_SEEDS_TO_SOL_INSN_REL_OFF_IMM, 616 +# From pda to signers_seeds. +.equ RM_FM_PDA_TO_SIGNERS_SEEDS_REL_OFF_IMM, 552 +# From create_account_data to cpi account metas. +.equ RM_FM_CREATE_ACCT_DATA_TO_CPI_ACCT_METAS_REL_OFF_IMM, 392 # ------------------------------------------------------------------------- # Miscellaneous market registration constants. @@ -205,6 +213,9 @@ .equ RM_MISC_QUOTE_DATA_LEN_OFF, 31096 # Number of seeds for market PDA derivation (base, quote). .equ RM_MISC_TRY_FIND_PDA_SEEDS_LEN, 2 +# Number of accounts for CreateAccount CPI (user, target). +.equ RM_MISC_CREATE_ACCOUNT_N_ACCOUNTS, 2 +.equ RM_MISC_N_PDA_SIGNERS, 1 # Number of PDA signers for CPI. # ------------------------------------------------------------------------- register_market: @@ -272,10 +283,10 @@ register_market: add64 r3, REGISTER_MARKET_DATA_LEN # syscall.seeds_len = register_misc.TRY_FIND_PDA_SEEDS_LEN mov64 r2, RM_MISC_TRY_FIND_PDA_SEEDS_LEN - # syscall.program_address = RegisterMarketFrame.pda + # syscall.program_address = &frame.pda mov64 r4, r10 add64 r4, RM_FM_PDA_OFF - # syscall.bump_seed = RegisterMarketFrame.bump + # syscall.bump_seed = &frame.bump mov64 r5, r10 add64 r5, RM_FM_BUMP_OFF call sol_try_find_program_address @@ -293,6 +304,20 @@ register_market: ldxdw r7, [r6 + IB_MARKET_PUBKEY_CHUNK_3_OFF] ldxdw r8, [r10 + RM_FM_PDA_CHUNK_3_OFF] jne r7, r8, e_invalid_market_pubkey + # frame.pda_seeds.bump.addr = &frame.bump + stxdw [r10 + RM_FM_PDA_SEEDS_BUMP_ADDR_OFF], r5 + # frame.pda_seeds.bump.len = u8.size + mov64 r7, SIZE_OF_U8 + stxdw [r10 + RM_FM_PDA_SEEDS_BUMP_LEN_OFF], r7 + # frame.create_account_data.owner = syscall.program_id + ldxdw r7, [r3 + PUBKEY_CHUNK_0_OFF] + stxdw [r10 + RM_FM_CREATE_ACCT_OWNER_CHUNK_0_UOFF], r7 + ldxdw r7, [r3 + PUBKEY_CHUNK_1_OFF] + stxdw [r10 + RM_FM_CREATE_ACCT_OWNER_CHUNK_1_UOFF], r7 + ldxdw r7, [r3 + PUBKEY_CHUNK_2_OFF] + stxdw [r10 + RM_FM_CREATE_ACCT_OWNER_CHUNK_2_UOFF], r7 + ldxdw r7, [r3 + PUBKEY_CHUNK_3_OFF] + stxdw [r10 + RM_FM_CREATE_ACCT_OWNER_CHUNK_3_UOFF], r7 # if acct.duplicate != account.NON_DUP_MARKER # return ErrorCode::SystemProgramIsDuplicate ldxb r7, [r9 + ACCT_DUPLICATE_OFF] @@ -311,6 +336,10 @@ register_market: ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] ldxdw r8, [r10 + RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_3_OFF] jne r7, r8, e_invalid_system_program_pubkey + # frame.sol_instruction.program_id = &acct.address + mov64 r7, r9 + add64 r7, ACCT_ADDRESS_OFF + stxdw [r10 + RM_FM_SOL_INSN_PROGRAM_ID_UOFF], r7 # system_program_padded_data_len = acct.padded_data_len ldxdw r7, [r9 + ACCT_DATA_LEN_OFF] add64 r7, DATA_LEN_MAX_PAD @@ -338,4 +367,84 @@ register_market: # (1 CU) replaces lddw (2 CUs). mov32 r8, PUBKEY_RENT_CHUNK_3_LO jne r7, r8, e_invalid_rent_sysvar_pubkey + # frame.create_account_data.space = MarketHeader.size + mov64 r7, SIZE_OF_MARKET_HEADER + stxdw [r10 + RM_FM_CREATE_ACCT_SPACE_UOFF], r7 + # acct_size = MarketHeader.size + account.STORAGE_OVERHEAD + add64 r7, ACCT_STORAGE_OVERHEAD + # lamports_per_byte = acct.data.lamports_per_byte + ldxdw r8, [r9 + ACCT_DATA_OFF] + # frame.create_account_data.lamports = acct_size * lamports_per_byte + mul64 r7, r8 + stxdw [r10 + RM_FM_CREATE_ACCT_LAMPORTS_UOFF], r7 + # frame.cpi.user_info.is_signer = true + # frame.cpi.user_info.is_writable = true + sth [r10 + RM_FM_CPI_USER_ACCT_INFO_IS_SIGNER_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi.user_meta.is_writable = true + # frame.cpi.user_meta.is_signer = true + sth [r10 + RM_FM_CPI_USER_ACCT_META_IS_WRITABLE_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi.target_info.is_signer = true + # frame.cpi.target_info.is_writable = true + sth [r10 + RM_FM_CPI_TARGET_ACCT_INFO_IS_SIGNER_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi.target_meta.is_writable = true + # frame.cpi.target_meta.is_signer = true + sth [r10 + RM_FM_CPI_TARGET_ACCT_META_IS_WRITABLE_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi.user_meta.pubkey = &input.user.address + # frame.cpi.user_info.key = &input.user.address + add64 r6, IB_USER_PUBKEY_OFF + stxdw [r10 + RM_FM_CPI_USER_ACCT_META_PUBKEY_UOFF], r6 + stxdw [r10 + RM_FM_CPI_USER_ACCT_INFO_KEY_UOFF], r6 + # frame.cpi.user_info.owner = &input.user.owner + add64 r6, IB_ADDRESS_TO_OWNER_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_USER_ACCT_INFO_OWNER_UOFF], r6 + # frame.cpi.user_info.lamports = &input.user.lamports + add64 r6, IB_OWNER_TO_LAMPORTS_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_USER_ACCT_INFO_LAMPORTS_UOFF], r6 + # frame.cpi.user_info.data = &input.user.data + add64 r6, IB_LAMPORTS_TO_DATA_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_USER_ACCT_INFO_DATA_UOFF], r6 + # frame.cpi.target_meta.pubkey = &input.market.address + # frame.cpi.target_info.key = &input.market.address + add64 r6, IB_USER_DATA_TO_MARKET_ADDRESS_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_TARGET_ACCT_META_PUBKEY_UOFF], r6 + stxdw [r10 + RM_FM_CPI_TARGET_ACCT_INFO_KEY_UOFF], r6 + # frame.cpi.target_info.owner = &input.market.owner + add64 r6, IB_ADDRESS_TO_OWNER_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_TARGET_ACCT_INFO_OWNER_UOFF], r6 + # frame.cpi.target_info.lamports = &input.market.lamports + add64 r6, IB_OWNER_TO_LAMPORTS_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_TARGET_ACCT_INFO_LAMPORTS_UOFF], r6 + # frame.cpi.target_info.data = &input.market.data + add64 r6, IB_LAMPORTS_TO_DATA_REL_OFF_IMM + stxdw [r10 + RM_FM_CPI_TARGET_ACCT_INFO_DATA_UOFF], r6 + # frame.signers_seeds.addr = &frame.pda_seeds + stxdw [r10 + RM_FM_SIGNERS_SEEDS_ADDR_UOFF], r1 + # frame.signers_seeds.len = frame.PDA_SEEDS_N_SEEDS + mov64 r7, RM_FM_PDA_SEEDS_N_SEEDS + stxdw [r10 + RM_FM_SIGNERS_SEEDS_LEN_UOFF], r7 + # frame.sol_instruction.data = &frame.create_account_data + mov64 r7, r10 + add64 r7, RM_FM_CREATE_ACCT_DATA_OFF + stxdw [r10 + RM_FM_SOL_INSN_DATA_UOFF], r7 + # frame.sol_instruction.accounts = &frame.cpi.account_metas + add64 r7, RM_FM_CREATE_ACCT_DATA_TO_CPI_ACCT_METAS_REL_OFF_IMM + stxdw [r10 + RM_FM_SOL_INSN_ACCOUNTS_UOFF], r7 + # frame.sol_instruction.account_len = register_misc.CREATE_ACCOUNT_N_ACCOUNTS + mov64 r7, RM_MISC_CREATE_ACCOUNT_N_ACCOUNTS + stxdw [r10 + RM_FM_SOL_INSN_ACCOUNT_LEN_UOFF], r7 + # frame.sol_instruction.data_len = CreateAccountData.size + mov64 r7, SIZE_OF_CREATE_ACCOUNT_DATA + stxdw [r10 + RM_FM_SOL_INSN_DATA_LEN_UOFF], r7 + # syscall.instruction = &frame.sol_instruction (r1 from pda_seeds) + add64 r1, RM_FM_PDA_SEEDS_TO_SOL_INSN_REL_OFF_IMM + # syscall.account_infos = &frame.cpi.account_infos + mov64 r2, r10 + add64 r2, RM_FM_CPI_SOL_ACCT_INFO_OFF + # syscall.account_infos_len = register_misc.CREATE_ACCOUNT_N_ACCOUNTS + mov64 r3, RM_MISC_CREATE_ACCOUNT_N_ACCOUNTS + # syscall.seeds = &frame.signers_seeds (r4 from pda) + add64 r4, RM_FM_PDA_TO_SIGNERS_SEEDS_REL_OFF_IMM + # syscall.seeds_len = register_misc.N_PDA_SIGNERS + mov64 r5, RM_MISC_N_PDA_SIGNERS + call sol_invoke_signed_c exit diff --git a/tests/src/lib.rs b/tests/src/lib.rs index b5bc8b0..6458721 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -45,8 +45,13 @@ pub struct TestSetup { } /// Creates a test environment for the default `dropset` program. +/// +/// Sets `exemption_threshold = 1.0` (SIMD-0194) so the rent calculation +/// in the program (`acct_size * lamports_per_byte`) matches the sysvar. pub fn setup() -> TestSetup { - setup_program(DEFAULT_PROGRAM) + let mut setup = setup_program(DEFAULT_PROGRAM); + setup.mollusk.sysvars.rent.exemption_threshold = 1.0; + setup } /// Creates a test environment for a named program binary under `target/asm/`. diff --git a/tests/tests/cases/register_market.rs b/tests/tests/cases/register_market.rs index e20b45b..1f216a8 100644 --- a/tests/tests/cases/register_market.rs +++ b/tests/tests/cases/register_market.rs @@ -1,11 +1,13 @@ -use dropset_interface::market::RegisterMarketAccounts; +use dropset_interface::market::{MarketHeader, RegisterMarketAccounts}; use dropset_interface::{Discriminant, ErrorCode}; use dropset_tests::{ CaseResult, TestCase, TestSetup, check, check_custom, check_with_accounts, find_pda_seed_pair, test_cases, }; +use mollusk_svm::program; +use mollusk_svm::result::ProgramResult as MolluskResult; use solana_account::Account; -use solana_sdk::instruction::AccountMeta; +use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_sdk::pubkey::Pubkey; test_cases! { @@ -33,6 +35,7 @@ test_cases! { InvalidRentSysvarPubkeyChunk2, InvalidRentSysvarPubkeyChunk3, InvalidRentSysvarPubkeyChunk3Hi, + CreateAccountHappyPath, } } @@ -57,6 +60,54 @@ fn into_metas_and_accounts( (metas, paired) } +const USER_LAMPORTS: u64 = 1_000_000; +const MARKET_HEADER_SIZE: usize = size_of::(); + +/// Build valid accounts that pass all checks for a successful CreateAccount CPI. +fn happy_path_accounts(setup: &TestSetup) -> (Vec, Vec<(Pubkey, Account)>) { + let (mut keys, mut accounts) = default_accounts(); + let (base_key, quote_key) = find_pda_seed_pair(&setup.program_id); + keys[RegisterMarketAccounts::BaseMint as usize] = base_key; + keys[RegisterMarketAccounts::QuoteMint as usize] = quote_key; + let (pda, _bump) = + Pubkey::find_program_address(&[base_key.as_ref(), quote_key.as_ref()], &setup.program_id); + keys[RegisterMarketAccounts::Market as usize] = pda; + + let (system_program_pubkey, system_program_account) = + program::keyed_account_for_system_program(); + keys[RegisterMarketAccounts::SystemProgram as usize] = system_program_pubkey; + accounts[RegisterMarketAccounts::SystemProgram as usize] = system_program_account; + + let (rent_sysvar_pubkey, rent_sysvar_account) = + setup.mollusk.sysvars.keyed_account_for_rent_sysvar(); + keys[RegisterMarketAccounts::RentSysvar as usize] = rent_sysvar_pubkey; + accounts[RegisterMarketAccounts::RentSysvar as usize] = rent_sysvar_account; + + // Fund the user account so it can pay for the CreateAccount CPI. + accounts[RegisterMarketAccounts::User as usize] = + Account::new(USER_LAMPORTS, 0, &system_program_pubkey); + + let metas: Vec = keys + .iter() + .enumerate() + .map(|(i, k)| { + let writable = matches!( + i, + i if i == RegisterMarketAccounts::User as usize + || i == RegisterMarketAccounts::Market as usize + ); + let signer = i == RegisterMarketAccounts::User as usize; + if writable { + AccountMeta::new(*k, signer) + } else { + AccountMeta::new_readonly(*k, signer) + } + }) + .collect(); + let paired = keys.into_iter().zip(accounts).collect(); + (metas, paired) +} + /// Build accounts where the market key is the correct PDA with one /// 8-byte chunk flipped, so the comparison fails at exactly that chunk. fn pda_mismatch_accounts( @@ -402,6 +453,54 @@ impl TestCase for Case { Some(ErrorCode::InvalidRentSysvarPubkey), ) } + // Verifies: REGISTER-MARKET (CreateAccount CPI happy path) + Self::CreateAccountHappyPath => { + let (metas, accounts) = happy_path_accounts(setup); + let instruction = Instruction::new_with_bytes(setup.program_id, insn, metas); + let result = setup.mollusk.process_instruction(&instruction, &accounts); + + let mut errors = Vec::new(); + match &result.program_result { + MolluskResult::Success => { + let market = + &result.resulting_accounts[RegisterMarketAccounts::Market as usize].1; + + if market.owner != setup.program_id { + errors.push(format!( + "owner: expected {:?}, got {:?}", + setup.program_id, market.owner + )); + } + if market.data.len() != MARKET_HEADER_SIZE { + errors.push(format!( + "data len: expected {}, got {}", + MARKET_HEADER_SIZE, + market.data.len() + )); + } + let rent = &setup.mollusk.sysvars.rent; + if !rent.is_exempt(market.lamports, market.data.len()) { + errors.push(format!( + "market not rent exempt: {} lamports for {} bytes", + market.lamports, + market.data.len() + )); + } + } + other => { + errors.push(format!("expected success, got {:?}", other)); + } + } + + CaseResult { + cu: result.compute_units_consumed, + error: if errors.is_empty() { + None + } else { + Some(errors.join("; ")) + }, + } + } } } }