Skip to main content

kvault_interface/state/
mod.rs

1//! Zero-copy account deserialization for on-chain Kvault accounts.
2//!
3//! All account types use `bytemuck` for zero-copy access and
4//! `SplDiscriminate` for 8-byte Anchor discriminator validation.
5//! Use [`from_account_data`] to cast raw account bytes (including
6//! the discriminator prefix) to a typed reference.
7
8mod global_config;
9mod pod;
10mod reserve_whitelist;
11mod vault_allocation;
12mod vault_reward_info;
13mod vault_state;
14
15pub use global_config::*;
16pub use pod::PodU128;
17pub use reserve_whitelist::*;
18pub use spl_discriminator::{ArrayDiscriminator, SplDiscriminate};
19pub use vault_allocation::*;
20pub use vault_reward_info::*;
21pub use vault_state::*;
22
23/// Size of the Anchor account discriminator (8 bytes).
24pub const DISCRIMINATOR_SIZE: usize = ArrayDiscriminator::LENGTH;
25
26/// Errors returned by [`from_account_data`].
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum AccountDataError {
29    /// Account data is shorter than discriminator + struct size.
30    DataTooShort { expected: usize, actual: usize },
31    /// The 8-byte discriminator does not match the expected value.
32    InvalidDiscriminator { expected: [u8; 8], actual: [u8; 8] },
33}
34
35impl core::fmt::Display for AccountDataError {
36    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37        match self {
38            Self::DataTooShort { expected, actual } => {
39                write!(
40                    f,
41                    "account data too short: expected {expected}, got {actual}"
42                )
43            }
44            Self::InvalidDiscriminator { expected, actual } => {
45                write!(
46                    f,
47                    "invalid discriminator: expected {expected:?}, got {actual:?}"
48                )
49            }
50        }
51    }
52}
53
54impl std::error::Error for AccountDataError {}
55
56/// Cast raw account data (including the 8-byte Anchor discriminator) to `&T`.
57///
58/// Verifies the discriminator matches `T::SPL_DISCRIMINATOR` before casting.
59pub fn from_account_data<T: bytemuck::Pod + SplDiscriminate>(
60    data: &[u8],
61) -> Result<&T, AccountDataError> {
62    let expected_len = DISCRIMINATOR_SIZE + core::mem::size_of::<T>();
63    if data.len() < expected_len {
64        return Err(AccountDataError::DataTooShort {
65            expected: expected_len,
66            actual: data.len(),
67        });
68    }
69    let disc = &data[..DISCRIMINATOR_SIZE];
70    if disc != T::SPL_DISCRIMINATOR_SLICE {
71        let mut actual = [0u8; 8];
72        actual.copy_from_slice(disc);
73        let mut expected = [0u8; 8];
74        expected.copy_from_slice(T::SPL_DISCRIMINATOR_SLICE);
75        return Err(AccountDataError::InvalidDiscriminator { expected, actual });
76    }
77    Ok(bytemuck::from_bytes(
78        &data[DISCRIMINATOR_SIZE..expected_len],
79    ))
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn verify_account_sizes() {
88        assert_eq!(core::mem::size_of::<VaultState>(), 62544);
89        assert_eq!(core::mem::size_of::<VaultAllocation>(), 2160);
90        assert_eq!(core::mem::size_of::<GlobalConfig>(), 1024);
91        assert_eq!(core::mem::size_of::<ReserveWhitelistEntry>(), 128);
92    }
93
94    #[test]
95    fn verify_account_discriminators() {
96        use sha2::{Digest, Sha256};
97
98        macro_rules! check {
99            ($ty:ty, $name:expr) => {{
100                let mut h = Sha256::new();
101                h.update(concat!("account:", $name).as_bytes());
102                let hash = h.finalize();
103                let mut expected = [0u8; 8];
104                expected.copy_from_slice(&hash[..8]);
105                assert_eq!(
106                    <$ty as SplDiscriminate>::SPL_DISCRIMINATOR_SLICE,
107                    &expected,
108                    concat!("Discriminator mismatch for ", $name),
109                );
110            }};
111        }
112
113        check!(VaultState, "VaultState");
114        check!(GlobalConfig, "GlobalConfig");
115    }
116}