diff --git a/Cargo.lock b/Cargo.lock
index e47dbc7b..332ee740 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1268,6 +1268,22 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+
[[package]]
name = "feature-probe"
version = "0.1.1"
@@ -1592,6 +1608,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
+ "serde",
+ "serde_core",
]
[[package]]
@@ -1778,6 +1796,12 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
[[package]]
name = "litesvm"
version = "0.11.0"
@@ -1905,6 +1929,33 @@ dependencies = [
"solana-transaction-error",
]
+[[package]]
+name = "litesvm-persistence"
+version = "0.11.0"
+dependencies = [
+ "agave-feature-set",
+ "bincode",
+ "indexmap",
+ "litesvm",
+ "serde",
+ "solana-account",
+ "solana-address 2.5.0",
+ "solana-clock",
+ "solana-compute-budget",
+ "solana-fee-structure",
+ "solana-hash 3.1.0",
+ "solana-instruction",
+ "solana-keypair",
+ "solana-message",
+ "solana-native-token",
+ "solana-signature",
+ "solana-signer",
+ "solana-system-interface 2.0.0",
+ "solana-transaction",
+ "tempfile",
+ "thiserror 2.0.18",
+]
+
[[package]]
name = "litesvm-token"
version = "0.11.0"
@@ -2614,6 +2665,19 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -4538,6 +4602,19 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
[[package]]
name = "test-log"
version = "0.2.19"
diff --git a/Cargo.toml b/Cargo.toml
index d56abc79..b852e134 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ indexmap = "2.12"
itertools = "0.14"
libsecp256k1 = "0.6.0"
litesvm = { path = "crates/litesvm", version = "0.11" }
+litesvm-persistence = { path = "crates/persistence", version = "0.11" }
log = "0.4"
napi = { version = "3.8.3", default-features = false }
napi-build = "2.3.1"
@@ -90,6 +91,7 @@ solana-vote-interface = "5.0.0"
spl-associated-token-account-interface = "2.0.0"
spl-token-2022-interface = "2.0.0"
spl-token-interface = "2.0.0"
+tempfile = "3"
test-log = "0.2"
thiserror = "2.0"
diff --git a/crates/litesvm/Cargo.toml b/crates/litesvm/Cargo.toml
index 4af9adc2..490237e3 100644
--- a/crates/litesvm/Cargo.toml
+++ b/crates/litesvm/Cargo.toml
@@ -14,6 +14,7 @@ invocation-inspect-callback = []
nodejs-internal = ["dep:qualifier_attr"]
hashbrown = ["dep:hashbrown"]
serde = []
+persistence-internal = ["serde"]
precompiles = ["dep:agave-precompiles"]
register-tracing = ["invocation-inspect-callback", "dep:hex", "dep:sha2"]
diff --git a/crates/litesvm/src/accounts_db.rs b/crates/litesvm/src/accounts_db.rs
index 4dad8daf..a2152422 100644
--- a/crates/litesvm/src/accounts_db.rs
+++ b/crates/litesvm/src/accounts_db.rs
@@ -213,6 +213,39 @@ impl AccountsDb {
self.inner.insert(address, data);
}
+ /// Rebuilds the sysvar cache from account data already present in `self.inner`.
+ #[cfg(feature = "persistence-internal")]
+ pub(crate) fn rebuild_sysvar_cache(&mut self) {
+ self.sysvar_cache.reset();
+ let accounts = &self.inner;
+ self.sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
+ if let Some(acc) = accounts.get(pubkey) {
+ set_sysvar(acc.data())
+ }
+ });
+ if let Ok(clock) = self.sysvar_cache.get_clock() {
+ self.programs_cache.set_slot_for_tests(clock.slot);
+ }
+ }
+
+ /// Scans all accounts for executable BPF programs and loads them into the program cache.
+ #[cfg(feature = "persistence-internal")]
+ pub(crate) fn load_all_existing_programs(&mut self) -> Result<(), LiteSVMError> {
+ let executable_keys: Vec
= self
+ .inner
+ .iter()
+ .filter(|(_, acc)| acc.executable() && acc.owner() != &native_loader::ID)
+ .map(|(k, _)| *k)
+ .collect();
+
+ for key in executable_keys {
+ let account = self.inner.get(&key).unwrap().clone();
+ let loaded = self.load_program(&account)?;
+ self.programs_cache.replenish(key, Arc::new(loaded));
+ }
+ Ok(())
+ }
+
pub(crate) fn sync_accounts(
&mut self,
mut accounts: Vec<(Address, AccountSharedData)>,
@@ -236,7 +269,11 @@ impl AccountsDb {
let owner = program_account.owner();
let program_runtime_v1 = self.environments.program_runtime_v1.clone();
- let slot = self.sysvar_cache.get_clock().unwrap().slot;
+ let slot = self
+ .sysvar_cache
+ .get_clock()
+ .map(|c| c.slot)
+ .unwrap_or(0);
if bpf_loader::check_id(owner) || bpf_loader_deprecated::check_id(owner) {
ProgramCacheEntry::new(
diff --git a/crates/litesvm/src/history.rs b/crates/litesvm/src/history.rs
index 13596fe3..551c36a9 100644
--- a/crates/litesvm/src/history.rs
+++ b/crates/litesvm/src/history.rs
@@ -34,4 +34,21 @@ impl TransactionHistory {
pub fn check_transaction(&self, signature: &Signature) -> bool {
self.0.contains_key(signature)
}
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn entries(&self) -> &IndexMap {
+ &self.0
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn capacity(&self) -> usize {
+ self.0.capacity()
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn from_entries(entries: IndexMap, capacity: usize) -> Self {
+ let mut history = TransactionHistory(entries);
+ history.set_capacity(capacity);
+ history
+ }
}
diff --git a/crates/litesvm/src/lib.rs b/crates/litesvm/src/lib.rs
index c199d450..d95a0ba4 100644
--- a/crates/litesvm/src/lib.rs
+++ b/crates/litesvm/src/lib.rs
@@ -374,6 +374,9 @@ use {
},
};
+#[cfg(feature = "persistence-internal")]
+use indexmap::IndexMap;
+
pub mod error;
pub mod types;
@@ -1678,6 +1681,90 @@ impl LiteSVM {
self
}
+
+ // ── persistence-internal: getters ──────────────────────────────────
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn airdrop_keypair_bytes(&self) -> &[u8; 64] {
+ &self.airdrop_kp
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn get_blockhash_check(&self) -> bool {
+ self.blockhash_check
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn get_fee_structure(&self) -> &FeeStructure {
+ &self.fee_structure
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn get_log_bytes_limit(&self) -> Option {
+ self.log_bytes_limit
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn get_feature_set_ref(&self) -> &FeatureSet {
+ &self.feature_set
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn transaction_history_entries(&self) -> &IndexMap {
+ self.history.entries()
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn transaction_history_capacity(&self) -> usize {
+ self.history.capacity()
+ }
+
+ // ── persistence-internal: setters ──────────────────────────────────
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn set_latest_blockhash(&mut self, hash: Hash) {
+ self.latest_blockhash = hash;
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn set_airdrop_keypair(&mut self, kp: [u8; 64]) {
+ self.airdrop_kp = kp;
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn set_account_no_checks(&mut self, pubkey: Address, account: AccountSharedData) {
+ self.accounts.add_account_no_checks(pubkey, account);
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn restore_transaction_history(
+ &mut self,
+ entries: IndexMap,
+ capacity: usize,
+ ) {
+ self.history = TransactionHistory::from_entries(entries, capacity);
+ }
+
+ #[cfg(feature = "persistence-internal")]
+ pub fn set_fee_structure(&mut self, fee_structure: FeeStructure) {
+ self.fee_structure = fee_structure;
+ }
+
+ // ── persistence-internal: cache rebuild ────────────────────────────
+
+ /// Rebuilds all derived caches after bulk account insertion.
+ ///
+ /// Must be called after restoring accounts via `set_account_no_checks`.
+ /// Order matters: environments first, then sysvars, then BPF programs.
+ #[cfg(feature = "persistence-internal")]
+ pub fn rebuild_caches(&mut self) -> Result<(), LiteSVMError> {
+ self.reserved_account_keys =
+ Self::reserved_account_keys_for_feature_set(&self.feature_set);
+ self.set_builtins();
+ self.accounts.rebuild_sysvar_cache();
+ self.accounts.load_all_existing_programs()?;
+ Ok(())
+ }
}
struct CheckAndProcessTransactionSuccessCore<'ix_data> {
diff --git a/crates/persistence/Cargo.toml b/crates/persistence/Cargo.toml
new file mode 100644
index 00000000..3e74ce6f
--- /dev/null
+++ b/crates/persistence/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "litesvm-persistence"
+description = "Save and load LiteSVM state snapshots"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+litesvm = { workspace = true, features = ["persistence-internal"] }
+bincode.workspace = true
+serde = { workspace = true, features = ["derive"] }
+indexmap = { workspace = true, features = ["serde"] }
+solana-account.workspace = true
+solana-address.workspace = true
+solana-compute-budget.workspace = true
+solana-fee-structure.workspace = true
+solana-hash.workspace = true
+solana-signature.workspace = true
+agave-feature-set = { workspace = true, features = ["agave-unstable-api"] }
+thiserror.workspace = true
+
+[dev-dependencies]
+solana-instruction.workspace = true
+solana-keypair.workspace = true
+solana-signer.workspace = true
+solana-system-interface = { workspace = true, features = ["bincode"] }
+solana-native-token.workspace = true
+solana-message.workspace = true
+solana-transaction = { workspace = true, features = ["verify"] }
+solana-clock.workspace = true
+tempfile.workspace = true
+
+[lints]
+workspace = true
diff --git a/crates/persistence/src/error.rs b/crates/persistence/src/error.rs
new file mode 100644
index 00000000..8e32a974
--- /dev/null
+++ b/crates/persistence/src/error.rs
@@ -0,0 +1,15 @@
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum PersistenceError {
+ #[error("IO error: {0}")]
+ Io(#[from] std::io::Error),
+ #[error("serialization error: {0}")]
+ Serialize(#[from] bincode::Error),
+ #[error("unsupported snapshot version: {0}")]
+ UnsupportedVersion(u8),
+ #[error("failed to rebuild caches: {0}")]
+ CacheRebuild(#[from] litesvm::error::LiteSVMError),
+ #[error("serialization thread panicked")]
+ ThreadPanic,
+}
diff --git a/crates/persistence/src/lib.rs b/crates/persistence/src/lib.rs
new file mode 100644
index 00000000..b5dae152
--- /dev/null
+++ b/crates/persistence/src/lib.rs
@@ -0,0 +1,165 @@
+mod error;
+mod types;
+
+pub use error::PersistenceError;
+
+use {
+ litesvm::LiteSVM,
+ std::{
+ fs::File,
+ io::{BufReader, BufWriter, Read, Write},
+ path::Path,
+ },
+ types::{FeatureSetSnapshot, LiteSvmSnapshot},
+};
+
+const STATE_VERSION: u8 = 1;
+const LARGE_STACK_SIZE: usize = 64 * 1024 * 1024; // 64 MB
+
+fn extract_snapshot(svm: &LiteSVM) -> LiteSvmSnapshot {
+ LiteSvmSnapshot {
+ accounts: svm
+ .accounts_db()
+ .inner
+ .iter()
+ .map(|(k, v)| (*k, v.clone()))
+ .collect(),
+ airdrop_kp: svm.airdrop_keypair_bytes().to_vec(),
+ feature_set: FeatureSetSnapshot::from_feature_set(svm.get_feature_set_ref()),
+ latest_blockhash: svm.latest_blockhash(),
+ history: svm
+ .transaction_history_entries()
+ .iter()
+ .map(|(k, v)| (*k, v.clone()))
+ .collect(),
+ history_capacity: svm.transaction_history_capacity(),
+ compute_budget: svm.get_compute_budget(),
+ sigverify: svm.get_sigverify(),
+ blockhash_check: svm.get_blockhash_check(),
+ fee_structure: svm.get_fee_structure().clone(),
+ log_bytes_limit: svm.get_log_bytes_limit(),
+ }
+}
+
+fn restore_from_snapshot(snapshot: LiteSvmSnapshot) -> Result {
+ let feature_set = snapshot.feature_set.into_feature_set();
+ let mut svm = LiteSVM::default().with_feature_set(feature_set);
+
+ // Set scalar config
+ svm = svm
+ .with_sigverify(snapshot.sigverify)
+ .with_blockhash_check(snapshot.blockhash_check)
+ .with_log_bytes_limit(snapshot.log_bytes_limit);
+
+ if let Some(cb) = snapshot.compute_budget {
+ svm = svm.with_compute_budget(cb);
+ }
+
+ svm.set_fee_structure(snapshot.fee_structure);
+ svm.set_latest_blockhash(snapshot.latest_blockhash);
+
+ let airdrop_kp: [u8; 64] = snapshot
+ .airdrop_kp
+ .try_into()
+ .map_err(|_| PersistenceError::Serialize(
+ Box::new(bincode::ErrorKind::Custom("invalid airdrop keypair length".into())).into(),
+ ))?;
+ svm.set_airdrop_keypair(airdrop_kp);
+
+ // Pass 1: insert all accounts without triggering cache updates
+ for (address, account) in snapshot.accounts {
+ svm.set_account_no_checks(address, account);
+ }
+
+ // Restore transaction history with original capacity
+ svm.restore_transaction_history(
+ snapshot.history.into_iter().collect(),
+ snapshot.history_capacity,
+ );
+
+ // Pass 2: rebuild all derived caches
+ svm.rebuild_caches()?;
+
+ Ok(svm)
+}
+
+fn serialize_snapshot(snapshot: &LiteSvmSnapshot) -> Result, PersistenceError> {
+ let mut buf = vec![STATE_VERSION];
+ bincode::serialize_into(&mut buf, snapshot)?;
+ Ok(buf)
+}
+
+fn deserialize_snapshot(bytes: &[u8]) -> Result {
+ if bytes.is_empty() {
+ return Err(PersistenceError::Serialize(Box::new(bincode::ErrorKind::Custom(
+ "empty input".into(),
+ )).into()));
+ }
+ let version = bytes[0];
+ if version != STATE_VERSION {
+ return Err(PersistenceError::UnsupportedVersion(version));
+ }
+ let snapshot: LiteSvmSnapshot = bincode::deserialize(&bytes[1..])?;
+ Ok(snapshot)
+}
+
+/// Runs `f` on a thread with a large stack to prevent stack overflow
+/// during bincode serialization/deserialization of large account maps.
+fn on_large_stack(f: F) -> Result
+where
+ F: FnOnce() -> Result + Send + 'static,
+ T: Send + 'static,
+{
+ let handle = std::thread::Builder::new()
+ .stack_size(LARGE_STACK_SIZE)
+ .name("litesvm-persistence".into())
+ .spawn(f)
+ .map_err(PersistenceError::Io)?;
+
+ handle.join().map_err(|_| PersistenceError::ThreadPanic)?
+}
+
+/// Saves the full LiteSVM state to a file.
+pub fn save_to_file(svm: &LiteSVM, path: impl AsRef) -> Result<(), PersistenceError> {
+ let snapshot = extract_snapshot(svm);
+ let path = path.as_ref().to_path_buf();
+ on_large_stack(move || {
+ let file = File::create(&path)?;
+ let mut writer = BufWriter::new(file);
+ writer.write_all(&[STATE_VERSION])?;
+ bincode::serialize_into(&mut writer, &snapshot)?;
+ writer.flush()?;
+ Ok(())
+ })
+}
+
+/// Loads a full LiteSVM state from a file.
+pub fn load_from_file(path: impl AsRef) -> Result {
+ let path = path.as_ref().to_path_buf();
+ on_large_stack(move || {
+ let file = File::open(&path)?;
+ let mut reader = BufReader::new(file);
+ let mut version = [0u8; 1];
+ reader.read_exact(&mut version)?;
+ if version[0] != STATE_VERSION {
+ return Err(PersistenceError::UnsupportedVersion(version[0]));
+ }
+ let snapshot: LiteSvmSnapshot = bincode::deserialize_from(&mut reader)?;
+ restore_from_snapshot(snapshot)
+ })
+}
+
+/// Serializes the full LiteSVM state to bytes.
+pub fn to_bytes(svm: &LiteSVM) -> Result, PersistenceError> {
+ let snapshot = extract_snapshot(svm);
+ on_large_stack(move || serialize_snapshot(&snapshot))
+}
+
+/// Deserializes the full LiteSVM state from bytes.
+pub fn from_bytes(bytes: &[u8]) -> Result {
+ let bytes = bytes.to_vec();
+ on_large_stack(move || {
+ let snapshot = deserialize_snapshot(&bytes)?;
+ restore_from_snapshot(snapshot)
+ })
+}
diff --git a/crates/persistence/src/types.rs b/crates/persistence/src/types.rs
new file mode 100644
index 00000000..68fd5147
--- /dev/null
+++ b/crates/persistence/src/types.rs
@@ -0,0 +1,161 @@
+use {
+ agave_feature_set::FeatureSet,
+ litesvm::types::TransactionResult,
+ serde::{Deserialize, Serialize},
+ solana_account::AccountSharedData,
+ solana_address::Address,
+ solana_compute_budget::compute_budget::ComputeBudget,
+ solana_fee_structure::{FeeBin, FeeStructure},
+ solana_hash::Hash,
+ solana_signature::Signature,
+};
+
+// ── Serde remote definitions for upstream types without serde ──────────
+
+#[derive(Serialize, Deserialize)]
+#[serde(remote = "FeeBin")]
+struct FeeBinDef {
+ pub limit: u64,
+ pub fee: u64,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(remote = "FeeStructure")]
+struct FeeStructureDef {
+ pub lamports_per_signature: u64,
+ pub lamports_per_write_lock: u64,
+ #[serde(with = "fee_bin_vec")]
+ pub compute_fee_bins: Vec,
+}
+
+/// Helper module to serialize `Vec` using the remote definition.
+mod fee_bin_vec {
+ use super::*;
+ use serde::{Deserializer, Serializer};
+
+ #[derive(Serialize, Deserialize)]
+ struct FeeBinWrapper(#[serde(with = "FeeBinDef")] FeeBin);
+
+ pub fn serialize(v: &[FeeBin], s: S) -> Result {
+ let wrappers: Vec = v.iter().map(|b| FeeBinWrapper(b.clone())).collect();
+ wrappers.serialize(s)
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> {
+ let wrappers = Vec::::deserialize(d)?;
+ Ok(wrappers.into_iter().map(|w| w.0).collect())
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(remote = "ComputeBudget")]
+struct ComputeBudgetDef {
+ pub compute_unit_limit: u64,
+ pub log_64_units: u64,
+ pub create_program_address_units: u64,
+ pub invoke_units: u64,
+ pub max_instruction_stack_depth: usize,
+ pub max_instruction_trace_length: usize,
+ pub sha256_base_cost: u64,
+ pub sha256_byte_cost: u64,
+ pub sha256_max_slices: u64,
+ pub max_call_depth: usize,
+ pub stack_frame_size: usize,
+ pub log_pubkey_units: u64,
+ pub cpi_bytes_per_unit: u64,
+ pub sysvar_base_cost: u64,
+ pub secp256k1_recover_cost: u64,
+ pub syscall_base_cost: u64,
+ pub curve25519_edwards_validate_point_cost: u64,
+ pub curve25519_edwards_add_cost: u64,
+ pub curve25519_edwards_subtract_cost: u64,
+ pub curve25519_edwards_multiply_cost: u64,
+ pub curve25519_edwards_msm_base_cost: u64,
+ pub curve25519_edwards_msm_incremental_cost: u64,
+ pub curve25519_ristretto_validate_point_cost: u64,
+ pub curve25519_ristretto_add_cost: u64,
+ pub curve25519_ristretto_subtract_cost: u64,
+ pub curve25519_ristretto_multiply_cost: u64,
+ pub curve25519_ristretto_msm_base_cost: u64,
+ pub curve25519_ristretto_msm_incremental_cost: u64,
+ pub heap_size: u32,
+ pub heap_cost: u64,
+ pub mem_op_base_cost: u64,
+ pub alt_bn128_addition_cost: u64,
+ pub alt_bn128_multiplication_cost: u64,
+ pub alt_bn128_pairing_one_pair_cost_first: u64,
+ pub alt_bn128_pairing_one_pair_cost_other: u64,
+ pub big_modular_exponentiation_base_cost: u64,
+ pub big_modular_exponentiation_cost_divisor: u64,
+ pub poseidon_cost_coefficient_a: u64,
+ pub poseidon_cost_coefficient_c: u64,
+ pub get_remaining_compute_units_cost: u64,
+ pub alt_bn128_g1_compress: u64,
+ pub alt_bn128_g1_decompress: u64,
+ pub alt_bn128_g2_compress: u64,
+ pub alt_bn128_g2_decompress: u64,
+}
+
+/// Helper module to serialize `Option` using the remote definition.
+mod compute_budget_option {
+ use super::*;
+ use serde::{Deserializer, Serializer};
+
+ #[derive(Serialize, Deserialize)]
+ struct Wrapper(#[serde(with = "ComputeBudgetDef")] ComputeBudget);
+
+ pub fn serialize(v: &Option, s: S) -> Result {
+ v.as_ref().map(|cb| Wrapper(*cb)).serialize(s)
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(
+ d: D,
+ ) -> Result