spl_token_group_interface/
state.rs

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