clone_spl_token_group_interface/
state.rs

1//! Interface state types
2
3use {
4    crate::error::TokenGroupError,
5    bytemuck::{Pod, Zeroable},
6    clone_solana_program_error::ProgramError,
7    clone_solana_pubkey::Pubkey,
8    clone_spl_discriminator::SplDiscriminate,
9    clone_spl_pod::{
10        error::PodSliceError, optional_keys::OptionalNonZeroPubkey, primitives::PodU64,
11    },
12};
13
14/// Data struct for a `TokenGroup`
15#[repr(C)]
16#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable, SplDiscriminate)]
17#[discriminator_hash_input("clone_spl_token_group_interface:group")]
18pub struct TokenGroup {
19    /// The authority that can sign to update the group
20    pub update_authority: OptionalNonZeroPubkey,
21    /// The associated mint, used to counter spoofing to be sure that group
22    /// belongs to a particular mint
23    pub mint: Pubkey,
24    /// The current number of group members
25    pub size: PodU64,
26    /// The maximum number of group members
27    pub max_size: PodU64,
28}
29
30impl TokenGroup {
31    /// Creates a new `TokenGroup` state
32    pub fn new(mint: &Pubkey, update_authority: OptionalNonZeroPubkey, max_size: u64) -> Self {
33        Self {
34            mint: *mint,
35            update_authority,
36            size: PodU64::default(), // [0, 0, 0, 0, 0, 0, 0, 0]
37            max_size: max_size.into(),
38        }
39    }
40
41    /// Updates the max size for a group
42    pub fn update_max_size(&mut self, new_max_size: u64) -> Result<(), ProgramError> {
43        // The new max size cannot be less than the current size
44        if new_max_size < u64::from(self.size) {
45            return Err(TokenGroupError::SizeExceedsNewMaxSize.into());
46        }
47        self.max_size = new_max_size.into();
48        Ok(())
49    }
50
51    /// Increment the size for a group, returning the new size
52    pub fn increment_size(&mut self) -> Result<u64, ProgramError> {
53        // The new size cannot be greater than the max size
54        let new_size = u64::from(self.size)
55            .checked_add(1)
56            .ok_or::<ProgramError>(PodSliceError::CalculationFailure.into())?;
57        if new_size > u64::from(self.max_size) {
58            return Err(TokenGroupError::SizeExceedsMaxSize.into());
59        }
60        self.size = new_size.into();
61        Ok(new_size)
62    }
63}
64
65/// Data struct for a `TokenGroupMember`
66#[repr(C)]
67#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable, SplDiscriminate)]
68#[discriminator_hash_input("clone_spl_token_group_interface:member")]
69pub struct TokenGroupMember {
70    /// The associated mint, used to counter spoofing to be sure that member
71    /// belongs to a particular mint
72    pub mint: Pubkey,
73    /// The pubkey of the `TokenGroup`
74    pub group: Pubkey,
75    /// The member number
76    pub member_number: PodU64,
77}
78impl TokenGroupMember {
79    /// Creates a new `TokenGroupMember` state
80    pub fn new(mint: &Pubkey, group: &Pubkey, member_number: u64) -> Self {
81        Self {
82            mint: *mint,
83            group: *group,
84            member_number: member_number.into(),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use {
92        super::*,
93        crate::NAMESPACE,
94        clone_solana_sha256_hasher::hashv,
95        clone_spl_discriminator::ArrayDiscriminator,
96        clone_spl_type_length_value::state::{TlvState, TlvStateBorrowed, TlvStateMut},
97        std::mem::size_of,
98    };
99
100    #[test]
101    fn discriminators() {
102        let preimage = hashv(&[format!("{NAMESPACE}:group").as_bytes()]);
103        let discriminator =
104            ArrayDiscriminator::try_from(&preimage.as_ref()[..ArrayDiscriminator::LENGTH]).unwrap();
105        assert_eq!(TokenGroup::SPL_DISCRIMINATOR, discriminator);
106
107        let preimage = hashv(&[format!("{NAMESPACE}:member").as_bytes()]);
108        let discriminator =
109            ArrayDiscriminator::try_from(&preimage.as_ref()[..ArrayDiscriminator::LENGTH]).unwrap();
110        assert_eq!(TokenGroupMember::SPL_DISCRIMINATOR, discriminator);
111    }
112
113    #[test]
114    fn tlv_state_pack() {
115        // Make sure we can pack more than one instance of each type
116        let group = TokenGroup {
117            mint: Pubkey::new_unique(),
118            update_authority: OptionalNonZeroPubkey::try_from(Some(Pubkey::new_unique())).unwrap(),
119            size: 10.into(),
120            max_size: 20.into(),
121        };
122
123        let member = TokenGroupMember {
124            mint: Pubkey::new_unique(),
125            group: Pubkey::new_unique(),
126            member_number: 0.into(),
127        };
128
129        let account_size = TlvStateBorrowed::get_base_len()
130            + size_of::<TokenGroup>()
131            + TlvStateBorrowed::get_base_len()
132            + size_of::<TokenGroupMember>();
133        let mut buffer = vec![0; account_size];
134        let mut state = TlvStateMut::unpack(&mut buffer).unwrap();
135
136        let group_data = state.init_value::<TokenGroup>(false).unwrap().0;
137        *group_data = group;
138
139        let member_data = state.init_value::<TokenGroupMember>(false).unwrap().0;
140        *member_data = member;
141
142        assert_eq!(state.get_first_value::<TokenGroup>().unwrap(), &group);
143        assert_eq!(
144            state.get_first_value::<TokenGroupMember>().unwrap(),
145            &member
146        );
147    }
148
149    #[test]
150    fn update_max_size() {
151        // Test with a `Some` max size
152        let max_size = 10;
153        let mut group = TokenGroup {
154            mint: Pubkey::new_unique(),
155            update_authority: OptionalNonZeroPubkey::try_from(Some(Pubkey::new_unique())).unwrap(),
156            size: 0.into(),
157            max_size: max_size.into(),
158        };
159
160        let new_max_size = 30;
161        group.update_max_size(new_max_size).unwrap();
162        assert_eq!(u64::from(group.max_size), new_max_size);
163
164        // Change the current size to 30
165        group.size = 30.into();
166
167        // Try to set the max size to 20, which is less than the current size
168        let new_max_size = 20;
169        assert_eq!(
170            group.update_max_size(new_max_size),
171            Err(ProgramError::from(TokenGroupError::SizeExceedsNewMaxSize))
172        );
173
174        let new_max_size = 30;
175        group.update_max_size(new_max_size).unwrap();
176        assert_eq!(u64::from(group.max_size), new_max_size);
177    }
178
179    #[test]
180    fn increment_current_size() {
181        let mut group = TokenGroup {
182            mint: Pubkey::new_unique(),
183            update_authority: OptionalNonZeroPubkey::try_from(Some(Pubkey::new_unique())).unwrap(),
184            size: 0.into(),
185            max_size: 1.into(),
186        };
187
188        group.increment_size().unwrap();
189        assert_eq!(u64::from(group.size), 1);
190
191        // Try to increase the current size to 2, which is greater than the max size
192        assert_eq!(
193            group.increment_size(),
194            Err(ProgramError::from(TokenGroupError::SizeExceedsMaxSize))
195        );
196    }
197}