spl_token_group_interface/
state.rs

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