diff --git a/Cargo.lock b/Cargo.lock index b257ddd582124e..f7cbf06831bc11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6901,6 +6901,16 @@ dependencies = [ "trees", ] +[[package]] +name = "solana-limited-deserialize" +version = "2.1.0" +dependencies = [ + "bincode", + "serde", + "solana-instruction", + "solana-program", +] + [[package]] name = "solana-loader-v4-program" version = "2.1.0" @@ -7232,6 +7242,7 @@ dependencies = [ "solana-frozen-abi-macro", "solana-hash", "solana-instruction", + "solana-limited-deserialize", "solana-logger", "solana-msg", "solana-program-error", diff --git a/Cargo.toml b/Cargo.toml index c4bd10559a8c17..880640cef2e10b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ members = [ "sdk/gen-headers", "sdk/hash", "sdk/instruction", + "sdk/limited-deserialize", "sdk/macro", "sdk/msg", "sdk/package-metadata-macro", @@ -409,6 +410,7 @@ solana-inline-spl = { path = "inline-spl", version = "=2.1.0" } solana-instruction = { path = "sdk/instruction", version = "=2.1.0", default-features = false } solana-lattice-hash = { path = "lattice-hash", version = "=2.1.0" } solana-ledger = { path = "ledger", version = "=2.1.0" } +solana-limited-deserialize = { path = "sdk/limited-deserialize", version = "=2.1.0" } solana-loader-v4-program = { path = "programs/loader-v4", version = "=2.1.0" } solana-local-cluster = { path = "local-cluster", version = "=2.1.0" } solana-log-collector = { path = "log-collector", version = "=2.1.0" } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 626f2624711289..2655923d0bf540 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5463,6 +5463,15 @@ dependencies = [ "trees", ] +[[package]] +name = "solana-limited-deserialize" +version = "2.1.0" +dependencies = [ + "bincode", + "serde", + "solana-instruction", +] + [[package]] name = "solana-loader-v4-program" version = "2.1.0" @@ -5644,6 +5653,7 @@ dependencies = [ "solana-define-syscall", "solana-hash", "solana-instruction", + "solana-limited-deserialize", "solana-msg", "solana-program-error", "solana-program-memory", diff --git a/sdk/limited-deserialize/Cargo.toml b/sdk/limited-deserialize/Cargo.toml new file mode 100644 index 00000000000000..d33663b48293cc --- /dev/null +++ b/sdk/limited-deserialize/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "solana-limited-deserialize" +description = "Solana function for deserializing with a limit" +documentation = "https://docs.rs/solana-limited-deserialize" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +bincode = { workspace = true } +serde = { workspace = true } +solana-instruction = { workspace = true, default-features = false, features = [ + "std", +] } + +[dev-dependencies] +solana-program = { path = "../program" } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lints] +workspace = true diff --git a/sdk/limited-deserialize/src/lib.rs b/sdk/limited-deserialize/src/lib.rs new file mode 100644 index 00000000000000..28bd19dec7bdca --- /dev/null +++ b/sdk/limited-deserialize/src/lib.rs @@ -0,0 +1,48 @@ +//! Contains a single utility function for deserializing from [bincode]. +//! +//! [bincode]: https://docs.rs/bincode + +use {bincode::config::Options, solana_instruction::error::InstructionError}; + +/// Deserialize with a limit based the maximum amount of data a program can expect to get. +/// This function should be used in place of direct deserialization to help prevent OOM errors +pub fn limited_deserialize(instruction_data: &[u8], limit: u64) -> Result +where + T: serde::de::DeserializeOwned, +{ + bincode::options() + .with_limit(limit) + .with_fixint_encoding() // As per https://github.com/servo/bincode/issues/333, these two options are needed + .allow_trailing_bytes() // to retain the behavior of bincode::deserialize with the new `options()` method + .deserialize_from(instruction_data) + .map_err(|_| InstructionError::InvalidInstructionData) +} + +#[cfg(test)] +pub mod tests { + use {super::*, solana_program::system_instruction::SystemInstruction}; + + #[test] + fn test_limited_deserialize_advance_nonce_account() { + let item = SystemInstruction::AdvanceNonceAccount; + let mut serialized = bincode::serialize(&item).unwrap(); + + assert_eq!( + serialized.len(), + 4, + "`SanitizedMessage::get_durable_nonce()` may need a change" + ); + + assert_eq!( + limited_deserialize::(&serialized, 4).as_ref(), + Ok(&item) + ); + assert!(limited_deserialize::(&serialized, 3).is_err()); + + serialized.push(0); + assert_eq!( + limited_deserialize::(&serialized, 4).as_ref(), + Ok(&item) + ); + } +} diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 6b0214ce1edcb3..7762c5a2e9f4e6 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -47,6 +47,7 @@ solana-instruction = { workspace = true, default-features = false, features = [ "serde", "std", ] } +solana-limited-deserialize = { workspace = true } solana-msg = { workspace = true } solana-program-error = { workspace = true, features = ["serde"] } solana-program-memory = { workspace = true } diff --git a/sdk/program/src/program_utils.rs b/sdk/program/src/program_utils.rs index f624a0f69b13fd..df185e23a238d5 100644 --- a/sdk/program/src/program_utils.rs +++ b/sdk/program/src/program_utils.rs @@ -1,48 +1 @@ -//! Contains a single utility function for deserializing from [bincode]. -//! -//! [bincode]: https://docs.rs/bincode - -use {crate::instruction::InstructionError, bincode::config::Options}; - -/// Deserialize with a limit based the maximum amount of data a program can expect to get. -/// This function should be used in place of direct deserialization to help prevent OOM errors -pub fn limited_deserialize(instruction_data: &[u8], limit: u64) -> Result -where - T: serde::de::DeserializeOwned, -{ - bincode::options() - .with_limit(limit) - .with_fixint_encoding() // As per https://github.com/servo/bincode/issues/333, these two options are needed - .allow_trailing_bytes() // to retain the behavior of bincode::deserialize with the new `options()` method - .deserialize_from(instruction_data) - .map_err(|_| InstructionError::InvalidInstructionData) -} - -#[cfg(test)] -pub mod tests { - use {super::*, solana_program::system_instruction::SystemInstruction}; - - #[test] - fn test_limited_deserialize_advance_nonce_account() { - let item = SystemInstruction::AdvanceNonceAccount; - let mut serialized = bincode::serialize(&item).unwrap(); - - assert_eq!( - serialized.len(), - 4, - "`SanitizedMessage::get_durable_nonce()` may need a change" - ); - - assert_eq!( - limited_deserialize::(&serialized, 4).as_ref(), - Ok(&item) - ); - assert!(limited_deserialize::(&serialized, 3).is_err()); - - serialized.push(0); - assert_eq!( - limited_deserialize::(&serialized, 4).as_ref(), - Ok(&item) - ); - } -} +pub use solana_limited_deserialize::limited_deserialize;