Skip to main content

spl_token_group_interface/
state.rs

1//! Interface state types
2
3use {
4    crate::error::TokenGroupError,
5    bytemuck::{Pod, Zeroable},
6    solana_address::Address,
7    solana_nullable::MaybeNull,
8    solana_program_error::ProgramError,
9    solana_zero_copy::unaligned::U64,
10    spl_discriminator::SplDiscriminate,
11};
12
13/// Data struct for a `TokenGroup`
14#[repr(C)]
15#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable, SplDiscriminate)]
16#[discriminator_hash_input("spl_token_group_interface:group")]
17pub struct TokenGroup {
18    /// The authority that can sign to update the group
19    pub update_authority: MaybeNull<Address>,
20    /// The associated mint, used to counter spoofing to be sure that group
21    /// belongs to a particular mint
22    pub mint: Address,
23    /// The current number of group members
24    pub size: U64,
25    /// The maximum number of group members
26    pub max_size: U64,
27}
28
29impl TokenGroup {
30    /// Creates a new `TokenGroup` state
31    pub fn new(mint: &Address, update_authority: MaybeNull<Address>, max_size: u64) -> Self {
32        Self {
33            mint: *mint,
34            update_authority,
35            size: U64::default(), // [0, 0, 0, 0, 0, 0, 0, 0]
36            max_size: max_size.into(),
37        }
38    }
39
40    /// Updates the max size for a group
41    pub fn update_max_size(&mut self, new_max_size: u64) -> Result<(), ProgramError> {
42        // The new max size cannot be less than the current size
43        if new_max_size < u64::from(self.size) {
44            return Err(TokenGroupError::SizeExceedsNewMaxSize.into());
45        }
46        self.max_size = new_max_size.into();
47        Ok(())
48    }
49
50    /// Increment the size for a group, returning the new size
51    pub fn increment_size(&mut self) -> Result<u64, ProgramError> {
52        // The new size cannot be greater than the max size
53        let new_size = u64::from(self.size)
54            .checked_add(1)
55            .ok_or(ProgramError::ArithmeticOverflow)?;
56        if new_size > u64::from(self.max_size) {
57            return Err(TokenGroupError::SizeExceedsMaxSize.into());
58        }
59        self.size = new_size.into();
60        Ok(new_size)
61    }
62}
63
64/// Data struct for a `TokenGroupMember`
65#[repr(C)]
66#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable, SplDiscriminate)]
67#[discriminator_hash_input("spl_token_group_interface:member")]
68pub struct TokenGroupMember {
69    /// The associated mint, used to counter spoofing to be sure that member
70    /// belongs to a particular mint
71    pub mint: Address,
72    /// The pubkey of the `TokenGroup`
73    pub group: Address,
74    /// The member number
75    pub member_number: U64,
76}
77impl TokenGroupMember {
78    /// Creates a new `TokenGroupMember` state
79    pub fn new(mint: &Address, group: &Address, member_number: u64) -> Self {
80        Self {
81            mint: *mint,
82            group: *group,
83            member_number: member_number.into(),
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use {
91        super::*,
92        crate::NAMESPACE,
93        alloc::{format, vec},
94        core::mem::size_of,
95        solana_sha256_hasher::hashv,
96        spl_discriminator::ArrayDiscriminator,
97        spl_type_length_value::state::{TlvState, TlvStateBorrowed, TlvStateMut},
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: Address::new_unique(),
118            update_authority: MaybeNull::<Address>::try_from(Some(Address::new_unique())).unwrap(),
119            size: 10.into(),
120            max_size: 20.into(),
121        };
122
123        let member = TokenGroupMember {
124            mint: Address::new_unique(),
125            group: Address::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: Address::new_unique(),
155            update_authority: MaybeNull::<Address>::try_from(Some(Address::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: Address::new_unique(),
183            update_authority: MaybeNull::<Address>::try_from(Some(Address::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}