solana_frozen_abi/lib.rs
1//! # StableAbi
2//!
3//! The `StableAbi` is an optional extension to `frozen-abi` that provides functionality for
4//! detecting unintended encoding changes.
5//!
6//! ## How it works?
7//!
8//! When you annotate a type with:
9//!
10//! ```rust,ignore
11//! #[frozen_abi(abi_digest = "...")]
12//! struct MyType { ... }
13//! ```
14//!
15//! The macro would generate the `test_abi_digest` test that verifies binary layout stability:
16//! - Initializes a deterministic random number generator with fixed seed
17//! - Generates 10_000 instances of the type via `StableAbi::random()`
18//! - Serializes each instance
19//! - Hashes all serialized bytes together
20//! - Compares the resulting hash against the provided in `abi_digest` attribute
21//!
22//! Using the seeded RNG ensures same sequence of random values across runs, while the 10_000
23//! iterations brings a wide range of value combinations. By hashing serialized bytes, changes
24//! to padding, endianness or encoding are detected.
25//!
26//!
27//! ## Adding StableAbi to a New Type
28//!
29//! Deriving `StableAbi` adds:
30//!
31//! ```rust,ignore
32//! impl ::solana_frozen_abi::stable_abi::StableAbi for MyType {}
33//! ```
34//!
35//! The `StableAbi::random()` default implementation calls `rng.random::<MyType>()`, so your type
36//! also needs `Distribution<MyType> for StandardUniform`.
37//!
38//! There are two ways to provide it:
39//!
40//! 1. Derive `StableAbiSample`
41//!
42//! `StableAbiSample` auto-generates `Distribution<MyType> for StandardUniform` and, by default,
43//! tries to sample each field via `rng.random()`.
44//!
45//! ```rust,ignore
46//! #[derive(StableAbi, StableAbiSample)]
47//! #[frozen_abi(abi_digest = "...")]
48//! struct MyType {
49//! a: u64,
50//! b: bool,
51//! c: [u8; 32],
52//! d: (u8, u8),
53//! }
54//! ```
55//!
56//! Field override is optional and only needed for fields that cannot be sampled with plain
57//! `rng.random()` (for example `Vec<_>` or `HashMap<_, _>`), or when you want a specific shape.
58//!
59//! ```rust,ignore
60//! #[derive(StableAbi, StableAbiSample)]
61//! #[frozen_abi(abi_digest = "...", abi_serializer = "wincode")]
62//! struct MyTypeWithOverride {
63//! #[stable_abi_sample(
64//! with = "(0..rng.random::<u8>() % 4).map(|_| rng.random::<bool>()).collect()")]
65//! a: Vec<bool>,
66//! #[stable_abi_sample(
67//! with = "std::collections::HashMap::from_iter([(rng.random(), rng.random())])"
68//! )]
69//! b: std::collections::HashMap<u64, bool>,
70//! c: [u8; 32],
71//! }
72//! ```
73//!
74//! 2. Write a manual `Distribution` implementation
75//!
76//! ```rust,ignore
77//! #[cfg(feature = "frozen-abi")]
78//! impl solana_frozen_abi::rand::prelude::Distribution<MyType>
79//! for solana_frozen_abi::rand::distr::StandardUniform
80//! {
81//! fn sample<R: solana_frozen_abi::rand::Rng + ?Sized>(&self, rng: &mut R) -> MyType {
82//! MyType {
83//! field: rng.random(),
84//! ...
85//! }
86//! }
87//! }
88//! ```
89//!
90//! For `wincode`-based types, add `abi_serializer = "wincode"` to `#[frozen_abi(...)]`.
91//!
92//! ```rust,ignore
93//! #[derive(StableAbi, StableAbiSample, wincode::SchemaWrite)]
94//! #[frozen_abi(
95//! api_digest = "...",
96//! abi_digest = "...",
97//! abi_serializer = "wincode",
98//! )]
99//! struct MyWincodeType {
100//! a: u64,
101//! b: bool,
102//! }
103//! ```
104//!
105//! ## Edge Cases
106//!
107//! 1. It will not detect field name or order changes, nor same size type swaps (e.g., `i64`
108//! and `u64`). These cases are still covered by `AbiExample`
109//! 2. The implementor must ensure a consistent order of `rng.random()` calls in case of manual implementation
110//! or with overrides, as any change to these will result in different hash
111//! 3. For collection types with non deterministic ordering (e.g., `HashMap`), it is recommended
112//! to insert only one item to avoid false positives caused by iteration order differences
113
114#![allow(incomplete_features)]
115#![cfg_attr(docsrs, feature(doc_cfg))]
116#![cfg_attr(feature = "frozen-abi", feature(specialization))]
117// Activate some of the Rust 2024 lints to make the future migration easier.
118#![warn(if_let_rescope)]
119#![warn(keyword_idents_2024)]
120#![warn(rust_2024_incompatible_pat)]
121#![warn(tail_expr_drop_order)]
122#![warn(unsafe_attr_outside_unsafe)]
123#![warn(unsafe_op_in_unsafe_fn)]
124
125// Allows macro expansion of `use ::solana_frozen_abi::*` to work within this crate
126extern crate self as solana_frozen_abi;
127
128#[cfg(feature = "frozen-abi")]
129pub mod abi_digester;
130#[cfg(feature = "frozen-abi")]
131pub mod abi_example;
132#[cfg(feature = "frozen-abi")]
133pub mod hash;
134
135#[cfg(all(feature = "frozen-abi", not(target_os = "solana")))]
136pub mod stable_abi;
137
138#[cfg(feature = "frozen-abi")]
139#[macro_use]
140extern crate solana_frozen_abi_macro;
141
142#[cfg(all(feature = "frozen-abi", not(target_os = "solana")))]
143pub use {bincode, rand, rand_chacha, wincode};