diff --git a/.github/actions/install-sbpf/action.yml b/.github/actions/install-sbpf/action.yml index af1ae51..856e492 100644 --- a/.github/actions/install-sbpf/action.yml +++ b/.github/actions/install-sbpf/action.yml @@ -17,10 +17,18 @@ runs: path: '~/.cargo/bin/sbpf' - if: 'steps.cache-sbpf.outputs.cache-hit != ''true''' name: 'Install sbpf CLI' - run: >- - cargo install - --git https://github.com/blueshift-gg/sbpf.git - --rev ${{ inputs.sbpf-cli-revision }} + # Clone, generate a lock file with blake3 <1.8.4 to avoid + # digest 0.10/0.11 conflict, then install from the local path. + run: | + git init /tmp/sbpf + cd /tmp/sbpf + git remote add origin https://github.com/blueshift-gg/sbpf.git + git fetch --depth 1 origin ${{ inputs.sbpf-cli-revision }} + git checkout FETCH_HEAD + cargo generate-lockfile + cargo update blake3 --precise 1.8.3 + cargo install --path . --locked + rm -rf /tmp/sbpf shell: 'sh' - if: 'steps.cache-sbpf.outputs.cache-hit != ''true''' name: 'Save sbpf CLI cache' diff --git a/CLAUDE.md b/CLAUDE.md index 4a9b1c7..2abdcd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,7 +59,7 @@ make test # Assemble + run Mollusk tests make asm # Assemble program to SBPF binary make lint # Run all lints (pre-commit + prettier) make clean # Clean all build artifacts -make docs-dev # Serve docs locally +make docs # Serve docs locally make docs-build # Production docs build make docs-links # Check for broken links ``` diff --git a/Cargo.lock b/Cargo.lock index ecf7ef8..372ca85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,6 +829,8 @@ dependencies = [ "dropset-build", "dropset-macros", "pinocchio", + "pinocchio-token", + "pinocchio-token-2022", ] [[package]] @@ -1737,6 +1739,30 @@ dependencies = [ "solana-program-error", ] +[[package]] +name = "pinocchio-token" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "febf3bbe37f4e2723b9b41a1768c6542a1ae1b1d7bcac27f892f30cabcf70ec4" +dependencies = [ + "solana-account-view", + "solana-address 2.3.0", + "solana-instruction-view", + "solana-program-error", +] + +[[package]] +name = "pinocchio-token-2022" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe4f1997ce2443f99333d8ae2ee1075f9c94ed13ff941178663ae3601ad99ad" +dependencies = [ + "solana-account-view", + "solana-address 2.3.0", + "solana-instruction-view", + "solana-program-error", +] + [[package]] name = "pkcs8" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index 4c3bfd0..44eb5c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ bindgen = "0.72.1" heck = "0.5" mollusk-svm = "0.11.0" pinocchio = "0.10.2" +pinocchio-token = "0.5.0" +pinocchio-token-2022 = "0.2.0" proc-macro2 = "1.0.106" quote = "1.0.45" solana-account = "3.2" diff --git a/Makefile b/Makefile index d669157..abeb420 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ .PHONY: all .PHONY: asm +.PHONY: docs .PHONY: clean .PHONY: lint .PHONY: test @@ -35,7 +36,7 @@ docs-links: docs-build --root-dir docs/.vitepress/dist 'docs/.vitepress/dist/**/*.html' # Build and serve docs locally for development. -docs-dev: +docs: cd docs && npm install \ && rm -rf .vitepress/cache .vitepress/dist node_modules/.vite \ && npx vitepress dev --open diff --git a/docs/.vitepress/components/Algorithm.vue b/docs/.vitepress/components/Algorithm.vue index acb2b19..9564369 100644 --- a/docs/.vitepress/components/Algorithm.vue +++ b/docs/.vitepress/components/Algorithm.vue @@ -131,6 +131,17 @@ onMounted(async () => { ); container.value.insertBefore(rendered, container.value.firstChild); + // Indent comments that precede a block so they align with the + // block's first line rather than the parent control keyword. + rendered.querySelectorAll(".ps-comment").forEach((span) => { + const line = span.closest(".ps-line"); + if (!line) return; + const block = line.nextElementSibling; + if (block?.classList.contains("ps-block")) { + span.style.paddingLeft = block.style.marginLeft; + } + }); + // Add a class to \texttt{} spans for styling. rendered .querySelectorAll('span[style*="KaTeX_Typewriter"]') @@ -392,6 +403,7 @@ onMounted(async () => { .pseudocode-container :deep(.ps-linenum) { color: var(--vp-c-text-3); user-select: none; + width: 2.4em; } /* Dependency links above/below the algorithm. */ diff --git a/docs/algorithms/REGISTER-MARKET.tex b/docs/algorithms/REGISTER-MARKET.tex index e315fad..22761ed 100644 --- a/docs/algorithms/REGISTER-MARKET.tex +++ b/docs/algorithms/REGISTER-MARKET.tex @@ -11,6 +11,7 @@ \INPUT $r_4$ = insn\_len \REQUIRE insn.discriminant == \texttt{Discriminant::RegisterMarket} \PROCEDURE{REGISTER-MARKET}{input, insn, n\_accounts, insn\_len} + \COMMENT{Check number of accounts and instruction data length.} \IF{n\_accounts $<$ \texttt{RegisterMarketAccounts.LEN}} \RETURN \texttt{ErrorCode::InvalidNumberOfAccounts} \ENDIF @@ -35,6 +36,7 @@ \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 frame.input\_shifted = input\_shifted \COMMENT{Check quote mint account, create signer seed.} \IF{input\_shifted.quote\_mint.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} \RETURN \texttt{ErrorCode::QuoteMintIsDuplicate} @@ -46,7 +48,7 @@ \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 frame.input = 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} @@ -54,6 +56,7 @@ \STATE syscall.bump\_seed = \&frame.bump \STATE \CALL{sol-try-find-program-address}{} \COMMENT{Verify derived market PDA matches market account pubkey.} + \STATE input = frame.input \IF{input.market.pubkey $\neq$ frame.market\_pda} \RETURN \texttt{ErrorCode::InvalidMarketPubkey} \ENDIF @@ -86,6 +89,9 @@ \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{Advance to base token program account.} + \STATE rent\_sysvar\_padded\_data\_len = acct.padded\_data\_len + \STATE acct += rent\_sysvar\_padded\_data\_len + \texttt{EmptyAccount.size} \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} @@ -123,6 +129,48 @@ \STATE syscall.seeds = \&frame.signers\_seeds \STATE syscall.seeds\_len = \texttt{register\_misc.N\_PDA\_SIGNERS} \STATE \CALL{sol-invoke-signed-c}{} + \COMMENT{Retrieve input buffer pointers.} + \STATE input = frame.input + \STATE input\_shifted = frame.input\_shifted + \COMMENT{Check base token program account.} + \IF{acct.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} + \RETURN \texttt{ErrorCode::BaseTokenProgramIsDuplicate} + \ENDIF + \COMMENT{Verify base token program owns the base mint.} + \IF{acct.pubkey $\neq$ input.base\_mint.owner} + \RETURN \texttt{ErrorCode::BaseTokenProgramNotBaseMintOwner} + \ENDIF + \COMMENT{Verify base token program is Token Program or Token 2022.} + \IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_PROGRAM}} + \IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_2022\_PROGRAM}} + \RETURN \texttt{ErrorCode::BaseTokenProgramNotTokenProgram} + \ENDIF + \ENDIF + \COMMENT{Advance to quote token program account.} + \STATE base\_token\_program\_padded\_data\_len = acct.padded\_data\_len + \STATE acct += base\_token\_program\_padded\_data\_len + \texttt{EmptyAccount.size} + \COMMENT{Check quote token program account.} + \IF{acct.duplicate == \texttt{account.NON\_DUP\_MARKER}} + \COMMENT{Non-duplicate; verify quote token program owns the quote mint.} + \IF{acct.pubkey $\neq$ input\_shifted.quote\_mint.owner} + \RETURN \texttt{ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner} + \ENDIF + \COMMENT{Verify quote token program is Token Program or Token 2022.} + \IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_PROGRAM}} + \IF{acct.pubkey $\neq$ \texttt{pubkey.TOKEN\_2022\_PROGRAM}} + \RETURN \texttt{ErrorCode::QuoteTokenProgramNotTokenProgram} + \ENDIF + \ENDIF + \ELSE + \COMMENT{Duplicate; verify position is base token program.} + \IF{acct.duplicate $\neq$ \texttt{RegisterMarketAccounts::BaseTokenProgram}} + \RETURN \texttt{ErrorCode::InvalidQuoteTokenProgramDuplicate} + \ENDIF + \COMMENT{Verify duplicate owns the quote mint.} + \IF{input.base\_mint.owner $\neq$ input\_shifted.quote\_mint.owner} + \RETURN \texttt{ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner} + \ENDIF + \ENDIF \ENDPROCEDURE \end{algorithmic} \end{algorithm} diff --git a/docs/src/development/build-scaffolding.md b/docs/src/development/build-scaffolding.md index 0fff373..aa92dd2 100644 --- a/docs/src/development/build-scaffolding.md +++ b/docs/src/development/build-scaffolding.md @@ -50,7 +50,7 @@ syntax forms (parsed within the proc macro, not standalone macros): - `signer_seeds!(field)`: expands a [`signer_seeds!`](#signer_seeds) field into an `_OFF` offset to the struct, an `N_SEEDS` count, and per-seed `_ADDR_OFF` and `_LEN_OFF` constants (requires `#[frame(Type)]`, see below) -- `address!(expr)`: splits a 32-byte address into four 8-byte chunks, emitting +- `pubkey!(expr)`: splits a 32-byte pubkey into four 8-byte chunks, emitting full 64-bit `_CHUNK_{0..3}` `i64` constants (for `lddw`) plus `_CHUNK_{0..3}_LO` and `_CHUNK_{0..3}_HI` `i32` immediates (twelve constants total) @@ -133,10 +133,11 @@ The length is accessible in Rust as `RegisterMarketData::LEN`. ### `#[instruction_accounts("target")]` -Attribute macro for instruction accounts enums. Automatically generates a -`LEN` associated constant (`u64`) from the number of enum variants, and a hidden -module with a `_LEN` suffixed assembly constant and `GROUP` for build-time -injection. +Attribute macro for instruction accounts enums. Generates a `LEN` associated +constant (`u64`) from the number of enum variants, plus a per-variant `_POS` +position constant (`i32`) for each variant. A hidden module with assembly +constants and `GROUP` is emitted for build-time injection. Assembly comments +are auto-generated from the variant names. The count is accessible in Rust as `RegisterMarketAccounts::LEN`. diff --git a/docs/src/program/layout.md b/docs/src/program/layout.md index d876c11..91063bb 100644 --- a/docs/src/program/layout.md +++ b/docs/src/program/layout.md @@ -75,7 +75,7 @@ Each 32-byte pubkey is accessed as four 8-byte (`u64`) chunks at offsets 0, 8, Known addresses (such as the rent sysvar ID) are split into full 64-bit `_CHUNK_{0..3}` constants (loadable with a single `lddw`) and `_CHUNK_{0..3}_LO` / `_CHUNK_{0..3}_HI` `i32` immediates (loadable with `mov32` / `lsh64` pairs) -using [`address!`][bs-constant-group]. The `lddw` form costs 2 CUs but uses one +using [`pubkey!`][bs-constant-group]. The `lddw` form costs 2 CUs but uses one instruction; the `mov32` / `lsh64` pair also costs 2 CUs but can be optimized to 1 CU with `mov32` alone when the high bits are zero. diff --git a/interface/Cargo.toml b/interface/Cargo.toml index ba295c4..e08c17a 100644 --- a/interface/Cargo.toml +++ b/interface/Cargo.toml @@ -2,6 +2,8 @@ dropset-build = {path = "../build"} dropset-macros = {path = "../macros"} pinocchio = {workspace = true} +pinocchio-token = {workspace = true} +pinocchio-token-2022 = {workspace = true} [package] name = "dropset-interface" diff --git a/interface/src/lib.rs b/interface/src/lib.rs index 419e7c1..6d157a3 100644 --- a/interface/src/lib.rs +++ b/interface/src/lib.rs @@ -44,6 +44,20 @@ pub enum ErrorCode { RentSysvarIsDuplicate, /// The Rent sysvar account pubkey is invalid. InvalidRentSysvarPubkey, + /// The base token program account is a duplicate. + BaseTokenProgramIsDuplicate, + /// The base token program does not own the base mint. + BaseTokenProgramNotBaseMintOwner, + /// The base token program is not Token Program or Token 2022. + BaseTokenProgramNotTokenProgram, + /// The quote token program duplicate position is invalid. + InvalidQuoteTokenProgramDuplicate, + /// The duplicate quote token program does not own the quote mint. + DupQuoteTokenProgramNotQuoteMintOwner, + /// The non-duplicate quote token program does not own the quote mint. + NonDupQuoteTokenProgramNotQuoteMintOwner, + /// The quote token program is not Token Program or Token 2022. + QuoteTokenProgramNotTokenProgram, } // endregion: error_enum diff --git a/interface/src/market.rs b/interface/src/market.rs index b875964..5cdc48d 100644 --- a/interface/src/market.rs +++ b/interface/src/market.rs @@ -56,12 +56,16 @@ constant_group! { BASE_DATA_LEN = offset!(RegisterMarketInputBuffer.base_mint.header.data_len), /// From input buffer to base mint address. BASE_ADDR = offset!(RegisterMarketInputBuffer.base_mint.header.address), + /// From input buffer to base mint owner. + BASE_OWNER = pubkey_offsets!(RegisterMarketInputBuffer.base_mint.header.owner), /// From input buffer to quote mint. QUOTE = offset!(RegisterMarketInputBuffer.quote_mint), /// From input buffer to quote mint duplicate flag. QUOTE_DUPLICATE = offset!(RegisterMarketInputBuffer.quote_mint.header.borrow_state), /// From input buffer to quote mint address. QUOTE_ADDR = offset!(RegisterMarketInputBuffer.quote_mint.header.address), + /// From input buffer to quote mint owner. + QUOTE_OWNER = pubkey_offsets!(RegisterMarketInputBuffer.quote_mint.header.owner), /// From input buffer to quote mint data length. QUOTE_DATA_LEN = offset!(RegisterMarketInputBuffer.quote_mint.header.data_len), /// Number of seeds for market PDA derivation (base, quote). @@ -140,6 +144,10 @@ signer_seeds! { #[frame] /// Stack frame for REGISTER-MARKET. pub struct RegisterMarketFrame { + /// Saved input buffer pointer. + pub input: u64, + /// Saved input_shifted pointer. + pub input_shifted: u64, /// For CreateAccount CPI. pub pda_seeds: PDASignerSeeds, /// From `sol_try_find_program_address`. @@ -164,6 +172,10 @@ constant_group! { #[inject("market/register")] #[frame(RegisterMarketFrame)] frame { + /// Saved input buffer pointer. + INPUT = offset!(input), + /// Saved input_shifted pointer. + INPUT_SHIFTED = offset!(input_shifted), /// PDA signer seeds. PDA_SEEDS = signer_seeds!(pda_seeds), /// PDA address. @@ -192,7 +204,7 @@ constant_group! { 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. + /// 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/pubkey.rs b/interface/src/pubkey.rs index 066346b..b524a2c 100644 --- a/interface/src/pubkey.rs +++ b/interface/src/pubkey.rs @@ -1,5 +1,7 @@ use dropset_macros::constant_group; use pinocchio::sysvars::rent::RENT_ID; +pub use pinocchio_token::ID as TOKEN_PROGRAM_ID; +pub use pinocchio_token_2022::ID as TOKEN_2022_PROGRAM_ID; // region: pubkey_constants constant_group! { @@ -10,13 +12,17 @@ constant_group! { /// Offset for the first 8 bytes. CHUNK_0_OFF = immediate!(0), /// Offset for the second 8 bytes. - CHUNK_1_OFF = immediate!(8), + CHUNK_1_OFF = immediate!(size_of::()), /// Offset for the third 8 bytes. - CHUNK_2_OFF = immediate!(16), + CHUNK_2_OFF = immediate!(2 * size_of::()), /// Offset for the fourth 8 bytes. - CHUNK_3_OFF = immediate!(24), + CHUNK_3_OFF = immediate!(3 * size_of::()), /// Rent sysvar ID. - RENT = address!(RENT_ID), + RENT = pubkey!(RENT_ID), + /// SPL Token Program ID. + TOKEN_PROGRAM = pubkey!(TOKEN_PROGRAM_ID), + /// SPL Token 2022 Program ID. + TOKEN_2022_PROGRAM = pubkey!(TOKEN_2022_PROGRAM_ID), } } // endregion: pubkey_constants diff --git a/macros/src/constant_group/expand/address.rs b/macros/src/constant_group/expand/address.rs index e048e3e..444484a 100644 --- a/macros/src/constant_group/expand/address.rs +++ b/macros/src/constant_group/expand/address.rs @@ -20,7 +20,7 @@ fn chunk_expr(chunk: usize) -> proc_macro2::TokenStream { } } -/// Expand `address!(expr)` into eight i32 constants: four chunk lo/hi pairs. +/// Expand `pubkey!(expr)` into eight i32 constants: four chunk lo/hi pairs. /// /// For a pubkey named `RENT` with prefix `PUBKEY`, this emits: /// - `PUBKEY_RENT_CHUNK_0_LO` / `PUBKEY_RENT_CHUNK_0_HI` diff --git a/macros/src/constant_group/expand/mod.rs b/macros/src/constant_group/expand/mod.rs index b1c6dd1..4391893 100644 --- a/macros/src/constant_group/expand/mod.rs +++ b/macros/src/constant_group/expand/mod.rs @@ -93,7 +93,7 @@ pub fn expand(input: &ConstantGroupInput) -> proc_macro2::TokenStream { const_defs.push(def); meta_idents.push(meta); } - ConstantKind::Address { expr } => { + ConstantKind::Pubkey { expr } => { address::expand_address(&asm_name, doc, expr, &mut const_defs, &mut meta_idents); } ConstantKind::PubkeyOffsets { expr } => { diff --git a/macros/src/constant_group/mod.rs b/macros/src/constant_group/mod.rs index d588273..a213099 100644 --- a/macros/src/constant_group/mod.rs +++ b/macros/src/constant_group/mod.rs @@ -23,9 +23,9 @@ pub(crate) enum ConstantKind { }, /// `immediate!(expr)`: value must fit i32, exposed as i32 in Rust. Immediate { expr: Expr }, - /// `address!(expr)`: splits an `Address` into four 8-byte chunks, emitting + /// `pubkey!(expr)`: splits a pubkey into four 8-byte chunks, emitting /// `_CHUNK_{0..3}_LO` and `_CHUNK_{0..3}_HI` i32 immediates. - Address { expr: Expr }, + Pubkey { expr: Expr }, /// `pubkey_offsets!(Type.field.path)`: emits `_OFF` plus four /// `_CHUNK_{0..3}_OFF` offset constants for the 32-byte field. PubkeyOffsets { expr: Expr }, diff --git a/macros/src/constant_group/parse/mod.rs b/macros/src/constant_group/parse/mod.rs index 5cecd43..65693bf 100644 --- a/macros/src/constant_group/parse/mod.rs +++ b/macros/src/constant_group/parse/mod.rs @@ -111,11 +111,11 @@ impl Parse for ConstantGroupInput { let expr: Expr = inner.parse()?; ConstantKind::Immediate { expr } } - "address" => { + "pubkey" => { let inner; syn::parenthesized!(inner in content); let expr: Expr = inner.parse()?; - ConstantKind::Address { expr } + ConstantKind::Pubkey { expr } } "unaligned_offset" => { let inner; diff --git a/macros/src/instruction_accounts/expand.rs b/macros/src/instruction_accounts/expand.rs index cbfaa19..d4a9f7f 100644 --- a/macros/src/instruction_accounts/expand.rs +++ b/macros/src/instruction_accounts/expand.rs @@ -1,3 +1,4 @@ +use heck::{ToShoutySnakeCase, ToTitleCase}; use quote::quote; use crate::codegen; @@ -5,17 +6,61 @@ use crate::codegen; /// Expand `#[instruction_accounts("target")]` on an enum into: /// - The original enum /// - `impl EnumName { pub const LEN: u64 = ...; }` +/// - Per-variant position constants (e.g., `ENUM_NAME_VARIANT = 0`) /// - A hidden module with `GROUP` metadata for assembly injection pub fn expand(target: &str, input: &syn::ItemEnum) -> proc_macro2::TokenStream { let enum_name = &input.ident; let n_variants = input.variants.len(); - let doc = format!("{} number of accounts.", enum_name); - - codegen::len_group( - target, - enum_name, - &doc, - quote! { #n_variants as u64 }, - quote! { #input }, - ) + let prefix = enum_name.to_string().to_shouty_snake_case(); + + // LEN constant. + let len_doc = format!("{} number of accounts.", enum_name); + let len_asm = format!("{}_LEN", prefix); + let len_meta_ident = codegen::meta_ident(&len_asm, enum_name.span()); + let len_meta = codegen::immediate_meta( + &len_meta_ident, + &len_asm, + &len_doc, + quote! { super::#enum_name::LEN as i32 }, + ); + + let mut meta_defs = vec![len_meta]; + let mut meta_idents = vec![len_meta_ident]; + + // Per-variant position constants. + for (i, variant) in input.variants.iter().enumerate() { + let variant_name = &variant.ident; + let asm_name = format!( + "{}_{}_POS", + prefix, + variant_name.to_string().to_shouty_snake_case() + ); + + let doc = format!( + "{} account position.", + variant_name.to_string().to_title_case() + ); + + let value = i; + let meta_ident = codegen::meta_ident(&asm_name, variant_name.span()); + + meta_defs.push(codegen::immediate_meta( + &meta_ident, + &asm_name, + &doc, + quote! { #value as i32 }, + )); + meta_idents.push(meta_ident); + } + + let body = quote! { + #input + + impl #enum_name { + #[doc = #len_doc] + pub const LEN: u64 = #n_variants as u64; + } + }; + + codegen::with_group(target, enum_name, body, &meta_defs, &meta_idents) } diff --git a/program/src/dropset/common/error.s b/program/src/dropset/common/error.s index 0bd68e2..4cd4189 100644 --- a/program/src/dropset/common/error.s +++ b/program/src/dropset/common/error.s @@ -18,6 +18,20 @@ .equ E_RENT_SYSVAR_IS_DUPLICATE, 12 # The Rent sysvar account pubkey is invalid. .equ E_INVALID_RENT_SYSVAR_PUBKEY, 13 +# The base token program account is a duplicate. +.equ E_BASE_TOKEN_PROGRAM_IS_DUPLICATE, 14 +# The base token program does not own the base mint. +.equ E_BASE_TOKEN_PROGRAM_NOT_BASE_MINT_OWNER, 15 +# The base token program is not Token Program or Token 2022. +.equ E_BASE_TOKEN_PROGRAM_NOT_TOKEN_PROGRAM, 16 +# The quote token program duplicate position is invalid. +.equ E_INVALID_QUOTE_TOKEN_PROGRAM_DUPLICATE, 17 +# The duplicate quote token program does not own the quote mint. +.equ E_DUP_QUOTE_TOKEN_PROGRAM_NOT_QUOTE_MINT_OWNER, 18 +# The non-duplicate quote token program does not own the quote mint. +.equ E_NON_DUP_QUOTE_TOKEN_PROGRAM_NOT_QUOTE_MINT_OWNER, 19 +# The quote token program is not Token Program or Token 2022. +.equ E_QUOTE_TOKEN_PROGRAM_NOT_TOKEN_PROGRAM, 20 e_invalid_instruction_length: mov32 r0, E_INVALID_INSTRUCTION_LENGTH @@ -66,3 +80,31 @@ e_rent_sysvar_is_duplicate: e_invalid_rent_sysvar_pubkey: mov32 r0, E_INVALID_RENT_SYSVAR_PUBKEY exit + +e_base_token_program_is_duplicate: + mov32 r0, E_BASE_TOKEN_PROGRAM_IS_DUPLICATE + exit + +e_base_token_program_not_base_mint_owner: + mov32 r0, E_BASE_TOKEN_PROGRAM_NOT_BASE_MINT_OWNER + exit + +e_base_token_program_not_token_program: + mov32 r0, E_BASE_TOKEN_PROGRAM_NOT_TOKEN_PROGRAM + exit + +e_invalid_quote_token_program_duplicate: + mov32 r0, E_INVALID_QUOTE_TOKEN_PROGRAM_DUPLICATE + exit + +e_dup_quote_token_program_not_quote_mint_owner: + mov32 r0, E_DUP_QUOTE_TOKEN_PROGRAM_NOT_QUOTE_MINT_OWNER + exit + +e_non_dup_quote_token_program_not_quote_mint_owner: + mov32 r0, E_NON_DUP_QUOTE_TOKEN_PROGRAM_NOT_QUOTE_MINT_OWNER + exit + +e_quote_token_program_not_token_program: + mov32 r0, E_QUOTE_TOKEN_PROGRAM_NOT_TOKEN_PROGRAM + exit diff --git a/program/src/dropset/common/pubkey.s b/program/src/dropset/common/pubkey.s index f1977b1..01e7da1 100644 --- a/program/src/dropset/common/pubkey.s +++ b/program/src/dropset/common/pubkey.s @@ -16,4 +16,52 @@ .equ PUBKEY_RENT_CHUNK_3, 2329533411 # Rent sysvar ID (chunk 3). .equ PUBKEY_RENT_CHUNK_3_LO, -1965433885 # Rent sysvar ID (chunk 3 lo). .equ PUBKEY_RENT_CHUNK_3_HI, 0 # Rent sysvar ID (chunk 3 hi). +# SPL Token Program ID (chunk 0). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_0, -7808848301000303354 +# SPL Token Program ID (chunk 0 lo). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_0_LO, -503915258 +# SPL Token Program ID (chunk 0 hi). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_0_HI, -1818139177 +# SPL Token Program ID (chunk 1). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_1, -6018520155818964007 +# SPL Token Program ID (chunk 1 lo). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_1_LO, 1189202905 +# SPL Token Program ID (chunk 1 hi). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_1_HI, -1401295922 +# SPL Token Program ID (chunk 2). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_2, -7982811346925931492 +# SPL Token Program ID (chunk 2 lo). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_2_LO, -310004708 +# SPL Token Program ID (chunk 2 hi). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_2_HI, -1858643105 +# SPL Token Program ID (chunk 3). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_3, -6268729762421306310 +# SPL Token Program ID (chunk 3 lo). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_3_LO, -2047505350 +# SPL Token Program ID (chunk 3 hi). +.equ PUBKEY_TOKEN_PROGRAM_CHUNK_3_HI, -1459552386 +# SPL Token 2022 Program ID (chunk 0). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_0, -2409577606766207738 +# SPL Token 2022 Program ID (chunk 0 lo). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_0_LO, -503915258 +# SPL Token 2022 Program ID (chunk 0 hi). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_0_HI, -561023506 +# SPL Token 2022 Program ID (chunk 1). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_1, -2680366473547005416 +# SPL Token 2022 Program ID (chunk 1 lo). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_1_LO, -1134738920 +# SPL Token 2022 Program ID (chunk 1 hi). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_1_HI, -624071452 +# SPL Token 2022 Program ID (chunk 2). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_2, 2814109315776649910 +# SPL Token 2022 Program ID (chunk 2 lo). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_2_LO, 1308367542 +# SPL Token 2022 Program ID (chunk 2 hi). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_2_HI, 655210883 +# SPL Token 2022 Program ID (chunk 3). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_3, -248927404616466946 +# SPL Token 2022 Program ID (chunk 3 lo). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_3_LO, 687455742 +# SPL Token 2022 Program ID (chunk 3 hi). +.equ PUBKEY_TOKEN_2022_PROGRAM_CHUNK_3_HI, -57957928 # ------------------------------------------------------------------------- diff --git a/program/src/dropset/market/register.s b/program/src/dropset/market/register.s index ce498bb..8d73109 100644 --- a/program/src/dropset/market/register.s +++ b/program/src/dropset/market/register.s @@ -3,9 +3,29 @@ # RegisterMarketAccounts number of accounts. .equ REGISTER_MARKET_ACCOUNTS_LEN, 10 +.equ REGISTER_MARKET_ACCOUNTS_USER_POS, 0 # User account position. +.equ REGISTER_MARKET_ACCOUNTS_MARKET_POS, 1 # Market account position. +# Base Mint account position. +.equ REGISTER_MARKET_ACCOUNTS_BASE_MINT_POS, 2 +# Quote Mint account position. +.equ REGISTER_MARKET_ACCOUNTS_QUOTE_MINT_POS, 3 +# System Program account position. +.equ REGISTER_MARKET_ACCOUNTS_SYSTEM_PROGRAM_POS, 4 +# Rent Sysvar account position. +.equ REGISTER_MARKET_ACCOUNTS_RENT_SYSVAR_POS, 5 +# Base Token Program account position. +.equ REGISTER_MARKET_ACCOUNTS_BASE_TOKEN_PROGRAM_POS, 6 +# Quote Token Program account position. +.equ REGISTER_MARKET_ACCOUNTS_QUOTE_TOKEN_PROGRAM_POS, 7 +# Base Vault account position. +.equ REGISTER_MARKET_ACCOUNTS_BASE_VAULT_POS, 8 +# Quote Vault account position. +.equ REGISTER_MARKET_ACCOUNTS_QUOTE_VAULT_POS, 9 # Stack frame for REGISTER-MARKET. # ------------------------------------------------------------------------- +.equ RM_FM_INPUT_OFF, -680 # Saved input buffer pointer. +.equ RM_FM_INPUT_SHIFTED_OFF, -672 # Saved input_shifted pointer. .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, -664 # Base signer seed address. @@ -193,7 +213,7 @@ .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. +# From create_account_data to CPI account metas. .equ RM_FM_CREATE_ACCT_DATA_TO_CPI_ACCT_METAS_REL_OFF_IMM, 392 # ------------------------------------------------------------------------- @@ -204,11 +224,30 @@ # From input buffer to base mint data length. .equ RM_MISC_BASE_DATA_LEN_OFF, 20760 .equ RM_MISC_BASE_ADDR_OFF, 20688 # From input buffer to base mint address. +.equ RM_MISC_BASE_OWNER_OFF, 20720 # From input buffer to base mint owner. +# From input buffer to base mint owner (chunk 0). +.equ RM_MISC_BASE_OWNER_CHUNK_0_OFF, 20720 +# From input buffer to base mint owner (chunk 1). +.equ RM_MISC_BASE_OWNER_CHUNK_1_OFF, 20728 +# From input buffer to base mint owner (chunk 2). +.equ RM_MISC_BASE_OWNER_CHUNK_2_OFF, 20736 +# From input buffer to base mint owner (chunk 3). +.equ RM_MISC_BASE_OWNER_CHUNK_3_OFF, 20744 .equ RM_MISC_QUOTE_OFF, 31016 # From input buffer to quote mint. # From input buffer to quote mint duplicate flag. .equ RM_MISC_QUOTE_DUPLICATE_OFF, 31016 # From input buffer to quote mint address. .equ RM_MISC_QUOTE_ADDR_OFF, 31024 +# From input buffer to quote mint owner. +.equ RM_MISC_QUOTE_OWNER_OFF, 31056 +# From input buffer to quote mint owner (chunk 0). +.equ RM_MISC_QUOTE_OWNER_CHUNK_0_OFF, 31056 +# From input buffer to quote mint owner (chunk 1). +.equ RM_MISC_QUOTE_OWNER_CHUNK_1_OFF, 31064 +# From input buffer to quote mint owner (chunk 2). +.equ RM_MISC_QUOTE_OWNER_CHUNK_2_OFF, 31072 +# From input buffer to quote mint owner (chunk 3). +.equ RM_MISC_QUOTE_OWNER_CHUNK_3_OFF, 31080 # From input buffer to quote mint data length. .equ RM_MISC_QUOTE_DATA_LEN_OFF, 31096 # Number of seeds for market PDA derivation (base, quote). @@ -253,6 +292,8 @@ register_market: add64 r9, DATA_LEN_MAX_PAD and64 r9, DATA_LEN_AND_MASK add64 r9, r1 + # frame.input_shifted = input_shifted + stxdw [r10 + RM_FM_INPUT_SHIFTED_OFF], r9 # if input_shifted.quote_mint.duplicate != account.NON_DUP_MARKER # return ErrorCode::QuoteMintIsDuplicate ldxb r8, [r9 + RM_MISC_QUOTE_DUPLICATE_OFF] @@ -273,8 +314,8 @@ register_market: # frame.pda_seeds.quote.len = Address.size mov64 r8, SIZE_OF_ADDRESS stxdw [r10 + RM_FM_PDA_SEEDS_QUOTE_LEN_OFF], r8 - # Store(input) - mov64 r6, r1 + # frame.input = input + stxdw [r10 + RM_FM_INPUT_OFF], r1 # syscall.seeds = &frame.pda_seeds mov64 r1, r10 add64 r1, RM_FM_PDA_SEEDS_OFF @@ -290,6 +331,8 @@ register_market: mov64 r5, r10 add64 r5, RM_FM_BUMP_OFF call sol_try_find_program_address + # input = frame.input + ldxdw r6, [r10 + RM_FM_INPUT_OFF] # if input.market.pubkey != frame.market_pda # return ErrorCode::InvalidMarketPubkey ldxdw r7, [r6 + IB_MARKET_PUBKEY_CHUNK_0_OFF] @@ -377,6 +420,13 @@ register_market: # frame.create_account_data.lamports = acct_size * lamports_per_byte mul64 r7, r8 stxdw [r10 + RM_FM_CREATE_ACCT_LAMPORTS_UOFF], r7 + # rent_sysvar_padded_data_len = acct.padded_data_len + ldxdw r7, [r9 + ACCT_DATA_LEN_OFF] + add64 r7, DATA_LEN_MAX_PAD + and64 r7, DATA_LEN_AND_MASK + # acct += rent_sysvar_padded_data_len + EmptyAccount.size + add64 r9, r7 + add64 r9, SIZE_OF_EMPTY_ACCOUNT # 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 @@ -447,4 +497,127 @@ register_market: # syscall.seeds_len = register_misc.N_PDA_SIGNERS mov64 r5, RM_MISC_N_PDA_SIGNERS call sol_invoke_signed_c + # input = frame.input + ldxdw r8, [r10 + RM_FM_INPUT_OFF] + # input_shifted = frame.input_shifted + ldxdw r6, [r10 + RM_FM_INPUT_SHIFTED_OFF] + # if acct.duplicate != account.NON_DUP_MARKER + # return ErrorCode::BaseTokenProgramIsDuplicate + ldxb r7, [r9 + ACCT_DUPLICATE_OFF] + jne r7, ACCT_NON_DUP_MARKER, e_base_token_program_is_duplicate + # if acct.pubkey != input.base_mint.owner + # return ErrorCode::BaseTokenProgramNotBaseMintOwner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_0_OFF] + ldxdw r1, [r8 + RM_MISC_BASE_OWNER_CHUNK_0_OFF] + jne r7, r1, e_base_token_program_not_base_mint_owner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_1_OFF] + ldxdw r1, [r8 + RM_MISC_BASE_OWNER_CHUNK_1_OFF] + jne r7, r1, e_base_token_program_not_base_mint_owner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_2_OFF] + ldxdw r1, [r8 + RM_MISC_BASE_OWNER_CHUNK_2_OFF] + jne r7, r1, e_base_token_program_not_base_mint_owner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] + ldxdw r1, [r8 + RM_MISC_BASE_OWNER_CHUNK_3_OFF] + jne r7, r1, e_base_token_program_not_base_mint_owner + # if acct.pubkey != pubkey.TOKEN_PROGRAM + # if acct.pubkey != pubkey.TOKEN_2022_PROGRAM + # return ErrorCode::BaseTokenProgramNotTokenProgram + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_0_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_0 + jne r7, r1, register_market_check_base_token_2022 + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_1_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_1 + jne r7, r1, register_market_check_base_token_2022 + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_2_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_2 + jne r7, r1, register_market_check_base_token_2022 + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_3 + jeq r7, r1, register_market_quote_token_program +register_market_check_base_token_2022: + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_0_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_0 + jne r7, r1, e_base_token_program_not_token_program + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_1_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_1 + jne r7, r1, e_base_token_program_not_token_program + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_2_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_2 + jne r7, r1, e_base_token_program_not_token_program + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_3 + jne r7, r1, e_base_token_program_not_token_program +register_market_quote_token_program: + # base_token_program_padded_data_len = acct.padded_data_len + ldxdw r7, [r9 + ACCT_DATA_LEN_OFF] + add64 r7, DATA_LEN_MAX_PAD + and64 r7, DATA_LEN_AND_MASK + # acct += base_token_program_padded_data_len + EmptyAccount.size + add64 r9, r7 + add64 r9, SIZE_OF_EMPTY_ACCOUNT + # if acct.duplicate == account.NON_DUP_MARKER + ldxb r7, [r9 + ACCT_DUPLICATE_OFF] + jne r7, ACCT_NON_DUP_MARKER, register_market_quote_token_program_dup + # if acct.pubkey != input_shifted.quote_mint.owner + # return ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_0_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_0_OFF] + jne r7, r1, e_non_dup_quote_token_program_not_quote_mint_owner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_1_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_1_OFF] + jne r7, r1, e_non_dup_quote_token_program_not_quote_mint_owner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_2_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_2_OFF] + jne r7, r1, e_non_dup_quote_token_program_not_quote_mint_owner + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_3_OFF] + jne r7, r1, e_non_dup_quote_token_program_not_quote_mint_owner + # if acct.pubkey != pubkey.TOKEN_PROGRAM + # if acct.pubkey != pubkey.TOKEN_2022_PROGRAM + # return ErrorCode::QuoteTokenProgramNotTokenProgram + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_0_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_0 + jne r7, r1, register_market_check_quote_token_2022 + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_1_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_1 + jne r7, r1, register_market_check_quote_token_2022 + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_2_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_2 + jne r7, r1, register_market_check_quote_token_2022 + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] + lddw r1, PUBKEY_TOKEN_PROGRAM_CHUNK_3 + jeq r7, r1, register_market_done_token_programs +register_market_check_quote_token_2022: + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_0_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_0 + jne r7, r1, e_quote_token_program_not_token_program + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_1_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_1 + jne r7, r1, e_quote_token_program_not_token_program + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_2_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_2 + jne r7, r1, e_quote_token_program_not_token_program + ldxdw r7, [r9 + ACCT_ADDRESS_CHUNK_3_OFF] + lddw r1, PUBKEY_TOKEN_2022_PROGRAM_CHUNK_3 + jne r7, r1, e_quote_token_program_not_token_program + ja register_market_done_token_programs +register_market_quote_token_program_dup: + # if acct.duplicate != RegisterMarketAccounts::BaseTokenProgram + # return ErrorCode::InvalidQuoteTokenProgramDuplicate + jne r7, REGISTER_MARKET_ACCOUNTS_BASE_TOKEN_PROGRAM_POS, e_invalid_quote_token_program_duplicate + # if input.base_mint.owner != input_shifted.quote_mint.owner + # return ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner + ldxdw r7, [r8 + RM_MISC_BASE_OWNER_CHUNK_0_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_0_OFF] + jne r7, r1, e_dup_quote_token_program_not_quote_mint_owner + ldxdw r7, [r8 + RM_MISC_BASE_OWNER_CHUNK_1_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_1_OFF] + jne r7, r1, e_dup_quote_token_program_not_quote_mint_owner + ldxdw r7, [r8 + RM_MISC_BASE_OWNER_CHUNK_2_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_2_OFF] + jne r7, r1, e_dup_quote_token_program_not_quote_mint_owner + ldxdw r7, [r8 + RM_MISC_BASE_OWNER_CHUNK_3_OFF] + ldxdw r1, [r6 + RM_MISC_QUOTE_OWNER_CHUNK_3_OFF] + jne r7, r1, e_dup_quote_token_program_not_quote_mint_owner +register_market_done_token_programs: exit diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 6458721..3867c03 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -206,18 +206,19 @@ pub fn find_pda_seed_pair(program_id: &Pubkey) -> (Pubkey, Pubkey) { /// Runs all cases, prints a CU table, and panics if any case failed. pub fn run_and_report(heading: &str, cases: &[T], setup: &TestSetup) { let mut failures = Vec::new(); + let col = cases.iter().map(|c| c.name().len()).max().unwrap_or(4); println!(); println!(" {heading}"); println!(" {}", "-".repeat(heading.len())); - println!(" {:<40} {:>8}", "Case", "CUs"); - println!(" {:<40} {:>8}", "----", "---"); + println!(" {:8}", "Case", "CUs"); + println!(" {:8}", "----", "---"); for case in cases { let name = case.name(); let result = case.run(setup); let status = if result.error.is_some() { "FAIL" } else { "ok" }; - println!(" {:<40} {:>8} {status}", name, result.cu); + println!(" {:8} {status}", name, result.cu); if let Some(msg) = result.error { failures.push((name, msg)); } diff --git a/tests/tests/cases/register_market.rs b/tests/tests/cases/register_market.rs index 1f216a8..cf9b07b 100644 --- a/tests/tests/cases/register_market.rs +++ b/tests/tests/cases/register_market.rs @@ -1,4 +1,6 @@ use dropset_interface::market::{MarketHeader, RegisterMarketAccounts}; +use dropset_interface::pubkey::pubkey::{CHUNK_0_OFF, CHUNK_1_OFF, CHUNK_2_OFF, CHUNK_3_OFF}; +use dropset_interface::pubkey::{TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID}; use dropset_interface::{Discriminant, ErrorCode}; use dropset_tests::{ CaseResult, TestCase, TestSetup, check, check_custom, check_with_accounts, find_pda_seed_pair, @@ -35,6 +37,31 @@ test_cases! { InvalidRentSysvarPubkeyChunk2, InvalidRentSysvarPubkeyChunk3, InvalidRentSysvarPubkeyChunk3Hi, + BaseTokenProgramIsDuplicate, + BaseTokenProgramNotBaseMintOwnerChunk0, + BaseTokenProgramNotBaseMintOwnerChunk1, + BaseTokenProgramNotBaseMintOwnerChunk2, + BaseTokenProgramNotBaseMintOwnerChunk3, + BaseTokenProgramNotTokenProgramChunk0, + BaseTokenProgramNotTokenProgramChunk1, + BaseTokenProgramNotTokenProgramChunk2, + BaseTokenProgramNotTokenProgramChunk3, + NonDupQuoteTokenProgramNotQuoteMintOwnerChunk0, + NonDupQuoteTokenProgramNotQuoteMintOwnerChunk1, + NonDupQuoteTokenProgramNotQuoteMintOwnerChunk2, + NonDupQuoteTokenProgramNotQuoteMintOwnerChunk3, + QuoteTokenProgramNotTokenProgramChunk0, + QuoteTokenProgramNotTokenProgramChunk1, + QuoteTokenProgramNotTokenProgramChunk2, + QuoteTokenProgramNotTokenProgramChunk3, + InvalidQuoteTokenProgramDuplicateChunk0, + InvalidQuoteTokenProgramDuplicateChunk1, + InvalidQuoteTokenProgramDuplicateChunk2, + InvalidQuoteTokenProgramDuplicateChunk3, + DupQuoteTokenProgramNotQuoteMintOwnerChunk0, + DupQuoteTokenProgramNotQuoteMintOwnerChunk1, + DupQuoteTokenProgramNotQuoteMintOwnerChunk2, + DupQuoteTokenProgramNotQuoteMintOwnerChunk3, CreateAccountHappyPath, } } @@ -83,6 +110,15 @@ fn happy_path_accounts(setup: &TestSetup) -> (Vec, Vec<(Pubkey, Acc keys[RegisterMarketAccounts::RentSysvar as usize] = rent_sysvar_pubkey; accounts[RegisterMarketAccounts::RentSysvar as usize] = rent_sysvar_account; + // Set mint account owners to the Token Program. + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + accounts[RegisterMarketAccounts::BaseMint as usize].owner = token_program_id; + accounts[RegisterMarketAccounts::QuoteMint as usize].owner = token_program_id; + + // Set up token program accounts (both use Token Program, so quote is a duplicate). + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = token_program_id; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = token_program_id; + // 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); @@ -182,6 +218,66 @@ fn rent_sysvar_mismatch_accounts( into_metas_and_accounts(keys, accounts) } +/// Build accounts that pass all checks through the CPI, with the given +/// token programs as owners of the respective mints. Returns keys and +/// accounts that can be further modified for specific error cases. +fn token_program_base_accounts( + setup: &TestSetup, + base_token_program: Pubkey, + quote_token_program: Pubkey, +) -> (Vec, Vec) { + 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; + + accounts[RegisterMarketAccounts::BaseMint as usize].owner = base_token_program; + accounts[RegisterMarketAccounts::QuoteMint as usize].owner = quote_token_program; + + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = base_token_program; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = quote_token_program; + + accounts[RegisterMarketAccounts::User as usize] = + Account::new(USER_LAMPORTS, 0, &system_program_pubkey); + + (keys, accounts) +} + +fn token_program_metas_and_accounts( + keys: Vec, + accounts: Vec, +) -> (Vec, Vec<(Pubkey, Account)>) { + let metas: Vec = keys + .iter() + .enumerate() + .map(|(i, k)| { + let writable = 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) +} + impl TestCase for Case { fn run(&self, setup: &TestSetup) -> CaseResult { let insn = &[Discriminant::RegisterMarket.into()]; @@ -453,6 +549,422 @@ impl TestCase for Case { Some(ErrorCode::InvalidRentSysvarPubkey), ) } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramIsDuplicate => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + // BaseTokenProgram shares key with User, causing duplicate. + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = + keys[RegisterMarketAccounts::User as usize]; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramIsDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotBaseMintOwnerChunk0 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + let mut bad_key = token_program_id; + bad_key.as_mut()[CHUNK_0_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotBaseMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotBaseMintOwnerChunk1 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + let mut bad_key = token_program_id; + bad_key.as_mut()[CHUNK_1_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotBaseMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotBaseMintOwnerChunk2 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + let mut bad_key = token_program_id; + bad_key.as_mut()[CHUNK_2_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotBaseMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotBaseMintOwnerChunk3 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + let mut bad_key = token_program_id; + bad_key.as_mut()[CHUNK_3_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::BaseTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotBaseMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotTokenProgramChunk0 => { + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_0_OFF as usize] ^= 0xFF; + let (keys, accounts) = token_program_base_accounts(setup, bad_program, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotTokenProgramChunk1 => { + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_1_OFF as usize] ^= 0xFF; + let (keys, accounts) = token_program_base_accounts(setup, bad_program, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotTokenProgramChunk2 => { + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_2_OFF as usize] ^= 0xFF; + let (keys, accounts) = token_program_base_accounts(setup, bad_program, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseTokenProgramNotTokenProgramChunk3 => { + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_3_OFF as usize] ^= 0xFF; + let (keys, accounts) = token_program_base_accounts(setup, bad_program, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::NonDupQuoteTokenProgramNotQuoteMintOwnerChunk0 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let token_2022_id = Pubkey::from(TOKEN_2022_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_2022_id); + // Quote token program key doesn't match quote mint owner. + let mut bad_key = token_2022_id; + bad_key.as_mut()[CHUNK_0_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::NonDupQuoteTokenProgramNotQuoteMintOwnerChunk1 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let token_2022_id = Pubkey::from(TOKEN_2022_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_2022_id); + let mut bad_key = token_2022_id; + bad_key.as_mut()[CHUNK_1_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::NonDupQuoteTokenProgramNotQuoteMintOwnerChunk2 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let token_2022_id = Pubkey::from(TOKEN_2022_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_2022_id); + let mut bad_key = token_2022_id; + bad_key.as_mut()[CHUNK_2_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::NonDupQuoteTokenProgramNotQuoteMintOwnerChunk3 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let token_2022_id = Pubkey::from(TOKEN_2022_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_2022_id); + let mut bad_key = token_2022_id; + bad_key.as_mut()[CHUNK_3_OFF as usize] ^= 0xFF; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = bad_key; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::NonDupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteTokenProgramNotTokenProgramChunk0 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_0_OFF as usize] ^= 0xFF; + let (keys, accounts) = + token_program_base_accounts(setup, token_program_id, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteTokenProgramNotTokenProgramChunk1 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_1_OFF as usize] ^= 0xFF; + let (keys, accounts) = + token_program_base_accounts(setup, token_program_id, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteTokenProgramNotTokenProgramChunk2 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_2_OFF as usize] ^= 0xFF; + let (keys, accounts) = + token_program_base_accounts(setup, token_program_id, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteTokenProgramNotTokenProgramChunk3 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_program = Pubkey::from(TOKEN_PROGRAM_ID); + bad_program.as_mut()[CHUNK_3_OFF as usize] ^= 0xFF; + let (keys, accounts) = + token_program_base_accounts(setup, token_program_id, bad_program); + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteTokenProgramNotTokenProgram), + ) + } + // Verifies: REGISTER-MARKET + Self::InvalidQuoteTokenProgramDuplicateChunk0 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = + keys[RegisterMarketAccounts::User as usize]; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::InvalidQuoteTokenProgramDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::InvalidQuoteTokenProgramDuplicateChunk1 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = + keys[RegisterMarketAccounts::Market as usize]; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::InvalidQuoteTokenProgramDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::InvalidQuoteTokenProgramDuplicateChunk2 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = + keys[RegisterMarketAccounts::BaseMint as usize]; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::InvalidQuoteTokenProgramDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::InvalidQuoteTokenProgramDuplicateChunk3 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = + keys[RegisterMarketAccounts::QuoteMint as usize]; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::InvalidQuoteTokenProgramDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::DupQuoteTokenProgramNotQuoteMintOwnerChunk0 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let token_2022_id = Pubkey::from(TOKEN_2022_PROGRAM_ID); + // Base uses Token Program, quote uses Token 2022 (different owners), + // but quote key duplicates base key (Token Program). + let (mut keys, mut accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + accounts[RegisterMarketAccounts::QuoteMint as usize].owner = token_2022_id; + // Force duplicate by sharing key. + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = token_program_id; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::DupQuoteTokenProgramNotQuoteMintOwnerChunk1 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_owner = token_program_id; + bad_owner.as_mut()[CHUNK_1_OFF as usize] ^= 0xFF; + let (mut keys, mut accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + accounts[RegisterMarketAccounts::QuoteMint as usize].owner = bad_owner; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = token_program_id; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::DupQuoteTokenProgramNotQuoteMintOwnerChunk2 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_owner = token_program_id; + bad_owner.as_mut()[CHUNK_2_OFF as usize] ^= 0xFF; + let (mut keys, mut accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + accounts[RegisterMarketAccounts::QuoteMint as usize].owner = bad_owner; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = token_program_id; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner), + ) + } + // Verifies: REGISTER-MARKET + Self::DupQuoteTokenProgramNotQuoteMintOwnerChunk3 => { + let token_program_id = Pubkey::from(TOKEN_PROGRAM_ID); + let mut bad_owner = token_program_id; + bad_owner.as_mut()[CHUNK_3_OFF as usize] ^= 0xFF; + let (mut keys, mut accounts) = + token_program_base_accounts(setup, token_program_id, token_program_id); + accounts[RegisterMarketAccounts::QuoteMint as usize].owner = bad_owner; + keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = token_program_id; + let (metas, accounts) = token_program_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::DupQuoteTokenProgramNotQuoteMintOwner), + ) + } // Verifies: REGISTER-MARKET (CreateAccount CPI happy path) Self::CreateAccountHappyPath => { let (metas, accounts) = happy_path_accounts(setup);