From ddf2d6396894dc584ec29cce4445388711eeb830 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 3 Apr 2026 20:44:23 +0300 Subject: [PATCH 1/3] =?UTF-8?q?test(platform):=20coverage=20round=203=20?= =?UTF-8?q?=E2=80=94=20replace,=20index,=20bytes,=20distribution=20encode?= =?UTF-8?q?=20+=20exclusions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 218 tests across 5 files covering real logic: - replace.rs: recursive path-based type replacement, clean_recursive - index.rs: Index/IndexMut for Value, auto-map creation, nested access - bytes_20.rs: conversions, hashing, ordering, string encoding - bytes_32.rs: conversions, hashing, ordering, identifier interop - distribution_function/encode.rs: all 9 variants encode/decode, edge cases, truncated input, tag validation, determinism Exclude 3 accessor-only files from coverage: - token_configuration/v0/accessors.rs - document/v0/accessors.rs - platform_state/accessors.rs Co-Authored-By: Claude Opus 4.6 (1M context) --- .codecov.yml | 4 + .../distribution_function/encode.rs | 1095 +++++++++++++++++ packages/rs-platform-value/src/index.rs | 369 ++++++ packages/rs-platform-value/src/replace.rs | 661 ++++++++++ .../rs-platform-value/src/types/bytes_20.rs | 410 ++++++ .../rs-platform-value/src/types/bytes_32.rs | 423 +++++++ 6 files changed, 2962 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 89b413c72f5..5501e9a0e20 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -88,6 +88,10 @@ ignore: # Value Display and string encoding — trivial formatting, not logic - "packages/rs-platform-value/src/display.rs" - "packages/rs-platform-value/src/string_encoding.rs" + # Accessor-only files — pure getters/setters with no logic + - "packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs" + - "packages/rs-dpp/src/document/v0/accessors.rs" + - "packages/rs-drive-abci/src/platform_types/platform_state/accessors.rs" # Core chain type wrappers — masternode entry structs, deserialization # boilerplate, thin type aliases - "packages/rs-dpp/src/core_types/**" diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/encode.rs b/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/encode.rs index a68fc360248..002cd993366 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/encode.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/encode.rs @@ -450,3 +450,1098 @@ impl<'de, C> BorrowDecode<'de, C> for DistributionFunction { } } } + +#[cfg(test)] +mod tests { + use super::*; + + const CONFIG: bincode::config::Configuration = bincode::config::standard(); + + /// Helper: encode then decode a DistributionFunction and assert round-trip equality. + fn round_trip(original: &DistributionFunction) -> DistributionFunction { + let bytes = bincode::encode_to_vec(original, CONFIG).expect("encode failed"); + let (decoded, _): (DistributionFunction, _) = + bincode::decode_from_slice(&bytes, CONFIG).expect("decode failed"); + decoded + } + + /// Helper: encode then borrow-decode a DistributionFunction and assert round-trip equality. + fn round_trip_borrow(original: &DistributionFunction) -> DistributionFunction { + let bytes = bincode::encode_to_vec(original, CONFIG).expect("encode failed"); + let (decoded, _): (DistributionFunction, _) = + bincode::borrow_decode_from_slice(&bytes, CONFIG).expect("borrow_decode failed"); + decoded + } + + // ----------------------------------------------------------------------- + // Round-trip tests for each variant + // ----------------------------------------------------------------------- + + #[test] + fn round_trip_fixed_amount() { + let original = DistributionFunction::FixedAmount { amount: 42 }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_random() { + let original = DistributionFunction::Random { min: 10, max: 100 }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_step_decreasing_amount() { + let original = DistributionFunction::StepDecreasingAmount { + step_count: 210_000, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(100), + max_interval_count: Some(64), + distribution_start_amount: 5000, + trailing_distribution_interval_amount: 1, + min_value: Some(10), + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_step_decreasing_amount_none_options() { + let original = DistributionFunction::StepDecreasingAmount { + step_count: 1000, + decrease_per_interval_numerator: 7, + decrease_per_interval_denominator: 100, + start_decreasing_offset: None, + max_interval_count: None, + distribution_start_amount: 999, + trailing_distribution_interval_amount: 0, + min_value: None, + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_stepwise() { + let mut steps = BTreeMap::new(); + steps.insert(0, 100); + steps.insert(10, 50); + steps.insert(20, 25); + let original = DistributionFunction::Stepwise(steps); + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_stepwise_empty() { + let original = DistributionFunction::Stepwise(BTreeMap::new()); + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_linear() { + let original = DistributionFunction::Linear { + a: -5, + d: 100, + start_step: Some(10), + starting_amount: 1000, + min_value: Some(50), + max_value: Some(2000), + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_linear_none_options() { + let original = DistributionFunction::Linear { + a: 3, + d: 1, + start_step: None, + starting_amount: 500, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_polynomial() { + let original = DistributionFunction::Polynomial { + a: -3, + d: 10, + m: 2, + n: 1, + o: -1, + start_moment: Some(5), + b: 100, + min_value: Some(0), + max_value: Some(10000), + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_polynomial_none_options() { + let original = DistributionFunction::Polynomial { + a: 1, + d: 1, + m: -2, + n: 3, + o: 0, + start_moment: None, + b: 50, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_exponential() { + let original = DistributionFunction::Exponential { + a: 100, + d: 20, + m: -3, + n: 100, + o: 5, + start_moment: Some(10), + b: 10, + min_value: Some(1), + max_value: Some(500), + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_exponential_none_options() { + let original = DistributionFunction::Exponential { + a: 50, + d: 10, + m: 2, + n: 50, + o: 0, + start_moment: None, + b: 5, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_logarithmic() { + let original = DistributionFunction::Logarithmic { + a: 100, + d: 10, + m: 2, + n: 1, + o: 1, + start_moment: Some(0), + b: 50, + min_value: Some(10), + max_value: Some(200), + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_logarithmic_none_options() { + let original = DistributionFunction::Logarithmic { + a: -5, + d: 1, + m: 1, + n: 1, + o: 0, + start_moment: None, + b: 100, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_inverted_logarithmic() { + let original = DistributionFunction::InvertedLogarithmic { + a: 10000, + d: 1, + m: 1, + n: 5000, + o: 0, + start_moment: Some(0), + b: 0, + min_value: Some(0), + max_value: Some(100000), + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + #[test] + fn round_trip_inverted_logarithmic_none_options() { + let original = DistributionFunction::InvertedLogarithmic { + a: -20, + d: 5, + m: 3, + n: 10, + o: -2, + start_moment: None, + b: 200, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + // ----------------------------------------------------------------------- + // Edge cases: zero values + // ----------------------------------------------------------------------- + + #[test] + fn round_trip_fixed_amount_zero() { + let original = DistributionFunction::FixedAmount { amount: 0 }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_random_zero_range() { + let original = DistributionFunction::Random { min: 0, max: 0 }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_linear_all_zeros() { + let original = DistributionFunction::Linear { + a: 0, + d: 0, + start_step: Some(0), + starting_amount: 0, + min_value: Some(0), + max_value: Some(0), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_polynomial_all_zeros() { + let original = DistributionFunction::Polynomial { + a: 0, + d: 0, + m: 0, + n: 0, + o: 0, + start_moment: Some(0), + b: 0, + min_value: Some(0), + max_value: Some(0), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_exponential_all_zeros() { + let original = DistributionFunction::Exponential { + a: 0, + d: 0, + m: 0, + n: 0, + o: 0, + start_moment: Some(0), + b: 0, + min_value: Some(0), + max_value: Some(0), + }; + assert_eq!(round_trip(&original), original); + } + + // ----------------------------------------------------------------------- + // Edge cases: max values + // ----------------------------------------------------------------------- + + #[test] + fn round_trip_fixed_amount_max() { + let original = DistributionFunction::FixedAmount { amount: u64::MAX }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_random_max_values() { + let original = DistributionFunction::Random { + min: u64::MAX - 1, + max: u64::MAX, + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_step_decreasing_max_values() { + let original = DistributionFunction::StepDecreasingAmount { + step_count: u32::MAX, + decrease_per_interval_numerator: u16::MAX, + decrease_per_interval_denominator: u16::MAX, + start_decreasing_offset: Some(u64::MAX), + max_interval_count: Some(u16::MAX), + distribution_start_amount: u64::MAX, + trailing_distribution_interval_amount: u64::MAX, + min_value: Some(u64::MAX), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_linear_extreme_values() { + let original = DistributionFunction::Linear { + a: i64::MIN, + d: u64::MAX, + start_step: Some(u64::MAX), + starting_amount: u64::MAX, + min_value: Some(u64::MAX), + max_value: Some(u64::MAX), + }; + assert_eq!(round_trip(&original), original); + + let original2 = DistributionFunction::Linear { + a: i64::MAX, + d: 0, + start_step: None, + starting_amount: 0, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original2), original2); + } + + #[test] + fn round_trip_polynomial_extreme_values() { + let original = DistributionFunction::Polynomial { + a: i64::MIN, + d: u64::MAX, + m: i64::MIN, + n: u64::MAX, + o: i64::MAX, + start_moment: Some(u64::MAX), + b: u64::MAX, + min_value: Some(u64::MAX), + max_value: Some(u64::MAX), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_exponential_extreme_values() { + let original = DistributionFunction::Exponential { + a: u64::MAX, + d: u64::MAX, + m: i64::MIN, + n: u64::MAX, + o: i64::MIN, + start_moment: Some(u64::MAX), + b: u64::MAX, + min_value: Some(u64::MAX), + max_value: Some(u64::MAX), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_logarithmic_extreme_values() { + let original = DistributionFunction::Logarithmic { + a: i64::MIN, + d: u64::MAX, + m: u64::MAX, + n: u64::MAX, + o: i64::MIN, + start_moment: Some(u64::MAX), + b: u64::MAX, + min_value: Some(u64::MAX), + max_value: Some(u64::MAX), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_inverted_logarithmic_extreme_values() { + let original = DistributionFunction::InvertedLogarithmic { + a: i64::MAX, + d: u64::MAX, + m: u64::MAX, + n: u64::MAX, + o: i64::MAX, + start_moment: Some(u64::MAX), + b: u64::MAX, + min_value: Some(u64::MAX), + max_value: Some(u64::MAX), + }; + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_stepwise_single_entry() { + let mut steps = BTreeMap::new(); + steps.insert(0, u64::MAX); + let original = DistributionFunction::Stepwise(steps); + assert_eq!(round_trip(&original), original); + } + + #[test] + fn round_trip_stepwise_many_entries() { + let steps: BTreeMap = (0..100).map(|i| (i * 10, i * 100 + 1)).collect(); + let original = DistributionFunction::Stepwise(steps); + assert_eq!(round_trip(&original), original); + assert_eq!(round_trip_borrow(&original), original); + } + + // ----------------------------------------------------------------------- + // Determinism: same input always produces the same bytes + // ----------------------------------------------------------------------- + + #[test] + fn encoding_is_deterministic() { + let variants: Vec = vec![ + DistributionFunction::FixedAmount { amount: 42 }, + DistributionFunction::Random { min: 1, max: 99 }, + DistributionFunction::StepDecreasingAmount { + step_count: 100, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(5), + max_interval_count: Some(10), + distribution_start_amount: 500, + trailing_distribution_interval_amount: 1, + min_value: Some(1), + }, + DistributionFunction::Stepwise({ + let mut m = BTreeMap::new(); + m.insert(0, 100); + m.insert(50, 50); + m + }), + DistributionFunction::Linear { + a: -2, + d: 1, + start_step: None, + starting_amount: 100, + min_value: None, + max_value: Some(200), + }, + DistributionFunction::Polynomial { + a: 3, + d: 1, + m: 2, + n: 1, + o: 0, + start_moment: None, + b: 10, + min_value: None, + max_value: None, + }, + DistributionFunction::Exponential { + a: 100, + d: 10, + m: -3, + n: 100, + o: 0, + start_moment: None, + b: 10, + min_value: None, + max_value: None, + }, + DistributionFunction::Logarithmic { + a: 100, + d: 10, + m: 2, + n: 1, + o: 1, + start_moment: None, + b: 50, + min_value: None, + max_value: None, + }, + DistributionFunction::InvertedLogarithmic { + a: 10000, + d: 1, + m: 1, + n: 5000, + o: 0, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }, + ]; + + for variant in &variants { + let bytes1 = bincode::encode_to_vec(variant, CONFIG).unwrap(); + let bytes2 = bincode::encode_to_vec(variant, CONFIG).unwrap(); + assert_eq!( + bytes1, bytes2, + "encoding was not deterministic for {:?}", + variant + ); + } + } + + // ----------------------------------------------------------------------- + // Variant tag correctness: first byte encodes the variant discriminant + // ----------------------------------------------------------------------- + + #[test] + fn variant_tags_are_correct() { + let cases: Vec<(DistributionFunction, u8)> = vec![ + (DistributionFunction::FixedAmount { amount: 1 }, 0), + (DistributionFunction::Random { min: 0, max: 1 }, 1), + ( + DistributionFunction::StepDecreasingAmount { + step_count: 1, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: None, + max_interval_count: None, + distribution_start_amount: 1, + trailing_distribution_interval_amount: 0, + min_value: None, + }, + 2, + ), + (DistributionFunction::Stepwise(BTreeMap::new()), 3), + ( + DistributionFunction::Linear { + a: 0, + d: 1, + start_step: None, + starting_amount: 0, + min_value: None, + max_value: None, + }, + 4, + ), + ( + DistributionFunction::Polynomial { + a: 0, + d: 1, + m: 0, + n: 1, + o: 0, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }, + 5, + ), + ( + DistributionFunction::Exponential { + a: 0, + d: 1, + m: 0, + n: 1, + o: 0, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }, + 6, + ), + ( + DistributionFunction::Logarithmic { + a: 0, + d: 1, + m: 1, + n: 1, + o: 0, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }, + 7, + ), + ( + DistributionFunction::InvertedLogarithmic { + a: 0, + d: 1, + m: 1, + n: 1, + o: 0, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }, + 8, + ), + ]; + + for (variant, expected_tag) in cases { + let bytes = bincode::encode_to_vec(&variant, CONFIG).unwrap(); + assert_eq!( + bytes[0], expected_tag, + "wrong tag for {:?}: got {}, expected {}", + variant, bytes[0], expected_tag + ); + } + } + + // ----------------------------------------------------------------------- + // Error paths: invalid variant tag + // ----------------------------------------------------------------------- + + #[test] + fn decode_invalid_variant_tag_9() { + let valid = DistributionFunction::FixedAmount { amount: 1 }; + let mut bytes = bincode::encode_to_vec(&valid, CONFIG).unwrap(); + bytes[0] = 9; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(&bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_invalid_variant_tag_255() { + let valid = DistributionFunction::FixedAmount { amount: 1 }; + let mut bytes = bincode::encode_to_vec(&valid, CONFIG).unwrap(); + bytes[0] = 255; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(&bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn borrow_decode_invalid_variant_tag() { + let valid = DistributionFunction::FixedAmount { amount: 1 }; + let mut bytes = bincode::encode_to_vec(&valid, CONFIG).unwrap(); + bytes[0] = 42; + let result: Result<(DistributionFunction, _), _> = + bincode::borrow_decode_from_slice(&bytes, CONFIG); + assert!(result.is_err()); + } + + // ----------------------------------------------------------------------- + // Error paths: truncated input + // ----------------------------------------------------------------------- + + #[test] + fn decode_empty_input() { + let bytes: &[u8] = &[]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_fixed_amount() { + let bytes: &[u8] = &[0]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_random() { + let bytes: &[u8] = &[1]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_step_decreasing() { + let bytes: &[u8] = &[2]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_stepwise() { + let bytes: &[u8] = &[3]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_linear() { + let bytes: &[u8] = &[4]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_polynomial() { + let bytes: &[u8] = &[5]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_exponential() { + let bytes: &[u8] = &[6]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_logarithmic() { + let bytes: &[u8] = &[7]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_tag_only_inverted_logarithmic() { + let bytes: &[u8] = &[8]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_truncated_random_missing_max() { + let original = DistributionFunction::Random { min: 10, max: 100 }; + let bytes = bincode::encode_to_vec(&original, CONFIG).unwrap(); + let truncated = &bytes[..bytes.len() / 2]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(truncated, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_truncated_linear_partial_payload() { + let original = DistributionFunction::Linear { + a: 5, + d: 10, + start_step: Some(100), + starting_amount: 500, + min_value: Some(1), + max_value: Some(1000), + }; + let bytes = bincode::encode_to_vec(&original, CONFIG).unwrap(); + let truncated = &bytes[..5]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(truncated, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_truncated_polynomial_partial_payload() { + let original = DistributionFunction::Polynomial { + a: 3, + d: 1, + m: 2, + n: 1, + o: -1, + start_moment: Some(5), + b: 100, + min_value: Some(0), + max_value: Some(10000), + }; + let bytes = bincode::encode_to_vec(&original, CONFIG).unwrap(); + let truncated = &bytes[..bytes.len() - 3]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(truncated, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_truncated_exponential_partial_payload() { + let original = DistributionFunction::Exponential { + a: 100, + d: 20, + m: -3, + n: 100, + o: 5, + start_moment: Some(10), + b: 10, + min_value: Some(1), + max_value: Some(500), + }; + let bytes = bincode::encode_to_vec(&original, CONFIG).unwrap(); + let truncated = &bytes[..bytes.len() - 5]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(truncated, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn decode_truncated_step_decreasing_partial_payload() { + let original = DistributionFunction::StepDecreasingAmount { + step_count: 210_000, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(100), + max_interval_count: Some(64), + distribution_start_amount: 5000, + trailing_distribution_interval_amount: 1, + min_value: Some(10), + }; + let bytes = bincode::encode_to_vec(&original, CONFIG).unwrap(); + let truncated = &bytes[..bytes.len() / 2]; + let result: Result<(DistributionFunction, _), _> = + bincode::decode_from_slice(truncated, CONFIG); + assert!(result.is_err()); + } + + // ----------------------------------------------------------------------- + // Error paths: borrow_decode with truncated input + // ----------------------------------------------------------------------- + + #[test] + fn borrow_decode_empty_input() { + let bytes: &[u8] = &[]; + let result: Result<(DistributionFunction, _), _> = + bincode::borrow_decode_from_slice(bytes, CONFIG); + assert!(result.is_err()); + } + + #[test] + fn borrow_decode_tag_only() { + for tag in 0u8..=8 { + let bytes: &[u8] = &[tag]; + let result: Result<(DistributionFunction, _), _> = + bincode::borrow_decode_from_slice(bytes, CONFIG); + assert!( + result.is_err(), + "borrow_decode should fail for tag-only input with tag {}", + tag + ); + } + } + + #[test] + fn borrow_decode_invalid_tag() { + for tag in [9u8, 10, 50, 128, 255] { + let bytes: &[u8] = &[tag]; + let result: Result<(DistributionFunction, _), _> = + bincode::borrow_decode_from_slice(bytes, CONFIG); + assert!( + result.is_err(), + "borrow_decode should fail for invalid tag {}", + tag + ); + } + } + + // ----------------------------------------------------------------------- + // Decode and BorrowDecode produce the same results + // ----------------------------------------------------------------------- + + #[test] + fn decode_and_borrow_decode_match_for_all_variants() { + let variants: Vec = vec![ + DistributionFunction::FixedAmount { amount: 777 }, + DistributionFunction::Random { min: 10, max: 1000 }, + DistributionFunction::StepDecreasingAmount { + step_count: 500, + decrease_per_interval_numerator: 3, + decrease_per_interval_denominator: 100, + start_decreasing_offset: Some(50), + max_interval_count: Some(200), + distribution_start_amount: 10000, + trailing_distribution_interval_amount: 5, + min_value: Some(1), + }, + DistributionFunction::Stepwise({ + let mut m = BTreeMap::new(); + m.insert(0, 500); + m.insert(100, 250); + m.insert(200, 125); + m + }), + DistributionFunction::Linear { + a: -10, + d: 3, + start_step: Some(20), + starting_amount: 1000, + min_value: Some(100), + max_value: None, + }, + DistributionFunction::Polynomial { + a: 5, + d: 2, + m: -1, + n: 3, + o: 7, + start_moment: Some(10), + b: 200, + min_value: None, + max_value: Some(5000), + }, + DistributionFunction::Exponential { + a: 250, + d: 50, + m: 1, + n: 10, + o: -3, + start_moment: Some(5), + b: 100, + min_value: Some(50), + max_value: Some(10000), + }, + DistributionFunction::Logarithmic { + a: 500, + d: 20, + m: 3, + n: 2, + o: -1, + start_moment: Some(0), + b: 75, + min_value: Some(10), + max_value: Some(1000), + }, + DistributionFunction::InvertedLogarithmic { + a: -100, + d: 10, + m: 5, + n: 100, + o: 2, + start_moment: Some(3), + b: 300, + min_value: Some(0), + max_value: Some(500), + }, + ]; + + for variant in &variants { + let bytes = bincode::encode_to_vec(variant, CONFIG).unwrap(); + let (decoded, consumed1): (DistributionFunction, _) = + bincode::decode_from_slice(&bytes, CONFIG).unwrap(); + let (borrow_decoded, consumed2): (DistributionFunction, _) = + bincode::borrow_decode_from_slice(&bytes, CONFIG).unwrap(); + assert_eq!( + decoded, borrow_decoded, + "decode and borrow_decode differ for {:?}", + variant + ); + assert_eq!( + consumed1, consumed2, + "consumed bytes differ for {:?}", + variant + ); + } + } + + // ----------------------------------------------------------------------- + // Negative i64 values round-trip correctly + // ----------------------------------------------------------------------- + + #[test] + fn round_trip_negative_signed_fields() { + let original = DistributionFunction::Polynomial { + a: i64::MIN, + d: 1, + m: -8, + n: 1, + o: i64::MIN, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original), original); + + let original2 = DistributionFunction::Exponential { + a: 1, + d: 1, + m: i64::MIN, + n: 1, + o: i64::MIN, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original2), original2); + + let original3 = DistributionFunction::InvertedLogarithmic { + a: i64::MIN, + d: 1, + m: 1, + n: 1, + o: i64::MIN, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }; + assert_eq!(round_trip(&original3), original3); + } + + // ----------------------------------------------------------------------- + // Corrupted payload bytes + // ----------------------------------------------------------------------- + + #[test] + fn decode_corrupted_option_byte_does_not_panic() { + let original = DistributionFunction::Linear { + a: 1, + d: 1, + start_step: None, + starting_amount: 10, + min_value: None, + max_value: None, + }; + let mut bytes = bincode::encode_to_vec(&original, CONFIG).unwrap(); + // Corrupt the last byte (an option discriminant for max_value) + let last = bytes.len() - 1; + bytes[last] = 5; + // Should not panic regardless of outcome + let _ = bincode::decode_from_slice::(&bytes, CONFIG); + } + + // ----------------------------------------------------------------------- + // Encode length varies correctly between variants + // ----------------------------------------------------------------------- + + #[test] + fn fixed_amount_is_shortest_encoding() { + let fixed = DistributionFunction::FixedAmount { amount: 1 }; + let random = DistributionFunction::Random { min: 1, max: 1 }; + let fixed_bytes = bincode::encode_to_vec(&fixed, CONFIG).unwrap(); + let random_bytes = bincode::encode_to_vec(&random, CONFIG).unwrap(); + assert!( + fixed_bytes.len() <= random_bytes.len(), + "FixedAmount should be shorter than or equal to Random" + ); + } + + // ----------------------------------------------------------------------- + // Full round-trip: encode -> decode -> re-encode produces identical bytes + // ----------------------------------------------------------------------- + + #[test] + fn double_round_trip_produces_identical_bytes() { + let original = DistributionFunction::StepDecreasingAmount { + step_count: 210_000, + decrease_per_interval_numerator: 1, + decrease_per_interval_denominator: 2, + start_decreasing_offset: Some(100), + max_interval_count: Some(64), + distribution_start_amount: 5000, + trailing_distribution_interval_amount: 1, + min_value: Some(10), + }; + let bytes1 = bincode::encode_to_vec(&original, CONFIG).unwrap(); + let (decoded, _): (DistributionFunction, _) = + bincode::decode_from_slice(&bytes1, CONFIG).unwrap(); + let bytes2 = bincode::encode_to_vec(&decoded, CONFIG).unwrap(); + assert_eq!(bytes1, bytes2); + } +} diff --git a/packages/rs-platform-value/src/index.rs b/packages/rs-platform-value/src/index.rs index fd0f7a505dc..a39ccdd8e92 100644 --- a/packages/rs-platform-value/src/index.rs +++ b/packages/rs-platform-value/src/index.rs @@ -272,3 +272,372 @@ where index.index_or_insert(self) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::platform_value; + + // =============================================================== + // Index for Value — access array element + // =============================================================== + + #[test] + fn index_usize_access_array_element() { + let value = platform_value!([10, 20, 30]); + assert_eq!(value[0], platform_value!(10)); + assert_eq!(value[1], platform_value!(20)); + assert_eq!(value[2], platform_value!(30)); + } + + // =============================================================== + // Index for Value — out-of-bounds returns Null + // =============================================================== + + #[test] + fn index_usize_out_of_bounds_returns_null() { + let value = platform_value!([10, 20]); + // The ops::Index impl returns &NULL for missing indices + assert_eq!(value[99], Value::Null); + } + + // =============================================================== + // Index for Value — non-array returns Null + // =============================================================== + + #[test] + fn index_usize_on_non_array_returns_null() { + let value = platform_value!(42); + // ops::Index returns &NULL when index_into returns None + assert_eq!(value[0], Value::Null); + } + + #[test] + fn index_usize_on_map_returns_null() { + let value = platform_value!({ "key": "val" }); + assert_eq!(value[0], Value::Null); + } + + // =============================================================== + // IndexMut — panic on out-of-bounds + // =============================================================== + + #[test] + #[should_panic(expected = "cannot access index 5 of JSON array of length 2")] + fn index_mut_usize_out_of_bounds_panics() { + let mut value = platform_value!([10, 20]); + value[5] = platform_value!(99); + } + + // =============================================================== + // IndexMut — panic on non-array + // =============================================================== + + #[test] + #[should_panic(expected = "cannot access index 0 of JSON")] + fn index_mut_usize_on_non_array_panics() { + let mut value = platform_value!(42); + value[0] = platform_value!(99); + } + + // =============================================================== + // IndexMut — successfully write + // =============================================================== + + #[test] + fn index_mut_usize_write() { + let mut value = platform_value!([10, 20, 30]); + value[1] = platform_value!(99); + assert_eq!(value[1], platform_value!(99)); + } + + // =============================================================== + // Index<&str> for Value — access map key + // =============================================================== + + #[test] + fn index_str_access_map_key() { + let value = platform_value!({ "name": "Alice", "age": 30 }); + assert_eq!(value["name"], platform_value!("Alice")); + assert_eq!(value["age"], platform_value!(30)); + } + + // =============================================================== + // Index<&str> for Value — missing key returns Null + // =============================================================== + + #[test] + fn index_str_missing_key_returns_null() { + let value = platform_value!({ "name": "Alice" }); + assert_eq!(value["missing"], Value::Null); + } + + // =============================================================== + // Index<&str> for Value — non-map returns Null + // =============================================================== + + #[test] + fn index_str_on_non_map_returns_null() { + let value = platform_value!(42); + assert_eq!(value["key"], Value::Null); + } + + #[test] + fn index_str_on_array_returns_null() { + let value = platform_value!([1, 2, 3]); + assert_eq!(value["key"], Value::Null); + } + + // =============================================================== + // Index<&str> for Value — nested access + // =============================================================== + + #[test] + fn index_str_nested_access() { + let value = platform_value!({ + "outer": { + "inner": { + "deep": 42 + } + } + }); + assert_eq!(value["outer"]["inner"]["deep"], platform_value!(42)); + } + + #[test] + fn index_str_nested_missing_returns_null_chain() { + let value = platform_value!({ "a": { "b": 1 } }); + // "a" -> "c" -> doesn't exist, returns Null + // then Null["anything"] also returns Null + assert_eq!(value["a"]["c"], Value::Null); + assert_eq!(value["a"]["c"]["d"], Value::Null); + } + + // =============================================================== + // IndexMut<&str> — write to existing key + // =============================================================== + + #[test] + fn index_mut_str_write_existing() { + let mut value = platform_value!({ "x": 0 }); + value["x"] = platform_value!(42); + assert_eq!(value["x"], platform_value!(42)); + } + + // =============================================================== + // IndexMut<&str> — insert new key + // =============================================================== + + #[test] + fn index_mut_str_insert_new_key() { + let mut value = platform_value!({ "x": 0 }); + value["y"] = platform_value!("hello"); + assert_eq!(value["y"], platform_value!("hello")); + } + + // =============================================================== + // IndexMut<&str> — Null becomes empty map + // =============================================================== + + #[test] + fn index_mut_str_null_becomes_map() { + let mut value = Value::Null; + value["key"] = platform_value!(1); + assert_eq!(value["key"], platform_value!(1)); + assert!(value.is_map()); + } + + // =============================================================== + // IndexMut<&str> — deeply nested insert via Null + // =============================================================== + + #[test] + fn index_mut_str_deeply_nested_insert() { + let mut value = platform_value!({ "x": 0 }); + // "a" -> inserts Null, then Null becomes map for "b", etc. + value["a"]["b"]["c"] = platform_value!(true); + assert_eq!(value["a"]["b"]["c"], platform_value!(true)); + } + + // =============================================================== + // IndexMut<&str> — panic on non-map non-null + // =============================================================== + + #[test] + #[should_panic(expected = "cannot access key")] + fn index_mut_str_on_non_map_panics() { + let mut value = platform_value!(42); + value["key"] = platform_value!(1); + } + + // =============================================================== + // Index delegates to str + // =============================================================== + + #[test] + fn index_string_delegates_to_str() { + let value = platform_value!({ "name": "Bob" }); + let key = String::from("name"); + assert_eq!(value[&key], platform_value!("Bob")); + } + + // =============================================================== + // IndexMut delegates to str + // =============================================================== + + #[test] + fn index_mut_string_delegates_to_str() { + let mut value = platform_value!({ "name": "Bob" }); + let key = String::from("name"); + value[&key] = platform_value!("Alice"); + assert_eq!(value["name"], platform_value!("Alice")); + } + + // =============================================================== + // index_into — returns None for various non-matching types + // =============================================================== + + #[test] + fn index_into_usize_returns_none_for_non_array() { + let value = Value::Text("hello".into()); + assert!(0usize.index_into(&value).is_none()); + } + + #[test] + fn index_into_str_returns_none_for_non_map() { + let value = Value::Array(vec![Value::U32(1)]); + assert!("key".index_into(&value).is_none()); + } + + // =============================================================== + // index_into_mut — returns None for non-matching types + // =============================================================== + + #[test] + fn index_into_mut_usize_returns_none_for_non_array() { + let mut value = Value::Bool(true); + assert!(0usize.index_into_mut(&mut value).is_none()); + } + + #[test] + fn index_into_mut_str_returns_none_for_non_map() { + let mut value = Value::U64(100); + assert!("key".index_into_mut(&mut value).is_none()); + } + + // =============================================================== + // index_into_mut — returns Some for valid accesses + // =============================================================== + + #[test] + fn index_into_mut_usize_returns_some() { + let mut value = platform_value!([10, 20]); + let got = 0usize.index_into_mut(&mut value); + assert!(got.is_some()); + *got.unwrap() = platform_value!(99); + assert_eq!(value[0], platform_value!(99)); + } + + #[test] + fn index_into_mut_str_returns_some() { + let mut value = platform_value!({ "k": 1 }); + let got = "k".index_into_mut(&mut value); + assert!(got.is_some()); + *got.unwrap() = platform_value!(42); + assert_eq!(value["k"], platform_value!(42)); + } + + // =============================================================== + // Combined array + map indexing + // =============================================================== + + #[test] + fn combined_array_map_indexing() { + let value = platform_value!({ + "items": [ + { "name": "first" }, + { "name": "second" } + ] + }); + assert_eq!(value["items"][0]["name"], platform_value!("first")); + assert_eq!(value["items"][1]["name"], platform_value!("second")); + } + + #[test] + fn combined_array_map_indexing_mut() { + let mut value = platform_value!({ + "items": [ + { "name": "first" }, + { "name": "second" } + ] + }); + value["items"][0]["name"] = platform_value!("updated"); + assert_eq!(value["items"][0]["name"], platform_value!("updated")); + } + + // =============================================================== + // get() method — returns Some for existing, None for missing + // =============================================================== + + #[test] + fn get_method_returns_some_for_existing_key() { + let value = platform_value!({ "x": 10 }); + let result = value.get("x").unwrap(); + assert!(result.is_some()); + assert_eq!(result.unwrap(), &platform_value!(10)); + } + + #[test] + fn get_method_returns_none_for_missing_key() { + let value = platform_value!({ "x": 10 }); + let result = value.get("y").unwrap(); + assert!(result.is_none()); + } + + #[test] + fn get_method_errors_on_non_map() { + let value = platform_value!(42); + let result = value.get("key"); + assert!(result.is_err()); + } + + // =============================================================== + // Type display coverage (used in panic messages) + // =============================================================== + + #[test] + fn type_display_covers_all_variants() { + use core::fmt::Write; + let variants: Vec = vec![ + Value::Null, + Value::Bool(true), + Value::Float(1.0), + Value::Text("s".into()), + Value::Array(vec![]), + Value::Map(vec![]), + Value::U128(1), + Value::I128(1), + Value::U64(1), + Value::I64(1), + Value::U32(1), + Value::I32(1), + Value::U16(1), + Value::I16(1), + Value::U8(1), + Value::I8(1), + Value::Bytes(vec![]), + Value::Bytes20([0u8; 20]), + Value::Bytes32([0u8; 32]), + Value::Bytes36([0u8; 36]), + Value::Identifier([0u8; 32]), + Value::EnumU8(vec![]), + Value::EnumString(vec![]), + ]; + for v in &variants { + let t = Type(v); + let mut buf = String::new(); + write!(buf, "{}", t).unwrap(); + assert!(!buf.is_empty()); + } + } +} diff --git a/packages/rs-platform-value/src/replace.rs b/packages/rs-platform-value/src/replace.rs index 5dcfcfcb6d8..c3088f1d470 100644 --- a/packages/rs-platform-value/src/replace.rs +++ b/packages/rs-platform-value/src/replace.rs @@ -497,3 +497,664 @@ impl Value { )) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{IntegerReplacementType, ReplacementType, Value}; + + // --------------------------------------------------------------- + // Helper: builds a 32-byte base58-encoded string from a seed byte + // --------------------------------------------------------------- + fn base58_of_32_bytes(seed: u8) -> String { + bs58::encode([seed; 32]).into_string() + } + + fn make_32_u8_array(seed: u8) -> Vec { + vec![Value::U8(seed); 32] + } + + // =============================================================== + // replace_at_path — single segment, ReplacementType::Identifier + // =============================================================== + + #[test] + fn replace_at_path_single_segment_identifier_from_text() { + let b58 = base58_of_32_bytes(1); + let mut value = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58))]); + value + .replace_at_path("id", ReplacementType::Identifier) + .unwrap(); + assert_eq!( + value.get_value_at_path("id").unwrap(), + &Value::Identifier([1u8; 32]) + ); + } + + #[test] + fn replace_at_path_single_segment_identifier_from_u8_array() { + let mut value = Value::Map(vec![( + Value::Text("id".into()), + Value::Array(make_32_u8_array(7)), + )]); + value + .replace_at_path("id", ReplacementType::Identifier) + .unwrap(); + assert_eq!( + value.get_value_at_path("id").unwrap(), + &Value::Identifier([7u8; 32]) + ); + } + + // =============================================================== + // replace_at_path — single segment, ReplacementType::BinaryBytes + // =============================================================== + + #[test] + fn replace_at_path_single_segment_binary_bytes_from_base64() { + use base64::prelude::*; + let raw = vec![10u8, 20, 30]; + let b64 = BASE64_STANDARD.encode(&raw); + let mut value = Value::Map(vec![(Value::Text("data".into()), Value::Text(b64))]); + value + .replace_at_path("data", ReplacementType::BinaryBytes) + .unwrap(); + assert_eq!(value.get_value_at_path("data").unwrap(), &Value::Bytes(raw)); + } + + // =============================================================== + // replace_at_path — single segment, ReplacementType::TextBase58 + // =============================================================== + + #[test] + fn replace_at_path_single_segment_text_base58() { + let mut value = Value::Map(vec![( + Value::Text("id".into()), + Value::Bytes(vec![1, 2, 3]), + )]); + value + .replace_at_path("id", ReplacementType::TextBase58) + .unwrap(); + let expected_b58 = bs58::encode(vec![1u8, 2, 3]).into_string(); + assert_eq!( + value.get_value_at_path("id").unwrap(), + &Value::Text(expected_b58) + ); + } + + // =============================================================== + // replace_at_path — single segment, ReplacementType::TextBase64 + // =============================================================== + + #[test] + fn replace_at_path_single_segment_text_base64() { + use base64::prelude::*; + let raw = vec![1u8, 2, 3]; + let mut value = Value::Map(vec![(Value::Text("bin".into()), Value::Bytes(raw.clone()))]); + value + .replace_at_path("bin", ReplacementType::TextBase64) + .unwrap(); + let expected_b64 = BASE64_STANDARD.encode(&raw); + assert_eq!( + value.get_value_at_path("bin").unwrap(), + &Value::Text(expected_b64) + ); + } + + // =============================================================== + // replace_at_path — multi-segment nested path + // =============================================================== + + #[test] + fn replace_at_path_multi_segment_nested() { + let b58 = base58_of_32_bytes(5); + let inner = Value::Map(vec![(Value::Text("owner_id".into()), Value::Text(b58))]); + let mut value = Value::Map(vec![(Value::Text("doc".into()), inner)]); + + value + .replace_at_path("doc.owner_id", ReplacementType::Identifier) + .unwrap(); + assert_eq!( + value.get_value_at_path("doc.owner_id").unwrap(), + &Value::Identifier([5u8; 32]) + ); + } + + // =============================================================== + // replace_at_path — array path with [] (all members) + // =============================================================== + + #[test] + fn replace_at_path_array_all_members() { + let b58 = base58_of_32_bytes(3); + let elem1 = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58.clone()))]); + let elem2 = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58))]); + let arr = Value::Array(vec![elem1, elem2]); + let mut value = Value::Map(vec![(Value::Text("items".into()), arr)]); + + value + .replace_at_path("items[].id", ReplacementType::Identifier) + .unwrap(); + assert_eq!( + value.get_value_at_path("items[0].id").unwrap(), + &Value::Identifier([3u8; 32]) + ); + assert_eq!( + value.get_value_at_path("items[1].id").unwrap(), + &Value::Identifier([3u8; 32]) + ); + } + + // =============================================================== + // replace_at_path — optional key missing returns Ok + // =============================================================== + + #[test] + fn replace_at_path_missing_key_returns_ok() { + let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(42))]); + // "b" does not exist — filter_map filters it out + let result = value.replace_at_path("b", ReplacementType::Identifier); + assert!(result.is_ok()); + } + + // =============================================================== + // replace_at_path — non-map at root gives error + // =============================================================== + + #[test] + fn replace_at_path_on_non_map_errors() { + let mut value = Value::U32(42); + let result = value.replace_at_path("key", ReplacementType::Identifier); + assert!(result.is_err()); + } + + // =============================================================== + // replace_at_path — non-32-byte data for Identifier errors + // =============================================================== + + #[test] + fn replace_at_path_identifier_wrong_length_errors() { + // base58-encode only 10 bytes, not 32 + let b58 = bs58::encode([1u8; 10]).into_string(); + let mut value = Value::Map(vec![(Value::Text("id".into()), Value::Text(b58))]); + let result = value.replace_at_path("id", ReplacementType::Identifier); + assert!(result.is_err()); + } + + // =============================================================== + // replace_at_paths — multiple paths + // =============================================================== + + #[test] + fn replace_at_paths_replaces_multiple() { + let b58 = base58_of_32_bytes(9); + let inner = Value::Map(vec![ + (Value::Text("a".into()), Value::Text(b58.clone())), + (Value::Text("b".into()), Value::Text(b58)), + ]); + let mut value = Value::Map(vec![(Value::Text("root".into()), inner)]); + value + .replace_at_paths(vec!["root.a", "root.b"], ReplacementType::Identifier) + .unwrap(); + assert_eq!( + value.get_value_at_path("root.a").unwrap(), + &Value::Identifier([9u8; 32]) + ); + assert_eq!( + value.get_value_at_path("root.b").unwrap(), + &Value::Identifier([9u8; 32]) + ); + } + + // =============================================================== + // replace_integer_type_at_path — single segment, U32 + // =============================================================== + + #[test] + fn replace_integer_type_single_segment_u32() { + let mut value = Value::Map(vec![(Value::Text("count".into()), Value::U8(5))]); + value + .replace_integer_type_at_path("count", IntegerReplacementType::U32) + .unwrap(); + assert_eq!(value.get_value_at_path("count").unwrap(), &Value::U32(5)); + } + + // =============================================================== + // replace_integer_type_at_path — single segment, various types + // =============================================================== + + #[test] + fn replace_integer_type_single_segment_u16() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U128(100))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::U16) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U16(100)); + } + + #[test] + fn replace_integer_type_single_segment_u64() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U8(42))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::U64) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U64(42)); + } + + #[test] + fn replace_integer_type_single_segment_i32() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I8(-3))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::I32) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I32(-3)); + } + + #[test] + fn replace_integer_type_single_segment_u128() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U64(999))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::U128) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U128(999)); + } + + #[test] + fn replace_integer_type_single_segment_i128() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I64(-500))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::I128) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I128(-500)); + } + + #[test] + fn replace_integer_type_single_segment_u8() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::U16(7))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::U8) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::U8(7)); + } + + #[test] + fn replace_integer_type_single_segment_i8() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I16(-2))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::I8) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I8(-2)); + } + + #[test] + fn replace_integer_type_single_segment_i16() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I32(-100))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::I16) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I16(-100)); + } + + #[test] + fn replace_integer_type_single_segment_i64() { + let mut value = Value::Map(vec![(Value::Text("v".into()), Value::I128(-9999))]); + value + .replace_integer_type_at_path("v", IntegerReplacementType::I64) + .unwrap(); + assert_eq!(value.get_value_at_path("v").unwrap(), &Value::I64(-9999)); + } + + // =============================================================== + // replace_integer_type_at_path — multi-segment + // =============================================================== + + #[test] + fn replace_integer_type_multi_segment() { + let inner = Value::Map(vec![(Value::Text("level".into()), Value::U8(10))]); + let mut value = Value::Map(vec![(Value::Text("nested".into()), inner)]); + value + .replace_integer_type_at_path("nested.level", IntegerReplacementType::U32) + .unwrap(); + assert_eq!( + value.get_value_at_path("nested.level").unwrap(), + &Value::U32(10) + ); + } + + // =============================================================== + // replace_integer_type_at_path — array path + // =============================================================== + + #[test] + fn replace_integer_type_array_all_members() { + let elem1 = Value::Map(vec![(Value::Text("n".into()), Value::U128(8))]); + let elem2 = Value::Map(vec![(Value::Text("n".into()), Value::U32(2))]); + let arr = Value::Array(vec![elem1, elem2]); + let mut value = Value::Map(vec![(Value::Text("items".into()), arr)]); + value + .replace_integer_type_at_path("items[].n", IntegerReplacementType::U16) + .unwrap(); + assert_eq!( + value.get_value_at_path("items[0].n").unwrap(), + &Value::U16(8) + ); + assert_eq!( + value.get_value_at_path("items[1].n").unwrap(), + &Value::U16(2) + ); + } + + // =============================================================== + // replace_integer_type_at_path — missing key returns Ok + // =============================================================== + + #[test] + fn replace_integer_type_missing_key_returns_ok() { + let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(1))]); + let result = value.replace_integer_type_at_path("missing", IntegerReplacementType::U32); + assert!(result.is_ok()); + } + + // =============================================================== + // replace_integer_type_at_path — non-map errors + // =============================================================== + + #[test] + fn replace_integer_type_on_non_map_errors() { + let mut value = Value::U32(42); + let result = value.replace_integer_type_at_path("key", IntegerReplacementType::U32); + assert!(result.is_err()); + } + + // =============================================================== + // replace_integer_type_at_paths — multiple paths + // =============================================================== + + #[test] + fn replace_integer_type_at_paths_replaces_multiple() { + let inner = Value::Map(vec![ + (Value::Text("x".into()), Value::U16(5)), + (Value::Text("y".into()), Value::I32(6)), + ]); + let mut value = Value::Map(vec![(Value::Text("root".into()), inner)]); + value + .replace_integer_type_at_paths(vec!["root.x", "root.y"], IntegerReplacementType::U32) + .unwrap(); + assert_eq!(value.get_value_at_path("root.x").unwrap(), &Value::U32(5)); + assert_eq!(value.get_value_at_path("root.y").unwrap(), &Value::U32(6)); + } + + // =============================================================== + // replace_to_binary_types_of_root_value_when_setting_at_path + // — identifier match (exact path in identifier_paths) + // =============================================================== + + #[test] + fn replace_root_binary_types_identifier_exact_match() { + let b58 = base58_of_32_bytes(2); + let mut value = Value::Text(b58); + let identifier_paths = HashSet::from(["my_id"]); + value + .replace_to_binary_types_of_root_value_when_setting_at_path( + "my_id", + identifier_paths, + HashSet::new(), + ) + .unwrap(); + assert_eq!(value, Value::Identifier([2u8; 32])); + } + + // =============================================================== + // replace_to_binary_types_of_root_value_when_setting_at_path + // — binary match (exact path in binary_paths) + // =============================================================== + + #[test] + fn replace_root_binary_types_binary_exact_match() { + let b58 = base58_of_32_bytes(4); + let mut value = Value::Text(b58); + let binary_paths = HashSet::from(["my_data"]); + value + .replace_to_binary_types_of_root_value_when_setting_at_path( + "my_data", + HashSet::new(), + binary_paths, + ) + .unwrap(); + // BinaryBytes uses into_identifier_bytes (base58 decode) then replace_for_bytes -> Value::Bytes + assert_eq!(value, Value::Bytes([4u8; 32].to_vec())); + } + + // =============================================================== + // replace_to_binary_types_of_root_value_when_setting_at_path + // — prefix-based partial replacement (path starts_with) + // =============================================================== + + #[test] + fn replace_root_binary_types_prefix_based() { + let b58 = base58_of_32_bytes(6); + let inner = Value::Map(vec![(Value::Text("sub_id".into()), Value::Text(b58))]); + let mut value = Value::Map(vec![(Value::Text("nested".into()), inner)]); + let identifier_paths = HashSet::from(["root.nested.sub_id"]); + value + .replace_to_binary_types_of_root_value_when_setting_at_path( + "root", + identifier_paths, + HashSet::new(), + ) + .unwrap(); + // The identifier_path "root.nested.sub_id" starts_with "root", so + // replace_at_path("root.nested.sub_id", Identifier) is called on self. + // But self is the map starting at "nested", so the full path from self's + // perspective is "root.nested.sub_id" which includes the "root" prefix -- + // this means it tries to find "root" key in self. Since our value doesn't + // have a "root" key, the replacement is silently skipped. + // This is the actual behavior of the method. + } + + #[test] + fn replace_root_binary_types_prefix_replaces_sub_path() { + let b58 = base58_of_32_bytes(6); + let inner = Value::Map(vec![(Value::Text("sub_id".into()), Value::Text(b58))]); + let mut value = Value::Map(vec![(Value::Text("nested".into()), inner)]); + // The identifier path starts with the path prefix + let identifier_paths = HashSet::from(["doc.nested.sub_id"]); + value + .replace_to_binary_types_of_root_value_when_setting_at_path( + "doc", + identifier_paths, + HashSet::new(), + ) + .unwrap(); + // "doc.nested.sub_id".starts_with("doc") is true, so + // self.replace_at_path("doc.nested.sub_id", Identifier) is called. + // self doesn't have key "doc", so the filter_map returns empty vec. + // This is the expected silent skip behavior. + } + + // =============================================================== + // replace_to_binary_types_of_root_value_when_setting_at_path + // — no match at all, returns Ok + // =============================================================== + + #[test] + fn replace_root_binary_types_no_match_returns_ok() { + let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(1))]); + let result = value.replace_to_binary_types_of_root_value_when_setting_at_path( + "unrelated", + HashSet::from(["other.path"]), + HashSet::new(), + ); + assert!(result.is_ok()); + } + + // =============================================================== + // replace_to_binary_types_when_setting_with_path — identifier + // =============================================================== + + #[test] + fn replace_when_setting_with_path_identifier_exact() { + let b58 = base58_of_32_bytes(11); + let mut value = Value::Text(b58); + let identifier_paths = HashSet::from(["doc.owner"]); + value + .replace_to_binary_types_when_setting_with_path( + "doc.owner", + identifier_paths, + HashSet::new(), + ) + .unwrap(); + assert_eq!(value, Value::Identifier([11u8; 32])); + } + + // =============================================================== + // replace_to_binary_types_when_setting_with_path — strip prefix + // =============================================================== + + #[test] + fn replace_when_setting_with_path_strip_prefix() { + let b58 = base58_of_32_bytes(15); + let mut value = Value::Map(vec![(Value::Text("sub_id".into()), Value::Text(b58))]); + let identifier_paths = HashSet::from(["container.sub_id"]); + value + .replace_to_binary_types_when_setting_with_path( + "container", + identifier_paths, + HashSet::new(), + ) + .unwrap(); + assert_eq!( + value.get_value_at_path("sub_id").unwrap(), + &Value::Identifier([15u8; 32]) + ); + } + + // =============================================================== + // replace_to_binary_types_when_setting_with_path — binary strip prefix + // =============================================================== + + #[test] + fn replace_when_setting_with_path_binary_strip_prefix() { + use base64::prelude::*; + let raw = vec![1u8, 2, 3, 4, 5]; + let b64 = BASE64_STANDARD.encode(&raw); + let mut value = Value::Map(vec![(Value::Text("blob".into()), Value::Text(b64))]); + let binary_paths = HashSet::from(["container.blob"]); + value + .replace_to_binary_types_when_setting_with_path( + "container", + HashSet::new(), + binary_paths, + ) + .unwrap(); + assert_eq!(value.get_value_at_path("blob").unwrap(), &Value::Bytes(raw)); + } + + // =============================================================== + // replace_to_binary_types_when_setting_with_path — no match + // =============================================================== + + #[test] + fn replace_when_setting_with_path_no_match_ok() { + let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(1))]); + let result = value.replace_to_binary_types_when_setting_with_path( + "container", + HashSet::from(["other.path"]), + HashSet::new(), + ); + assert!(result.is_ok()); + } + + // =============================================================== + // clean_recursive — removes nulls from nested maps + // =============================================================== + + #[test] + fn clean_recursive_removes_nulls() { + let inner = Value::Map(vec![ + (Value::Text("keep".into()), Value::U32(1)), + (Value::Text("drop".into()), Value::Null), + ]); + let value = Value::Map(vec![ + (Value::Text("inner".into()), inner), + (Value::Text("also_drop".into()), Value::Null), + ]); + let cleaned = value.clean_recursive().unwrap(); + let map = cleaned.to_map().unwrap(); + assert_eq!(map.len(), 1); + let inner_map = map.get_key("inner").unwrap().to_map().unwrap(); + assert_eq!(inner_map.len(), 1); + assert!(inner_map.get_optional_key("keep").is_some()); + assert!(inner_map.get_optional_key("drop").is_none()); + } + + #[test] + fn clean_recursive_deeply_nested() { + let deep = Value::Map(vec![ + (Value::Text("a".into()), Value::Null), + (Value::Text("b".into()), Value::U8(1)), + ]); + let mid = Value::Map(vec![ + (Value::Text("deep".into()), deep), + (Value::Text("c".into()), Value::Null), + ]); + let value = Value::Map(vec![(Value::Text("mid".into()), mid)]); + let cleaned = value.clean_recursive().unwrap(); + let mid_map = cleaned.get_value_at_path("mid").unwrap().to_map().unwrap(); + assert_eq!(mid_map.len(), 1); // only "deep" remains + let deep_map = cleaned + .get_value_at_path("mid.deep") + .unwrap() + .to_map() + .unwrap(); + assert_eq!(deep_map.len(), 1); // only "b" remains + } + + #[test] + fn clean_recursive_preserves_non_null_non_map_values() { + let value = Value::Map(vec![ + (Value::Text("num".into()), Value::U64(42)), + (Value::Text("text".into()), Value::Text("hello".into())), + ( + Value::Text("arr".into()), + Value::Array(vec![Value::Null, Value::U8(1)]), + ), + ]); + let cleaned = value.clean_recursive().unwrap(); + let map = cleaned.to_map().unwrap(); + assert_eq!(map.len(), 3); + // Arrays with Null inside are NOT cleaned (clean_recursive only filters map entries) + let arr = map.get_key("arr").unwrap().to_array_ref().unwrap(); + assert_eq!(arr.len(), 2); + } + + #[test] + fn clean_recursive_all_null() { + let value = Value::Map(vec![ + (Value::Text("a".into()), Value::Null), + (Value::Text("b".into()), Value::Null), + ]); + let cleaned = value.clean_recursive().unwrap(); + let map = cleaned.to_map().unwrap(); + assert_eq!(map.len(), 0); + } + + #[test] + fn clean_recursive_on_non_map_errors() { + let value = Value::U32(42); + let result = value.clean_recursive(); + assert!(result.is_err()); + } + + // =============================================================== + // replace_at_path — intermediate non-map value errors + // =============================================================== + + #[test] + fn replace_at_path_intermediate_non_map_errors() { + let mut value = Value::Map(vec![(Value::Text("a".into()), Value::U32(42))]); + // "a" is U32, not a map, so traversing "a.b" should error + let result = value.replace_at_path("a.b", ReplacementType::Identifier); + assert!(result.is_err()); + } +} diff --git a/packages/rs-platform-value/src/types/bytes_20.rs b/packages/rs-platform-value/src/types/bytes_20.rs index 1afe384baf1..67e2b408cca 100644 --- a/packages/rs-platform-value/src/types/bytes_20.rs +++ b/packages/rs-platform-value/src/types/bytes_20.rs @@ -208,3 +208,413 @@ impl From<&Bytes20> for String { val.to_string(Encoding::Base64) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + fn compute_hash(value: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + // --------------------------------------------------------------- + // From<[u8; 20]> and Into<[u8; 20]> round-trip + // --------------------------------------------------------------- + + #[test] + fn from_array_round_trip() { + let arr = [0xABu8; 20]; + let b = Bytes20::from(arr); + assert_eq!(b.0, arr); + let back: [u8; 20] = b.to_buffer(); + assert_eq!(back, arr); + } + + #[test] + fn from_ref_array() { + let arr = [7u8; 20]; + let b = Bytes20::from(&arr); + assert_eq!(b.0, arr); + } + + #[test] + fn into_buffer_consumes_and_returns_inner() { + let arr = [42u8; 20]; + let b = Bytes20::new(arr); + let inner = b.into_buffer(); + assert_eq!(inner, arr); + } + + // --------------------------------------------------------------- + // from_vec — correct and wrong sizes + // --------------------------------------------------------------- + + #[test] + fn from_vec_correct_size() { + let v = vec![1u8; 20]; + let b = Bytes20::from_vec(v).unwrap(); + assert_eq!(b.0, [1u8; 20]); + } + + #[test] + fn from_vec_too_short() { + let v = vec![1u8; 19]; + let err = Bytes20::from_vec(v).unwrap_err(); + match err { + Error::ByteLengthNot20BytesError(_) => {} + other => panic!("expected ByteLengthNot20BytesError, got {:?}", other), + } + } + + #[test] + fn from_vec_too_long() { + let v = vec![1u8; 21]; + let err = Bytes20::from_vec(v).unwrap_err(); + match err { + Error::ByteLengthNot20BytesError(_) => {} + other => panic!("expected ByteLengthNot20BytesError, got {:?}", other), + } + } + + #[test] + fn from_vec_empty() { + let v = vec![]; + assert!(Bytes20::from_vec(v).is_err()); + } + + // --------------------------------------------------------------- + // TryFrom for owned Value + // --------------------------------------------------------------- + + #[test] + fn try_from_value_bytes20() { + let arr = [9u8; 20]; + let val = Value::Bytes20(arr); + let b = Bytes20::try_from(val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_value_bytes_correct_len() { + let v = vec![3u8; 20]; + let val = Value::Bytes(v); + let b = Bytes20::try_from(val).unwrap(); + assert_eq!(b.0, [3u8; 20]); + } + + #[test] + fn try_from_value_bytes_wrong_len() { + let val = Value::Bytes(vec![1, 2, 3]); + assert!(Bytes20::try_from(val).is_err()); + } + + #[test] + fn try_from_value_unsupported_variant() { + let val = Value::Bool(true); + assert!(Bytes20::try_from(val).is_err()); + } + + #[test] + fn try_from_value_null_errors() { + let val = Value::Null; + assert!(Bytes20::try_from(val).is_err()); + } + + // --------------------------------------------------------------- + // TryFrom<&Value> for borrowed Value + // --------------------------------------------------------------- + + #[test] + fn try_from_ref_value_bytes20() { + let arr = [10u8; 20]; + let val = Value::Bytes20(arr); + let b = Bytes20::try_from(&val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_ref_value_bytes_correct_len() { + let val = Value::Bytes(vec![4u8; 20]); + let b = Bytes20::try_from(&val).unwrap(); + assert_eq!(b.0, [4u8; 20]); + } + + #[test] + fn try_from_ref_value_bytes_wrong_len() { + let val = Value::Bytes(vec![1, 2]); + assert!(Bytes20::try_from(&val).is_err()); + } + + #[test] + fn try_from_ref_value_unsupported() { + let val = Value::Float(3.14); + assert!(Bytes20::try_from(&val).is_err()); + } + + // --------------------------------------------------------------- + // as_slice(), to_vec() + // --------------------------------------------------------------- + + #[test] + fn as_slice_returns_inner_bytes() { + let arr = [0xFFu8; 20]; + let b = Bytes20::new(arr); + assert_eq!(b.as_slice(), &arr[..]); + assert_eq!(b.as_slice().len(), 20); + } + + #[test] + fn to_vec_returns_copy() { + let arr = [5u8; 20]; + let b = Bytes20::new(arr); + let v = b.to_vec(); + assert_eq!(v.len(), 20); + assert_eq!(v, arr.to_vec()); + } + + #[test] + fn as_ref_returns_inner_bytes() { + let arr = [11u8; 20]; + let b = Bytes20::new(arr); + let r: &[u8] = b.as_ref(); + assert_eq!(r, &arr[..]); + } + + // --------------------------------------------------------------- + // Hash impl: equal values hash equally, different values differ + // --------------------------------------------------------------- + + #[test] + fn hash_equal_values() { + let a = Bytes20::new([1u8; 20]); + let b = Bytes20::new([1u8; 20]); + assert_eq!(compute_hash(&a), compute_hash(&b)); + } + + #[test] + fn hash_different_values() { + let a = Bytes20::new([1u8; 20]); + let b = Bytes20::new([2u8; 20]); + // Highly unlikely to collide + assert_ne!(compute_hash(&a), compute_hash(&b)); + } + + // --------------------------------------------------------------- + // PartialOrd / Ord: ordering matches byte ordering + // --------------------------------------------------------------- + + #[test] + fn ordering_matches_byte_ordering() { + let mut low = [0u8; 20]; + low[0] = 1; + let mut high = [0u8; 20]; + high[0] = 2; + let a = Bytes20::new(low); + let b = Bytes20::new(high); + assert!(a < b); + assert!(b > a); + } + + #[test] + fn ordering_equal() { + let a = Bytes20::new([5u8; 20]); + let b = Bytes20::new([5u8; 20]); + assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal); + } + + #[test] + fn ordering_last_byte_differs() { + let mut low = [0u8; 20]; + low[19] = 1; + let mut high = [0u8; 20]; + high[19] = 2; + assert!(Bytes20::new(low) < Bytes20::new(high)); + } + + // --------------------------------------------------------------- + // Display output format (Base58) + // --------------------------------------------------------------- + + #[test] + fn display_uses_base58() { + let arr = [1u8; 20]; + let b = Bytes20::new(arr); + let display_str = format!("{}", b); + let base58_str = b.to_string(Encoding::Base58); + assert_eq!(display_str, base58_str); + // Ensure it's non-empty + assert!(!display_str.is_empty()); + } + + #[test] + fn display_all_zeros() { + let b = Bytes20::new([0u8; 20]); + let display_str = format!("{}", b); + // Base58 encoding of 20 zero bytes + let expected = bs58::encode([0u8; 20]).into_string(); + assert_eq!(display_str, expected); + } + + // --------------------------------------------------------------- + // Default is all zeros + // --------------------------------------------------------------- + + #[test] + fn default_is_all_zeros() { + let b = Bytes20::default(); + assert_eq!(b.0, [0u8; 20]); + } + + // --------------------------------------------------------------- + // Value round-trips + // --------------------------------------------------------------- + + #[test] + fn into_value_and_back() { + let arr = [42u8; 20]; + let b = Bytes20::new(arr); + let val: Value = b.into(); + assert_eq!(val, Value::Bytes20(arr)); + let back = Bytes20::try_from(val).unwrap(); + assert_eq!(back, b); + } + + #[test] + fn ref_into_value() { + let b = Bytes20::new([7u8; 20]); + let val: Value = (&b).into(); + assert_eq!(val, Value::Bytes20([7u8; 20])); + } + + // --------------------------------------------------------------- + // String conversions + // --------------------------------------------------------------- + + #[test] + fn try_from_string_base64_round_trip() { + let arr = [99u8; 20]; + let b = Bytes20::new(arr); + let s: String = b.into(); + let recovered = Bytes20::try_from(s).unwrap(); + assert_eq!(recovered, Bytes20::new(arr)); + } + + #[test] + fn try_from_string_invalid_base64() { + let s = "not-valid-base64!!!".to_string(); + assert!(Bytes20::try_from(s).is_err()); + } + + #[test] + fn ref_to_string() { + let b = Bytes20::new([0u8; 20]); + let s: String = (&b).into(); + // Verify it's valid base64 + let decoded = BASE64_STANDARD.decode(&s).unwrap(); + assert_eq!(decoded.len(), 20); + } + + // --------------------------------------------------------------- + // from_string with different encodings + // --------------------------------------------------------------- + + #[test] + fn from_string_base58_round_trip() { + let arr = [0xABu8; 20]; + let b = Bytes20::new(arr); + let encoded = b.to_string(Encoding::Base58); + let recovered = Bytes20::from_string(&encoded, Encoding::Base58).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn from_string_hex_round_trip() { + let arr = [0xCDu8; 20]; + let b = Bytes20::new(arr); + let encoded = b.to_string(Encoding::Hex); + let recovered = Bytes20::from_string(&encoded, Encoding::Hex).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn from_string_with_encoding_string_none_defaults_to_base58() { + let arr = [0x01u8; 20]; + let b = Bytes20::new(arr); + let encoded = b.to_string_with_encoding_string(None); + let recovered = Bytes20::from_string_with_encoding_string(&encoded, None).unwrap(); + assert_eq!(recovered, b); + } + + // --------------------------------------------------------------- + // Serde round-trips + // --------------------------------------------------------------- + + #[test] + #[cfg(feature = "json")] + fn serde_json_round_trip() { + let arr = [0x12u8; 20]; + let b = Bytes20::new(arr); + let json = serde_json::to_string(&b).unwrap(); + let recovered: Bytes20 = serde_json::from_str(&json).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn serde_bincode_round_trip() { + let arr = [0x34u8; 20]; + let b = Bytes20::new(arr); + let config = bincode::config::standard(); + let encoded = bincode::encode_to_vec(&b, config).unwrap(); + let (decoded, _): (Bytes20, _) = bincode::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(decoded, b); + } + + // --------------------------------------------------------------- + // Clone and Copy semantics + // --------------------------------------------------------------- + + #[test] + fn clone_is_equal() { + let b = Bytes20::new([77u8; 20]); + let c = b.clone(); + assert_eq!(b, c); + } + + #[test] + fn copy_semantics() { + let b = Bytes20::new([88u8; 20]); + let c = b; // Copy + assert_eq!(b, c); // b is still valid because Bytes20 is Copy + } + + // --------------------------------------------------------------- + // Equality + // --------------------------------------------------------------- + + #[test] + fn equality_same_bytes() { + let a = Bytes20::new([1u8; 20]); + let b = Bytes20::new([1u8; 20]); + assert_eq!(a, b); + } + + #[test] + fn inequality_different_bytes() { + let a = Bytes20::new([1u8; 20]); + let b = Bytes20::new([2u8; 20]); + assert_ne!(a, b); + } + + #[test] + fn inequality_single_byte_diff() { + let mut arr = [0u8; 20]; + let a = Bytes20::new(arr); + arr[10] = 1; + let b = Bytes20::new(arr); + assert_ne!(a, b); + } +} diff --git a/packages/rs-platform-value/src/types/bytes_32.rs b/packages/rs-platform-value/src/types/bytes_32.rs index 041abb07d8e..3cb7b35593c 100644 --- a/packages/rs-platform-value/src/types/bytes_32.rs +++ b/packages/rs-platform-value/src/types/bytes_32.rs @@ -205,3 +205,426 @@ impl From<&Bytes32> for String { val.to_string(Encoding::Base64) } } + +#[cfg(test)] +mod tests { + use super::*; + use rand::SeedableRng; + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + fn compute_hash(value: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + // --------------------------------------------------------------- + // From<[u8; 32]> and Into<[u8; 32]> round-trip + // --------------------------------------------------------------- + + #[test] + fn from_array_round_trip() { + let arr = [0xABu8; 32]; + let b = Bytes32::from(arr); + assert_eq!(b.0, arr); + let back: [u8; 32] = b.to_buffer(); + assert_eq!(back, arr); + } + + #[test] + fn from_ref_array() { + let arr = [7u8; 32]; + let b = Bytes32::from(&arr); + assert_eq!(b.0, arr); + } + + // --------------------------------------------------------------- + // from_vec — correct and wrong sizes + // --------------------------------------------------------------- + + #[test] + fn from_vec_correct_size() { + let v = vec![1u8; 32]; + let b = Bytes32::from_vec(v).unwrap(); + assert_eq!(b.0, [1u8; 32]); + } + + #[test] + fn from_vec_too_short() { + let v = vec![1u8; 31]; + let err = Bytes32::from_vec(v).unwrap_err(); + match err { + Error::ByteLengthNot32BytesError(_) => {} + other => panic!("expected ByteLengthNot32BytesError, got {:?}", other), + } + } + + #[test] + fn from_vec_too_long() { + let v = vec![1u8; 33]; + let err = Bytes32::from_vec(v).unwrap_err(); + match err { + Error::ByteLengthNot32BytesError(_) => {} + other => panic!("expected ByteLengthNot32BytesError, got {:?}", other), + } + } + + #[test] + fn from_vec_empty() { + let v = vec![]; + assert!(Bytes32::from_vec(v).is_err()); + } + + // --------------------------------------------------------------- + // TryFrom for owned Value + // --------------------------------------------------------------- + + #[test] + fn try_from_value_bytes32() { + let arr = [9u8; 32]; + let val = Value::Bytes32(arr); + let b = Bytes32::try_from(val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_value_bytes_correct_len() { + let v = vec![3u8; 32]; + let val = Value::Bytes(v); + let b = Bytes32::try_from(val).unwrap(); + assert_eq!(b.0, [3u8; 32]); + } + + #[test] + fn try_from_value_bytes_wrong_len() { + let val = Value::Bytes(vec![1, 2, 3]); + assert!(Bytes32::try_from(val).is_err()); + } + + #[test] + fn try_from_value_identifier() { + let arr = [0xCCu8; 32]; + let val = Value::Identifier(arr); + let b = Bytes32::try_from(val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_value_unsupported_variant() { + let val = Value::Bool(false); + assert!(Bytes32::try_from(val).is_err()); + } + + #[test] + fn try_from_value_null_errors() { + let val = Value::Null; + assert!(Bytes32::try_from(val).is_err()); + } + + // --------------------------------------------------------------- + // TryFrom<&Value> for borrowed Value + // --------------------------------------------------------------- + + #[test] + fn try_from_ref_value_bytes32() { + let arr = [10u8; 32]; + let val = Value::Bytes32(arr); + let b = Bytes32::try_from(&val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_ref_value_bytes_correct_len() { + let val = Value::Bytes(vec![4u8; 32]); + let b = Bytes32::try_from(&val).unwrap(); + assert_eq!(b.0, [4u8; 32]); + } + + #[test] + fn try_from_ref_value_bytes_wrong_len() { + let val = Value::Bytes(vec![1, 2]); + assert!(Bytes32::try_from(&val).is_err()); + } + + #[test] + fn try_from_ref_value_identifier() { + let arr = [0xDDu8; 32]; + let val = Value::Identifier(arr); + let b = Bytes32::try_from(&val).unwrap(); + assert_eq!(b.0, arr); + } + + #[test] + fn try_from_ref_value_unsupported() { + let val = Value::Float(3.14); + assert!(Bytes32::try_from(&val).is_err()); + } + + // --------------------------------------------------------------- + // as_slice(), to_vec() + // --------------------------------------------------------------- + + #[test] + fn as_slice_returns_inner_bytes() { + let arr = [0xFFu8; 32]; + let b = Bytes32::new(arr); + assert_eq!(b.as_slice(), &arr[..]); + assert_eq!(b.as_slice().len(), 32); + } + + #[test] + fn to_vec_returns_copy() { + let arr = [5u8; 32]; + let b = Bytes32::new(arr); + let v = b.to_vec(); + assert_eq!(v.len(), 32); + assert_eq!(v, arr.to_vec()); + } + + #[test] + fn as_ref_returns_inner_bytes() { + let arr = [11u8; 32]; + let b = Bytes32::new(arr); + let r: &[u8] = b.as_ref(); + assert_eq!(r, &arr[..]); + } + + // --------------------------------------------------------------- + // Hash impl: equal values hash equally, different values differ + // --------------------------------------------------------------- + + #[test] + fn hash_equal_values() { + let a = Bytes32::new([1u8; 32]); + let b = Bytes32::new([1u8; 32]); + assert_eq!(compute_hash(&a), compute_hash(&b)); + } + + #[test] + fn hash_different_values() { + let a = Bytes32::new([1u8; 32]); + let b = Bytes32::new([2u8; 32]); + assert_ne!(compute_hash(&a), compute_hash(&b)); + } + + // --------------------------------------------------------------- + // PartialOrd / Ord: ordering matches byte ordering + // --------------------------------------------------------------- + + #[test] + fn ordering_matches_byte_ordering() { + let mut low = [0u8; 32]; + low[0] = 1; + let mut high = [0u8; 32]; + high[0] = 2; + let a = Bytes32::new(low); + let b = Bytes32::new(high); + assert!(a < b); + assert!(b > a); + } + + #[test] + fn ordering_equal() { + let a = Bytes32::new([5u8; 32]); + let b = Bytes32::new([5u8; 32]); + assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal); + } + + #[test] + fn ordering_last_byte_differs() { + let mut low = [0u8; 32]; + low[31] = 1; + let mut high = [0u8; 32]; + high[31] = 2; + assert!(Bytes32::new(low) < Bytes32::new(high)); + } + + // --------------------------------------------------------------- + // Default is all zeros + // --------------------------------------------------------------- + + #[test] + fn default_is_all_zeros() { + let b = Bytes32::default(); + assert_eq!(b.0, [0u8; 32]); + } + + // --------------------------------------------------------------- + // Value round-trips + // --------------------------------------------------------------- + + #[test] + fn into_value_and_back() { + let arr = [42u8; 32]; + let b = Bytes32::new(arr); + let val: Value = b.into(); + assert_eq!(val, Value::Bytes32(arr)); + let back = Bytes32::try_from(val).unwrap(); + assert_eq!(back, b); + } + + #[test] + fn ref_into_value() { + let b = Bytes32::new([7u8; 32]); + let val: Value = (&b).into(); + assert_eq!(val, Value::Bytes32([7u8; 32])); + } + + // --------------------------------------------------------------- + // String conversions + // --------------------------------------------------------------- + + #[test] + fn try_from_string_base64_round_trip() { + let arr = [99u8; 32]; + let b = Bytes32::new(arr); + let s: String = b.into(); + let recovered = Bytes32::try_from(s).unwrap(); + assert_eq!(recovered, Bytes32::new(arr)); + } + + #[test] + fn try_from_string_invalid_base64() { + let s = "not-valid-base64!!!".to_string(); + assert!(Bytes32::try_from(s).is_err()); + } + + #[test] + fn ref_to_string() { + let b = Bytes32::new([0u8; 32]); + let s: String = (&b).into(); + let decoded = BASE64_STANDARD.decode(&s).unwrap(); + assert_eq!(decoded.len(), 32); + } + + // --------------------------------------------------------------- + // from_string with different encodings + // --------------------------------------------------------------- + + #[test] + fn from_string_base58_round_trip() { + let arr = [0xABu8; 32]; + let b = Bytes32::new(arr); + let encoded = b.to_string(Encoding::Base58); + let recovered = Bytes32::from_string(&encoded, Encoding::Base58).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn from_string_hex_round_trip() { + let arr = [0xCDu8; 32]; + let b = Bytes32::new(arr); + let encoded = b.to_string(Encoding::Hex); + let recovered = Bytes32::from_string(&encoded, Encoding::Hex).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn from_string_with_encoding_string_none_defaults_to_base58() { + let arr = [0x01u8; 32]; + let b = Bytes32::new(arr); + let encoded = b.to_string_with_encoding_string(None); + let recovered = Bytes32::from_string_with_encoding_string(&encoded, None).unwrap(); + assert_eq!(recovered, b); + } + + // --------------------------------------------------------------- + // Serde round-trips + // --------------------------------------------------------------- + + #[test] + #[cfg(feature = "json")] + fn serde_json_round_trip() { + let arr = [0x12u8; 32]; + let b = Bytes32::new(arr); + let json = serde_json::to_string(&b).unwrap(); + let recovered: Bytes32 = serde_json::from_str(&json).unwrap(); + assert_eq!(recovered, b); + } + + #[test] + fn serde_bincode_round_trip() { + let arr = [0x34u8; 32]; + let b = Bytes32::new(arr); + let config = bincode::config::standard(); + let encoded = bincode::encode_to_vec(&b, config).unwrap(); + let (decoded, _): (Bytes32, _) = bincode::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(decoded, b); + } + + // --------------------------------------------------------------- + // Clone and Copy semantics + // --------------------------------------------------------------- + + #[test] + fn clone_is_equal() { + let b = Bytes32::new([77u8; 32]); + let c = b.clone(); + assert_eq!(b, c); + } + + #[test] + fn copy_semantics() { + let b = Bytes32::new([88u8; 32]); + let c = b; // Copy + assert_eq!(b, c); // b is still valid because Bytes32 is Copy + } + + // --------------------------------------------------------------- + // Equality + // --------------------------------------------------------------- + + #[test] + fn equality_same_bytes() { + let a = Bytes32::new([1u8; 32]); + let b = Bytes32::new([1u8; 32]); + assert_eq!(a, b); + } + + #[test] + fn inequality_different_bytes() { + let a = Bytes32::new([1u8; 32]); + let b = Bytes32::new([2u8; 32]); + assert_ne!(a, b); + } + + #[test] + fn inequality_single_byte_diff() { + let mut arr = [0u8; 32]; + let a = Bytes32::new(arr); + arr[16] = 1; + let b = Bytes32::new(arr); + assert_ne!(a, b); + } + + // --------------------------------------------------------------- + // random_with_rng + // --------------------------------------------------------------- + + #[test] + fn random_with_rng_produces_non_zero() { + let mut rng = StdRng::seed_from_u64(12345); + let b = Bytes32::random_with_rng(&mut rng); + // Extremely unlikely for 32 random bytes to all be zero + assert_ne!(b.0, [0u8; 32]); + } + + #[test] + fn random_with_rng_deterministic_with_same_seed() { + let mut rng1 = StdRng::seed_from_u64(42); + let mut rng2 = StdRng::seed_from_u64(42); + let a = Bytes32::random_with_rng(&mut rng1); + let b = Bytes32::random_with_rng(&mut rng2); + assert_eq!(a, b); + } + + #[test] + fn random_with_rng_different_seeds_differ() { + let mut rng1 = StdRng::seed_from_u64(1); + let mut rng2 = StdRng::seed_from_u64(2); + let a = Bytes32::random_with_rng(&mut rng1); + let b = Bytes32::random_with_rng(&mut rng2); + assert_ne!(a, b); + } +} From e633237bd0b889137e869207f76b6d7e36b0e1e8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 3 Apr 2026 21:01:29 +0300 Subject: [PATCH 2/3] fix(rs-scripts): remove unused serde_json dependency Fixes cargo-machete CI failure. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/rs-scripts/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rs-scripts/Cargo.toml b/packages/rs-scripts/Cargo.toml index dc639760994..18bb06e33b9 100644 --- a/packages/rs-scripts/Cargo.toml +++ b/packages/rs-scripts/Cargo.toml @@ -15,4 +15,3 @@ base64 = "0.22" chrono = "0.4" hex = "0.4" clap = { version = "4", features = ["derive"] } -serde_json = "1" From aab73a5d7362cad59879534ce41dde915d927bf2 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 3 Apr 2026 21:17:21 +0300 Subject: [PATCH 3/3] ci: retrigger --- Cargo.lock | 228 ++++++++++++++++++++++++++--------------------------- 1 file changed, 112 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b80e6dcc03..33bcc76f18c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -131,7 +131,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -857,7 +857,7 @@ checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "clap", "heck 0.4.1", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "proc-macro2", "quote", @@ -876,7 +876,7 @@ checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799" dependencies = [ "clap", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "proc-macro2", "quote", @@ -889,9 +889,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -1114,7 +1114,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1620,7 +1620,7 @@ dependencies = [ "futures", "hex", "hickory-resolver", - "indexmap 2.13.0", + "indexmap 2.13.1", "key-wallet", "key-wallet-manager", "log", @@ -1927,7 +1927,7 @@ dependencies = [ "getrandom 0.2.17", "grovedb-commitment-tree", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "itertools 0.13.0", "json-schema-compatibility-validator", @@ -1991,7 +1991,7 @@ dependencies = [ "grovedb-storage", "grovedb-version", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "intmap", "itertools 0.13.0", @@ -2035,7 +2035,7 @@ dependencies = [ "file-rotate", "grovedb-commitment-tree", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "itertools 0.13.0", "lazy_static", @@ -2078,7 +2078,7 @@ dependencies = [ "dpp", "drive", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "platform-serialization", "platform-serialization-derive", "serde", @@ -2285,7 +2285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2466,9 +2466,12 @@ dependencies = [ [[package]] name = "fragile" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "fs_extra" @@ -2714,7 +2717,7 @@ dependencies = [ "grovedbg-types", "hex", "hex-literal", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "intmap", "itertools 0.14.0", @@ -2829,7 +2832,7 @@ dependencies = [ "grovedb-version", "grovedb-visualize", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "num_cpus", "rand 0.10.0", @@ -2866,7 +2869,7 @@ dependencies = [ "grovedb-costs", "grovedb-storage", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "thiserror 2.0.18", ] @@ -2929,7 +2932,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.13.1", "slab", "tokio", "tokio-util", @@ -3262,9 +3265,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -3277,7 +3280,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3347,7 +3349,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -3381,12 +3383,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -3394,9 +3397,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -3407,9 +3410,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -3421,15 +3424,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -3441,15 +3444,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -3531,9 +3534,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -3603,7 +3606,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3923,9 +3926,9 @@ checksum = "744a4c881f502e98c2241d2e5f50040ac73b30194d64452bb6260393b53f0dc9" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libloading" @@ -3993,9 +3996,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -4132,7 +4135,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "indexmap 2.13.0", + "indexmap 2.13.1", "ipnet", "metrics", "metrics-util", @@ -4337,7 +4340,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4700,7 +4703,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", ] [[package]] @@ -4767,12 +4770,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs8" version = "0.10.2" @@ -4827,7 +4824,7 @@ dependencies = [ "bs58", "ciborium", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "platform-serialization", "platform-version", "rand 0.8.5", @@ -4872,7 +4869,7 @@ dependencies = [ "dash-sdk", "dashcore", "dpp", - "indexmap 2.13.0", + "indexmap 2.13.1", "key-wallet", "key-wallet-manager", "platform-encryption", @@ -4950,9 +4947,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -5034,7 +5031,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.8+spec-1.1.0", + "toml_edit 0.25.10+spec-1.1.0", ] [[package]] @@ -5109,7 +5106,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -5268,7 +5265,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -5306,7 +5303,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -5853,7 +5850,6 @@ dependencies = [ "dpp", "hex", "platform-version", - "serde_json", ] [[package]] @@ -6032,7 +6028,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6091,7 +6087,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6372,7 +6368,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "itoa", "memchr", "serde", @@ -6413,9 +6409,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -6458,7 +6454,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.13.1", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -6680,7 +6676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6912,7 +6908,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7110,9 +7106,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -7155,9 +7151,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -7173,9 +7169,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -7271,9 +7267,9 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "serde_core", - "serde_spanned 1.1.0", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -7300,9 +7296,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -7313,7 +7309,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -7324,7 +7320,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -7334,21 +7330,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.1.0+spec-1.1.0", + "indexmap 2.13.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow 1.0.1", ] @@ -7361,9 +7357,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" @@ -7519,7 +7515,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.0", + "indexmap 2.13.1", "pin-project-lite", "slab", "sync_wrapper", @@ -8142,7 +8138,7 @@ dependencies = [ "dpp", "drive", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "js-sys", "serde", "serde-wasm-bindgen 0.6.5", @@ -8180,7 +8176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.13.1", "wasm-encoder", "wasmparser", ] @@ -8242,7 +8238,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", "semver", ] @@ -8324,7 +8320,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8689,7 +8685,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -8720,7 +8716,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "serde", "serde_derive", @@ -8739,7 +8735,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "semver", "serde", @@ -8764,9 +8760,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -8791,9 +8787,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8802,9 +8798,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -8855,18 +8851,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -8920,9 +8916,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -8931,9 +8927,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -8942,9 +8938,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -8964,7 +8960,7 @@ dependencies = [ "flate2", "getrandom 0.3.4", "hmac", - "indexmap 2.13.0", + "indexmap 2.13.1", "lzma-rust2", "memchr", "pbkdf2", @@ -8981,7 +8977,7 @@ checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ "crc32fast", "flate2", - "indexmap 2.13.0", + "indexmap 2.13.1", "memchr", "typed-path", "zopfli",