From 46190bff32d5118445f64d7300973ad81c426ec8 Mon Sep 17 00:00:00 2001 From: L0STE <125566964+L0STE@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:15:14 +0100 Subject: [PATCH 1/2] Done with the first version of on-demand --- Cargo.lock | 599 ++++++++++++++++++++++++++++----- Cargo.toml | 4 + on-demand/Cargo.toml | 20 ++ on-demand/src/account_store.rs | 369 ++++++++++++++++++++ on-demand/src/lib.rs | 328 ++++++++++++++++++ 5 files changed, 1244 insertions(+), 76 deletions(-) create mode 100644 on-demand/Cargo.toml create mode 100644 on-demand/src/account_store.rs create mode 100644 on-demand/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index aaf24b98..49911c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.0" @@ -205,9 +196,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "ark-bn254" @@ -359,25 +350,21 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.3.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] [[package]] -name = "backtrace" -version = "0.3.74" +name = "autocfg" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "base16ct" @@ -539,6 +526,8 @@ version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -576,7 +565,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -665,6 +654,19 @@ dependencies = [ "unreachable", ] +[[package]] +name = "console" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1034,6 +1036,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "enum-iterator" version = "1.5.0" @@ -1193,9 +1201,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1224,9 +1232,9 @@ checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1239,6 +1247,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1260,6 +1279,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1327,12 +1347,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" - [[package]] name = "group" version = "0.13.0" @@ -1535,7 +1549,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1590,6 +1604,19 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "indicatif" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "ipnet" version = "2.10.0" @@ -1647,6 +1674,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1657,6 +1694,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "k256" version = "0.13.4" @@ -1978,6 +2030,20 @@ dependencies = [ "solana-transaction-context", ] +[[package]] +name = "mollusk-svm-on-demand" +version = "0.7.0" +dependencies = [ + "mollusk-svm", + "solana-account", + "solana-commitment-config", + "solana-instruction", + "solana-pubkey", + "solana-rpc-client", + "solana-rpc-client-api", + "thiserror 1.0.68", +] + [[package]] name = "mollusk-svm-programs-memo" version = "0.7.0" @@ -2148,15 +2214,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "object" -version = "0.36.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.20.2" @@ -2243,7 +2300,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2342,6 +2399,12 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2446,7 +2509,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.5.10", "thiserror 2.0.16", "tokio", "tracing", @@ -2483,7 +2546,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -2703,6 +2766,21 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror 1.0.68", + "tower-service", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -2840,19 +2918,29 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.15" @@ -2862,11 +2950,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2875,14 +2972,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -3058,6 +3156,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "solana-account" version = "3.0.0" @@ -3077,6 +3185,22 @@ dependencies = [ "solana-sysvar", ] +[[package]] +name = "solana-account-decoder-client-types" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76625f95bdb0c5c080b7ac34fab55a5b8f226164e0d4326d6132f4deba1bff1d" +dependencies = [ + "base64 0.22.1", + "bs58", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-pubkey", + "zstd", +] + [[package]] name = "solana-account-info" version = "3.0.0" @@ -3216,6 +3340,16 @@ dependencies = [ "solana-hash", ] +[[package]] +name = "solana-commitment-config" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa5933a62dadb7d3ed35e6329de5cebb0678acc8f9cfdf413269084eeccc63f" +dependencies = [ + "serde", + "serde_derive", +] + [[package]] name = "solana-compute-budget" version = "3.0.0" @@ -3289,6 +3423,16 @@ dependencies = [ "solana-sdk-ids", ] +[[package]] +name = "solana-epoch-info" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6b69bd71386f61344f2bcf0f527f5fd6dd3b22add5880e2e1bf1dd1fa8059" +dependencies = [ + "serde", + "serde_derive", +] + [[package]] name = "solana-epoch-rewards" version = "3.0.0" @@ -3316,6 +3460,19 @@ dependencies = [ "solana-sysvar-id", ] +[[package]] +name = "solana-feature-gate-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347ab62e6d47a82e340c865133795b394feea7c2b2771d293f57691c6544c3f" +dependencies = [ + "serde", + "serde_derive", + "solana-program-error", + "solana-pubkey", + "solana-sdk-ids", +] + [[package]] name = "solana-fee-calculator" version = "3.0.0" @@ -3543,12 +3700,16 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85666605c9fd727f865ed381665db0a8fc29f984a030ecc1e40f43bfb2541623" dependencies = [ + "bincode", "lazy_static", + "serde", + "serde_derive", "solana-address", "solana-hash", "solana-instruction", "solana-sanitize", "solana-sdk-ids", + "solana-short-vec", "solana-transaction-error", ] @@ -3736,6 +3897,104 @@ dependencies = [ "solana-sysvar-id", ] +[[package]] +name = "solana-reward-info" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82be7946105c2ee6be9f9ee7bd18a068b558389221d29efa92b906476102bfcc" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-rpc-client" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133f94f08c31fedb64d0ba717d93d7911c4609f2cd641d73c165684e69cf8b4d" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bincode", + "bs58", + "futures", + "indicatif", + "log", + "reqwest", + "reqwest-middleware", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-account-decoder-client-types", + "solana-clock", + "solana-commitment-config", + "solana-epoch-info", + "solana-epoch-schedule", + "solana-feature-gate-interface", + "solana-hash", + "solana-instruction", + "solana-message", + "solana-pubkey", + "solana-rpc-client-api", + "solana-signature", + "solana-transaction", + "solana-transaction-error", + "solana-transaction-status-client-types", + "solana-version", + "solana-vote-interface", + "tokio", +] + +[[package]] +name = "solana-rpc-client-api" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcab8bf22cdac34d26794d19909b056d9b1272d5e1ea92b4f83c49866d31142" +dependencies = [ + "anyhow", + "jsonrpc-core", + "reqwest", + "reqwest-middleware", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-clock", + "solana-rpc-client-types", + "solana-signer", + "solana-transaction-error", + "solana-transaction-status-client-types", + "thiserror 2.0.16", +] + +[[package]] +name = "solana-rpc-client-types" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cd26ea7669573179babcf9117ab16c449718b52c8c405ac19631b564c048ea" +dependencies = [ + "base64 0.22.1", + "bs58", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-account-decoder-client-types", + "solana-clock", + "solana-commitment-config", + "solana-fee-calculator", + "solana-inflation", + "solana-pubkey", + "solana-transaction-error", + "solana-transaction-status-client-types", + "solana-version", + "spl-generic-token", + "thiserror 2.0.16", +] + [[package]] name = "solana-sanitize" version = "3.0.0" @@ -3890,6 +4149,9 @@ checksum = "4bb8057cc0e9f7b5e89883d49de6f407df655bb6f3a71d0b7baf9986a2218fd9" dependencies = [ "ed25519-dalek 2.2.0", "five8", + "serde", + "serde-big-array", + "serde_derive", "solana-sanitize", ] @@ -4167,6 +4429,9 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64928e6af3058dcddd6da6680cbe08324b4e071ad73115738235bbaa9e9f72a5" dependencies = [ + "bincode", + "serde", + "serde_derive", "solana-address", "solana-hash", "solana-instruction", @@ -4174,7 +4439,9 @@ dependencies = [ "solana-message", "solana-sanitize", "solana-sdk-ids", + "solana-short-vec", "solana-signature", + "solana-signer", "solana-transaction-error", ] @@ -4203,10 +4470,52 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4222065402340d7e6aec9dc3e54d22992ddcf923d91edcd815443c2bfca3144a" dependencies = [ + "serde", + "serde_derive", "solana-instruction-error", "solana-sanitize", ] +[[package]] +name = "solana-transaction-status-client-types" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d4a438e78dee446765fd9003beb55e6901cd25e5bf9c70ac5648f38ac97f44" +dependencies = [ + "base64 0.22.1", + "bincode", + "bs58", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-commitment-config", + "solana-instruction", + "solana-message", + "solana-pubkey", + "solana-reward-info", + "solana-signature", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "thiserror 2.0.16", +] + +[[package]] +name = "solana-version" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b931acdb55e7954abef34495630d40a6d8f6d9f3115f8292d416a76f14f9d01" +dependencies = [ + "agave-feature-set", + "rand 0.8.5", + "semver", + "serde", + "serde_derive", + "solana-sanitize", + "solana-serde-varint", +] + [[package]] name = "solana-vote-interface" version = "3.0.0" @@ -4259,6 +4568,16 @@ dependencies = [ "solana-pubkey", ] +[[package]] +name = "spl-generic-token" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233df81b75ab99b42f002b5cdd6e65a7505ffa930624f7096a7580a56765e9cf" +dependencies = [ + "bytemuck", + "solana-pubkey", +] + [[package]] name = "spl-token-interface" version = "2.0.0" @@ -4446,27 +4765,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.1", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -4616,6 +4934,18 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + [[package]] name = "unreachable" version = "1.0.0" @@ -4859,16 +5189,22 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4877,7 +5213,25 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -4886,14 +5240,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -4902,48 +5273,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.11" @@ -5002,3 +5421,31 @@ dependencies = [ "quote", "syn 2.0.87", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 566dfde2..e9de03d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "programs/*", "result", "test-programs/*", + "on-demand" ] resolver = "2" @@ -58,6 +59,7 @@ solana-account-info = "3.0" solana-bpf-loader-program = "3.0" solana-clock = "3.0" solana-compute-budget = "3.0" +solana-commitment-config = "3.0" solana-cpi = "3.0" solana-ed25519-program = "3.0" solana-epoch-rewards = "3.0" @@ -78,6 +80,8 @@ solana-program-pack = "3.0" solana-program-runtime = "3.0" solana-pubkey = "3.0" solana-rent = "3.0" +solana-rpc-client = "3.0" +solana-rpc-client-api = "3.0" solana-sdk-ids = "3.0" solana-secp256k1-program = "3.0" solana-secp256r1-program = "3.0" diff --git a/on-demand/Cargo.toml b/on-demand/Cargo.toml new file mode 100644 index 00000000..28e45add --- /dev/null +++ b/on-demand/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mollusk-svm-on-demand" +description = "Automatically fetch and use mainnet accounts with the Mollusk SVM harness." +documentation = "" +authors = ["Leonardo Donatacci ", "Anza Technology Maintainers "] +repository = { workspace = true } +readme = { workspace = true } +license-file = { workspace = true } +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +mollusk-svm = { workspace = true } +solana-account = { workspace = true } +solana-commitment-config = { workspace = true } +solana-instruction = { workspace = true } +solana-pubkey = { workspace = true } +solana-rpc-client = { workspace = true } +solana-rpc-client-api = { workspace = true } +thiserror = { workspace = true } \ No newline at end of file diff --git a/on-demand/src/account_store.rs b/on-demand/src/account_store.rs new file mode 100644 index 00000000..2aed60b9 --- /dev/null +++ b/on-demand/src/account_store.rs @@ -0,0 +1,369 @@ +//! RPC account store for fetching accounts from Solana RPC endpoints. +//! +//! This module provides the `RpcAccountStore` type for automatically fetching +//! accounts from mainnet and managing them for use with Mollusk testing. + +use { + mollusk_svm::Mollusk, + solana_account::Account, + solana_commitment_config::CommitmentConfig, + solana_instruction::Instruction, + solana_pubkey::Pubkey, + solana_rpc_client::nonblocking::rpc_client::RpcClient, + solana_rpc_client_api::client_error::Error as ClientError, + std::collections::{HashMap, HashSet}, + std::fmt, + thiserror::Error, +}; + +/// Validates that the given data contains a valid ELF header. +/// +/// This performs basic validation to ensure the data is likely a valid ELF binary. +fn validate_elf(data: &[u8]) -> Result<(), String> { + // ELF magic number: 0x7F 'E' 'L' 'F' + const ELF_MAGIC: &[u8] = &[0x7F, 0x45, 0x4C, 0x46]; + + if data.len() < 52 { + return Err(format!( + "Data too small to be a valid ELF file: {} bytes (expected at least 52)", + data.len() + )); + } + + if !data.starts_with(ELF_MAGIC) { + return Err(format!( + "Invalid ELF magic number: expected {:?}, got {:?}", + ELF_MAGIC, + &data[..4.min(data.len())] + )); + } + + // Check ELF class (32-bit or 64-bit) + // 1 = 32-bit, 2 = 64-bit + if data[4] != 1 && data[4] != 2 { + return Err(format!("Invalid ELF class: {}", data[4])); + } + + Ok(()) +} + +/// Error types for RPC operations. +#[derive(Debug, Error)] +pub enum RpcError { + #[error("RPC client error: {0}")] + Client(#[from] ClientError), + + #[error("Account not found: {0}")] + AccountNotFound(Pubkey), + + #[error("Invalid program data account for program {program}: {reason}")] + InvalidProgramData { program: Pubkey, reason: String }, + + #[error("Malformed program account {program}: {reason}")] + MalformedProgram { program: Pubkey, reason: String }, +} + +/// Utility for fetching accounts from Solana RPC endpoints. +/// +/// Fetches accounts and stores them internally in a `HashMap`. +/// +/// # Cache Access +/// +/// The `cache` field is publicly accessible for advanced use cases where you need +/// direct access to fetched accounts (e.g., for use with MolluskContext or custom +/// account manipulation). For normal usage, prefer the builder methods. +pub struct RpcAccountStore { + client: RpcClient, + /// Publicly accessible cache of fetched accounts. + /// + /// Use this when you need direct access to accounts for custom operations. + /// Most users should rely on the builder methods instead. + pub cache: HashMap, + /// If true, fetching non-existent accounts will create default (empty) accounts. + /// If false, will return an error when accounts don't exist. + allow_missing_accounts: bool, + /// If true, validates program ELF headers before adding to Mollusk. + validate_programs: bool, +} + +impl fmt::Debug for RpcAccountStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RpcAccountStore") + .field("accounts_cached", &self.cache.len()) + .field("allow_missing_accounts", &self.allow_missing_accounts) + .field("validate_programs", &self.validate_programs) + .finish_non_exhaustive() + } +} + +impl RpcAccountStore { + /// Create a new account fetcher with the default commitment level (confirmed). + /// + /// By default: + /// - Missing accounts will cause an error (use `allow_missing_accounts()` to change) + /// - Program validation is enabled (use `skip_program_validation()` to disable) + pub fn new(rpc_url: impl Into) -> Self { + Self::new_with_commitment(rpc_url, CommitmentConfig::confirmed()) + } + + /// Create a new account fetcher with a specific commitment level. + /// + /// By default: + /// - Missing accounts will cause an error (use `allow_missing_accounts()` to change) + /// - Program validation is enabled (use `skip_program_validation()` to disable) + pub fn new_with_commitment( + rpc_url: impl Into, + commitment: CommitmentConfig, + ) -> Self { + Self { + client: RpcClient::new_with_commitment(rpc_url.into(), commitment), + cache: HashMap::new(), + allow_missing_accounts: false, + validate_programs: true, + } + } + + /// Allow missing accounts to be treated as default (empty) accounts. + /// + /// By default, fetching non-existent accounts returns an error. Use this + /// method when you want to test with accounts that may not exist on-chain. + pub fn allow_missing_accounts(mut self) -> Self { + self.allow_missing_accounts = true; + self + } + + /// Skip ELF validation when adding programs to Mollusk. + /// + /// By default, program ELF headers are validated before adding to Mollusk. + /// Use this to disable validation if you're confident in your program data. + pub fn skip_program_validation(mut self) -> Self { + self.validate_programs = false; + self + } + + /// Fetch accounts required by an instruction. + /// + /// Extracts all account pubkeys from the instruction's account metas + /// and fetches them from the RPC endpoint using getMultipleAccounts. + pub async fn from_instruction( + mut self, + instruction: &Instruction, + ) -> Result { + let pubkeys: Vec<_> = instruction.accounts.iter().map(|m| m.pubkey).collect(); + self.fetch_accounts(&pubkeys).await?; + Ok(self) + } + + /// Fetch accounts for multiple instructions. + /// + /// Collects all unique pubkeys across all instructions and fetches them + /// efficiently in a batch using getMultipleAccounts. + pub async fn from_instructions( + mut self, + instructions: &[Instruction], + ) -> Result { + let pubkeys: HashSet = instructions + .iter() + .flat_map(|ix| ix.accounts.iter().map(|m| m.pubkey)) + .collect(); + + self.fetch_accounts(&pubkeys.into_iter().collect::>()) + .await?; + Ok(self) + } + + /// Add accounts to the store. + pub fn with_accounts(mut self, accounts: &[(Pubkey, Account)]) -> Self { + for (pubkey, account) in accounts { + self.cache.insert(*pubkey, account.clone()); + } + self + } + + /// Internal method to fetch accounts from RPC using `getMultipleAccounts`. + /// + /// Only fetches accounts that aren't already in the cache, allowing for + /// efficient incremental fetching. + async fn fetch_accounts(&mut self, pubkeys: &[Pubkey]) -> Result<(), RpcError> { + // Filter out already cached accounts + let missing_pubkeys: Vec = pubkeys + .iter() + .filter(|pubkey| !self.cache.contains_key(pubkey)) + .copied() + .collect(); + + if missing_pubkeys.is_empty() { + return Ok(()); + } + + let accounts = self.client.get_multiple_accounts(&missing_pubkeys).await?; + + // Store fetched accounts in cache + for (pubkey, account_opt) in missing_pubkeys.iter().zip(accounts) { + match account_opt { + Some(account) => { + self.cache.insert(*pubkey, account); + } + None => { + if self.allow_missing_accounts { + // Create a default (empty) account for missing accounts + self.cache.insert(*pubkey, Account::default()); + } else { + // Return an error if the account doesn't exist + return Err(RpcError::AccountNotFound(*pubkey)); + } + } + } + } + + Ok(()) + } + + /// Add programs to the Mollusk environment. + /// + /// This function fetches the program data accounts for all programs that are + /// stored in the cache and adds them to the Mollusk environment. + /// + /// Note: This is needed because mollusk-svm doesn't load the programs for CPIs directly from the accounts. + /// + /// # Errors + /// + /// Returns an error if: + /// - Program account data is malformed + /// - Program data account is invalid or missing + /// - ELF validation fails (if enabled) + pub async fn add_programs(mut self, mollusk: &mut Mollusk) -> Result { + // First pass: collect program data pubkeys that need to be fetched + let mut program_data_pubkeys = Vec::new(); + for (pubkey, account) in self.cache.iter() { + if account.executable && account.owner == mollusk_svm::program::loader_keys::LOADER_V3 { + if account.data.len() < 36 { + return Err(RpcError::MalformedProgram { + program: *pubkey, + reason: format!( + "BPF Loader v3 program account too small: {} bytes (expected at least 36)", + account.data.len() + ), + }); + } + + let program_data_pubkey = Pubkey::try_from(&account.data[4..36]).map_err(|e| { + RpcError::MalformedProgram { + program: *pubkey, + reason: format!("Invalid program data pubkey: {}", e), + } + })?; + + if !self.cache.contains_key(&program_data_pubkey) { + program_data_pubkeys.push(program_data_pubkey); + } + } + } + + // Fetch all program data accounts at once + if !program_data_pubkeys.is_empty() { + self.fetch_accounts(&program_data_pubkeys).await?; + } + + // Second pass: add programs to mollusk + for (pubkey, account) in self.cache.iter() { + if account.executable { + // For BPF Loader v2 programs, the ELF is directly in the account data + if account.owner == mollusk_svm::program::loader_keys::LOADER_V2 { + if self.validate_programs { + validate_elf(&account.data).map_err(|reason| { + RpcError::InvalidProgramData { + program: *pubkey, + reason, + } + })?; + } + + mollusk.add_program_with_elf_and_loader( + pubkey, + &account.data, + &account.owner, + ); + } + // For BPF Loader v3 + else if account.owner == mollusk_svm::program::loader_keys::LOADER_V3 { + if account.data.len() < 36 { + return Err(RpcError::MalformedProgram { + program: *pubkey, + reason: format!( + "BPF Loader v3 program account too small: {} bytes (expected at least 36)", + account.data.len() + ), + }); + } + + let program_data_pubkey = Pubkey::try_from(&account.data[4..36]).map_err(|e| { + RpcError::MalformedProgram { + program: *pubkey, + reason: format!("Invalid program data pubkey: {}", e), + } + })?; + + let program_data_account = self.cache.get(&program_data_pubkey).ok_or_else(|| { + RpcError::InvalidProgramData { + program: *pubkey, + reason: format!("Program data account not found: {}", program_data_pubkey), + } + })?; + + // The ELF starts at offset 45 in the program data account + // (first 45 bytes are the ProgramData header) + if program_data_account.data.len() <= 45 { + return Err(RpcError::InvalidProgramData { + program: *pubkey, + reason: format!( + "Program data account too small: {} bytes (expected > 45)", + program_data_account.data.len() + ), + }); + } + + let elf_data = &program_data_account.data[45..]; + + if self.validate_programs { + validate_elf(elf_data).map_err(|reason| { + RpcError::InvalidProgramData { + program: *pubkey, + reason, + } + })?; + } + + mollusk.add_program_with_elf_and_loader( + pubkey, + elf_data, + &account.owner, + ); + } + } + } + + Ok(self) + } + + + /// Sync the Mollusk environment to the current mainnet slot. + /// + /// This function fetches the current slot from the RPC endpoint and updates + /// the Mollusk instance to use that slot by calling `warp_to_slot`. + /// + /// Note: This is useful for oracles that need to be synced to the current mainnet slot. + pub async fn with_synced_slot(self, mollusk: &mut Mollusk) -> Result { + let slot = self.client.get_slot().await?; + mollusk.warp_to_slot(slot); + Ok(self) + } + + /// Deprecated: Use `with_synced_slot` instead. + /// + /// This method will be removed in a future version. + #[deprecated(since = "0.0.1", note = "Use `with_synced_slot` instead")] + pub async fn sync_slot(self, mollusk: &mut Mollusk) -> Result { + self.with_synced_slot(mollusk).await + } +} diff --git a/on-demand/src/lib.rs b/on-demand/src/lib.rs new file mode 100644 index 00000000..a94279e6 --- /dev/null +++ b/on-demand/src/lib.rs @@ -0,0 +1,328 @@ +//! # Mollusk On-Demand +//! +//! Simplify Solana program testing with Mollusk by automatically fetching mainnet accounts. +//! +//! This crate provides utility functions for testing Solana programs with real mainnet state, +//! built on top of [Mollusk](https://github.com/buffalojoec/mollusk). The functions automatically +//! fetch accounts from RPC and execute using Mollusk's `MolluskContext`. +//! +//! # Quick Start +//! +//! ```rust,ignore +//! use mollusk_on_demand::process_instruction_with_context; +//! use mollusk_svm::Mollusk; +//! +//! #[tokio::test] +//! async fn test_with_mainnet_accounts() -> Result<(), Box> { +//! let mollusk = Mollusk::new(&program_id, "program_name"); +//! +//! // Process instruction - accounts are fetched automatically! +//! let result = process_instruction_with_context( +//! "https://api.mainnet-beta.solana.com", +//! mollusk, +//! &instruction, +//! &[] +//! ).await?; +//! +//! Ok(()) +//! } +//! ``` + +pub mod account_store; + +use { + account_store::{RpcAccountStore, RpcError}, + mollusk_svm::{result::InstructionResult, Mollusk}, + solana_account::Account, + solana_instruction::Instruction, + solana_pubkey::Pubkey, +}; + +/// Result type for the harness functions. +pub type Result = std::result::Result; + +// ============================================================================= +// Permissive variants (allow missing accounts, skip validation) +// ============================================================================= + +/// Process an instruction with automatic account fetching (permissive mode). +/// +/// This variant: +/// - Allows missing accounts (creates default empty accounts) +/// - Skips ELF validation +/// +/// # Example +/// +/// ```rust,ignore +/// let mollusk = Mollusk::new(&program_id, "program_name"); +/// let result = process_instruction_with_context( +/// "https://api.mainnet-beta.solana.com", +/// mollusk, +/// &instruction, +/// &[(pubkey, account)] // Optional: provide mocked accounts +/// ).await?; +/// ``` +pub async fn process_instruction_with_context( + rpc_url: &str, + mut mollusk: Mollusk, + instruction: &Instruction, + accounts: &[(Pubkey, Account)], +) -> Result { + let cache = RpcAccountStore::new(rpc_url) + .allow_missing_accounts() + .skip_program_validation() + .with_accounts(accounts) + .from_instruction(instruction) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_instruction(instruction)) +} + +/// Process a chain of instructions with automatic account fetching (permissive mode). +/// +/// # Example +/// +/// ```rust,ignore +/// let result = process_instruction_chain_with_context( +/// rpc_url, +/// mollusk, +/// &[ix1, ix2, ix3], +/// &[] +/// ).await?; +/// ``` +pub async fn process_instruction_chain_with_context( + rpc_url: &str, + mut mollusk: Mollusk, + instructions: &[Instruction], + accounts: &[(Pubkey, Account)], +) -> Result { + let cache = RpcAccountStore::new(rpc_url) + .allow_missing_accounts() + .skip_program_validation() + .with_accounts(accounts) + .from_instructions(instructions) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_instruction_chain(instructions)) +} + +/// Process an instruction with validation checks (permissive mode). +/// +/// # Example +/// +/// ```rust,ignore +/// use mollusk_svm::result::Check; +/// +/// let checks = vec![Check::success()]; +/// process_and_validate_instruction_with_context( +/// rpc_url, +/// mollusk, +/// &instruction, +/// &[], +/// &checks +/// ).await?; +/// ``` +pub async fn process_and_validate_instruction_with_context( + rpc_url: &str, + mut mollusk: Mollusk, + instruction: &Instruction, + accounts: &[(Pubkey, Account)], + checks: &[mollusk_svm::result::Check<'_>], +) -> Result { + let cache = RpcAccountStore::new(rpc_url) + .allow_missing_accounts() + .skip_program_validation() + .with_accounts(accounts) + .from_instruction(instruction) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_and_validate_instruction(instruction, checks)) +} + +/// Process a chain of instructions with validation checks (permissive mode). +/// +/// # Example +/// +/// ```rust,ignore +/// use mollusk_svm::result::Check; +/// +/// let checks1 = vec![Check::success()]; +/// let checks2 = vec![Check::success()]; +/// +/// process_and_validate_instruction_chain_with_context( +/// rpc_url, +/// mollusk, +/// &[(&ix1, &checks1[..]), (&ix2, &checks2[..])], +/// &[] +/// ).await?; +/// ``` +pub async fn process_and_validate_instruction_chain_with_context( + rpc_url: &str, + mut mollusk: Mollusk, + instructions: &[(&Instruction, &[mollusk_svm::result::Check<'_>])], + accounts: &[(Pubkey, Account)], +) -> Result { + let instructions_only: Vec<_> = instructions.iter().map(|(ix, _)| (*ix).clone()).collect(); + + let cache = RpcAccountStore::new(rpc_url) + .allow_missing_accounts() + .skip_program_validation() + .with_accounts(accounts) + .from_instructions(&instructions_only) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_and_validate_instruction_chain(instructions)) +} + +// ============================================================================= +// Strict variants (error on missing accounts, validate ELF) +// ============================================================================= + +/// Process an instruction with automatic account fetching (strict mode). +/// +/// This variant: +/// - Errors if any account is missing +/// - Validates program ELF headers +/// +/// # Example +/// +/// ```rust,ignore +/// let result = process_instruction_with_context_strict( +/// rpc_url, +/// mollusk, +/// &instruction, +/// &[] +/// ).await?; +/// ``` +pub async fn process_instruction_with_context_strict( + rpc_url: &str, + mut mollusk: Mollusk, + instruction: &Instruction, + accounts: &[(Pubkey, Account)], +) -> Result { + let cache = RpcAccountStore::new(rpc_url) + .with_accounts(accounts) + .from_instruction(instruction) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_instruction(instruction)) +} + +/// Process a chain of instructions with automatic account fetching (strict mode). +/// +/// # Example +/// +/// ```rust,ignore +/// let result = process_instruction_chain_with_context_strict( +/// rpc_url, +/// mollusk, +/// &[ix1, ix2, ix3], +/// &[] +/// ).await?; +/// ``` +pub async fn process_instruction_chain_with_context_strict( + rpc_url: &str, + mut mollusk: Mollusk, + instructions: &[Instruction], + accounts: &[(Pubkey, Account)], +) -> Result { + let cache = RpcAccountStore::new(rpc_url) + .with_accounts(accounts) + .from_instructions(instructions) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_instruction_chain(instructions)) +} + +/// Process an instruction with validation checks (strict mode). +/// +/// # Example +/// +/// ```rust,ignore +/// use mollusk_svm::result::Check; +/// +/// let checks = vec![Check::success()]; +/// process_and_validate_instruction_with_context_strict( +/// rpc_url, +/// mollusk, +/// &instruction, +/// &[], +/// &checks +/// ).await?; +/// ``` +pub async fn process_and_validate_instruction_with_context_strict( + rpc_url: &str, + mut mollusk: Mollusk, + instruction: &Instruction, + accounts: &[(Pubkey, Account)], + checks: &[mollusk_svm::result::Check<'_>], +) -> Result { + let cache = RpcAccountStore::new(rpc_url) + .with_accounts(accounts) + .from_instruction(instruction) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_and_validate_instruction(instruction, checks)) +} + +/// Process a chain of instructions with validation checks (strict mode). +/// +/// # Example +/// +/// ```rust,ignore +/// use mollusk_svm::result::Check; +/// +/// process_and_validate_instruction_chain_with_context_strict( +/// rpc_url, +/// mollusk, +/// &[(&ix1, &checks1[..]), (&ix2, &checks2[..])], +/// &[] +/// ).await?; +/// ``` +pub async fn process_and_validate_instruction_chain_with_context_strict( + rpc_url: &str, + mut mollusk: Mollusk, + instructions: &[(&Instruction, &[mollusk_svm::result::Check<'_>])], + accounts: &[(Pubkey, Account)], +) -> Result { + let instructions_only: Vec<_> = instructions.iter().map(|(ix, _)| (*ix).clone()).collect(); + + let cache = RpcAccountStore::new(rpc_url) + .with_accounts(accounts) + .from_instructions(&instructions_only) + .await? + .add_programs(&mut mollusk) + .await? + .cache; + + let context = mollusk.with_context(cache); + Ok(context.process_and_validate_instruction_chain(instructions)) +} From 8519ce57460c87df77252a11919c48ac580fa95b Mon Sep 17 00:00:00 2001 From: L0STE <125566964+L0STE@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:19:52 +0100 Subject: [PATCH 2/2] Nit --- on-demand/src/account_store.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/on-demand/src/account_store.rs b/on-demand/src/account_store.rs index 2aed60b9..9d02d3be 100644 --- a/on-demand/src/account_store.rs +++ b/on-demand/src/account_store.rs @@ -358,12 +358,4 @@ impl RpcAccountStore { mollusk.warp_to_slot(slot); Ok(self) } - - /// Deprecated: Use `with_synced_slot` instead. - /// - /// This method will be removed in a future version. - #[deprecated(since = "0.0.1", note = "Use `with_synced_slot` instead")] - pub async fn sync_slot(self, mollusk: &mut Mollusk) -> Result { - self.with_synced_slot(mollusk).await - } }