Skip to main content

klend_interface/state/
mod.rs

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