spl_token_group_interface/
state.rs1use {
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#[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 pub update_authority: OptionalNonZeroPubkey,
19 pub mint: Pubkey,
22 pub size: PodU64,
24 pub max_size: PodU64,
26}
27
28impl TokenGroup {
29 pub fn new(mint: &Pubkey, update_authority: OptionalNonZeroPubkey, max_size: u64) -> Self {
31 Self {
32 mint: *mint,
33 update_authority,
34 size: PodU64::default(), max_size: max_size.into(),
36 }
37 }
38
39 pub fn update_max_size(&mut self, new_max_size: u64) -> Result<(), ProgramError> {
41 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 pub fn increment_size(&mut self) -> Result<u64, ProgramError> {
51 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#[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 pub mint: Pubkey,
71 pub group: Pubkey,
73 pub member_number: PodU64,
75}
76impl TokenGroupMember {
77 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 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 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 group.size = 30.into();
164
165 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 assert_eq!(
191 group.increment_size(),
192 Err(ProgramError::from(TokenGroupError::SizeExceedsMaxSize))
193 );
194 }
195}