From 78b7a564dede626ccacf710b05e9865978a59b0c Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 29 Jan 2026 17:39:05 +0000 Subject: [PATCH 1/2] sync to latest main --- Cargo.lock | 477 ++++++++++++------ Cargo.toml | 7 + programs/cp-swap/Cargo.toml | 10 +- programs/cp-swap/src/instructions/deposit.rs | 3 +- .../cp-swap/src/instructions/initialize.rs | 29 +- programs/cp-swap/src/instructions/withdraw.rs | 2 + programs/cp-swap/tests/functional_test.rs | 6 +- programs/cp-swap/tests/helpers.rs | 41 +- programs/cp-swap/tests/program.rs | 179 ++++++- programs/cp-swap/tests/program_test.rs | 6 +- 10 files changed, 551 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 785203f..854f4ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,29 @@ dependencies = [ "regex", ] +[[package]] +name = "account-compression" +version = "2.0.0" +dependencies = [ + "aligned-sized", + "anchor-lang", + "bytemuck", + "light-account-checks", + "light-batched-merkle-tree", + "light-bounded-vec", + "light-compressed-account", + "light-concurrent-merkle-tree", + "light-hash-set", + "light-hasher", + "light-indexed-merkle-tree", + "light-merkle-tree-metadata", + "light-zero-copy", + "num-bigint 0.4.6", + "solana-sdk", + "solana-security-txt", + "zerocopy", +] + [[package]] name = "adler2" version = "2.0.1" @@ -126,8 +149,6 @@ dependencies = [ [[package]] name = "aligned-sized" version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48a526ec4434d531d488af59fe866f36b310fe8906691c75dffa664450a3800a" dependencies = [ "proc-macro2", "quote", @@ -231,6 +252,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "anchor-compressed-token" +version = "2.0.0" +dependencies = [ + "account-compression", + "anchor-lang", + "anchor-spl", + "light-compressed-account", + "light-hasher", + "light-heap", + "light-system-program-anchor", + "light-token-interface", + "light-zero-copy", + "pinocchio-pubkey", + "solana-sdk", + "solana-security-txt", + "spl-token 7.0.0", + "spl-token-2022 7.0.0", + "zerocopy", +] + [[package]] name = "anchor-derive-accounts" version = "0.31.1" @@ -315,6 +357,21 @@ dependencies = [ "serde", ] +[[package]] +name = "anchor-spl" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c08cb5d762c0694f74bd02c9a5b04ea53cefc496e2c27b3234acffca5cd076b" +dependencies = [ + "anchor-lang", + "spl-associated-token-account 6.0.0", + "spl-pod", + "spl-token 7.0.0", + "spl-token-2022 6.0.0", + "spl-token-group-interface 0.5.0", + "spl-token-metadata-interface 0.6.0", +] + [[package]] name = "anchor-syn" version = "0.31.1" @@ -935,9 +992,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.53" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -2085,7 +2142,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration 0.6.1", "tokio", "tower-service", @@ -2095,9 +2152,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2444,10 +2501,9 @@ dependencies = [ [[package]] name = "light-account-checks" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0785da22cd4a7667583141ca56c790a5c8afa2b22ad2a08204d78881035524e8" +version = "0.7.0" dependencies = [ + "pinocchio", "solana-account-info", "solana-msg", "solana-program-error", @@ -2474,29 +2530,25 @@ dependencies = [ [[package]] name = "light-array-map" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859dc5b406a8bf0b114f686e6f2e36d0e939bad6f579492a520d309b52fde1f8" +version = "0.2.0" dependencies = [ "tinyvec", ] [[package]] name = "light-batched-merkle-tree" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13cb8bc778065ee71d1990fdc94112e35dc63a5e387a323284a49f40d123d8e0" +version = "0.9.0" dependencies = [ "aligned-sized", "borsh 0.10.4", "light-account-checks", "light-bloom-filter", - "light-compressed-account 0.8.0", + "light-compressed-account", "light-hasher", "light-macros", "light-merkle-tree-metadata", "light-verifier", - "light-zero-copy 0.6.0", + "light-zero-copy", "solana-account-info", "solana-msg", "solana-program-error", @@ -2508,9 +2560,7 @@ dependencies = [ [[package]] name = "light-bloom-filter" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a609e3c9179f0ae8488cc70c5413c86dfd97dad7ad85fee2ad8da2d0a11e61" +version = "0.6.0" dependencies = [ "bitvec", "num-bigint 0.4.6", @@ -2533,9 +2583,7 @@ dependencies = [ [[package]] name = "light-client" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1f3cd013364dbe5c45a9e9a8faee1af30dccb600cd56a41e296ed8d5684768" +version = "0.19.0" dependencies = [ "anchor-lang", "async-trait", @@ -2544,7 +2592,8 @@ dependencies = [ "bs58", "futures", "lazy_static", - "light-compressed-account 0.8.0", + "light-compressed-account", + "light-compressed-token-sdk", "light-compressible", "light-concurrent-merkle-tree", "light-event", @@ -2588,61 +2637,101 @@ dependencies = [ [[package]] name = "light-compressed-account" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058df2733fa6a3e4bda6f162a6c5d41f10fc8c6f6ddb992af1de76b60214e4a6" +version = "0.9.0" dependencies = [ + "anchor-lang", "borsh 0.10.4", + "bytemuck", "light-hasher", "light-macros", + "light-poseidon 0.3.0", "light-program-profiler", - "light-zero-copy 0.5.0", + "light-zero-copy", + "pinocchio", + "solana-msg", + "solana-program-error", + "solana-pubkey", "thiserror 2.0.18", "tinyvec", "zerocopy", ] [[package]] -name = "light-compressed-account" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "768ae5a56d8c9cf315d132b3faa5b067f95b3d6a294c579e82f8f0e0bf29c7cc" +name = "light-compressed-token" +version = "2.1.0" dependencies = [ + "account-compression", + "anchor-compressed-token", "anchor-lang", + "arrayvec", + "bitvec", "borsh 0.10.4", - "bytemuck", + "light-account-checks", + "light-array-map", + "light-compressed-account", + "light-compressible", "light-hasher", + "light-heap", "light-macros", - "light-poseidon 0.3.0", "light-program-profiler", - "light-zero-copy 0.6.0", + "light-system-program-anchor", + "light-token-interface", + "light-zero-copy", + "pinocchio", + "pinocchio-pubkey", + "pinocchio-system", + "pinocchio-token-program", + "solana-pubkey", + "solana-security-txt", + "spl-pod", + "spl-token 7.0.0", + "spl-token-2022 7.0.0", + "tinyvec", + "zerocopy", +] + +[[package]] +name = "light-compressed-token-sdk" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "arrayvec", + "borsh 0.10.4", + "light-account-checks", + "light-compressed-account", + "light-program-profiler", + "light-sdk", + "light-sdk-types", + "light-token-interface", + "light-token-types", + "light-zero-copy", + "solana-account-info", + "solana-cpi", + "solana-instruction", "solana-msg", "solana-program-error", "solana-pubkey", "thiserror 2.0.18", - "tinyvec", - "zerocopy", ] [[package]] name = "light-compressible" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff0f0065beb8d16df587b3ea17082e11dea3f67c98813b4bcc061eecd94561f" +version = "0.4.0" dependencies = [ "aligned-sized", "anchor-lang", "borsh 0.10.4", "bytemuck", "light-account-checks", - "light-compressed-account 0.8.0", + "light-compressed-account", "light-hasher", "light-macros", "light-program-profiler", - "light-sdk-types", - "light-zero-copy 0.6.0", + "light-zero-copy", + "pinocchio", "pinocchio-pubkey", "solana-pubkey", + "solana-rent", "thiserror 2.0.18", "zerocopy", ] @@ -2650,8 +2739,6 @@ dependencies = [ [[package]] name = "light-concurrent-merkle-tree" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db96f47253a0907aaa46dac15cecb27b5510130e48da0b36690dcd2e99a6d558" dependencies = [ "borsh 0.10.4", "light-bounded-vec", @@ -2663,22 +2750,29 @@ dependencies = [ [[package]] name = "light-event" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674c9d85b32a9e8abb90cccdee18e35ae29daa1126fdb81a8a28c0a54802096" +version = "0.4.0" dependencies = [ "borsh 0.10.4", - "light-compressed-account 0.8.0", + "light-compressed-account", + "light-hasher", + "light-zero-copy", + "thiserror 2.0.18", +] + +[[package]] +name = "light-hash-set" +version = "4.0.0" +dependencies = [ "light-hasher", - "light-zero-copy 0.6.0", + "num-bigint 0.4.6", + "num-traits", + "solana-program-error", "thiserror 2.0.18", ] [[package]] name = "light-hasher" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c822662e6e109bac0e132a43fd52a4ef684811245a794e048cf9cda001e934c8" dependencies = [ "ark-bn254 0.5.0", "ark-ff 0.5.0", @@ -2692,11 +2786,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "light-heap" +version = "2.0.0" +dependencies = [ + "anchor-lang", +] + [[package]] name = "light-indexed-array" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f14f984030d86b6f07bd8f5ae04e2c40fcd0c3bdfcc7a291fff1ed59c9e6554" dependencies = [ "light-hasher", "num-bigint 0.4.6", @@ -2707,8 +2806,6 @@ dependencies = [ [[package]] name = "light-indexed-merkle-tree" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0824755289075f28de2820fc7d4ec4e6b9e99d404e033c07338b91cce8c71fb8" dependencies = [ "light-bounded-vec", "light-concurrent-merkle-tree", @@ -2720,11 +2817,39 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "light-instruction-decoder" +version = "0.2.0" +dependencies = [ + "borsh 0.10.4", + "bs58", + "light-compressed-account", + "light-instruction-decoder-derive", + "light-sdk-types", + "light-token-interface", + "serde", + "solana-instruction", + "solana-pubkey", + "solana-signature", + "tabled", +] + +[[package]] +name = "light-instruction-decoder-derive" +version = "0.2.0" +dependencies = [ + "bs58", + "darling", + "heck 0.5.0", + "proc-macro2", + "quote", + "sha2 0.10.9", + "syn 2.0.114", +] + [[package]] name = "light-macros" version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179ac51cadc1d0ca047b4d6265a7cc245ca3affc16a20a2749585aa6464d39c2" dependencies = [ "bs58", "proc-macro2", @@ -2735,14 +2860,12 @@ dependencies = [ [[package]] name = "light-merkle-tree-metadata" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d08edcc194eef61b0f499934ce398122d54ac57505d44480e5f079a4220566" +version = "0.9.0" dependencies = [ "anchor-lang", "borsh 0.10.4", "bytemuck", - "light-compressed-account 0.8.0", + "light-compressed-account", "solana-msg", "solana-program-error", "solana-sysvar", @@ -2753,8 +2876,6 @@ dependencies = [ [[package]] name = "light-merkle-tree-reference" version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d480f62ca32b38a6231bbc5310d693f91d6b5bdcc18bb13c2d9aab7a1c90e8" dependencies = [ "light-hasher", "light-indexed-array", @@ -2809,10 +2930,9 @@ dependencies = [ [[package]] name = "light-program-test" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a981dfbc19c529543ab1dd8d100319b89aac053b81415a681d1474c986218307" +version = "0.19.0" dependencies = [ + "account-compression", "anchor-lang", "async-trait", "base64 0.13.1", @@ -2820,21 +2940,27 @@ dependencies = [ "bs58", "bytemuck", "chrono", + "light-batched-merkle-tree", "light-client", - "light-compressed-account 0.8.0", + "light-compressed-account", + "light-compressed-token", + "light-compressed-token-sdk", "light-compressible", + "light-concurrent-merkle-tree", "light-event", "light-hasher", "light-indexed-array", "light-indexed-merkle-tree", + "light-instruction-decoder", "light-merkle-tree-metadata", "light-merkle-tree-reference", "light-prover-client", + "light-registry", "light-sdk", "light-sdk-types", "light-token", "light-token-interface", - "light-zero-copy 0.6.0", + "light-zero-copy", "litesvm", "log", "num-bigint 0.4.6", @@ -2861,14 +2987,12 @@ dependencies = [ [[package]] name = "light-prover-client" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d8c9b8b6e9d445b9ef27467da592ee231e614282c3c0bd2f30f567eb904845" +version = "6.0.0" dependencies = [ "ark-bn254 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", - "light-compressed-account 0.7.0", + "light-compressed-account", "light-hasher", "light-indexed-array", "light-sparse-merkle-tree", @@ -2883,23 +3007,46 @@ dependencies = [ "tracing", ] +[[package]] +name = "light-registry" +version = "2.1.0" +dependencies = [ + "account-compression", + "aligned-sized", + "anchor-lang", + "borsh 0.10.4", + "light-account-checks", + "light-batched-merkle-tree", + "light-compressible", + "light-macros", + "light-merkle-tree-metadata", + "light-program-profiler", + "light-system-program-anchor", + "light-token-interface", + "light-zero-copy", + "solana-account-info", + "solana-instruction", + "solana-pubkey", + "solana-sdk", + "solana-security-txt", + "spl-pod", +] + [[package]] name = "light-sdk" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dece106ebd0897bd23a12bad040e0999d93b54447d0473739f91b1f83b1d331" +version = "0.19.0" dependencies = [ "anchor-lang", "bincode", "borsh 0.10.4", "light-account-checks", - "light-compressed-account 0.8.0", + "light-compressed-account", "light-compressible", "light-hasher", "light-macros", "light-sdk-macros", "light-sdk-types", - "light-zero-copy 0.6.0", + "light-zero-copy", "num-bigint 0.4.6", "solana-account-info", "solana-clock", @@ -2917,9 +3064,7 @@ dependencies = [ [[package]] name = "light-sdk-macros" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d91992fa08093b1a274b3baed1d8368de794cc2645f9942718e5fe47a27dc2" +version = "0.19.0" dependencies = [ "darling", "light-hasher", @@ -2932,14 +3077,12 @@ dependencies = [ [[package]] name = "light-sdk-types" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b765f0a39428a137b8d449fa60ba147194cdbff08aa0add598c6047fff2cb7d2" +version = "0.19.0" dependencies = [ "anchor-lang", "borsh 0.10.4", "light-account-checks", - "light-compressed-account 0.8.0", + "light-compressed-account", "light-hasher", "light-macros", "solana-msg", @@ -2949,8 +3092,6 @@ dependencies = [ [[package]] name = "light-sparse-merkle-tree" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4251e79b6c63f4946572dcfd7623680ad0f9e0efe1a761a944733333c5645063" dependencies = [ "light-hasher", "light-indexed-array", @@ -2959,18 +3100,29 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "light-system-program-anchor" +version = "2.0.0" +dependencies = [ + "account-compression", + "aligned-sized", + "anchor-lang", + "light-compressed-account", + "light-zero-copy", + "zerocopy", +] + [[package]] name = "light-token" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62907a12a9801200e5f4c03bb7f2dbdd9aa679223a959167c456a06005291d79" +version = "0.4.0" dependencies = [ "anchor-lang", "arrayvec", "borsh 0.10.4", "light-account-checks", "light-batched-merkle-tree", - "light-compressed-account 0.8.0", + "light-compressed-account", + "light-compressed-token-sdk", "light-compressible", "light-macros", "light-program-profiler", @@ -2979,7 +3131,7 @@ dependencies = [ "light-sdk-types", "light-token-interface", "light-token-types", - "light-zero-copy 0.6.0", + "light-zero-copy", "solana-account-info", "solana-cpi", "solana-instruction", @@ -2992,21 +3144,19 @@ dependencies = [ [[package]] name = "light-token-interface" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fb19b8e268a0154a8e13b3a8f6f43fa4928643e2de102d98a90b2af21f482ba" +version = "0.3.0" dependencies = [ "aligned-sized", "anchor-lang", "borsh 0.10.4", "bytemuck", "light-array-map", - "light-compressed-account 0.8.0", + "light-compressed-account", "light-compressible", "light-hasher", "light-macros", "light-program-profiler", - "light-zero-copy 0.6.0", + "light-zero-copy", "pinocchio", "pinocchio-pubkey", "solana-account-info", @@ -3020,14 +3170,12 @@ dependencies = [ [[package]] name = "light-token-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278dddbf18d104f1225c480ca6d7b8710e1f9ff4104f24be70c522ecb6ed1dfc" +version = "0.4.0" dependencies = [ "anchor-lang", "borsh 0.10.4", "light-account-checks", - "light-compressed-account 0.8.0", + "light-compressed-account", "light-macros", "light-sdk-types", "solana-msg", @@ -3036,53 +3184,25 @@ dependencies = [ [[package]] name = "light-verifier" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f35f47736be493b60d8b56ef0c8e94afd6a99efafebb257f62b0b545e9aacab" +version = "8.0.0" dependencies = [ "groth16-solana", - "light-compressed-account 0.8.0", + "light-compressed-account", "thiserror 2.0.18", ] -[[package]] -name = "light-zero-copy" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8862f463792fd60ae8f5dc418150c16213e302e19d54fba0694cf8515be5ff" -dependencies = [ - "light-zero-copy-derive 0.5.0", - "zerocopy", -] - [[package]] name = "light-zero-copy" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5621fb515e14af46148699c0b65334aabe230a1d2cbd06736ccc7a408c8a4af" dependencies = [ - "light-zero-copy-derive 0.6.0", + "light-zero-copy-derive", "solana-program-error", "zerocopy", ] -[[package]] -name = "light-zero-copy-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af086d52100b3cab1f2993b146adc7a69fa6aaa878ae4c19514c77c50304379" -dependencies = [ - "lazy_static", - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "light-zero-copy-derive" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c46425e5c7ab5203ff5c86ae2615b169cca55f9283f5f60f5dd74143be6934" dependencies = [ "lazy_static", "proc-macro2", @@ -3324,9 +3444,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" @@ -3454,9 +3574,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.5.5+3.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" dependencies = [ "cc", ] @@ -3560,8 +3680,6 @@ dependencies = [ [[package]] name = "photon-api" version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e572dba0c255f5b8176f15b9e849330d915a8927804f7f9702d5bbbc70e4a1ad" dependencies = [ "reqwest 0.12.28", "serde", @@ -3610,6 +3728,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" +[[package]] +name = "pinocchio-log" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd11022408f312e6179ece321c1f7dc0d1b2aa7765fddd39b2a7378d65a899e8" + [[package]] name = "pinocchio-pubkey" version = "0.3.0" @@ -3621,6 +3745,35 @@ dependencies = [ "sha2-const-stable", ] +[[package]] +name = "pinocchio-system" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141ed5eafb4ab04568bb0e224e3dc9a9de13c933de4c004e0d1a553498be3a7c" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", +] + +[[package]] +name = "pinocchio-token-interface" +version = "0.0.0" +source = "git+https://github.com/Lightprotocol/token?rev=f7bee9bbc8039c224a88ea76e9ae2edd78e0f9c3#f7bee9bbc8039c224a88ea76e9ae2edd78e0f9c3" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", +] + +[[package]] +name = "pinocchio-token-program" +version = "0.1.0" +source = "git+https://github.com/Lightprotocol/token?rev=f7bee9bbc8039c224a88ea76e9ae2edd78e0f9c3#f7bee9bbc8039c224a88ea76e9ae2edd78e0f9c3" +dependencies = [ + "pinocchio", + "pinocchio-log", + "pinocchio-token-interface", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -3711,9 +3864,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3787,7 +3940,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.36", - "socket2 0.6.1", + "socket2 0.6.2", "thiserror 2.0.18", "tokio", "tracing", @@ -3824,16 +3977,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -4626,9 +4779,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -7871,9 +8024,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", @@ -7886,15 +8039,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -7937,7 +8090,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -8323,9 +8476,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -8887,18 +9040,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" dependencies = [ "proc-macro2", "quote", @@ -8981,9 +9134,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 1a290f6..28220f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,10 @@ codegen-units = 1 opt-level = 3 incremental = false codegen-units = 1 + +[patch.crates-io] +light-client = { path = "../light-protocol/sdk-libs/client" } +light-program-test = { path = "../light-protocol/sdk-libs/program-test" } +light-sdk = { path = "../light-protocol/sdk-libs/sdk" } +light-token = { path = "../light-protocol/sdk-libs/token-sdk" } +light-hasher = { path = "../light-protocol/program-libs/hasher" } diff --git a/programs/cp-swap/Cargo.toml b/programs/cp-swap/Cargo.toml index 7ba71c3..4b63df9 100644 --- a/programs/cp-swap/Cargo.toml +++ b/programs/cp-swap/Cargo.toml @@ -32,9 +32,9 @@ bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"] } arrayref = { version = "0.3.6" } blake3 = { workspace = true } -light-sdk = { version = "0.18.0", features = ["anchor", "anchor-discriminator", "idl-build", "cpi-context"] } -light-token = { version = "0.3.0", features = ["anchor", "idl-build"] } -light-hasher = "5" +light-sdk = { version = ">=0.18.0", features = ["anchor", "anchor-discriminator", "idl-build", "cpi-context"] } +light-token = { version = ">=0.3.0", features = ["anchor", "idl-build"] } +light-hasher = ">=5" light-anchor-spl = { version = "0.31.1", features = ["idl-build", "memo"] } solana-account-info = "2.3" solana-program = "2.2" @@ -48,8 +48,8 @@ quickcheck = "1.0.3" proptest = "1.0" rand = "0.9.0" -light-program-test = { version = "0.18.0" } -light-client = { version = "0.18.0" } +light-program-test = { version = ">=0.18.0", features = ["devenv"] } +light-client = { version = ">=0.18.0" } tokio = { version = "1", features = ["full"] } spl-token = "7.0.0" solana-keypair = { version = "2.2" } diff --git a/programs/cp-swap/src/instructions/deposit.rs b/programs/cp-swap/src/instructions/deposit.rs index 814038b..e2d983d 100644 --- a/programs/cp-swap/src/instructions/deposit.rs +++ b/programs/cp-swap/src/instructions/deposit.rs @@ -6,8 +6,8 @@ use crate::utils::token::*; use anchor_lang::prelude::*; use light_anchor_spl::token::Token; use light_anchor_spl::token_interface::Token2022; +use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use light_token::instruction::MintToCpi; -use light_anchor_spl::token_interface::{TokenAccount, Mint,TokenInterface}; #[derive(Accounts)] pub struct Deposit<'info> { @@ -207,6 +207,7 @@ pub fn deposit( pool_state.lp_supply = pool_state.lp_supply.checked_add(lp_token_amount).unwrap(); MintToCpi { + fee_payer: Some(ctx.accounts.owner.to_account_info()), mint: ctx.accounts.lp_mint.to_account_info(), destination: ctx.accounts.owner_lp_token.to_account_info(), amount: lp_token_amount, diff --git a/programs/cp-swap/src/instructions/initialize.rs b/programs/cp-swap/src/instructions/initialize.rs index 111bfbc..29baa5e 100644 --- a/programs/cp-swap/src/instructions/initialize.rs +++ b/programs/cp-swap/src/instructions/initialize.rs @@ -83,11 +83,13 @@ pub struct Initialize<'info> { #[account(mut)] #[light_account(init, mint, - mint_signer = lp_mint_signer, - authority = authority, - decimals = 9, - mint_seeds = &[LP_MINT_SIGNER_SEED, self.pool_state.to_account_info().key.as_ref(), &[params.lp_mint_signer_bump]], - authority_seeds = &[crate::AUTH_SEED.as_bytes(), &[params.authority_bump]] + mint::signer = lp_mint_signer, + mint::authority = authority, + mint::decimals = 9, + mint::seeds = &[LP_MINT_SIGNER_SEED, self.pool_state.to_account_info().key.as_ref()], + mint::bump = params.lp_mint_signer_bump, + mint::authority_seeds = &[crate::AUTH_SEED.as_bytes()], + mint::authority_bump = params.authority_bump )] pub lp_mint: UncheckedAccount<'info>, @@ -117,7 +119,7 @@ pub struct Initialize<'info> { ], bump, )] - #[light_account(token, authority = [crate::AUTH_SEED.as_bytes()])] + #[light_account(token, token::authority = [crate::AUTH_SEED.as_bytes()])] pub token_0_vault: UncheckedAccount<'info>, #[account( @@ -129,7 +131,7 @@ pub struct Initialize<'info> { ], bump, )] - #[light_account(token, authority = [crate::AUTH_SEED.as_bytes()])] + #[light_account(token, token::authority = [crate::AUTH_SEED.as_bytes()])] pub token_1_vault: UncheckedAccount<'info>, #[account( @@ -199,7 +201,9 @@ pub fn initialize<'info>( owner: ctx.accounts.authority.key(), } .rent_free( - ctx.accounts.light_token_compressible_config.to_account_info(), + ctx.accounts + .light_token_compressible_config + .to_account_info(), ctx.accounts.light_token_rent_sponsor.to_account_info(), ctx.accounts.system_program.to_account_info(), &crate::ID, @@ -219,7 +223,9 @@ pub fn initialize<'info>( owner: ctx.accounts.authority.key(), } .rent_free( - ctx.accounts.light_token_compressible_config.to_account_info(), + ctx.accounts + .light_token_compressible_config + .to_account_info(), ctx.accounts.light_token_rent_sponsor.to_account_info(), ctx.accounts.system_program.to_account_info(), &crate::ID, @@ -332,7 +338,9 @@ pub fn initialize<'info>( } .idempotent() .rent_free( - ctx.accounts.light_token_compressible_config.to_account_info(), + ctx.accounts + .light_token_compressible_config + .to_account_info(), ctx.accounts.light_token_rent_sponsor.to_account_info(), ctx.accounts.system_program.to_account_info(), ) @@ -340,6 +348,7 @@ pub fn initialize<'info>( // Mint LP tokens to creator MintToCpi { + fee_payer: Some(ctx.accounts.creator.to_account_info()), mint: ctx.accounts.lp_mint.to_account_info(), destination: ctx.accounts.creator_lp_token.to_account_info(), amount: user_lp_amount, diff --git a/programs/cp-swap/src/instructions/withdraw.rs b/programs/cp-swap/src/instructions/withdraw.rs index 484d08b..5cc0b92 100644 --- a/programs/cp-swap/src/instructions/withdraw.rs +++ b/programs/cp-swap/src/instructions/withdraw.rs @@ -183,10 +183,12 @@ pub fn withdraw( } BurnCpi { + fee_payer: Some(ctx.accounts.owner.to_account_info()), source: ctx.accounts.owner_lp_token.to_account_info(), mint: ctx.accounts.lp_mint.to_account_info(), amount: lp_token_amount, authority: ctx.accounts.owner.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), max_top_up: None, } .invoke()?; diff --git a/programs/cp-swap/tests/functional_test.rs b/programs/cp-swap/tests/functional_test.rs index 9b6218c..664eaa5 100644 --- a/programs/cp-swap/tests/functional_test.rs +++ b/programs/cp-swap/tests/functional_test.rs @@ -269,13 +269,13 @@ async fn test_sdk_from_keyed_accounts() { assert_eq!(sdk.token_1_mint, Some(setup.tokens.token_1_mint)); // Check account requirements for each instruction type - let swap_accounts = sdk.get_accounts_to_update(&CpSwapInstruction::Swap); + let swap_accounts = sdk.get_accounts_for_instruction(CpSwapInstruction::Swap); assert_eq!(swap_accounts.len(), 6, "Swap needs 6 accounts: pool, observation, vault0, vault1, mint0, mint1"); - let deposit_accounts = sdk.get_accounts_to_update(&CpSwapInstruction::Deposit); + let deposit_accounts = sdk.get_accounts_for_instruction(CpSwapInstruction::Deposit); assert_eq!(deposit_accounts.len(), 7, "Deposit needs 7 accounts: pool, observation, vault0, vault1, lp_mint, mint0, mint1"); - let withdraw_accounts = sdk.get_accounts_to_update(&CpSwapInstruction::Withdraw); + let withdraw_accounts = sdk.get_accounts_for_instruction(CpSwapInstruction::Withdraw); assert_eq!(withdraw_accounts.len(), 7, "Withdraw needs 7 accounts: pool, observation, vault0, vault1, lp_mint, mint0, mint1"); // Verify program_id method diff --git a/programs/cp-swap/tests/helpers.rs b/programs/cp-swap/tests/helpers.rs index b0e4b32..76451fb 100644 --- a/programs/cp-swap/tests/helpers.rs +++ b/programs/cp-swap/tests/helpers.rs @@ -2,25 +2,24 @@ /// Functional integration test for cp-swap program. /// Tests pool initialization with light-program-test framework. - use anchor_lang::{InstructionData, ToAccountMetas}; +use light_anchor_spl::memo::spl_memo; use light_client::interface::{ get_create_accounts_proof, CreateAccountsProofInput, CreateAccountsProofResult, InitializeRentFreeConfig, }; -use solana_pubkey::pubkey; use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest, TestRpc}, Indexer, ProgramTestConfig, Rpc, }; use light_token::{ constants::CPI_AUTHORITY_PDA, + constants::LIGHT_TOKEN_PROGRAM_ID, instruction::{ find_mint_address, get_associated_token_address_and_bump, CreateAssociatedTokenAccount, CreateMint, CreateMintParams, MintTo, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR, }, - constants::LIGHT_TOKEN_PROGRAM_ID, }; use raydium_cp_swap::{ instructions::initialize::LP_MINT_SIGNER_SEED, @@ -29,14 +28,12 @@ use raydium_cp_swap::{ }; use solana_instruction::Instruction; use solana_keypair::Keypair; +use solana_pubkey::pubkey; use solana_pubkey::Pubkey; -use solana_signer::Signer; use solana_sdk::{program_pack::Pack, signature::SeedDerivable}; -use light_anchor_spl::memo::spl_memo; +use solana_signer::Signer; use spl_token_2022; - - // ============================================================================ // Constants // ============================================================================ @@ -88,8 +85,7 @@ pub struct TokenSetup { /// Initialize the test environment with LightProgramTest and compression config. pub async fn setup_test_environment(program_id: Pubkey) -> TestEnv { - let mut config = - ProgramTestConfig::new_v2(true, Some(vec![("raydium_cp_swap", program_id)])); + let mut config = ProgramTestConfig::new_v2(true, Some(vec![("raydium_cp_swap", program_id)])); config = config.with_light_protocol_events(); let mut rpc = LightProgramTest::new(config).await.unwrap(); @@ -197,6 +193,7 @@ pub async fn setup_create_mint( for (idx, (amount, _)) in recipients.iter().enumerate() { if *amount > 0 { let mint_instruction = MintTo { + fee_payer: Some(payer.pubkey()), mint, destination: ata_pubkeys[idx], amount: *amount, @@ -430,7 +427,7 @@ pub async fn get_pool_create_accounts_proof( vec![ CreateAccountsProofInput::pda(pdas.pool_state), CreateAccountsProofInput::pda(pdas.observation_state), - CreateAccountsProofInput::mint(pdas.lp_mint_signer), + CreateAccountsProofInput::mint_from_signer(pdas.lp_mint_signer), ], ) .await @@ -696,7 +693,11 @@ pub async fn assert_onchain_closed(rpc: &mut LightProgramTest, pda: &Pubkey) { } /// Assert all pool accounts exist on-chain (hot or decompressed state). -pub async fn assert_pool_accounts_exist(rpc: &mut LightProgramTest, pdas: &AmmPdas, tokens: &TokenSetup) { +pub async fn assert_pool_accounts_exist( + rpc: &mut LightProgramTest, + pdas: &AmmPdas, + tokens: &TokenSetup, +) { assert_onchain_exists(rpc, &pdas.pool_state).await; assert_onchain_exists(rpc, &pdas.observation_state).await; assert_onchain_exists(rpc, &pdas.lp_mint).await; @@ -708,7 +709,11 @@ pub async fn assert_pool_accounts_exist(rpc: &mut LightProgramTest, pdas: &AmmPd } /// Assert all pool accounts are compressed (closed on-chain). -pub async fn assert_pool_accounts_compressed(rpc: &mut LightProgramTest, pdas: &AmmPdas, tokens: &TokenSetup) { +pub async fn assert_pool_accounts_compressed( + rpc: &mut LightProgramTest, + pdas: &AmmPdas, + tokens: &TokenSetup, +) { assert_onchain_closed(rpc, &pdas.pool_state).await; assert_onchain_closed(rpc, &pdas.observation_state).await; assert_onchain_closed(rpc, &pdas.lp_mint).await; @@ -834,9 +839,17 @@ pub async fn setup_pool_environment(program_id: Pubkey, amm_config_index: u16) - .unwrap(); let initial_balance = 1_000_000; - let tokens = setup_token_mints(&mut env.rpc, &env.payer, &creator.pubkey(), initial_balance).await; + let tokens = + setup_token_mints(&mut env.rpc, &env.payer, &creator.pubkey(), initial_balance).await; - let amm_config = create_amm_config(&mut env.rpc, &env.payer, &admin, program_id, amm_config_index).await; + let amm_config = create_amm_config( + &mut env.rpc, + &env.payer, + &admin, + program_id, + amm_config_index, + ) + .await; setup_create_pool_fee_account(&mut env.rpc, &env.payer.pubkey()); let pdas = derive_amm_pdas( diff --git a/programs/cp-swap/tests/program.rs b/programs/cp-swap/tests/program.rs index 3af4711..9240405 100644 --- a/programs/cp-swap/tests/program.rs +++ b/programs/cp-swap/tests/program.rs @@ -6,11 +6,10 @@ /// - Parsing pool accounts from AccountInterface /// - Tracking account state (hot/cold) /// - Building AccountSpec for load instructions - use anchor_lang::AnchorDeserialize; use light_client::interface::{ - AccountInterface, AccountSpec, AccountToFetch, ColdContext, LightProgramInterface, PdaSpec, - TokenAccountInterface, + AccountInterface, AccountSpec, AccountToFetch, ColdAccountSpec, ColdContext, + LightProgramInterface, PdaSpec, TokenAccountInterface, }; use light_sdk::LightDiscriminator; use light_token::compat::{CTokenData, TokenData}; @@ -156,7 +155,10 @@ impl CpSwapSdk { } /// Parse observation state from AccountInterface. - fn parse_observation_state(&mut self, interface: AccountInterface) -> Result<(), CpSwapSdkError> { + fn parse_observation_state( + &mut self, + interface: AccountInterface, + ) -> Result<(), CpSwapSdkError> { let pool_state = self .pool_state_pubkey .ok_or(CpSwapSdkError::PoolStateNotParsed)?; @@ -186,7 +188,9 @@ impl CpSwapSdk { /// Vaults are program-owned PDAs, so we convert them to PdaSpec with CTokenData variant. pub fn set_token_vault(&mut self, interface: TokenAccountInterface, is_vault_0: bool) { let key = interface.key; - let pool_state = self.pool_state_pubkey.expect("pool_state must be set before vaults"); + let pool_state = self + .pool_state_pubkey + .expect("pool_state must be set before vaults"); let mint = if is_vault_0 { self.token_0_mint.expect("token_0_mint must be set") } else { @@ -408,7 +412,7 @@ impl CpSwapSdk { impl LightProgramInterface for CpSwapSdk { type Variant = LightAccountVariant; - type Instruction = CpSwapInstruction; + type InstructionKind = CpSwapInstruction; type Error = CpSwapSdkError; fn program_id(&self) -> Pubkey { @@ -448,7 +452,7 @@ impl LightProgramInterface for CpSwapSdk { Ok(sdk) } - fn get_accounts_to_update(&self, ix: &Self::Instruction) -> Vec { + fn get_accounts_for_instruction(&self, kind: Self::InstructionKind) -> Vec { let mut accounts = Vec::new(); // All instructions need pool_state and observation_state @@ -476,7 +480,7 @@ impl LightProgramInterface for CpSwapSdk { } // Deposit and Withdraw also need LP mint - match ix { + match kind { CpSwapInstruction::Deposit | CpSwapInstruction::Withdraw => { if let Some(pubkey) = self.lp_mint { accounts.push(AccountToFetch::mint(pubkey)); @@ -488,8 +492,25 @@ impl LightProgramInterface for CpSwapSdk { accounts } - fn update(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error> { + fn update_with_interfaces(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error> { for account in accounts { + // Handle decompression: if account was cold but now hot, remove from specs + if account.is_hot() { + if self + .pda_specs + .get(&account.key) + .map_or(false, |s| s.is_cold()) + { + self.pda_specs.remove(&account.key); + } + if self + .mint_specs + .get(&account.key) + .map_or(false, |s| s.is_cold()) + { + self.mint_specs.remove(&account.key); + } + } self.parse_account(account)?; } Ok(()) @@ -511,7 +532,7 @@ impl LightProgramInterface for CpSwapSdk { specs } - fn get_specs_for_instruction(&self, ix: &Self::Instruction) -> Vec> { + fn get_specs_for_instruction(&self, kind: Self::InstructionKind) -> Vec> { let mut specs = Vec::new(); // Pool state and observation state needed for all instructions @@ -551,7 +572,7 @@ impl LightProgramInterface for CpSwapSdk { } // LP mint needed for deposit/withdraw - match ix { + match kind { CpSwapInstruction::Deposit | CpSwapInstruction::Withdraw => { if let Some(pubkey) = self.lp_mint { if let Some(spec) = self.mint_specs.get(&pubkey) { @@ -564,4 +585,140 @@ impl LightProgramInterface for CpSwapSdk { specs } + + fn get_compressible_accounts(&self) -> Vec { + let mut accounts = Vec::new(); + if let Some(pubkey) = self.pool_state_pubkey { + accounts.push(pubkey); + } + if let Some(pubkey) = self.observation_key { + accounts.push(pubkey); + } + if let Some(pubkey) = self.token_0_vault { + accounts.push(pubkey); + } + if let Some(pubkey) = self.token_1_vault { + accounts.push(pubkey); + } + if let Some(pubkey) = self.lp_mint { + accounts.push(pubkey); + } + accounts + } + + fn get_compressible_accounts_for_instruction(&self, kind: Self::InstructionKind) -> Vec { + let mut accounts = Vec::new(); + if let Some(pubkey) = self.pool_state_pubkey { + accounts.push(pubkey); + } + if let Some(pubkey) = self.observation_key { + accounts.push(pubkey); + } + if let Some(pubkey) = self.token_0_vault { + accounts.push(pubkey); + } + if let Some(pubkey) = self.token_1_vault { + accounts.push(pubkey); + } + match kind { + CpSwapInstruction::Deposit | CpSwapInstruction::Withdraw => { + if let Some(pubkey) = self.lp_mint { + accounts.push(pubkey); + } + } + CpSwapInstruction::Swap => {} + } + accounts + } + + fn get_cold_specs_for_instruction( + &self, + kind: Self::InstructionKind, + ) -> Vec> { + let mut cold_specs = Vec::new(); + + // Check pool_state + if let Some(pubkey) = self.pool_state_pubkey { + if let Some(spec) = self.pda_specs.get(&pubkey) { + if spec.is_cold() { + if let Some(compressed) = spec.interface.as_compressed_account() { + cold_specs.push(ColdAccountSpec::Pda { + key: pubkey, + compressed: compressed.clone(), + variant: spec.variant.clone(), + program_id: PROGRAM_ID, + }); + } + } + } + } + + // Check observation_state + if let Some(pubkey) = self.observation_key { + if let Some(spec) = self.pda_specs.get(&pubkey) { + if spec.is_cold() { + if let Some(compressed) = spec.interface.as_compressed_account() { + cold_specs.push(ColdAccountSpec::Pda { + key: pubkey, + compressed: compressed.clone(), + variant: spec.variant.clone(), + program_id: PROGRAM_ID, + }); + } + } + } + } + + // Check vaults (stored as PDAs) + for vault_pubkey in [self.token_0_vault, self.token_1_vault].into_iter().flatten() { + if let Some(spec) = self.pda_specs.get(&vault_pubkey) { + if spec.is_cold() { + if let Some(compressed) = spec.interface.as_compressed_account() { + cold_specs.push(ColdAccountSpec::Pda { + key: vault_pubkey, + compressed: compressed.clone(), + variant: spec.variant.clone(), + program_id: PROGRAM_ID, + }); + } + } + } + } + + // Check mints + for mint_pubkey in [self.token_0_mint, self.token_1_mint].into_iter().flatten() { + if let Some(iface) = self.mint_specs.get(&mint_pubkey) { + if iface.is_cold() { + if let Some(compressed) = iface.as_compressed_account() { + cold_specs.push(ColdAccountSpec::Mint { + key: mint_pubkey, + compressed: compressed.clone(), + }); + } + } + } + } + + // LP mint only for deposit/withdraw + match kind { + CpSwapInstruction::Deposit | CpSwapInstruction::Withdraw => { + if let Some(lp_mint) = self.lp_mint { + if let Some(iface) = self.mint_specs.get(&lp_mint) { + if iface.is_cold() { + if let Some(compressed) = iface.as_compressed_account() { + cold_specs.push(ColdAccountSpec::Mint { + key: lp_mint, + compressed: compressed.clone(), + }); + } + } + } + } + } + CpSwapInstruction::Swap => {} + } + + cold_specs + } + } diff --git a/programs/cp-swap/tests/program_test.rs b/programs/cp-swap/tests/program_test.rs index 78a5fda..e303d72 100644 --- a/programs/cp-swap/tests/program_test.rs +++ b/programs/cp-swap/tests/program_test.rs @@ -81,7 +81,7 @@ async fn test_sdk_lifecycle() { .expect("from_keyed_accounts should succeed"); // ==================== PHASE 6: Fetch & Update SDK ==================== - let accounts_to_fetch = sdk.get_accounts_to_update(&CpSwapInstruction::Deposit); + let accounts_to_fetch = sdk.get_accounts_for_instruction(CpSwapInstruction::Deposit); let keyed_accounts = setup .env .rpc @@ -89,11 +89,11 @@ async fn test_sdk_lifecycle() { .await .expect("get_multiple_account_interfaces should succeed"); - sdk.update(&keyed_accounts) + sdk.update_with_interfaces(&keyed_accounts) .expect("sdk.update should succeed"); // ==================== PHASE 7: Build Specs for Load ==================== - let mut all_specs = sdk.get_specs_for_instruction(&CpSwapInstruction::Deposit); + let mut all_specs = sdk.get_specs_for_instruction(CpSwapInstruction::Deposit); // Fetch creator's ATAs (compressed) and add to specs let creator_lp_ata_interface = setup From 40d7a7e7aeedb50f7fb02f9c93b4c90136175f18 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 29 Jan 2026 19:18:57 +0000 Subject: [PATCH 2/2] sync to branch, add spl, t22, combo test cov --- .../instructions/admin/collect_fund_fee.rs | 14 + .../admin/collect_protocol_fee.rs | 14 + programs/cp-swap/src/instructions/deposit.rs | 14 + .../cp-swap/src/instructions/initialize.rs | 16 + .../src/instructions/swap_base_input.rs | 36 +- .../src/instructions/swap_base_output.rs | 22 + programs/cp-swap/src/instructions/withdraw.rs | 14 + programs/cp-swap/src/lib.rs | 54 +- programs/cp-swap/src/utils/token.rs | 21 +- programs/cp-swap/tests/functional_test.rs | 272 ++++++++- programs/cp-swap/tests/helpers.rs | 523 +++++++++++++++++- programs/cp-swap/tests/program_test.rs | 20 +- 12 files changed, 974 insertions(+), 46 deletions(-) diff --git a/programs/cp-swap/src/instructions/admin/collect_fund_fee.rs b/programs/cp-swap/src/instructions/admin/collect_fund_fee.rs index 068d806..77be19e 100644 --- a/programs/cp-swap/src/instructions/admin/collect_fund_fee.rs +++ b/programs/cp-swap/src/instructions/admin/collect_fund_fee.rs @@ -73,12 +73,22 @@ pub struct CollectFundFee<'info> { /// CHECK: light_token CPI authority. pub light_token_cpi_authority: AccountInfo<'info>, + + /// Optional SPL interface PDA for token_0 (only needed if token_0 is SPL) + #[account(mut)] + pub spl_interface_pda_0: Option>, + + /// Optional SPL interface PDA for token_1 (only needed if token_1 is SPL) + #[account(mut)] + pub spl_interface_pda_1: Option>, } pub fn collect_fund_fee( ctx: Context, amount_0_requested: u64, amount_1_requested: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { let amount_0: u64; let amount_1: u64; @@ -109,6 +119,8 @@ pub fn collect_fund_fee( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, )?; transfer_from_pool_vault_to_user( @@ -126,6 +138,8 @@ pub fn collect_fund_fee( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, )?; Ok(()) diff --git a/programs/cp-swap/src/instructions/admin/collect_protocol_fee.rs b/programs/cp-swap/src/instructions/admin/collect_protocol_fee.rs index 830be27..aaac2b5 100644 --- a/programs/cp-swap/src/instructions/admin/collect_protocol_fee.rs +++ b/programs/cp-swap/src/instructions/admin/collect_protocol_fee.rs @@ -74,12 +74,22 @@ pub struct CollectProtocolFee<'info> { /// CHECK: light_token CPI authority. pub light_token_cpi_authority: AccountInfo<'info>, + + /// Optional SPL interface PDA for token_0 (only needed if token_0 is SPL) + #[account(mut)] + pub spl_interface_pda_0: Option>, + + /// Optional SPL interface PDA for token_1 (only needed if token_1 is SPL) + #[account(mut)] + pub spl_interface_pda_1: Option>, } pub fn collect_protocol_fee( ctx: Context, amount_0_requested: u64, amount_1_requested: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { let amount_0: u64; let amount_1: u64; @@ -118,6 +128,8 @@ pub fn collect_protocol_fee( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, )?; transfer_from_pool_vault_to_user( @@ -135,6 +147,8 @@ pub fn collect_protocol_fee( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, )?; Ok(()) diff --git a/programs/cp-swap/src/instructions/deposit.rs b/programs/cp-swap/src/instructions/deposit.rs index e2d983d..a0bb379 100644 --- a/programs/cp-swap/src/instructions/deposit.rs +++ b/programs/cp-swap/src/instructions/deposit.rs @@ -93,6 +93,14 @@ pub struct Deposit<'info> { /// CHECK: light-token CPI authority. pub light_token_cpi_authority: AccountInfo<'info>, + + /// Optional SPL interface PDA for token_0 (only needed if token_0 is SPL) + #[account(mut)] + pub spl_interface_pda_0: Option>, + + /// Optional SPL interface PDA for token_1 (only needed if token_1 is SPL) + #[account(mut)] + pub spl_interface_pda_1: Option>, } pub fn deposit( @@ -100,6 +108,8 @@ pub fn deposit( lp_token_amount: u64, maximum_token_0_amount: u64, maximum_token_1_amount: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { require_gt!(lp_token_amount, 0); let pool_id = ctx.accounts.pool_state.key(); @@ -186,6 +196,8 @@ pub fn deposit( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, )?; transfer_from_user_to_pool_vault( @@ -202,6 +214,8 @@ pub fn deposit( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, )?; pool_state.lp_supply = pool_state.lp_supply.checked_add(lp_token_amount).unwrap(); diff --git a/programs/cp-swap/src/instructions/initialize.rs b/programs/cp-swap/src/instructions/initialize.rs index 29baa5e..3abd2f5 100644 --- a/programs/cp-swap/src/instructions/initialize.rs +++ b/programs/cp-swap/src/instructions/initialize.rs @@ -34,6 +34,10 @@ pub struct InitializeParams { pub lp_mint_signer_bump: u8, pub creator_lp_token_bump: u8, pub authority_bump: u8, + /// SPL interface PDA bump for token_0 (None for Light tokens) + pub spl_interface_bump_0: Option, + /// SPL interface PDA bump for token_1 (None for Light tokens) + pub spl_interface_bump_1: Option, } #[derive(Accounts, LightAccounts)] @@ -166,6 +170,14 @@ pub struct Initialize<'info> { /// CHECK: light-token CPI authority. pub light_token_cpi_authority: AccountInfo<'info>, + + /// Optional SPL interface PDA for token_0 (only needed if token_0 is SPL) + #[account(mut)] + pub spl_interface_pda_0: Option>, + + /// Optional SPL interface PDA for token_1 (only needed if token_1 is SPL) + #[account(mut)] + pub spl_interface_pda_1: Option>, } pub fn initialize<'info>( @@ -248,6 +260,8 @@ pub fn initialize<'info>( ctx.accounts.creator.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_0.clone(), + params.spl_interface_bump_0, )?; transfer_from_user_to_pool_vault( @@ -260,6 +274,8 @@ pub fn initialize<'info>( ctx.accounts.creator.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_1.clone(), + params.spl_interface_bump_1, )?; // Get vault balances - supports both light token and spl token accounts diff --git a/programs/cp-swap/src/instructions/swap_base_input.rs b/programs/cp-swap/src/instructions/swap_base_input.rs index 1c686a0..234a90b 100644 --- a/programs/cp-swap/src/instructions/swap_base_input.rs +++ b/programs/cp-swap/src/instructions/swap_base_input.rs @@ -79,9 +79,23 @@ pub struct Swap<'info> { /// CHECK: light_token CPI authority. pub light_token_cpi_authority: AccountInfo<'info>, + + /// Optional SPL interface PDA for token_0 (only needed if token_0 is SPL) + #[account(mut)] + pub spl_interface_pda_0: Option>, + + /// Optional SPL interface PDA for token_1 (only needed if token_1 is SPL) + #[account(mut)] + pub spl_interface_pda_1: Option>, } -pub fn swap_base_input(ctx: Context, amount_in: u64, minimum_amount_out: u64) -> Result<()> { +pub fn swap_base_input( + ctx: Context, + amount_in: u64, + minimum_amount_out: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, +) -> Result<()> { let block_timestamp = solana_program::clock::Clock::get()?.unix_timestamp as u64; let pool_id = ctx.accounts.pool_state.key(); let pool_state = &mut ctx.accounts.pool_state; @@ -233,6 +247,22 @@ pub fn swap_base_input(ctx: Context, amount_in: u64, minimum_amount_out: u }); require_gte!(constant_after, constant_before); + // Select SPL interface PDAs based on trade direction + let (input_spl_pda, input_spl_bump, output_spl_pda, output_spl_bump) = match trade_direction { + TradeDirection::ZeroForOne => ( + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, + ), + TradeDirection::OneForZero => ( + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, + ), + }; + transfer_from_user_to_pool_vault( ctx.accounts.payer.to_account_info(), ctx.accounts.input_token_account.to_account_info(), @@ -243,6 +273,8 @@ pub fn swap_base_input(ctx: Context, amount_in: u64, minimum_amount_out: u ctx.accounts.payer.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + input_spl_pda, + input_spl_bump, )?; transfer_from_pool_vault_to_user( @@ -256,6 +288,8 @@ pub fn swap_base_input(ctx: Context, amount_in: u64, minimum_amount_out: u ctx.accounts.payer.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + output_spl_pda, + output_spl_bump, )?; // update the previous price to the observation diff --git a/programs/cp-swap/src/instructions/swap_base_output.rs b/programs/cp-swap/src/instructions/swap_base_output.rs index 853e460..209e0d7 100644 --- a/programs/cp-swap/src/instructions/swap_base_output.rs +++ b/programs/cp-swap/src/instructions/swap_base_output.rs @@ -10,6 +10,8 @@ pub fn swap_base_output( ctx: Context, max_amount_in: u64, amount_out_less_fee: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { require_gt!(amount_out_less_fee, 0); let block_timestamp = solana_program::clock::Clock::get()?.unix_timestamp as u64; @@ -165,6 +167,22 @@ pub fn swap_base_output( }); require_gte!(constant_after, constant_before); + // Select SPL interface PDAs based on trade direction + let (input_spl_pda, input_spl_bump, output_spl_pda, output_spl_bump) = match trade_direction { + TradeDirection::ZeroForOne => ( + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, + ), + TradeDirection::OneForZero => ( + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, + ), + }; + transfer_from_user_to_pool_vault( ctx.accounts.payer.to_account_info(), ctx.accounts.input_token_account.to_account_info(), @@ -175,6 +193,8 @@ pub fn swap_base_output( ctx.accounts.payer.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + input_spl_pda, + input_spl_bump, )?; transfer_from_pool_vault_to_user( @@ -188,6 +208,8 @@ pub fn swap_base_output( ctx.accounts.payer.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + output_spl_pda, + output_spl_bump, )?; // update the previous price to the observation diff --git a/programs/cp-swap/src/instructions/withdraw.rs b/programs/cp-swap/src/instructions/withdraw.rs index 5cc0b92..b3e35c6 100644 --- a/programs/cp-swap/src/instructions/withdraw.rs +++ b/programs/cp-swap/src/instructions/withdraw.rs @@ -102,6 +102,14 @@ pub struct Withdraw<'info> { /// Light Token program for CPI pub light_token_program: Interface<'info, TokenInterface>, + + /// Optional SPL interface PDA for token_0 (only needed if token_0 is SPL) + #[account(mut)] + pub spl_interface_pda_0: Option>, + + /// Optional SPL interface PDA for token_1 (only needed if token_1 is SPL) + #[account(mut)] + pub spl_interface_pda_1: Option>, } pub fn withdraw( @@ -109,6 +117,8 @@ pub fn withdraw( lp_token_amount: u64, minimum_token_0_amount: u64, minimum_token_1_amount: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { require_gt!(lp_token_amount, 0); let pool_id = ctx.accounts.pool_state.key(); @@ -210,6 +220,8 @@ pub fn withdraw( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_0.clone(), + spl_interface_bump_0, )?; transfer_from_pool_vault_to_user( @@ -227,6 +239,8 @@ pub fn withdraw( ctx.accounts.owner.to_account_info(), ctx.accounts.light_token_cpi_authority.to_account_info(), ctx.accounts.system_program.to_account_info(), + ctx.accounts.spl_interface_pda_1.clone(), + spl_interface_bump_1, )?; pool_state.recent_epoch = Clock::get()?.epoch; diff --git a/programs/cp-swap/src/lib.rs b/programs/cp-swap/src/lib.rs index 2d80571..cb5fc8e 100644 --- a/programs/cp-swap/src/lib.rs +++ b/programs/cp-swap/src/lib.rs @@ -145,8 +145,16 @@ pub mod raydium_cp_swap { ctx: Context, amount_0_requested: u64, amount_1_requested: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { - instructions::collect_protocol_fee(ctx, amount_0_requested, amount_1_requested) + instructions::collect_protocol_fee( + ctx, + amount_0_requested, + amount_1_requested, + spl_interface_bump_0, + spl_interface_bump_1, + ) } /// Collect the fund fee accrued to the pool @@ -161,8 +169,16 @@ pub mod raydium_cp_swap { ctx: Context, amount_0_requested: u64, amount_1_requested: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { - instructions::collect_fund_fee(ctx, amount_0_requested, amount_1_requested) + instructions::collect_fund_fee( + ctx, + amount_0_requested, + amount_1_requested, + spl_interface_bump_0, + spl_interface_bump_1, + ) } /// Creates a pool for the given token pair and the initial price @@ -193,12 +209,16 @@ pub mod raydium_cp_swap { lp_token_amount: u64, maximum_token_0_amount: u64, maximum_token_1_amount: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { instructions::deposit( ctx, lp_token_amount, maximum_token_0_amount, maximum_token_1_amount, + spl_interface_bump_0, + spl_interface_bump_1, ) } @@ -216,12 +236,16 @@ pub mod raydium_cp_swap { lp_token_amount: u64, minimum_token_0_amount: u64, minimum_token_1_amount: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { instructions::withdraw( ctx, lp_token_amount, minimum_token_0_amount, minimum_token_1_amount, + spl_interface_bump_0, + spl_interface_bump_1, ) } @@ -237,8 +261,16 @@ pub mod raydium_cp_swap { ctx: Context, amount_in: u64, minimum_amount_out: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, ) -> Result<()> { - instructions::swap_base_input(ctx, amount_in, minimum_amount_out) + instructions::swap_base_input( + ctx, + amount_in, + minimum_amount_out, + spl_interface_bump_0, + spl_interface_bump_1, + ) } /// Swap the tokens in the pool base output amount @@ -249,7 +281,19 @@ pub mod raydium_cp_swap { /// * `max_amount_in` - input amount prevents excessive slippage /// * `amount_out` - amount of output token /// - pub fn swap_base_output(ctx: Context, max_amount_in: u64, amount_out: u64) -> Result<()> { - instructions::swap_base_output(ctx, max_amount_in, amount_out) + pub fn swap_base_output( + ctx: Context, + max_amount_in: u64, + amount_out: u64, + spl_interface_bump_0: Option, + spl_interface_bump_1: Option, + ) -> Result<()> { + instructions::swap_base_output( + ctx, + max_amount_in, + amount_out, + spl_interface_bump_0, + spl_interface_bump_1, + ) } } diff --git a/programs/cp-swap/src/utils/token.rs b/programs/cp-swap/src/utils/token.rs index 1849b79..5a1d089 100644 --- a/programs/cp-swap/src/utils/token.rs +++ b/programs/cp-swap/src/utils/token.rs @@ -6,6 +6,7 @@ use light_anchor_spl::{ token_interface::{initialize_account3, InitializeAccount3, Mint}, }; use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID; +pub use light_token::instruction::get_spl_interface_pda_and_bump; use light_token::instruction::TransferInterfaceCpi; use spl_token_2022::{ self, @@ -55,11 +56,13 @@ pub fn transfer_from_user_to_pool_vault<'a>( from: AccountInfo<'a>, to_vault: AccountInfo<'a>, mint: AccountInfo<'a>, - _token_program: AccountInfo<'a>, + token_program: AccountInfo<'a>, amount: u64, payer: AccountInfo<'a>, light_token_cpi_authority: AccountInfo<'a>, system_program: AccountInfo<'a>, + spl_interface_pda: Option>, + spl_interface_pda_bump: Option, ) -> Result<()> { if amount == 0 { return Ok(()); @@ -79,6 +82,12 @@ pub fn transfer_from_user_to_pool_vault<'a>( light_token_cpi_authority, system_program, ) + .spl_interface( + mint.clone(), + token_program, + spl_interface_pda, + spl_interface_pda_bump, + ) .invoke() .map_err(|e| anchor_lang::prelude::ProgramError::from(e))?; @@ -90,12 +99,14 @@ pub fn transfer_from_pool_vault_to_user<'a>( from_vault: AccountInfo<'a>, to: AccountInfo<'a>, mint: AccountInfo<'a>, - _token_program: AccountInfo<'a>, + token_program: AccountInfo<'a>, amount: u64, signer_seeds: &[&[&[u8]]], payer: AccountInfo<'a>, light_token_cpi_authority: AccountInfo<'a>, system_program: AccountInfo<'a>, + spl_interface_pda: Option>, + spl_interface_pda_bump: Option, ) -> Result<()> { if amount == 0 { return Ok(()); @@ -115,6 +126,12 @@ pub fn transfer_from_pool_vault_to_user<'a>( light_token_cpi_authority, system_program, ) + .spl_interface( + mint.clone(), + token_program, + spl_interface_pda, + spl_interface_pda_bump, + ) .invoke_signed(signer_seeds) .map_err(|e| anchor_lang::prelude::ProgramError::from(e))?; diff --git a/programs/cp-swap/tests/functional_test.rs b/programs/cp-swap/tests/functional_test.rs index 664eaa5..ea58a20 100644 --- a/programs/cp-swap/tests/functional_test.rs +++ b/programs/cp-swap/tests/functional_test.rs @@ -1,6 +1,5 @@ /// Functional integration test for cp-swap program. /// Tests pool initialization with light-program-test framework. - use light_client::interface::AccountInterfaceExt; use light_program_test::program_test::TestRpc; use light_program_test::Rpc; @@ -86,7 +85,10 @@ async fn test_full_lifecycle() { // Check initial LP token balance (should have received initial LP tokens from initialize) let lp_balance_after_init = get_token_balance(&mut env.rpc, pdas.creator_lp_token).await; println!("LP balance after init: {}", lp_balance_after_init); - assert!(lp_balance_after_init > 0, "Should have received LP tokens from initialization"); + assert!( + lp_balance_after_init > 0, + "Should have received LP tokens from initialization" + ); // ======================================================================== // Deposit @@ -108,6 +110,7 @@ async fn test_full_lifecycle() { deposit_lp_amount, max_token_0, max_token_1, + SplInterfaceInfo::default(), ); env.rpc @@ -152,6 +155,9 @@ async fn test_full_lifecycle() { true, // is_token_0_input swap_amount_in, min_amount_out, + SplInterfaceInfo::default(), + light_token_program_id(), + light_token_program_id(), ); env.rpc @@ -193,6 +199,7 @@ async fn test_full_lifecycle() { withdraw_lp_amount, 0, // minimum_token_0_amount - accept any 0, // minimum_token_1_amount - accept any + SplInterfaceInfo::default(), ); env.rpc @@ -218,11 +225,241 @@ async fn test_full_lifecycle() { println!("Full lifecycle test completed successfully!"); } +// ============================================================================ +// Token Type Pool Tests - Full Lifecycle +// ============================================================================ + +/// Generic full lifecycle test for any token type combination. +/// Tests: Initialize -> Deposit -> Swap -> Withdraw +async fn test_pool_with_token_types(type_a: TokenType, type_b: TokenType, config_index: u16) { + let test_name = format!("{:?} + {:?}", type_a, type_b); + println!("\n=== {} Full Lifecycle ===", test_name); + + let program_id = raydium_cp_swap::ID; + + // Setup environment + let mut env = setup_test_environment(program_id).await; + + let creator = Keypair::new(); + env.rpc + .airdrop_lamports(&creator.pubkey(), 100_000_000_000) + .await + .unwrap(); + + let admin = get_admin_keypair(); + env.rpc + .airdrop_lamports(&admin.pubkey(), 10_000_000_000) + .await + .unwrap(); + + // Create token pair with specified types + let initial_balance = 1_000_000; + let flex_tokens = setup_token_pair( + &mut env.rpc, + &env.payer, + &creator.pubkey(), + initial_balance, + type_a, + type_b, + ) + .await; + + println!( + "Tokens: 0={:?}, 1={:?}", + flex_tokens.token_0_type, flex_tokens.token_1_type + ); + + // Create SPL interface PDAs for any SPL/Token-2022 mints + create_spl_interface_pdas_for_setup(&mut env.rpc, &env.payer, &flex_tokens).await; + + let tokens = flex_tokens.to_token_setup(); + let spl_interface = flex_tokens.build_spl_interface(); + + // Create AMM config + let amm_config = + create_amm_config(&mut env.rpc, &env.payer, &admin, program_id, config_index).await; + assert_amm_config_created(&mut env.rpc, amm_config).await; + setup_create_pool_fee_account(&mut env.rpc, &env.payer.pubkey()); + + let pdas = derive_amm_pdas( + &program_id, + &amm_config, + &tokens.token_0_mint, + &tokens.token_1_mint, + &creator.pubkey(), + ); + + // ======================================================================== + // Initialize + // ======================================================================== + let proof_result = get_pool_create_accounts_proof(&env.rpc, &program_id, &pdas).await; + + let init_instruction = build_initialize_instruction_with_spl( + program_id, + creator.pubkey(), + amm_config, + &pdas, + &tokens, + env.config_pda, + &proof_result, + 100_000, + 100_000, + 0, + flex_tokens.token_0_type.program_id(), + flex_tokens.token_1_type.program_id(), + spl_interface.clone(), + ); + + env.rpc + .create_and_send_transaction(&[init_instruction], &creator.pubkey(), &[&creator]) + .await + .expect("Initialize should succeed"); + + assert_pool_initialized(&mut env.rpc, &pdas).await; + let lp_after_init = get_token_balance(&mut env.rpc, pdas.creator_lp_token).await; + assert!(lp_after_init > 0, "Should have LP tokens after init"); + println!("[Init] LP: {}", lp_after_init); + + // ======================================================================== + // Deposit + // ======================================================================== + let lp_before_deposit = get_token_balance(&mut env.rpc, pdas.creator_lp_token).await; + + let deposit_instruction = build_deposit_instruction( + program_id, + creator.pubkey(), + &pdas, + &tokens, + tokens.creator_token_0, + tokens.creator_token_1, + 500, // lp_amount + 10_000, // max_token_0 + 10_000, // max_token_1 + spl_interface.clone(), + ); + + env.rpc + .create_and_send_transaction(&[deposit_instruction], &creator.pubkey(), &[&creator]) + .await + .expect("Deposit should succeed"); + + assert_deposit_succeeded(&mut env.rpc, pdas.creator_lp_token, lp_before_deposit, 500).await; + println!( + "[Deposit] LP: {}", + get_token_balance(&mut env.rpc, pdas.creator_lp_token).await + ); + + // ======================================================================== + // Swap (token_0 -> token_1) + // ======================================================================== + env.rpc.warp_to_slot(100).unwrap(); + + let t0_before = get_token_balance(&mut env.rpc, tokens.creator_token_0).await; + let t1_before = get_token_balance(&mut env.rpc, tokens.creator_token_1).await; + + let swap_instruction = build_swap_instruction( + program_id, + creator.pubkey(), + amm_config, + &pdas, + &tokens, + tokens.creator_token_0, + tokens.creator_token_1, + true, // is_token_0_input + 100, // amount_in + 1, // min_amount_out + spl_interface.clone(), + flex_tokens.token_0_type.program_id(), + flex_tokens.token_1_type.program_id(), + ); + + env.rpc + .create_and_send_transaction(&[swap_instruction], &creator.pubkey(), &[&creator]) + .await + .expect("Swap should succeed"); + + assert_swap_succeeded( + &mut env.rpc, + tokens.creator_token_0, + tokens.creator_token_1, + t0_before, + t1_before, + 100, + 1, + ) + .await; + println!( + "[Swap] T0: {}, T1: {}", + get_token_balance(&mut env.rpc, tokens.creator_token_0).await, + get_token_balance(&mut env.rpc, tokens.creator_token_1).await + ); + + // ======================================================================== + // Withdraw + // ======================================================================== + let lp_before_withdraw = get_token_balance(&mut env.rpc, pdas.creator_lp_token).await; + let withdraw_amount = lp_before_withdraw / 2; + + let withdraw_instruction = build_withdraw_instruction( + program_id, + creator.pubkey(), + &pdas, + &tokens, + tokens.creator_token_0, + tokens.creator_token_1, + withdraw_amount, + 0, + 0, + spl_interface, + ); + + env.rpc + .create_and_send_transaction(&[withdraw_instruction], &creator.pubkey(), &[&creator]) + .await + .expect("Withdraw should succeed"); + + assert_withdraw_succeeded( + &mut env.rpc, + pdas.creator_lp_token, + lp_before_withdraw, + withdraw_amount, + ) + .await; + println!( + "[Withdraw] LP: {}, T0: {}, T1: {}", + get_token_balance(&mut env.rpc, pdas.creator_lp_token).await, + get_token_balance(&mut env.rpc, tokens.creator_token_0).await, + get_token_balance(&mut env.rpc, tokens.creator_token_1).await + ); + + println!("=== {} Full Lifecycle PASSED ===\n", test_name); +} + +#[tokio::test] +async fn test_pool_spl_light() { + test_pool_with_token_types(TokenType::Spl, TokenType::Light, 10).await; +} + +#[tokio::test] +async fn test_pool_spl_spl() { + test_pool_with_token_types(TokenType::Spl, TokenType::Spl, 11).await; +} + +#[tokio::test] +async fn test_pool_spl_token2022() { + test_pool_with_token_types(TokenType::Spl, TokenType::Token2022, 12).await; +} + +#[tokio::test] +async fn test_pool_light_token2022() { + test_pool_with_token_types(TokenType::Light, TokenType::Token2022, 13).await; +} + /// Test SDK initialization from fetched accounts and account requirements. #[tokio::test] async fn test_sdk_from_keyed_accounts() { - use program::{CpSwapSdk, CpSwapInstruction}; use light_client::interface::LightProgramInterface; + use program::{CpSwapInstruction, CpSwapSdk}; let program_id = raydium_cp_swap::ID; @@ -230,7 +467,8 @@ async fn test_sdk_from_keyed_accounts() { let mut setup = setup_pool_environment(program_id, 2).await; // Initialize pool first (SDK requires actual account data) - let proof_result = get_pool_create_accounts_proof(&setup.env.rpc, &program_id, &setup.pdas).await; + let proof_result = + get_pool_create_accounts_proof(&setup.env.rpc, &program_id, &setup.pdas).await; let init_ix = build_initialize_instruction( program_id, setup.creator.pubkey(), @@ -243,13 +481,17 @@ async fn test_sdk_from_keyed_accounts() { 100_000, 0, ); - setup.env.rpc + setup + .env + .rpc .create_and_send_transaction(&[init_ix], &setup.creator.pubkey(), &[&setup.creator]) .await .expect("Initialize should succeed"); // Fetch pool state account - let pool_interface = setup.env.rpc + let pool_interface = setup + .env + .rpc .get_account_interface(&setup.pdas.pool_state, &program_id) .await .expect("get_account_interface should succeed"); @@ -270,13 +512,25 @@ async fn test_sdk_from_keyed_accounts() { // Check account requirements for each instruction type let swap_accounts = sdk.get_accounts_for_instruction(CpSwapInstruction::Swap); - assert_eq!(swap_accounts.len(), 6, "Swap needs 6 accounts: pool, observation, vault0, vault1, mint0, mint1"); + assert_eq!( + swap_accounts.len(), + 6, + "Swap needs 6 accounts: pool, observation, vault0, vault1, mint0, mint1" + ); let deposit_accounts = sdk.get_accounts_for_instruction(CpSwapInstruction::Deposit); - assert_eq!(deposit_accounts.len(), 7, "Deposit needs 7 accounts: pool, observation, vault0, vault1, lp_mint, mint0, mint1"); + assert_eq!( + deposit_accounts.len(), + 7, + "Deposit needs 7 accounts: pool, observation, vault0, vault1, lp_mint, mint0, mint1" + ); let withdraw_accounts = sdk.get_accounts_for_instruction(CpSwapInstruction::Withdraw); - assert_eq!(withdraw_accounts.len(), 7, "Withdraw needs 7 accounts: pool, observation, vault0, vault1, lp_mint, mint0, mint1"); + assert_eq!( + withdraw_accounts.len(), + 7, + "Withdraw needs 7 accounts: pool, observation, vault0, vault1, lp_mint, mint0, mint1" + ); // Verify program_id method assert_eq!(sdk.program_id(), program_id); diff --git a/programs/cp-swap/tests/helpers.rs b/programs/cp-swap/tests/helpers.rs index 76451fb..0179205 100644 --- a/programs/cp-swap/tests/helpers.rs +++ b/programs/cp-swap/tests/helpers.rs @@ -16,10 +16,11 @@ use light_token::{ constants::CPI_AUTHORITY_PDA, constants::LIGHT_TOKEN_PROGRAM_ID, instruction::{ - find_mint_address, get_associated_token_address_and_bump, CreateAssociatedTokenAccount, - CreateMint, CreateMintParams, MintTo, COMPRESSIBLE_CONFIG_V1, + find_mint_address, get_associated_token_address_and_bump, get_spl_interface_pda_and_bump, + CreateAssociatedTokenAccount, CreateMint, CreateMintParams, MintTo, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR, }, + spl_interface::CreateSplInterfacePda, }; use raydium_cp_swap::{ instructions::initialize::LP_MINT_SIGNER_SEED, @@ -212,6 +213,60 @@ pub async fn setup_create_mint( (mint, ata_pubkeys, mint_seed) } +/// Create the SPL interface PDA (token pool) for an SPL/Token-2022 mint. +/// This is required before SPL tokens can be transferred to/from Light Token accounts. +pub async fn create_spl_interface_pda( + rpc: &mut LightProgramTest, + payer: &Keypair, + mint: &Pubkey, +) -> Pubkey { + create_spl_interface_pda_with_program(rpc, payer, mint, spl_token::id()).await +} + +/// Create the SPL interface PDA for a mint with specified token program. +pub async fn create_spl_interface_pda_with_program( + rpc: &mut LightProgramTest, + payer: &Keypair, + mint: &Pubkey, + token_program: Pubkey, +) -> Pubkey { + let (spl_interface_pda, _bump) = get_spl_interface_pda_and_bump(mint); + + let ix = CreateSplInterfacePda::new(payer.pubkey(), *mint, token_program, false).instruction(); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer]) + .await + .expect("Create SPL interface PDA should succeed"); + + spl_interface_pda +} + +/// Create SPL interface PDAs for any non-Light tokens in the setup. +pub async fn create_spl_interface_pdas_for_setup( + rpc: &mut LightProgramTest, + payer: &Keypair, + setup: &FlexibleTokenSetup, +) { + if setup.token_0_type.needs_spl_interface() { + create_spl_interface_pda_with_program( + rpc, + payer, + &setup.token_0_mint, + setup.token_0_type.program_id(), + ) + .await; + } + if setup.token_1_type.needs_spl_interface() { + create_spl_interface_pda_with_program( + rpc, + payer, + &setup.token_1_mint, + setup.token_1_type.program_id(), + ) + .await; + } +} + /// Create token mints and fund creator with initial balances. pub async fn setup_token_mints( rpc: &mut LightProgramTest, @@ -259,6 +314,364 @@ pub async fn setup_token_mints( } } +/// Create an SPL token mint (not Light token) and fund creator with initial balance. +pub async fn setup_spl_mint( + rpc: &mut LightProgramTest, + payer: &Keypair, + decimals: u8, + recipients: Vec<(u64, Pubkey)>, +) -> (Pubkey, Keypair, Vec) { + use light_anchor_spl::associated_token::spl_associated_token_account; + use solana_sdk::program_pack::Pack; + + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + + // Create mint account + let mint_rent = rpc + .get_minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN) + .await + .unwrap(); + let create_mint_ix = solana_sdk::system_instruction::create_account( + &payer.pubkey(), + &mint_pubkey, + mint_rent, + spl_token::state::Mint::LEN as u64, + &spl_token::id(), + ); + + let init_mint_ix = spl_token::instruction::initialize_mint( + &spl_token::id(), + &mint_pubkey, + &payer.pubkey(), + None, + decimals, + ) + .unwrap(); + + rpc.create_and_send_transaction( + &[create_mint_ix, init_mint_ix], + &payer.pubkey(), + &[payer, &mint_keypair], + ) + .await + .expect("Create SPL mint should succeed"); + + if recipients.is_empty() { + return (mint_pubkey, mint_keypair, vec![]); + } + + let mut ata_pubkeys = Vec::with_capacity(recipients.len()); + + for (amount, owner) in &recipients { + let ata = spl_associated_token_account::get_associated_token_address(owner, &mint_pubkey); + ata_pubkeys.push(ata); + + let create_ata_ix = + spl_associated_token_account::instruction::create_associated_token_account( + &payer.pubkey(), + owner, + &mint_pubkey, + &spl_token::id(), + ); + + rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[payer]) + .await + .expect("Create SPL ATA should succeed"); + + if *amount > 0 { + let mint_to_ix = spl_token::instruction::mint_to( + &spl_token::id(), + &mint_pubkey, + &ata, + &payer.pubkey(), + &[], + *amount, + ) + .unwrap(); + + rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[payer]) + .await + .expect("Mint SPL tokens should succeed"); + } + } + + (mint_pubkey, mint_keypair, ata_pubkeys) +} + +/// Token type for flexible test setup. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TokenType { + Light, + Spl, + Token2022, +} + +impl TokenType { + pub fn program_id(&self) -> Pubkey { + match self { + TokenType::Light => light_token_program_id(), + TokenType::Spl => spl_token::id(), + TokenType::Token2022 => spl_token_2022::id(), + } + } + + pub fn needs_spl_interface(&self) -> bool { + matches!(self, TokenType::Spl | TokenType::Token2022) + } +} + +/// Flexible token setup that works with any combination of token types. +pub struct FlexibleTokenSetup { + pub token_0_mint: Pubkey, + pub token_1_mint: Pubkey, + pub token_0_mint_signer: Pubkey, + pub token_1_mint_signer: Pubkey, + pub creator_token_0: Pubkey, + pub creator_token_1: Pubkey, + pub token_0_type: TokenType, + pub token_1_type: TokenType, +} + +impl FlexibleTokenSetup { + pub fn to_token_setup(&self) -> TokenSetup { + TokenSetup { + token_0_mint: self.token_0_mint, + token_1_mint: self.token_1_mint, + token_0_mint_signer: self.token_0_mint_signer, + token_1_mint_signer: self.token_1_mint_signer, + creator_token_0: self.creator_token_0, + creator_token_1: self.creator_token_1, + } + } + + pub fn build_spl_interface(&self) -> SplInterfaceInfo { + let (token_0_pda, token_0_bump) = if self.token_0_type.needs_spl_interface() { + let (pda, bump) = get_spl_interface_pda_and_bump(&self.token_0_mint); + (Some(pda), Some(bump)) + } else { + (None, None) + }; + + let (token_1_pda, token_1_bump) = if self.token_1_type.needs_spl_interface() { + let (pda, bump) = get_spl_interface_pda_and_bump(&self.token_1_mint); + (Some(pda), Some(bump)) + } else { + (None, None) + }; + + SplInterfaceInfo { + token_0_pda, + token_0_bump, + token_1_pda, + token_1_bump, + } + } +} + +/// Create a Token-2022 mint (no extensions) and fund creator with initial balance. +pub async fn setup_token2022_mint( + rpc: &mut LightProgramTest, + payer: &Keypair, + decimals: u8, + recipients: Vec<(u64, Pubkey)>, +) -> (Pubkey, Keypair, Vec) { + use light_anchor_spl::associated_token::spl_associated_token_account; + use solana_sdk::program_pack::Pack; + + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + + // Create mint account for Token-2022 + let mint_rent = rpc + .get_minimum_balance_for_rent_exemption(spl_token_2022::state::Mint::LEN) + .await + .unwrap(); + let create_mint_ix = solana_sdk::system_instruction::create_account( + &payer.pubkey(), + &mint_pubkey, + mint_rent, + spl_token_2022::state::Mint::LEN as u64, + &spl_token_2022::id(), + ); + + let init_mint_ix = spl_token_2022::instruction::initialize_mint( + &spl_token_2022::id(), + &mint_pubkey, + &payer.pubkey(), + None, + decimals, + ) + .unwrap(); + + rpc.create_and_send_transaction( + &[create_mint_ix, init_mint_ix], + &payer.pubkey(), + &[payer, &mint_keypair], + ) + .await + .expect("Create Token-2022 mint should succeed"); + + if recipients.is_empty() { + return (mint_pubkey, mint_keypair, vec![]); + } + + let mut ata_pubkeys = Vec::with_capacity(recipients.len()); + + for (amount, owner) in &recipients { + let ata = spl_associated_token_account::get_associated_token_address_with_program_id( + owner, + &mint_pubkey, + &spl_token_2022::id(), + ); + ata_pubkeys.push(ata); + + let create_ata_ix = + spl_associated_token_account::instruction::create_associated_token_account( + &payer.pubkey(), + owner, + &mint_pubkey, + &spl_token_2022::id(), + ); + + rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[payer]) + .await + .expect("Create Token-2022 ATA should succeed"); + + if *amount > 0 { + let mint_to_ix = spl_token_2022::instruction::mint_to( + &spl_token_2022::id(), + &mint_pubkey, + &ata, + &payer.pubkey(), + &[], + *amount, + ) + .unwrap(); + + rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[payer]) + .await + .expect("Mint Token-2022 tokens should succeed"); + } + } + + (mint_pubkey, mint_keypair, ata_pubkeys) +} + +/// Create a single token based on type. +async fn create_single_token( + rpc: &mut LightProgramTest, + payer: &Keypair, + creator: &Pubkey, + initial_balance: u64, + token_type: TokenType, +) -> (Pubkey, Pubkey, Pubkey) { + // Returns (mint, mint_signer, creator_ata) + match token_type { + TokenType::Light => { + let (mint, atas, mint_seed) = setup_create_mint( + rpc, + payer, + payer.pubkey(), + 9, + vec![(initial_balance, *creator)], + ) + .await; + (mint, mint_seed.pubkey(), atas[0]) + } + TokenType::Spl => { + let (mint, _keypair, atas) = + setup_spl_mint(rpc, payer, 9, vec![(initial_balance, *creator)]).await; + (mint, Pubkey::default(), atas[0]) + } + TokenType::Token2022 => { + let (mint, _keypair, atas) = + setup_token2022_mint(rpc, payer, 9, vec![(initial_balance, *creator)]).await; + (mint, Pubkey::default(), atas[0]) + } + } +} + +/// Create token pair with specified types. +pub async fn setup_token_pair( + rpc: &mut LightProgramTest, + payer: &Keypair, + creator: &Pubkey, + initial_balance: u64, + type_a: TokenType, + type_b: TokenType, +) -> FlexibleTokenSetup { + let (mint_a, signer_a, ata_a) = + create_single_token(rpc, payer, creator, initial_balance, type_a).await; + let (mint_b, signer_b, ata_b) = + create_single_token(rpc, payer, creator, initial_balance, type_b).await; + + // Ensure proper ordering: token_0_mint < token_1_mint + if mint_a < mint_b { + FlexibleTokenSetup { + token_0_mint: mint_a, + token_1_mint: mint_b, + token_0_mint_signer: signer_a, + token_1_mint_signer: signer_b, + creator_token_0: ata_a, + creator_token_1: ata_b, + token_0_type: type_a, + token_1_type: type_b, + } + } else { + FlexibleTokenSetup { + token_0_mint: mint_b, + token_1_mint: mint_a, + token_0_mint_signer: signer_b, + token_1_mint_signer: signer_a, + creator_token_0: ata_b, + creator_token_1: ata_a, + token_0_type: type_b, + token_1_type: type_a, + } + } +} + +/// Legacy: Token setup for mixed SPL + Light token pair. +pub struct MixedTokenSetup { + pub token_0_mint: Pubkey, + pub token_1_mint: Pubkey, + pub token_0_mint_signer: Pubkey, + pub token_1_mint_signer: Pubkey, + pub creator_token_0: Pubkey, + pub creator_token_1: Pubkey, + pub token_0_is_spl: bool, + pub token_1_is_spl: bool, +} + +/// Legacy: Create token mints where one is SPL and other is Light. +pub async fn setup_mixed_token_mints( + rpc: &mut LightProgramTest, + payer: &Keypair, + creator: &Pubkey, + initial_balance: u64, +) -> MixedTokenSetup { + let flex = setup_token_pair( + rpc, + payer, + creator, + initial_balance, + TokenType::Spl, + TokenType::Light, + ) + .await; + MixedTokenSetup { + token_0_mint: flex.token_0_mint, + token_1_mint: flex.token_1_mint, + token_0_mint_signer: flex.token_0_mint_signer, + token_1_mint_signer: flex.token_1_mint_signer, + creator_token_0: flex.creator_token_0, + creator_token_1: flex.creator_token_1, + token_0_is_spl: flex.token_0_type == TokenType::Spl, + token_1_is_spl: flex.token_1_type == TokenType::Spl, + } +} + // ============================================================================ // AMM Config Functions // ============================================================================ @@ -445,6 +858,7 @@ pub fn build_withdraw_instruction( lp_token_amount: u64, minimum_token_0_amount: u64, minimum_token_1_amount: u64, + spl_interface: SplInterfaceInfo, ) -> Instruction { let accounts = raydium_cp_swap::accounts::Withdraw { owner, @@ -464,12 +878,16 @@ pub fn build_withdraw_instruction( system_program: solana_sdk::system_program::ID, light_token_cpi_authority: CPI_AUTHORITY_PDA, light_token_program: light_token_program_id(), + spl_interface_pda_0: spl_interface.token_0_pda, + spl_interface_pda_1: spl_interface.token_1_pda, }; let instruction_data = raydium_cp_swap::instruction::Withdraw { lp_token_amount, minimum_token_0_amount, minimum_token_1_amount, + spl_interface_bump_0: spl_interface.token_0_bump, + spl_interface_bump_1: spl_interface.token_1_bump, }; Instruction { @@ -491,22 +909,30 @@ pub fn build_swap_instruction( is_token_0_input: bool, // true = swap 0->1, false = swap 1->0 amount_in: u64, minimum_amount_out: u64, + spl_interface: SplInterfaceInfo, + token_0_program: Pubkey, + token_1_program: Pubkey, ) -> Instruction { - let (input_vault, output_vault, input_mint, output_mint) = if is_token_0_input { - ( - pdas.token_0_vault, - pdas.token_1_vault, - tokens.token_0_mint, - tokens.token_1_mint, - ) - } else { - ( - pdas.token_1_vault, - pdas.token_0_vault, - tokens.token_1_mint, - tokens.token_0_mint, - ) - }; + let (input_vault, output_vault, input_mint, output_mint, input_program, output_program) = + if is_token_0_input { + ( + pdas.token_0_vault, + pdas.token_1_vault, + tokens.token_0_mint, + tokens.token_1_mint, + token_0_program, + token_1_program, + ) + } else { + ( + pdas.token_1_vault, + pdas.token_0_vault, + tokens.token_1_mint, + tokens.token_0_mint, + token_1_program, + token_0_program, + ) + }; let accounts = raydium_cp_swap::accounts::Swap { payer, @@ -517,19 +943,23 @@ pub fn build_swap_instruction( output_token_account, input_vault, output_vault, - input_token_program: light_token_program_id(), - output_token_program: light_token_program_id(), + input_token_program: input_program, + output_token_program: output_program, input_token_mint: input_mint, output_token_mint: output_mint, observation_state: pdas.observation_state, light_token_program: light_token_program_id(), system_program: solana_sdk::system_program::ID, light_token_cpi_authority: CPI_AUTHORITY_PDA, + spl_interface_pda_0: spl_interface.token_0_pda, + spl_interface_pda_1: spl_interface.token_1_pda, }; let instruction_data = raydium_cp_swap::instruction::SwapBaseInput { amount_in, minimum_amount_out, + spl_interface_bump_0: spl_interface.token_0_bump, + spl_interface_bump_1: spl_interface.token_1_bump, }; Instruction { @@ -550,6 +980,7 @@ pub fn build_deposit_instruction( lp_token_amount: u64, maximum_token_0_amount: u64, maximum_token_1_amount: u64, + spl_interface: SplInterfaceInfo, ) -> Instruction { let accounts = raydium_cp_swap::accounts::Deposit { owner, @@ -568,12 +999,16 @@ pub fn build_deposit_instruction( lp_mint: pdas.lp_mint, system_program: solana_sdk::system_program::ID, light_token_cpi_authority: CPI_AUTHORITY_PDA, + spl_interface_pda_0: spl_interface.token_0_pda, + spl_interface_pda_1: spl_interface.token_1_pda, }; let instruction_data = raydium_cp_swap::instruction::Deposit { lp_token_amount, maximum_token_0_amount, maximum_token_1_amount, + spl_interface_bump_0: spl_interface.token_0_bump, + spl_interface_bump_1: spl_interface.token_1_bump, }; Instruction { @@ -583,6 +1018,15 @@ pub fn build_deposit_instruction( } } +/// SPL interface info for instructions involving SPL/Token-2022 tokens. +#[derive(Default, Clone)] +pub struct SplInterfaceInfo { + pub token_0_pda: Option, + pub token_0_bump: Option, + pub token_1_pda: Option, + pub token_1_bump: Option, +} + /// Build the Initialize instruction. pub fn build_initialize_instruction( program_id: Pubkey, @@ -595,6 +1039,39 @@ pub fn build_initialize_instruction( init_amount_0: u64, init_amount_1: u64, open_time: u64, +) -> Instruction { + build_initialize_instruction_with_spl( + program_id, + creator, + amm_config, + pdas, + tokens, + config_pda, + proof_result, + init_amount_0, + init_amount_1, + open_time, + light_token_program_id(), + light_token_program_id(), + SplInterfaceInfo::default(), + ) +} + +/// Build the Initialize instruction with SPL interface support. +pub fn build_initialize_instruction_with_spl( + program_id: Pubkey, + creator: Pubkey, + amm_config: Pubkey, + pdas: &AmmPdas, + tokens: &TokenSetup, + config_pda: Pubkey, + proof_result: &CreateAccountsProofResult, + init_amount_0: u64, + init_amount_1: u64, + open_time: u64, + token_0_program: Pubkey, + token_1_program: Pubkey, + spl_interface: SplInterfaceInfo, ) -> Instruction { let init_params = InitializeParams { init_amount_0, @@ -604,6 +1081,8 @@ pub fn build_initialize_instruction( lp_mint_signer_bump: pdas.lp_mint_signer_bump, creator_lp_token_bump: pdas.creator_lp_token_bump, authority_bump: pdas.authority_bump, + spl_interface_bump_0: spl_interface.token_0_bump, + spl_interface_bump_1: spl_interface.token_1_bump, }; let accounts = raydium_cp_swap::accounts::Initialize { @@ -623,8 +1102,8 @@ pub fn build_initialize_instruction( observation_state: pdas.observation_state, create_pool_fee: raydium_cp_swap::create_pool_fee_receiver::ID, token_program: spl_token::id(), - token_0_program: light_token_program_id(), - token_1_program: light_token_program_id(), + token_0_program, + token_1_program, associated_token_program: light_anchor_spl::associated_token::ID, system_program: solana_sdk::system_program::ID, rent: solana_sdk::sysvar::rent::ID, @@ -633,6 +1112,8 @@ pub fn build_initialize_instruction( light_token_rent_sponsor: Pubkey::from(LIGHT_TOKEN_RENT_SPONSOR), light_token_program: light_token_program_id(), light_token_cpi_authority: CPI_AUTHORITY_PDA, + spl_interface_pda_0: spl_interface.token_0_pda, + spl_interface_pda_1: spl_interface.token_1_pda, }; let instruction_data = raydium_cp_swap::instruction::Initialize { diff --git a/programs/cp-swap/tests/program_test.rs b/programs/cp-swap/tests/program_test.rs index e303d72..13818f4 100644 --- a/programs/cp-swap/tests/program_test.rs +++ b/programs/cp-swap/tests/program_test.rs @@ -1,6 +1,5 @@ /// Clean integration test for cp-swap using CpSwapSdk. /// Tests the full lifecycle: Initialize -> Warp -> Compress -> Load -> Execute Operations - use light_client::interface::{ create_load_instructions, AccountInterfaceExt, AccountSpec, LightProgramInterface, }; @@ -19,7 +18,12 @@ use program::{CpSwapInstruction, CpSwapSdk}; fn log_transaction_size(name: &str, ixs: &[Instruction]) { let tx = Transaction::new_with_payer(ixs, None); let serialized = bincode::serialize(&tx).expect("Failed to serialize transaction"); - println!("{}: {} bytes ({} instructions)", name, serialized.len(), ixs.len()); + println!( + "{}: {} bytes ({} instructions)", + name, + serialized.len(), + ixs.len() + ); } #[tokio::test] @@ -55,12 +59,7 @@ async fn test_sdk_lifecycle() { assert_pool_accounts_exist(&mut setup.env.rpc, &setup.pdas, &setup.tokens).await; // ==================== PHASE 3: Warp to Trigger Compression ==================== - setup - .env - .rpc - .warp_epoch_forward(30) - .await - .unwrap(); + setup.env.rpc.warp_epoch_forward(30).await.unwrap(); // ==================== PHASE 4: Assert All Accounts Are Compressed ==================== assert_pool_accounts_compressed(&mut setup.env.rpc, &setup.pdas, &setup.tokens).await; @@ -159,6 +158,7 @@ async fn test_sdk_lifecycle() { 500, 10_000, 10_000, + SplInterfaceInfo::default(), ); log_transaction_size("Deposit transaction", &[deposit_ix.clone()]); @@ -186,6 +186,9 @@ async fn test_sdk_lifecycle() { true, 100, 1, + SplInterfaceInfo::default(), + light_token_program_id(), + light_token_program_id(), ); log_transaction_size("Swap transaction", &[swap_ix.clone()]); @@ -213,6 +216,7 @@ async fn test_sdk_lifecycle() { lp_balance / 2, 0, 0, + SplInterfaceInfo::default(), ); setup .env