Expand description
§StableAbi
The StableAbi is an optional extension to frozen-abi that provides functionality for
detecting unintended encoding changes.
§How it works?
When you annotate a type with:
#[frozen_abi(abi_digest = "...")]
struct MyType { ... }The macro would generate the test_abi_digest test that verifies binary layout stability:
- Initializes a deterministic random number generator with fixed seed
- Generates 10_000 instances of the type via
StableAbi::random() - Serializes each instance
- Hashes all serialized bytes together
- Compares the resulting hash against the provided in
abi_digestattribute
Using the seeded RNG ensures same sequence of random values across runs, while the 10_000 iterations brings a wide range of value combinations. By hashing serialized bytes, changes to padding, endianness or encoding are detected.
§Adding StableAbi to a New Type
Deriving StableAbi adds:
impl ::solana_frozen_abi::stable_abi::StableAbi for MyType {}The StableAbi::random() default implementation calls rng.random::<MyType>(), so your type
also needs Distribution<MyType> for StandardUniform.
There are two ways to provide it:
- Derive
StableAbiSample
StableAbiSample auto-generates Distribution<MyType> for StandardUniform and, by default,
tries to sample each field via rng.random().
#[derive(StableAbi, StableAbiSample)]
#[frozen_abi(abi_digest = "...")]
struct MyType {
a: u64,
b: bool,
c: [u8; 32],
d: (u8, u8),
}Field override is optional and only needed for fields that cannot be sampled with plain
rng.random() (for example Vec<_> or HashMap<_, _>), or when you want a specific shape.
#[derive(StableAbi, StableAbiSample)]
#[frozen_abi(abi_digest = "...", abi_serializer = "wincode")]
struct MyTypeWithOverride {
#[stable_abi_sample(
with = "(0..rng.random::<u8>() % 4).map(|_| rng.random::<bool>()).collect()")]
a: Vec<bool>,
#[stable_abi_sample(
with = "std::collections::HashMap::from_iter([(rng.random(), rng.random())])"
)]
b: std::collections::HashMap<u64, bool>,
c: [u8; 32],
}- Write a manual
Distributionimplementation
#[cfg(feature = "frozen-abi")]
impl solana_frozen_abi::rand::prelude::Distribution<MyType>
for solana_frozen_abi::rand::distr::StandardUniform
{
fn sample<R: solana_frozen_abi::rand::Rng + ?Sized>(&self, rng: &mut R) -> MyType {
MyType {
field: rng.random(),
...
}
}
}For wincode-based types, add abi_serializer = "wincode" to #[frozen_abi(...)].
#[derive(StableAbi, StableAbiSample, wincode::SchemaWrite)]
#[frozen_abi(
api_digest = "...",
abi_digest = "...",
abi_serializer = "wincode",
)]
struct MyWincodeType {
a: u64,
b: bool,
}§Edge Cases
- It will not detect field name or order changes, nor same size type swaps (e.g.,
i64andu64). These cases are still covered byAbiExample - The implementor must ensure a consistent order of
rng.random()calls in case of manual implementation or with overrides, as any change to these will result in different hash - For collection types with non deterministic ordering (e.g.,
HashMap), it is recommended to insert only one item to avoid false positives caused by iteration order differences
Re-exports§
pub use bincode;frozen-abiand non-target_os=solanapub use rand;frozen-abiand non-target_os=solanapub use rand_chacha;frozen-abiand non-target_os=solanapub use wincode;frozen-abiand non-target_os=solana
Modules§
- abi_
digester frozen-abi - abi_
example frozen-abi - hash
frozen-abi - stable_
abi frozen-abiand non-target_os=solana