Skip to main content

klend_interface/state/
mod.rs

1mod common;
2mod global_config;
3mod lending_market;
4mod obligation;
5mod permissioning;
6mod pod;
7mod referral;
8mod reserve;
9mod withdraw_ticket;
10
11pub use common::*;
12pub use global_config::*;
13pub use lending_market::*;
14pub use obligation::*;
15pub use permissioning::*;
16pub use pod::PodU128;
17pub use referral::*;
18pub use reserve::*;
19pub use spl_discriminator::{ArrayDiscriminator, SplDiscriminate};
20pub use withdraw_ticket::*;
21
22/// Size of the Anchor account discriminator (8 bytes).
23pub const DISCRIMINATOR_SIZE: usize = ArrayDiscriminator::LENGTH;
24
25/// Errors returned by [`from_account_data`].
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum AccountDataError {
28    /// Account data is shorter than discriminator + struct size.
29    DataTooShort { expected: usize, actual: usize },
30    /// The 8-byte discriminator does not match the expected value.
31    InvalidDiscriminator { expected: [u8; 8], actual: [u8; 8] },
32    /// The data slice is not properly aligned for the target type.
33    AlignmentError,
34}
35
36impl core::fmt::Display for AccountDataError {
37    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38        match self {
39            Self::DataTooShort { expected, actual } => {
40                write!(
41                    f,
42                    "account data too short: expected {expected}, got {actual}"
43                )
44            }
45            Self::InvalidDiscriminator { expected, actual } => {
46                write!(
47                    f,
48                    "invalid discriminator: expected {expected:?}, got {actual:?}"
49                )
50            }
51            Self::AlignmentError => {
52                write!(
53                    f,
54                    "account data is not properly aligned for the target type"
55                )
56            }
57        }
58    }
59}
60
61impl std::error::Error for AccountDataError {}
62
63/// Cast raw account data (including the 8-byte Anchor discriminator) to `&T`.
64///
65/// Verifies the discriminator matches `T::SPL_DISCRIMINATOR` before casting.
66pub fn from_account_data<T: bytemuck::Pod + SplDiscriminate>(
67    data: &[u8],
68) -> Result<&T, AccountDataError> {
69    let expected_len = DISCRIMINATOR_SIZE + core::mem::size_of::<T>();
70    if data.len() < expected_len {
71        return Err(AccountDataError::DataTooShort {
72            expected: expected_len,
73            actual: data.len(),
74        });
75    }
76    let disc = &data[..DISCRIMINATOR_SIZE];
77    if disc != T::SPL_DISCRIMINATOR_SLICE {
78        let mut actual = [0u8; 8];
79        actual.copy_from_slice(disc);
80        let mut expected = [0u8; 8];
81        expected.copy_from_slice(T::SPL_DISCRIMINATOR_SLICE);
82        return Err(AccountDataError::InvalidDiscriminator { expected, actual });
83    }
84    bytemuck::try_from_bytes(&data[DISCRIMINATOR_SIZE..expected_len])
85        .map_err(|_| AccountDataError::AlignmentError)
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn verify_account_sizes() {
94        assert_eq!(core::mem::size_of::<Obligation>(), 3336);
95        assert_eq!(core::mem::size_of::<Reserve>(), 8616);
96        assert_eq!(core::mem::size_of::<ReserveConfig>(), 952);
97        assert_eq!(core::mem::size_of::<TokenInfo>(), 384);
98        assert_eq!(core::mem::size_of::<LendingMarket>(), 4656);
99        assert_eq!(core::mem::size_of::<GlobalConfig>(), 1024);
100        assert_eq!(core::mem::size_of::<ReferrerTokenState>(), 352);
101        assert_eq!(core::mem::size_of::<UserMetadata>(), 1024);
102        assert_eq!(core::mem::size_of::<ReferrerState>(), 64);
103        assert_eq!(core::mem::size_of::<WithdrawTicket>(), 512);
104    }
105
106    #[test]
107    fn verify_account_discriminators() {
108        // Anchor account discriminators use sha256("account:<Name>")[..8]
109        // but compute_discriminator uses "global:<name>". We verify against
110        // sha256 directly.
111        use sha2::{Digest, Sha256};
112
113        macro_rules! check {
114            ($ty:ty, $name:expr) => {{
115                let mut h = Sha256::new();
116                h.update(concat!("account:", $name).as_bytes());
117                let hash = h.finalize();
118                let mut expected = [0u8; 8];
119                expected.copy_from_slice(&hash[..8]);
120                assert_eq!(
121                    <$ty as SplDiscriminate>::SPL_DISCRIMINATOR_SLICE,
122                    &expected,
123                    concat!("Discriminator mismatch for ", $name),
124                );
125            }};
126        }
127
128        check!(Obligation, "Obligation");
129        check!(Reserve, "Reserve");
130        check!(LendingMarket, "LendingMarket");
131        check!(GlobalConfig, "GlobalConfig");
132        check!(ReferrerTokenState, "ReferrerTokenState");
133        check!(UserMetadata, "UserMetadata");
134        check!(ReferrerState, "ReferrerState");
135        check!(WithdrawTicket, "WithdrawTicket");
136    }
137}