clone_spl_token_group_interface/
state.rs1use {
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#[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 pub update_authority: OptionalNonZeroPubkey,
21 pub mint: Pubkey,
24 pub size: PodU64,
26 pub max_size: PodU64,
28}
29
30impl TokenGroup {
31 pub fn new(mint: &Pubkey, update_authority: OptionalNonZeroPubkey, max_size: u64) -> Self {
33 Self {
34 mint: *mint,
35 update_authority,
36 size: PodU64::default(), max_size: max_size.into(),
38 }
39 }
40
41 pub fn update_max_size(&mut self, new_max_size: u64) -> Result<(), ProgramError> {
43 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 pub fn increment_size(&mut self) -> Result<u64, ProgramError> {
53 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#[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 pub mint: Pubkey,
73 pub group: Pubkey,
75 pub member_number: PodU64,
77}
78impl TokenGroupMember {
79 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 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 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 group.size = 30.into();
166
167 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 assert_eq!(
193 group.increment_size(),
194 Err(ProgramError::from(TokenGroupError::SizeExceedsMaxSize))
195 );
196 }
197}