diff --git a/Cargo.lock b/Cargo.lock index 372ca85..83ec4b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -852,8 +852,10 @@ dependencies = [ "dropset-interface", "heck", "mollusk-svm", + "mollusk-svm-programs-token", "solana-account 3.4.0", "solana-sdk", + "spl-token-interface", ] [[package]] @@ -1465,9 +1467,9 @@ dependencies = [ [[package]] name = "mollusk-svm" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a3cac3aca9ffb1e97c0b60ac4fca70f1ef2cbe532010e62cfdcdf2a2a2a2ef" +checksum = "9506b7859ad9716fff8c5fcdb23abf9801793571e6106c29dd74a10374aa7d93" dependencies = [ "agave-feature-set", "agave-syscalls", @@ -1509,19 +1511,34 @@ dependencies = [ [[package]] name = "mollusk-svm-error" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e315980684803972f443b2d8dd765e3def7e4e20bb67d5125bb80639e858a040" +checksum = "ff7c52dc834fc5962f54c7ffcb61a73b9f0ae0ab63b5e84043fa387b751254c2" dependencies = [ "solana-pubkey 4.1.0", - "thiserror 1.0.69", + "thiserror 2.0.18", +] + +[[package]] +name = "mollusk-svm-programs-token" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8a9b81a9d39a7ed52fc3c9ed5872b80659c7b9ff34e650f523780e71b13afa" +dependencies = [ + "mollusk-svm", + "solana-account 3.4.0", + "solana-program-pack", + "solana-pubkey 4.1.0", + "solana-rent 3.1.0", + "spl-associated-token-account-interface", + "spl-token-interface", ] [[package]] name = "mollusk-svm-result" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d5087302293e891db8627166c41a8cded1d74d1ba6c5b7b3a823bf4d7503e37" +checksum = "92376062d0cad8a3b28f86e57b58c8949302aa6eaed518f584fdfc2f75d8face" dependencies = [ "solana-account 3.4.0", "solana-instruction", @@ -1586,6 +1603,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1732,10 +1760,10 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06810dac15a4ef83d3dabdb4f2f22fb39c9adff669cd2781da4f716510a647c" dependencies = [ - "solana-account-view", + "solana-account-view 1.0.0", "solana-address 2.3.0", "solana-define-syscall 4.0.1", - "solana-instruction-view", + "solana-instruction-view 1.0.0", "solana-program-error", ] @@ -1745,21 +1773,20 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "febf3bbe37f4e2723b9b41a1768c6542a1ae1b1d7bcac27f892f30cabcf70ec4" dependencies = [ - "solana-account-view", + "solana-account-view 1.0.0", "solana-address 2.3.0", - "solana-instruction-view", + "solana-instruction-view 1.0.0", "solana-program-error", ] [[package]] name = "pinocchio-token-2022" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe4f1997ce2443f99333d8ae2ee1075f9c94ed13ff941178663ae3601ad99ad" +source = "git+https://github.com/anza-xyz/pinocchio?branch=main#009301423f920fd105bd32a25560d127b6f0bf4f" dependencies = [ - "solana-account-view", + "solana-account-view 2.0.0", "solana-address 2.3.0", - "solana-instruction-view", + "solana-instruction-view 2.0.0", "solana-program-error", ] @@ -2313,6 +2340,16 @@ dependencies = [ "solana-program-error", ] +[[package]] +name = "solana-account-view" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc141b940560430425ebaadb7645496c45f6a10fad9911d719bd03eab7f4d422" +dependencies = [ + "solana-address 2.3.0", + "solana-program-error", +] + [[package]] name = "solana-address" version = "1.1.0" @@ -2692,12 +2729,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60147e4d0a4620013df40bf30a86dd299203ff12fcb8b593cd51014fce0875d8" dependencies = [ - "solana-account-view", + "solana-account-view 1.0.0", "solana-address 2.3.0", "solana-define-syscall 4.0.1", "solana-program-error", ] +[[package]] +name = "solana-instruction-view" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c35d02cd27575b1fd05938d3624e476b0173208139053b51aa48dd9bec149f1" +dependencies = [ + "solana-account-view 2.0.0", + "solana-address 2.3.0", + "solana-define-syscall 5.0.0", + "solana-program-error", +] + [[package]] name = "solana-instructions-sysvar" version = "3.0.0" @@ -3673,6 +3722,36 @@ dependencies = [ "der", ] +[[package]] +name = "spl-associated-token-account-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6433917b60441d68d99a17e121d9db0ea15a9a69c0e5afa34649cf5ba12612f" +dependencies = [ + "solana-instruction", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "spl-token-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c564ac05a7c8d8b12e988a37d82695b5ba4db376d07ea98bc4882c81f96c7f3" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-instruction", + "solana-program-error", + "solana-program-option", + "solana-program-pack", + "solana-pubkey 3.0.0", + "solana-sdk-ids", + "thiserror 2.0.18", +] + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 44eb5c6..db3bfc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,15 +14,18 @@ resolver = "3" [workspace.dependencies] bindgen = "0.72.1" heck = "0.5" -mollusk-svm = "0.11.0" +mollusk-svm = "0.12.0" +mollusk-svm-programs-token = "0.12.0" pinocchio = "0.10.2" pinocchio-token = "0.5.0" -pinocchio-token-2022 = "0.2.0" +# "0.2.0" does not export GetAccountDataSize. +pinocchio-token-2022 = {git = "https://github.com/anza-xyz/pinocchio", branch = "main"} proc-macro2 = "1.0.106" quote = "1.0.45" solana-account = "3.2" solana-sbpf = "0.16.0" solana-sdk = "4.0.1" +spl-token-interface = "2.0.0" syn = {version = "2.0.117", features = ["full"]} ureq = "3.3.0" diff --git a/docs/algorithms/INIT-MARKET-PDA.tex b/docs/algorithms/INIT-MARKET-PDA.tex index 8ed6005..9ba2f89 100644 --- a/docs/algorithms/INIT-MARKET-PDA.tex +++ b/docs/algorithms/INIT-MARKET-PDA.tex @@ -11,15 +11,20 @@ \INPUT $r_{10}$ = frame \COMMENT{Pointer to shifted input buffer for quote mint offsets.} \REQUIRE frame.input\_shifted - \COMMENT{Pointer to System Program ID in input buffer.} - \REQUIRE frame.sol\_instruction.program\_id + \REQUIRE *frame.system\_program\_id = input.system\_program.address + \REQUIRE frame.cpi[0].info.data\_len = \texttt{data.LEN\_ZERO} + \REQUIRE frame.cpi[1].info.data\_len = \texttt{data.LEN\_ZERO} \ENSURE $r_9$ = acct \ENSURE frame.input = input + \ENSURE frame.lamports\_per\_byte = acct.data.lamports\_per\_byte + \ENSURE frame.signers\_seeds.addr = \&frame.pda\_seeds + \ENSURE frame.signers\_seeds.len = \texttt{RegisterMarketFrame.PDA\_SEEDS\_N\_SEEDS} \PROCEDURE{INIT-MARKET-PDA}{input, insn, acct, frame} \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.lamports\_per\_byte = lamports\_per\_byte \STATE frame.create\_account\_data.lamports = acct\_size $\times$ lamports\_per\_byte \COMMENT{Initialize market PDA signer seeds.} \STATE frame.pda\_seeds[0].addr = input.base\_mint.address @@ -68,6 +73,7 @@ \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.program\_id = frame.system\_program\_id \STATE frame.sol\_instruction.accounts = \&frame.cpi.account\_metas \STATE frame.sol\_instruction.account\_len = \texttt{register\_misc.CREATE\_ACCOUNT\_N\_ACCOUNTS} diff --git a/docs/algorithms/INIT-VAULT.tex b/docs/algorithms/INIT-VAULT.tex index a1f3844..affa6cd 100644 --- a/docs/algorithms/INIT-VAULT.tex +++ b/docs/algorithms/INIT-VAULT.tex @@ -13,11 +13,23 @@ \texttt{true}, \texttt{false} \} - \COMMENT{Pointer to owning token program address.} + \REQUIRE *frame.mint $\in$ \{ + input.base\_mint, + input\_shifted.quote\_mint + \} \REQUIRE frame.token\_program\_id - \COMMENT{Pointer to input buffer.} + \REQUIRE frame.program\_id \REQUIRE frame.input + \REQUIRE frame.lamports\_per\_byte \REQUIRE frame.pda\_seeds[0].len = \texttt{Address.size} + \REQUIRE frame.pda\_seeds[2].addr = \&frame.bump + \REQUIRE frame.pda\_seeds[2].len = \texttt{u8.size} + \REQUIRE *frame.system\_program\_id = input.system\_program.address + \REQUIRE frame.signers\_seeds.addr = \&frame.pda\_seeds + \REQUIRE frame.signers\_seeds.len = \texttt{RegisterMarketFrame.PDA\_SEEDS\_N\_SEEDS} + \REQUIRE input.user.data\_len = \texttt{data.LEN\_ZERO} + \REQUIRE acct.data\_len = \texttt{data.LEN\_ZERO} + \REQUIRE frame.cpi[0].info.executable = \texttt{false} \FUNCTION{INIT-VAULT}{acct, frame} \STATE \CALL{Store}{frame} \STATE \CALL{Store}{acct} @@ -27,7 +39,7 @@ \STATE frame.pda\_seeds[1].len = \texttt{u8.size} \STATE syscall.seeds = \&frame.pda\_seeds \STATE syscall.seeds\_len = \texttt{register\_misc.TRY\_FIND\_VAULT\_PDA\_SEEDS\_LEN} - \STATE syscall.program\_id = frame.token\_program\_id + \STATE syscall.program\_id = frame.program\_id \STATE syscall.program\_address = \&frame.pda \STATE syscall.bump\_seed = \&frame.bump \STATE \CALL{sol-try-find-program-address}{} @@ -39,6 +51,89 @@ \RETURN \texttt{ErrorCode::InvalidQuoteVaultPubkey} \ENDIF \ENDIF + \COMMENT{Determine token account size.} + \IF{frame.token\_program\_is\_2022} + \COMMENT{Set up mint as CPI account.} + \STATE frame.cpi[0].info.is\_signer = \texttt{false} + \STATE frame.cpi[0].info.is\_writable = \texttt{false} + \STATE frame.cpi[0].meta.is\_signer = \texttt{false} + \STATE frame.cpi[0].meta.is\_writable = \texttt{false} + \STATE frame.cpi[0].meta.pubkey = \&mint.address + \STATE frame.cpi[0].info.key = \&mint.address + \STATE frame.cpi[0].info.owner = \&mint.owner + \STATE frame.cpi[0].info.lamports = \&mint.lamports + \STATE frame.cpi[0].info.data\_len = mint.data\_len + \STATE frame.cpi[0].info.data = \&mint.data + \COMMENT{Populate SolInstruction for GetAccountDataSize CPI.} + \STATE frame.sol\_instruction.program\_id = frame.token\_program\_id + \STATE frame.sol\_instruction.accounts = \&frame.cpi[0].meta + \STATE frame.sol\_instruction.account\_len = + \texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_N\_ACCOUNTS} + \STATE frame.get\_account\_data\_size\_data = + \texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_DISC} + \STATE frame.sol\_instruction.data = \&frame.get\_account\_data\_size\_data + \STATE frame.sol\_instruction.data\_len = \texttt{u8.size} + \COMMENT{Invoke GetAccountDataSize CPI (no signers).} + \STATE syscall.instruction = \&frame.sol\_instruction + \STATE syscall.account\_infos = \&frame.cpi[0].info + \STATE syscall.account\_infos\_len = + \texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_N\_ACCOUNTS} + \STATE syscall.seeds\_len = \texttt{token.GET\_ACCOUNT\_DATA\_SIZE\_N\_SEEDS} + \STATE \CALL{sol-invoke-signed-c}{} + \COMMENT{Get return data.} + \STATE syscall.bytes = \&frame.token\_account\_data\_size + \STATE syscall.bytes\_len = \texttt{u64.size} + \STATE syscall.program\_id = \&frame.get\_return\_data\_program\_id + \STATE \CALL{sol-get-return-data}{} + \STATE acct\_size = frame.token\_account\_data\_size + \COMMENT{Override default return data nonzero return code.} + \STATE result = \texttt{entrypoint.RETURN\_SUCCESS} + \ELSE + \STATE acct\_size = \texttt{token.ACCOUNT\_SIZE} + \ENDIF + \COMMENT{Prepare CreateAccount instruction fields.} + \STATE frame.create\_account\_data.space = acct\_size + \STATE acct\_size = acct\_size + \texttt{account.STORAGE\_OVERHEAD} + \STATE frame.create\_account\_data.lamports = + acct\_size $\times$ frame.lamports\_per\_byte + \STATE frame.create\_account\_data.owner = frame.token\_program\_id + \COMMENT{Assign CPI account fields via immediates.} + \STATE frame.cpi[0].info.is\_signer = \texttt{true} + \STATE frame.cpi[0].info.is\_writable = \texttt{true} + \STATE frame.cpi[0].meta.is\_signer = \texttt{true} + \STATE frame.cpi[0].meta.is\_writable = \texttt{true} + \STATE frame.cpi[1].info.is\_signer = \texttt{true} + \STATE frame.cpi[1].info.is\_writable = \texttt{true} + \STATE frame.cpi[1].meta.is\_signer = \texttt{true} + \STATE frame.cpi[1].meta.is\_writable = \texttt{true} + \STATE frame.cpi[0].info.data\_len = \texttt{data.LEN\_ZERO} + \STATE frame.cpi[1].info.data\_len = \texttt{data.LEN\_ZERO} + \COMMENT{Assign CPI account fields via pointers.} + \STATE frame.cpi[0].meta.pubkey = \&input.user.address + \STATE frame.cpi[0].info.key = \&input.user.address + \STATE frame.cpi[0].info.owner = \&input.user.owner + \STATE frame.cpi[0].info.lamports = \&input.user.lamports + \STATE frame.cpi[0].info.data = \&input.user.data + \STATE frame.cpi[1].meta.pubkey = \&acct.address + \STATE frame.cpi[1].info.key = \&acct.address + \STATE frame.cpi[1].info.owner = \&acct.owner + \STATE frame.cpi[1].info.lamports = \&acct.lamports + \STATE frame.cpi[1].info.data = \&acct.data + \COMMENT{Populate SolInstruction for CreateAccount CPI.} + \STATE frame.sol\_instruction.program\_id = frame.system\_program\_id + \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}{} \ENDFUNCTION \end{algorithmic} \end{algorithm} diff --git a/docs/algorithms/REGISTER-MARKET.tex b/docs/algorithms/REGISTER-MARKET.tex index e87f4c9..5ca59cf 100644 --- a/docs/algorithms/REGISTER-MARKET.tex +++ b/docs/algorithms/REGISTER-MARKET.tex @@ -20,6 +20,7 @@ \IF{insn\_len $\neq$ \texttt{RegisterMarketData.LEN}} \RETURN \texttt{ErrorCode::InvalidInstructionLength} \ENDIF + \STATE frame.program\_id = \&insn.program\_id \COMMENT{Check user, market accounts.} \IF{input.user.data\_len $\neq$ 0} \RETURN \texttt{ErrorCode::UserHasData} @@ -52,8 +53,7 @@ \IF{acct.address $\neq$ frame.system\_program\_pubkey} \RETURN \texttt{ErrorCode::InvalidSystemProgramPubkey} \ENDIF - \COMMENT{Populate CPI program ID field.} - \STATE frame.sol\_instruction.program\_id = \&acct.address + \STATE frame.system\_program\_id = \&acct.address \COMMENT{Advance to Rent sysvar account.} \STATE system\_program\_padded\_data\_len = acct.padded\_data\_len \STATE acct += system\_program\_padded\_data\_len + \texttt{EmptyAccount.size} @@ -90,15 +90,22 @@ \COMMENT{Advance to base vault 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 base vault account.} + \IF{acct.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} + \RETURN \texttt{ErrorCode::BaseVaultIsDuplicate} + \ENDIF + \IF{acct.data\_len $\neq$ \texttt{data.LEN\_ZERO}} + \RETURN \texttt{ErrorCode::BaseVaultHasData} + \ENDIF \COMMENT{Initialize base vault account.} \STATE frame.vault\_index = \texttt{register\_misc.VAULT\_INDEX\_BASE} + \STATE frame.mint = \&input.base\_mint \STATE result = \CALL{INIT-VAULT}{acct, frame} \IF{result $\neq$ \texttt{entrypoint.RETURN\_SUCCESS}} \RETURN result \ENDIF \COMMENT{Advance to quote token program account.} - \STATE base\_vault\_padded\_data\_len = acct.padded\_data\_len - \STATE acct += base\_vault\_padded\_data\_len + \texttt{EmptyAccount.size} + \STATE acct += \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.} @@ -130,8 +137,16 @@ \COMMENT{Advance to quote vault account.} \STATE acct += \texttt{u64.size} \ENDIF + \COMMENT{Check quote vault account.} + \IF{acct.duplicate $\neq$ \texttt{account.NON\_DUP\_MARKER}} + \RETURN \texttt{ErrorCode::QuoteVaultIsDuplicate} + \ENDIF + \IF{acct.data\_len $\neq$ \texttt{data.LEN\_ZERO}} + \RETURN \texttt{ErrorCode::QuoteVaultHasData} + \ENDIF \COMMENT{Initialize quote vault account.} \STATE frame.vault\_index = \texttt{register\_misc.VAULT\_INDEX\_QUOTE} + \STATE frame.mint = \&input\_shifted.quote\_mint \STATE \CALL{INIT-VAULT}{acct, frame} \ENDPROCEDURE \end{algorithmic} diff --git a/docs/algorithms/syscalls.json b/docs/algorithms/syscalls.json index 9d8a303..1accb1d 100644 --- a/docs/algorithms/syscalls.json +++ b/docs/algorithms/syscalls.json @@ -1,4 +1,5 @@ { + "sol_get_return_data": "https://github.com/anza-xyz/agave/blob/v4.0.0-beta.5/platform-tools-sdk/sbf/c/inc/sol/inc/return_data.inc#L27-L35", "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/program/layout.md b/docs/src/program/layout.md index 913000d..1373884 100644 --- a/docs/src/program/layout.md +++ b/docs/src/program/layout.md @@ -14,9 +14,11 @@ program/src/dropset/ │ ├── discriminant.s # Instruction discriminants │ ├── error.s # Error codes and subroutines │ ├── memory.s # Memory layout constants -│ └── pubkey.s # Pubkey chunk offsets and known addresses +│ ├── pubkey.s # Pubkey chunk offsets and known addresses +│ └── token.s # SPL Token constants └── market/ - ├── init_vault.s # InitVault function + ├── init_market_pda.s # Market PDA initialization + ├── init_vault.s # Vault initialization └── register.s # RegisterMarket handler ``` @@ -91,6 +93,13 @@ emits the same set of constants with a `_UOFF` suffix instead of `_OFF`. +### Token + +SPL Token constants (account size, instruction discriminators) are injected +from the [`token`][token-mod] module via [`constant_group!`][bs-constant-group]: + + + [input buffer]: ../program/inputs#input-buffer [`program/src/dropset/`]: https://github.com/DASMAC-com/dropset-beta/tree/main/program/src/dropset [multi-file assembly]: https://github.com/blueshift-gg/sbpf/pull/109 @@ -98,5 +107,6 @@ emits the same set of constants with a `_UOFF` suffix instead of `_OFF`. [bs-discriminant]: ../development/build-scaffolding#discriminant-enum-target [bs-error]: ../development/build-scaffolding#error-enum-target [pubkey-mod]: https://github.com/DASMAC-com/dropset-beta/blob/main/interface/src/pubkey.rs +[token-mod]: https://github.com/DASMAC-com/dropset-beta/blob/main/interface/src/token.rs [bs-constant-group]: ../development/build-scaffolding#constant_group [build scaffolding]: ../development/build-scaffolding diff --git a/interface/src/lib.rs b/interface/src/lib.rs index a135690..b1fcab7 100644 --- a/interface/src/lib.rs +++ b/interface/src/lib.rs @@ -6,6 +6,7 @@ pub mod memory; pub mod order; pub mod pubkey; pub mod seat; +pub mod token; // region: discriminant_enum #[discriminant_enum("common/discriminant")] @@ -62,6 +63,14 @@ pub enum ErrorCode { InvalidBaseVaultPubkey, /// The quote vault account pubkey is invalid. InvalidQuoteVaultPubkey, + /// The base vault account is a duplicate. + BaseVaultIsDuplicate, + /// The base vault account already has data. + BaseVaultHasData, + /// The quote vault account is a duplicate. + QuoteVaultIsDuplicate, + /// The quote vault account already has data. + QuoteVaultHasData, } // endregion: error_enum @@ -93,4 +102,5 @@ pub const INJECTION_GROUPS: &[&dropset_build::ConstantGroup] = &[ &memory::input_buffer::GROUP, &memory::size_of::GROUP, &pubkey::pubkey::GROUP, + &token::token::GROUP, ]; diff --git a/interface/src/market.rs b/interface/src/market.rs index ebd8617..2132828 100644 --- a/interface/src/market.rs +++ b/interface/src/market.rs @@ -10,6 +10,7 @@ use dropset_macros::{ svm_data, }; use pinocchio::Address; +use pinocchio::account::RuntimeAccount; // region: market_header #[svm_data] @@ -147,20 +148,34 @@ signer_seeds! { #[frame] /// Stack frame for REGISTER-MARKET. pub struct RegisterMarketFrame { - /// Pointer to owning token program address. - pub token_program_id: u64, + /// Pointer to token program address. + pub token_program_id: *const Address, + /// Pointer to program ID in input buffer. + pub program_id: *const Address, /// Saved input buffer pointer. pub input: u64, /// Saved input_shifted pointer. pub input_shifted: u64, + /// From Rent sysvar. + pub lamports_per_byte: u64, + /// Return value from GetAccountDataSize CPI, to check token account data size at runtime. + pub token_account_data_size: u64, + /// Pointer to mint account for vault initialization. + pub mint: *const RuntimeAccount, /// Signer seeds for PDA derivation and CPI signing. pub pda_seeds: PDASignerSeeds, /// From `sol_try_find_program_address`. pub pda: Address, /// System Program pubkey, zero-initialized on stack pub system_program_pubkey: Address, + /// Pointer to System Program ID in input buffer. + pub system_program_id: *const Address, + /// Get return data program ID for CPI calls, zero-initialized on stack. + pub get_return_data_program_id: Address, /// CPI instruction data for CreateAccount. pub create_account_data: CreateAccountData, + /// GetAccountDataSize CPI instruction data. + pub get_account_data_size_data: u8, /// CPI accounts for CreateAccount and InitializeAccount2. pub cpi_accounts: CPIAccounts, /// Signers seeds for CPI. @@ -169,9 +184,9 @@ pub struct RegisterMarketFrame { pub sol_instruction: SolInstruction, /// From `sol_try_find_program_address`. pub bump: u8, - /// Vault index for PDA derivation. + /// Vault index for vault PDA derivation. pub vault_index: u8, - /// Whether the current token program is Token 2022. + /// Whether the current token program is Token 2022 (zero-initialized on stack). pub token_program_is_2022: u8, } // endregion: frame_example @@ -181,18 +196,30 @@ constant_group! { #[inject("market/register")] #[frame(RegisterMarketFrame)] frame { - /// Pointer to owning token program address. + /// Pointer to token program address. TOKEN_PROGRAM_ID = offset!(token_program_id), + /// Pointer to program ID in input buffer. + PROGRAM_ID = offset!(program_id), /// Saved input buffer pointer. INPUT = offset!(input), /// Saved input_shifted pointer. INPUT_SHIFTED = offset!(input_shifted), + /// From Rent sysvar. + LAMPORTS_PER_BYTE = offset!(lamports_per_byte), + /// Return value from GetAccountDataSize CPI, to check token account data size at runtime. + TOKEN_ACCOUNT_DATA_SIZE = offset!(token_account_data_size), + /// Pointer to mint account for vault initialization. + MINT = offset!(mint), /// PDA signer seeds. PDA_SEEDS = signer_seeds!(pda_seeds), /// PDA address. PDA = pubkey_offsets!(pda), /// System Program pubkey. SYSTEM_PROGRAM_PUBKEY = pubkey_offsets!(system_program_pubkey), + /// System Program ID in input buffer. + SYSTEM_PROGRAM_ID = offset!(system_program_id), + /// Get return data program ID for CPI calls. + GET_RETURN_DATA_PROGRAM_ID = offset!(get_return_data_program_id), /// CreateAccount instruction data. CREATE_ACCT_DATA = offset!(create_account_data), /// Lamports field within CreateAccount instruction data. @@ -201,6 +228,8 @@ constant_group! { CREATE_ACCT_SPACE = unaligned_offset!(create_account_data.space), /// Owner field within CreateAccount instruction data. CREATE_ACCT_OWNER = unaligned_pubkey_offsets!(create_account_data.owner), + /// GetAccountDataSize CPI instruction data. + GET_ACCOUNT_DATA_SIZE_DATA = unaligned_offset!(get_account_data_size_data), /// CPI accounts. CPI = cpi_accounts!(cpi_accounts), /// Signers seeds address. diff --git a/interface/src/memory.rs b/interface/src/memory.rs index 9c8fb7e..302b95e 100644 --- a/interface/src/memory.rs +++ b/interface/src/memory.rs @@ -81,6 +81,8 @@ constant_group! { cpi { /// Mask for writable signer (is_writable | is_signer). WRITABLE_SIGNER = immediate!(0x0101), + /// Mask for readonly non-signer. + READONLY_NON_SIGNER = immediate!(0x0000), } } diff --git a/interface/src/token.rs b/interface/src/token.rs new file mode 100644 index 0000000..2690c56 --- /dev/null +++ b/interface/src/token.rs @@ -0,0 +1,20 @@ +use dropset_macros::constant_group; +use pinocchio_token::state::TokenAccount; +// pinocchio-token does not export GetAccountDataSize yet. +use pinocchio_token_2022::instructions::GetAccountDataSize; + +constant_group! { + #[prefix("TOKEN")] + #[inject("common/token")] + /// SPL Token constants. + token { + /// Size of a token account (SPL Token and Token 2022 base). + ACCOUNT_SIZE = immediate!(TokenAccount::LEN), + /// GetAccountDataSize instruction discriminator (Token 2022). + GET_ACCOUNT_DATA_SIZE_DISC = immediate!(GetAccountDataSize::DISCRIMINATOR), + /// GetAccountDataSize number of accounts. + GET_ACCOUNT_DATA_SIZE_N_ACCOUNTS = immediate!(1), + /// GetAccountDataSize number of seeds. + GET_ACCOUNT_DATA_SIZE_N_SEEDS = immediate!(0), + } +} diff --git a/program/src/dropset/common/error.s b/program/src/dropset/common/error.s index 4e78e23..d3ef38a 100644 --- a/program/src/dropset/common/error.s +++ b/program/src/dropset/common/error.s @@ -36,6 +36,11 @@ .equ E_INVALID_BASE_VAULT_PUBKEY, 21 # The quote vault account pubkey is invalid. .equ E_INVALID_QUOTE_VAULT_PUBKEY, 22 +.equ E_BASE_VAULT_IS_DUPLICATE, 23 # The base vault account is a duplicate. +.equ E_BASE_VAULT_HAS_DATA, 24 # The base vault account already has data. +# The quote vault account is a duplicate. +.equ E_QUOTE_VAULT_IS_DUPLICATE, 25 +.equ E_QUOTE_VAULT_HAS_DATA, 26 # The quote vault account already has data. e_invalid_instruction_length: mov32 r0, E_INVALID_INSTRUCTION_LENGTH @@ -116,3 +121,19 @@ e_quote_token_program_not_token_program: e_invalid_base_vault_pubkey: mov32 r0, E_INVALID_BASE_VAULT_PUBKEY exit + +e_base_vault_is_duplicate: + mov32 r0, E_BASE_VAULT_IS_DUPLICATE + exit + +e_base_vault_has_data: + mov32 r0, E_BASE_VAULT_HAS_DATA + exit + +e_quote_vault_is_duplicate: + mov32 r0, E_QUOTE_VAULT_IS_DUPLICATE + exit + +e_quote_vault_has_data: + mov32 r0, E_QUOTE_VAULT_HAS_DATA + exit diff --git a/program/src/dropset/common/memory.s b/program/src/dropset/common/memory.s index 394483e..e379a84 100644 --- a/program/src/dropset/common/memory.s +++ b/program/src/dropset/common/memory.s @@ -26,6 +26,7 @@ # ------------------------------------------------------------------------- # Mask for writable signer (is_writable | is_signer). .equ CPI_WRITABLE_SIGNER, 257 +.equ CPI_READONLY_NON_SIGNER, 0 # Mask for readonly non-signer. # ------------------------------------------------------------------------- # Common data-related constants. diff --git a/program/src/dropset/common/token.s b/program/src/dropset/common/token.s new file mode 100644 index 0000000..c91133a --- /dev/null +++ b/program/src/dropset/common/token.s @@ -0,0 +1,11 @@ +# SPL Token constants. +# ------------------------------------------------------------------------- +# Size of a token account (SPL Token and Token 2022 base). +.equ TOKEN_ACCOUNT_SIZE, 165 +# GetAccountDataSize instruction discriminator (Token 2022). +.equ TOKEN_GET_ACCOUNT_DATA_SIZE_DISC, 21 +# GetAccountDataSize number of accounts. +.equ TOKEN_GET_ACCOUNT_DATA_SIZE_N_ACCOUNTS, 1 +# GetAccountDataSize number of seeds. +.equ TOKEN_GET_ACCOUNT_DATA_SIZE_N_SEEDS, 0 +# ------------------------------------------------------------------------- diff --git a/program/src/dropset/dropset.s b/program/src/dropset/dropset.s index 424aae9..dcfb79e 100644 --- a/program/src/dropset/dropset.s +++ b/program/src/dropset/dropset.s @@ -5,6 +5,7 @@ .include "common/error.s" .include "common/memory.s" .include "common/pubkey.s" +.include "common/token.s" .include "entrypoint.s" .include "market/register.s" .include "market/init_market_pda.s" diff --git a/program/src/dropset/market/init_market_pda.s b/program/src/dropset/market/init_market_pda.s index cbe1a43..f503d84 100644 --- a/program/src/dropset/market/init_market_pda.s +++ b/program/src/dropset/market/init_market_pda.s @@ -6,6 +6,8 @@ init_market_pda: add64 r7, ACCT_STORAGE_OVERHEAD # lamports_per_byte = acct.data.lamports_per_byte ldxdw r8, [r9 + ACCT_DATA_OFF] + # frame.lamports_per_byte = lamports_per_byte + stxdw [r10 + RM_FM_LAMPORTS_PER_BYTE_OFF], r8 # frame.create_account_data.lamports = acct_size * lamports_per_byte mul64 r7, r8 stxdw [r10 + RM_FM_CREATE_ACCT_LAMPORTS_UOFF], r7 @@ -115,6 +117,9 @@ init_market_pda: # 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.program_id = frame.system_program_id + ldxdw r7, [r10 + RM_FM_SYSTEM_PROGRAM_ID_OFF] + stxdw [r10 + RM_FM_SOL_INSN_PROGRAM_ID_UOFF], r7 # frame.sol_instruction.data = &frame.create_account_data mov64 r7, r10 add64 r7, RM_FM_CREATE_ACCT_DATA_OFF diff --git a/program/src/dropset/market/init_vault.s b/program/src/dropset/market/init_vault.s index d868784..0403bde 100644 --- a/program/src/dropset/market/init_vault.s +++ b/program/src/dropset/market/init_vault.s @@ -19,8 +19,8 @@ init_vault: add64 r1, RM_FM_PDA_SEEDS_OFF # syscall.seeds_len = register_misc.TRY_FIND_VAULT_PDA_SEEDS_LEN mov64 r2, RM_MISC_TRY_FIND_VAULT_PDA_SEEDS_LEN - # syscall.program_id = frame.token_program_id - ldxdw r3, [r6 + RM_FM_TOKEN_PROGRAM_ID_OFF] + # syscall.program_id = frame.program_id + ldxdw r3, [r6 + RM_FM_PROGRAM_ID_OFF] # syscall.program_address = &frame.pda mov64 r4, r6 add64 r4, RM_FM_PDA_OFF @@ -41,6 +41,178 @@ init_vault: ldxdw r1, [r7 + ACCT_ADDRESS_CHUNK_3_OFF] ldxdw r2, [r6 + RM_FM_PDA_CHUNK_3_OFF] jne r1, r2, init_vault_invalid_pda + # if !frame.token_program_is_2022 + # acct_size = token.ACCOUNT_SIZE + ldxb r1, [r6 + RM_FM_TOKEN_PROGRAM_IS_2022_UOFF] + jne r1, DATA_BOOL_FALSE, init_vault_get_account_data_size + mov64 r1, TOKEN_ACCOUNT_SIZE + stxdw [r6 + RM_FM_TOKEN_ACCOUNT_DATA_SIZE_OFF], r1 + ja init_vault_create_account +init_vault_get_account_data_size: + # frame.cpi[0].info.is_signer = false + # frame.cpi[0].info.is_writable = false + sth [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_IS_SIGNER_UOFF], CPI_READONLY_NON_SIGNER + # frame.cpi[0].meta.is_writable = false + # frame.cpi[0].meta.is_signer = false + sth [r6 + RM_FM_CPI_IDX_0_ACCT_META_IS_WRITABLE_UOFF], CPI_READONLY_NON_SIGNER + # mint = frame.mint + ldxdw r8, [r6 + RM_FM_MINT_OFF] + # frame.cpi[0].meta.pubkey = &mint.address + mov64 r9, r8 + add64 r9, ACCT_ADDRESS_OFF + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_META_PUBKEY_UOFF], r9 + # frame.cpi[0].info.key = &mint.address + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_KEY_UOFF], r9 + # frame.cpi[0].info.owner = &mint.owner + add64 r9, IB_ADDRESS_TO_OWNER_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_OWNER_UOFF], r9 + # frame.cpi[0].info.lamports = &mint.lamports + add64 r9, IB_OWNER_TO_LAMPORTS_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_LAMPORTS_UOFF], r9 + # frame.cpi[0].info.data_len = mint.data_len + ldxdw r9, [r8 + ACCT_DATA_LEN_OFF] + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_DATA_LEN_UOFF], r9 + # frame.cpi[0].info.data = &mint.data + mov64 r9, r8 + add64 r9, ACCT_DATA_OFF + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_DATA_UOFF], r9 + # frame.sol_instruction.program_id = frame.token_program_id + ldxdw r9, [r6 + RM_FM_TOKEN_PROGRAM_ID_OFF] + stxdw [r6 + RM_FM_SOL_INSN_PROGRAM_ID_UOFF], r9 + # frame.sol_instruction.accounts = &frame.cpi[0].meta + mov64 r9, r6 + add64 r9, RM_FM_CPI_IDX_0_ACCT_META_PUBKEY_UOFF + stxdw [r6 + RM_FM_SOL_INSN_ACCOUNTS_UOFF], r9 + # frame.sol_instruction.account_len = token.GET_ACCOUNT_DATA_SIZE_N_ACCOUNTS + mov64 r9, TOKEN_GET_ACCOUNT_DATA_SIZE_N_ACCOUNTS + stxdw [r6 + RM_FM_SOL_INSN_ACCOUNT_LEN_UOFF], r9 + # frame.get_account_data_size_data = token.GET_ACCOUNT_DATA_SIZE_DISC + stb [r6 + RM_FM_GET_ACCOUNT_DATA_SIZE_DATA_UOFF], TOKEN_GET_ACCOUNT_DATA_SIZE_DISC + # frame.sol_instruction.data = &frame.get_account_data_size_data + mov64 r9, r6 + add64 r9, RM_FM_GET_ACCOUNT_DATA_SIZE_DATA_UOFF + stxdw [r6 + RM_FM_SOL_INSN_DATA_UOFF], r9 + # frame.sol_instruction.data_len = u8.size + mov64 r9, SIZE_OF_U8 + stxdw [r6 + RM_FM_SOL_INSN_DATA_LEN_UOFF], r9 + # syscall.instruction = &frame.sol_instruction + mov64 r1, r6 + add64 r1, RM_FM_SOL_INSN_OFF + # syscall.account_infos = &frame.cpi[0].info + mov64 r2, r6 + add64 r2, RM_FM_CPI_SOL_ACCT_INFO_OFF + # syscall.account_infos_len = token.GET_ACCOUNT_DATA_SIZE_N_ACCOUNTS + mov64 r3, TOKEN_GET_ACCOUNT_DATA_SIZE_N_ACCOUNTS + # syscall.seeds_len = token.GET_ACCOUNT_DATA_SIZE_N_SEEDS + mov64 r5, TOKEN_GET_ACCOUNT_DATA_SIZE_N_SEEDS + call sol_invoke_signed_c + # syscall.bytes = &frame.token_account_data_size + mov64 r1, r6 + add64 r1, RM_FM_TOKEN_ACCOUNT_DATA_SIZE_OFF + # syscall.bytes_len = u64.size + mov64 r2, SIZE_OF_U64 + # syscall.program_id = &frame.get_return_data_program_id + mov64 r3, r6 + add64 r3, RM_FM_GET_RETURN_DATA_PROGRAM_ID_OFF + call sol_get_return_data +init_vault_create_account: + # acct_size = frame.token_account_data_size + ldxdw r8, [r6 + RM_FM_TOKEN_ACCOUNT_DATA_SIZE_OFF] + # frame.create_account_data.space = acct_size + stxdw [r6 + RM_FM_CREATE_ACCT_SPACE_UOFF], r8 + # acct_size = acct_size + account.STORAGE_OVERHEAD + add64 r8, ACCT_STORAGE_OVERHEAD + # frame.create_account_data.lamports = acct_size * frame.lamports_per_byte + ldxdw r9, [r6 + RM_FM_LAMPORTS_PER_BYTE_OFF] + mul64 r8, r9 + stxdw [r6 + RM_FM_CREATE_ACCT_LAMPORTS_UOFF], r8 + # frame.create_account_data.owner = frame.token_program_id + ldxdw r8, [r6 + RM_FM_TOKEN_PROGRAM_ID_OFF] + ldxdw r9, [r8 + PUBKEY_CHUNK_0_OFF] + stxdw [r6 + RM_FM_CREATE_ACCT_OWNER_CHUNK_0_UOFF], r9 + ldxdw r9, [r8 + PUBKEY_CHUNK_1_OFF] + stxdw [r6 + RM_FM_CREATE_ACCT_OWNER_CHUNK_1_UOFF], r9 + ldxdw r9, [r8 + PUBKEY_CHUNK_2_OFF] + stxdw [r6 + RM_FM_CREATE_ACCT_OWNER_CHUNK_2_UOFF], r9 + ldxdw r9, [r8 + PUBKEY_CHUNK_3_OFF] + stxdw [r6 + RM_FM_CREATE_ACCT_OWNER_CHUNK_3_UOFF], r9 + # frame.cpi[0].info.is_signer = true + # frame.cpi[0].info.is_writable = true + sth [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_IS_SIGNER_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi[0].meta.is_writable = true + # frame.cpi[0].meta.is_signer = true + sth [r6 + RM_FM_CPI_IDX_0_ACCT_META_IS_WRITABLE_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi[1].info.is_signer = true + # frame.cpi[1].info.is_writable = true + sth [r6 + RM_FM_CPI_IDX_1_ACCT_INFO_IS_SIGNER_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi[1].meta.is_writable = true + # frame.cpi[1].meta.is_signer = true + sth [r6 + RM_FM_CPI_IDX_1_ACCT_META_IS_WRITABLE_UOFF], CPI_WRITABLE_SIGNER + # frame.cpi[0].info.data_len = data.LEN_ZERO + mov64 r9, DATA_LEN_ZERO + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_DATA_LEN_UOFF], r9 + # frame.cpi[1].info.data_len = data.LEN_ZERO + stxdw [r6 + RM_FM_CPI_IDX_1_ACCT_INFO_DATA_LEN_UOFF], r9 + # frame.cpi[0].meta.pubkey = &input.user.address + # frame.cpi[0].info.key = &input.user.address + ldxdw r8, [r6 + RM_FM_INPUT_OFF] + add64 r8, IB_USER_PUBKEY_OFF + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_META_PUBKEY_UOFF], r8 + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_KEY_UOFF], r8 + # frame.cpi[0].info.owner = &input.user.owner + add64 r8, IB_ADDRESS_TO_OWNER_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_OWNER_UOFF], r8 + # frame.cpi[0].info.lamports = &input.user.lamports + add64 r8, IB_OWNER_TO_LAMPORTS_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_LAMPORTS_UOFF], r8 + # frame.cpi[0].info.data = &input.user.data + add64 r8, IB_LAMPORTS_TO_DATA_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_0_ACCT_INFO_DATA_UOFF], r8 + # frame.cpi[1].meta.pubkey = &acct.address + # frame.cpi[1].info.key = &acct.address + mov64 r8, r7 + add64 r8, ACCT_ADDRESS_OFF + stxdw [r6 + RM_FM_CPI_IDX_1_ACCT_META_PUBKEY_UOFF], r8 + stxdw [r6 + RM_FM_CPI_IDX_1_ACCT_INFO_KEY_UOFF], r8 + # frame.cpi[1].info.owner = &acct.owner + add64 r8, IB_ADDRESS_TO_OWNER_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_1_ACCT_INFO_OWNER_UOFF], r8 + # frame.cpi[1].info.lamports = &acct.lamports + add64 r8, IB_OWNER_TO_LAMPORTS_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_1_ACCT_INFO_LAMPORTS_UOFF], r8 + # frame.cpi[1].info.data = &acct.data + add64 r8, IB_LAMPORTS_TO_DATA_REL_OFF_IMM + stxdw [r6 + RM_FM_CPI_IDX_1_ACCT_INFO_DATA_UOFF], r8 + # frame.sol_instruction.program_id = frame.system_program_id + ldxdw r8, [r6 + RM_FM_SYSTEM_PROGRAM_ID_OFF] + stxdw [r6 + RM_FM_SOL_INSN_PROGRAM_ID_UOFF], r8 + # frame.sol_instruction.data = &frame.create_account_data + mov64 r8, r6 + add64 r8, RM_FM_CREATE_ACCT_DATA_OFF + stxdw [r6 + RM_FM_SOL_INSN_DATA_UOFF], r8 + # frame.sol_instruction.accounts = &frame.cpi.account_metas + add64 r8, RM_FM_CREATE_ACCT_DATA_TO_CPI_ACCT_METAS_REL_OFF_IMM + stxdw [r6 + RM_FM_SOL_INSN_ACCOUNTS_UOFF], r8 + # frame.sol_instruction.account_len = register_misc.CREATE_ACCOUNT_N_ACCOUNTS + mov64 r8, RM_MISC_CREATE_ACCOUNT_N_ACCOUNTS + stxdw [r6 + RM_FM_SOL_INSN_ACCOUNT_LEN_UOFF], r8 + # frame.sol_instruction.data_len = CreateAccountData.size + mov64 r8, SIZE_OF_CREATE_ACCOUNT_DATA + stxdw [r6 + RM_FM_SOL_INSN_DATA_LEN_UOFF], r8 + # syscall.instruction = &frame.sol_instruction + mov64 r1, r6 + add64 r1, RM_FM_SOL_INSN_OFF + # syscall.account_infos = &frame.cpi.account_infos + mov64 r2, r6 + 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 + mov64 r4, r6 + add64 r4, RM_FM_SIGNERS_SEEDS_ADDR_UOFF + # syscall.seeds_len = register_misc.N_PDA_SIGNERS + mov64 r5, RM_MISC_N_PDA_SIGNERS + call sol_invoke_signed_c exit init_vault_invalid_pda: # if frame.vault_index == register_misc.VAULT_INDEX_BASE diff --git a/program/src/dropset/market/register.s b/program/src/dropset/market/register.s index bed72d7..3ed1f3c 100644 --- a/program/src/dropset/market/register.s +++ b/program/src/dropset/market/register.s @@ -24,47 +24,57 @@ # Stack frame for REGISTER-MARKET. # ------------------------------------------------------------------------- -# Pointer to owning token program address. -.equ RM_FM_TOKEN_PROGRAM_ID_OFF, -472 -.equ RM_FM_INPUT_OFF, -464 # Saved input buffer pointer. -.equ RM_FM_INPUT_SHIFTED_OFF, -456 # Saved input_shifted pointer. -.equ RM_FM_PDA_SEEDS_OFF, -448 # Signer seeds offset. +.equ RM_FM_TOKEN_PROGRAM_ID_OFF, -552 # Pointer to token program address. +.equ RM_FM_PROGRAM_ID_OFF, -544 # Pointer to program ID in input buffer. +.equ RM_FM_INPUT_OFF, -536 # Saved input buffer pointer. +.equ RM_FM_INPUT_SHIFTED_OFF, -528 # Saved input_shifted pointer. +.equ RM_FM_LAMPORTS_PER_BYTE_OFF, -520 # From Rent sysvar. +# Return value from GetAccountDataSize CPI, to check token account data size at runtime. +.equ RM_FM_TOKEN_ACCOUNT_DATA_SIZE_OFF, -512 +# Pointer to mint account for vault initialization. +.equ RM_FM_MINT_OFF, -504 +.equ RM_FM_PDA_SEEDS_OFF, -496 # Signer seeds offset. .equ RM_FM_PDA_SEEDS_N_SEEDS, 3 # Number of signer seeds. -.equ RM_FM_PDA_SEEDS_IDX_0_ADDR_OFF, -448 # Idx 0 signer seed address. -.equ RM_FM_PDA_SEEDS_IDX_0_LEN_OFF, -440 # Idx 0 signer seed length. -.equ RM_FM_PDA_SEEDS_IDX_1_ADDR_OFF, -432 # Idx 1 signer seed address. -.equ RM_FM_PDA_SEEDS_IDX_1_LEN_OFF, -424 # Idx 1 signer seed length. -.equ RM_FM_PDA_SEEDS_IDX_2_ADDR_OFF, -416 # Idx 2 signer seed address. -.equ RM_FM_PDA_SEEDS_IDX_2_LEN_OFF, -408 # Idx 2 signer seed length. -.equ RM_FM_PDA_OFF, -400 # PDA address. -.equ RM_FM_PDA_CHUNK_0_OFF, -400 # PDA address (chunk 0). -.equ RM_FM_PDA_CHUNK_1_OFF, -392 # PDA address (chunk 1). -.equ RM_FM_PDA_CHUNK_2_OFF, -384 # PDA address (chunk 2). -.equ RM_FM_PDA_CHUNK_3_OFF, -376 # PDA address (chunk 3). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_OFF, -368 # System Program pubkey. +.equ RM_FM_PDA_SEEDS_IDX_0_ADDR_OFF, -496 # Idx 0 signer seed address. +.equ RM_FM_PDA_SEEDS_IDX_0_LEN_OFF, -488 # Idx 0 signer seed length. +.equ RM_FM_PDA_SEEDS_IDX_1_ADDR_OFF, -480 # Idx 1 signer seed address. +.equ RM_FM_PDA_SEEDS_IDX_1_LEN_OFF, -472 # Idx 1 signer seed length. +.equ RM_FM_PDA_SEEDS_IDX_2_ADDR_OFF, -464 # Idx 2 signer seed address. +.equ RM_FM_PDA_SEEDS_IDX_2_LEN_OFF, -456 # Idx 2 signer seed length. +.equ RM_FM_PDA_OFF, -448 # PDA address. +.equ RM_FM_PDA_CHUNK_0_OFF, -448 # PDA address (chunk 0). +.equ RM_FM_PDA_CHUNK_1_OFF, -440 # PDA address (chunk 1). +.equ RM_FM_PDA_CHUNK_2_OFF, -432 # PDA address (chunk 2). +.equ RM_FM_PDA_CHUNK_3_OFF, -424 # PDA address (chunk 3). +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_OFF, -416 # System Program pubkey. # System Program pubkey (chunk 0). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_0_OFF, -368 +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_0_OFF, -416 # System Program pubkey (chunk 1). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_1_OFF, -360 +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_1_OFF, -408 # System Program pubkey (chunk 2). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_2_OFF, -352 +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_2_OFF, -400 # System Program pubkey (chunk 3). -.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_3_OFF, -344 -.equ RM_FM_CREATE_ACCT_DATA_OFF, -336 # CreateAccount instruction data. +.equ RM_FM_SYSTEM_PROGRAM_PUBKEY_CHUNK_3_OFF, -392 +.equ RM_FM_SYSTEM_PROGRAM_ID_OFF, -384 # System Program ID in input buffer. +# Get return data program ID for CPI calls. +.equ RM_FM_GET_RETURN_DATA_PROGRAM_ID_OFF, -376 +.equ RM_FM_CREATE_ACCT_DATA_OFF, -344 # CreateAccount instruction data. # Lamports field within CreateAccount instruction data. -.equ RM_FM_CREATE_ACCT_LAMPORTS_UOFF, -332 +.equ RM_FM_CREATE_ACCT_LAMPORTS_UOFF, -340 # Space field within CreateAccount instruction data. -.equ RM_FM_CREATE_ACCT_SPACE_UOFF, -324 +.equ RM_FM_CREATE_ACCT_SPACE_UOFF, -332 # Owner field within CreateAccount instruction data. -.equ RM_FM_CREATE_ACCT_OWNER_UOFF, -316 +.equ RM_FM_CREATE_ACCT_OWNER_UOFF, -324 # Owner field within CreateAccount instruction data (chunk 0). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_0_UOFF, -316 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_0_UOFF, -324 # Owner field within CreateAccount instruction data (chunk 1). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_1_UOFF, -308 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_1_UOFF, -316 # Owner field within CreateAccount instruction data (chunk 2). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_2_UOFF, -300 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_2_UOFF, -308 # Owner field within CreateAccount instruction data (chunk 3). -.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_3_UOFF, -292 +.equ RM_FM_CREATE_ACCT_OWNER_CHUNK_3_UOFF, -300 +# GetAccountDataSize CPI instruction data. +.equ RM_FM_GET_ACCOUNT_DATA_SIZE_DATA_UOFF, -288 .equ RM_FM_CPI_N_ACCOUNTS, 3 # Number of CPI accounts. .equ RM_FM_CPI_SOL_ACCT_INFO_OFF, -280 # Start of SolAccountInfo vector. .equ RM_FM_CPI_SOL_ACCT_META_OFF, -112 # Start of SolAccountMeta vector. @@ -144,11 +154,11 @@ # Whether the current token program is Token 2022. .equ RM_FM_TOKEN_PROGRAM_IS_2022_UOFF, -6 # From pda_seeds to sol_instruction. -.equ RM_FM_PDA_SEEDS_TO_SOL_INSN_REL_OFF_IMM, 400 +.equ RM_FM_PDA_SEEDS_TO_SOL_INSN_REL_OFF_IMM, 448 # From pda to signers_seeds. -.equ RM_FM_PDA_TO_SIGNERS_SEEDS_REL_OFF_IMM, 336 +.equ RM_FM_PDA_TO_SIGNERS_SEEDS_REL_OFF_IMM, 384 # From create_account_data to CPI account metas. -.equ RM_FM_CREATE_ACCT_DATA_TO_CPI_ACCT_METAS_REL_OFF_IMM, 224 +.equ RM_FM_CREATE_ACCT_DATA_TO_CPI_ACCT_METAS_REL_OFF_IMM, 232 # ------------------------------------------------------------------------- # Miscellaneous market registration constants. @@ -204,6 +214,10 @@ register_market: # if insn_len != RegisterMarketData.LEN # return ErrorCode::InvalidInstructionLength jne r4, REGISTER_MARKET_DATA_LEN, e_invalid_instruction_length + # frame.program_id = &insn.program_id + mov64 r4, r2 + add64 r4, REGISTER_MARKET_DATA_LEN + stxdw [r10 + RM_FM_PROGRAM_ID_OFF], r4 # if input.user.data_len != data.DATA_LEN_ZERO # return ErrorCode::UserHasData ldxdw r9, [r1 + IB_USER_DATA_LEN_OFF] @@ -258,10 +272,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 + # frame.system_program_id = &acct.address mov64 r7, r9 add64 r7, ACCT_ADDRESS_OFF - stxdw [r10 + RM_FM_SOL_INSN_PROGRAM_ID_UOFF], r7 + stxdw [r10 + RM_FM_SYSTEM_PROGRAM_ID_OFF], r7 # system_program_padded_data_len = acct.padded_data_len ldxdw r7, [r9 + ACCT_DATA_LEN_OFF] add64 r7, DATA_LEN_MAX_PAD @@ -363,8 +377,20 @@ register_market_base_vault: # 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 + # return ErrorCode::BaseVaultIsDuplicate + ldxb r7, [r9 + ACCT_DUPLICATE_OFF] + jne r7, ACCT_NON_DUP_MARKER, e_base_vault_is_duplicate + # if acct.data_len != data.LEN_ZERO + # return ErrorCode::BaseVaultHasData + ldxdw r7, [r9 + ACCT_DATA_LEN_OFF] + jne r7, DATA_LEN_ZERO, e_base_vault_has_data # frame.vault_index = register_misc.VAULT_INDEX_BASE stb [r10 + RM_FM_VAULT_INDEX_UOFF], RM_MISC_VAULT_INDEX_BASE + # frame.mint = &input.base_mint + mov64 r7, r8 + add64 r7, RM_MISC_BASE_DUPLICATE_OFF + stxdw [r10 + RM_FM_MINT_OFF], r7 # result = INIT-VAULT(acct, frame) mov64 r1, r9 mov64 r2, r10 @@ -374,12 +400,7 @@ register_market_base_vault: jeq r0, RETURN_SUCCESS, register_market_quote_token_program exit register_market_quote_token_program: - # base_vault_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_vault_padded_data_len + EmptyAccount.size - add64 r9, r7 + # acct += EmptyAccount.size add64 r9, SIZE_OF_EMPTY_ACCOUNT # if acct.duplicate == account.NON_DUP_MARKER ldxb r7, [r9 + ACCT_DUPLICATE_OFF] @@ -466,8 +487,20 @@ register_market_base_vault_dup: # acct += u64.size add64 r9, SIZE_OF_U64 register_market_done_token_programs: + # if acct.duplicate != account.NON_DUP_MARKER + # return ErrorCode::QuoteVaultIsDuplicate + ldxb r7, [r9 + ACCT_DUPLICATE_OFF] + jne r7, ACCT_NON_DUP_MARKER, e_quote_vault_is_duplicate + # if acct.data_len != data.LEN_ZERO + # return ErrorCode::QuoteVaultHasData + ldxdw r7, [r9 + ACCT_DATA_LEN_OFF] + jne r7, DATA_LEN_ZERO, e_quote_vault_has_data # frame.vault_index = register_misc.VAULT_INDEX_QUOTE stb [r10 + RM_FM_VAULT_INDEX_UOFF], RM_MISC_VAULT_INDEX_QUOTE + # frame.mint = &input_shifted.quote_mint + mov64 r7, r6 + add64 r7, RM_MISC_QUOTE_DUPLICATE_OFF + stxdw [r10 + RM_FM_MINT_OFF], r7 # INIT-VAULT(acct, frame) mov64 r1, r9 mov64 r2, r10 diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 897816e..5cd71ac 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -1,8 +1,10 @@ [dependencies] dropset-interface = {path = "../interface"} mollusk-svm = {workspace = true} +mollusk-svm-programs-token = {workspace = true} solana-account = {workspace = true} solana-sdk = {workspace = true} +spl-token-interface = {workspace = true} heck.workspace = true [package] diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 3867c03..b525aca 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -51,6 +51,8 @@ pub struct TestSetup { pub fn setup() -> TestSetup { let mut setup = setup_program(DEFAULT_PROGRAM); setup.mollusk.sysvars.rent.exemption_threshold = 1.0; + mollusk_svm_programs_token::token::add_program(&mut setup.mollusk); + mollusk_svm_programs_token::token2022::add_program(&mut setup.mollusk); setup } diff --git a/tests/tests/cases/register_market.rs b/tests/tests/cases/register_market.rs index 9e364a8..e050006 100644 --- a/tests/tests/cases/register_market.rs +++ b/tests/tests/cases/register_market.rs @@ -63,6 +63,12 @@ test_cases! { DupQuoteTokenProgramNotQuoteMintOwnerChunk1, DupQuoteTokenProgramNotQuoteMintOwnerChunk2, DupQuoteTokenProgramNotQuoteMintOwnerChunk3, + BaseVaultIsDuplicate, + BaseVaultHasData, + QuoteVaultIsDuplicateDup, + QuoteVaultIsDuplicateNonDup, + QuoteVaultHasDataDup, + QuoteVaultHasDataNonDup, InvalidBaseVaultPubkeyChunk0, InvalidBaseVaultPubkeyChunk1, InvalidBaseVaultPubkeyChunk2, @@ -105,8 +111,67 @@ fn into_metas_and_accounts( (metas, paired) } -const USER_LAMPORTS: u64 = 1_000_000; +const USER_LAMPORTS: u64 = 10_000_000; const MARKET_HEADER_SIZE: usize = size_of::(); +const TOKEN_ACCOUNT_SIZE: usize = 165; + +macro_rules! check_vault { + ($errors:expr, $label:expr, $vault:expr, $expected_owner:expr, $rent:expr) => {{ + let vault = $vault; + let expected_owner = $expected_owner; + if vault.owner != *expected_owner { + $errors.push(format!( + "{} owner: expected {:?}, got {:?}", + $label, expected_owner, vault.owner + )); + } + if vault.data.len() != TOKEN_ACCOUNT_SIZE { + $errors.push(format!( + "{} data len: expected {}, got {}", + $label, + TOKEN_ACCOUNT_SIZE, + vault.data.len() + )); + } + if !$rent.is_exempt(vault.lamports, vault.data.len()) { + $errors.push(format!( + "{} not rent exempt: {} lamports for {} bytes", + $label, + vault.lamports, + vault.data.len() + )); + } + }}; +} + +fn default_mint() -> spl_token_interface::state::Mint { + spl_token_interface::state::Mint { + is_initialized: true, + ..Default::default() + } +} + +fn mint_account(owner: Pubkey) -> Account { + if owner == Pubkey::from(TOKEN_PROGRAM_ID) { + mollusk_svm_programs_token::token::create_account_for_mint(default_mint()) + } else if owner == Pubkey::from(TOKEN_2022_PROGRAM_ID) { + mollusk_svm_programs_token::token2022::create_account_for_mint(default_mint()) + } else { + let mut acct = Account::default(); + acct.owner = owner; + acct + } +} + +fn token_program_account(id: Pubkey) -> Account { + if id == Pubkey::from(TOKEN_PROGRAM_ID) { + mollusk_svm_programs_token::token::account() + } else if id == Pubkey::from(TOKEN_2022_PROGRAM_ID) { + mollusk_svm_programs_token::token2022::account() + } else { + Account::default() + } +} /// Build valid accounts that pass all checks for a successful CreateAccount CPI. /// When `base_token_program` and `quote_token_program` share the same key, @@ -123,7 +188,7 @@ fn happy_path_accounts( let pda = keys[RegisterMarketAccounts::Market as usize]; let (quote_vault_pda, _) = Pubkey::find_program_address( &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], - "e_token_program, + &setup.program_id, ); keys[RegisterMarketAccounts::QuoteVault as usize] = quote_vault_pda; @@ -231,7 +296,7 @@ fn token_program_base_accounts( ); let (_vault_pda, vault_bump) = Pubkey::find_program_address( &[pda.as_ref(), &[VAULT_INDEX_BASE as u8]], - &base_token_program, + &setup.program_id, ); if vault_bump != u8::MAX { continue; @@ -239,7 +304,7 @@ fn token_program_base_accounts( if require_quote_vault_bump { let (_quote_vault, quote_bump) = Pubkey::find_program_address( &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], - "e_token_program, + &setup.program_id, ); if quote_bump != u8::MAX { continue; @@ -263,16 +328,20 @@ fn token_program_base_accounts( 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; + accounts[RegisterMarketAccounts::BaseMint as usize] = mint_account(base_token_program); + accounts[RegisterMarketAccounts::QuoteMint as usize] = mint_account(quote_token_program); keys[RegisterMarketAccounts::BaseTokenProgram as usize] = base_token_program; + accounts[RegisterMarketAccounts::BaseTokenProgram as usize] = + token_program_account(base_token_program); keys[RegisterMarketAccounts::QuoteTokenProgram as usize] = quote_token_program; + accounts[RegisterMarketAccounts::QuoteTokenProgram as usize] = + token_program_account(quote_token_program); // Derive base vault PDA from market address and vault index. let (base_vault_pda, _) = Pubkey::find_program_address( &[pda.as_ref(), &[VAULT_INDEX_BASE as u8]], - &base_token_program, + &setup.program_id, ); keys[RegisterMarketAccounts::BaseVault as usize] = base_vault_pda; @@ -288,8 +357,12 @@ fn writable_metas_and_accounts( .enumerate() .map(|(i, k)| { let writable = i == RegisterMarketAccounts::User as usize - || i == RegisterMarketAccounts::Market as usize; - let signer = i == RegisterMarketAccounts::User as usize; + || i == RegisterMarketAccounts::Market as usize + || i == RegisterMarketAccounts::BaseVault as usize + || i == RegisterMarketAccounts::QuoteVault as usize; + let signer = i == RegisterMarketAccounts::User as usize + || i == RegisterMarketAccounts::BaseVault as usize + || i == RegisterMarketAccounts::QuoteVault as usize; if writable { AccountMeta::new(*k, signer) } else { @@ -338,7 +411,7 @@ fn quote_vault_mismatch_accounts( let pda = keys[RegisterMarketAccounts::Market as usize]; let (mut quote_vault_pda, _) = Pubkey::find_program_address( &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], - "e_token_program, + &setup.program_id, ); quote_vault_pda.as_mut()[corrupt_byte] ^= 0xFF; keys[RegisterMarketAccounts::QuoteVault as usize] = quote_vault_pda; @@ -1063,6 +1136,155 @@ impl TestCase for Case { ) } // Verifies: REGISTER-MARKET + Self::BaseVaultIsDuplicate => { + let base_token_program = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = token_program_base_accounts( + setup, + base_token_program, + base_token_program, + false, + ); + // Base vault shares key with User, causing the runtime + // to serialize it as a duplicate. + keys[RegisterMarketAccounts::BaseVault as usize] = + keys[RegisterMarketAccounts::User as usize]; + let (metas, accounts) = writable_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseVaultIsDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::BaseVaultHasData => { + let base_token_program = Pubkey::from(TOKEN_PROGRAM_ID); + let (keys, mut accounts) = token_program_base_accounts( + setup, + base_token_program, + base_token_program, + false, + ); + accounts[RegisterMarketAccounts::BaseVault as usize].data = vec![0u8; 32]; + let (metas, accounts) = writable_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::BaseVaultHasData), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteVaultIsDuplicateDup => { + let base_token_program = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, accounts) = token_program_base_accounts( + setup, + base_token_program, + base_token_program, + true, + ); + let pda = keys[RegisterMarketAccounts::Market as usize]; + let (quote_vault_pda, _) = Pubkey::find_program_address( + &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], + &setup.program_id, + ); + keys[RegisterMarketAccounts::QuoteVault as usize] = quote_vault_pda; + // Quote vault shares key with User, causing the runtime + // to serialize it as a duplicate. + keys[RegisterMarketAccounts::QuoteVault as usize] = + keys[RegisterMarketAccounts::User as usize]; + let (metas, accounts) = writable_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteVaultIsDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteVaultIsDuplicateNonDup => { + let base_token_program = Pubkey::from(TOKEN_PROGRAM_ID); + let quote_token_program = Pubkey::from(TOKEN_2022_PROGRAM_ID); + let (mut keys, accounts) = token_program_base_accounts( + setup, + base_token_program, + quote_token_program, + true, + ); + let pda = keys[RegisterMarketAccounts::Market as usize]; + let (quote_vault_pda, _) = Pubkey::find_program_address( + &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], + &setup.program_id, + ); + keys[RegisterMarketAccounts::QuoteVault as usize] = quote_vault_pda; + // Quote vault shares key with User, causing the runtime + // to serialize it as a duplicate. + keys[RegisterMarketAccounts::QuoteVault as usize] = + keys[RegisterMarketAccounts::User as usize]; + let (metas, accounts) = writable_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteVaultIsDuplicate), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteVaultHasDataDup => { + let base_token_program = Pubkey::from(TOKEN_PROGRAM_ID); + let (mut keys, mut accounts) = token_program_base_accounts( + setup, + base_token_program, + base_token_program, + true, + ); + let pda = keys[RegisterMarketAccounts::Market as usize]; + let (quote_vault_pda, _) = Pubkey::find_program_address( + &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], + &setup.program_id, + ); + keys[RegisterMarketAccounts::QuoteVault as usize] = quote_vault_pda; + accounts[RegisterMarketAccounts::QuoteVault as usize].data = vec![0u8; 32]; + let (metas, accounts) = writable_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteVaultHasData), + ) + } + // Verifies: REGISTER-MARKET + Self::QuoteVaultHasDataNonDup => { + let base_token_program = Pubkey::from(TOKEN_PROGRAM_ID); + let quote_token_program = Pubkey::from(TOKEN_2022_PROGRAM_ID); + let (mut keys, mut accounts) = token_program_base_accounts( + setup, + base_token_program, + quote_token_program, + true, + ); + let pda = keys[RegisterMarketAccounts::Market as usize]; + let (quote_vault_pda, _) = Pubkey::find_program_address( + &[pda.as_ref(), &[VAULT_INDEX_QUOTE as u8]], + &setup.program_id, + ); + keys[RegisterMarketAccounts::QuoteVault as usize] = quote_vault_pda; + accounts[RegisterMarketAccounts::QuoteVault as usize].data = vec![0u8; 32]; + let (metas, accounts) = writable_metas_and_accounts(keys, accounts); + check_custom( + setup, + insn, + metas, + accounts, + Some(ErrorCode::QuoteVaultHasData), + ) + } + // Verifies: REGISTER-MARKET // Verifies: INIT-MARKET-PDA // Verifies: INIT-VAULT Self::InvalidBaseVaultPubkeyChunk0 => { @@ -1263,6 +1485,16 @@ impl TestCase for Case { market.data.len() )); } + + let base_vault = &result.resulting_accounts + [RegisterMarketAccounts::BaseVault as usize] + .1; + check_vault!(errors, "base vault", base_vault, &token_program_id, rent); + + let quote_vault = &result.resulting_accounts + [RegisterMarketAccounts::QuoteVault as usize] + .1; + check_vault!(errors, "quote vault", quote_vault, &token_program_id, rent); } other => { errors.push(format!("expected success, got {:?}", other)); @@ -1315,6 +1547,16 @@ impl TestCase for Case { market.data.len() )); } + + let base_vault = &result.resulting_accounts + [RegisterMarketAccounts::BaseVault as usize] + .1; + check_vault!(errors, "base vault", base_vault, &token_program_id, rent); + + let quote_vault = &result.resulting_accounts + [RegisterMarketAccounts::QuoteVault as usize] + .1; + check_vault!(errors, "quote vault", quote_vault, &token_2022_id, rent); } other => { errors.push(format!("expected success, got {:?}", other)); @@ -1366,6 +1608,16 @@ impl TestCase for Case { market.data.len() )); } + + let base_vault = &result.resulting_accounts + [RegisterMarketAccounts::BaseVault as usize] + .1; + check_vault!(errors, "base vault", base_vault, &token_2022_id, rent); + + let quote_vault = &result.resulting_accounts + [RegisterMarketAccounts::QuoteVault as usize] + .1; + check_vault!(errors, "quote vault", quote_vault, &token_2022_id, rent); } other => { errors.push(format!("expected success, got {:?}", other)); @@ -1418,6 +1670,16 @@ impl TestCase for Case { market.data.len() )); } + + let base_vault = &result.resulting_accounts + [RegisterMarketAccounts::BaseVault as usize] + .1; + check_vault!(errors, "base vault", base_vault, &token_2022_id, rent); + + let quote_vault = &result.resulting_accounts + [RegisterMarketAccounts::QuoteVault as usize] + .1; + check_vault!(errors, "quote vault", quote_vault, &token_program_id, rent); } other => { errors.push(format!("expected success, got {:?}", other));