spl_token_group_interface/
state.rs1use {
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#[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 pub update_authority: MaybeNull<Address>,
20 pub mint: Address,
23 pub size: U64,
25 pub max_size: U64,
27}
28
29impl TokenGroup {
30 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(), max_size: max_size.into(),
37 }
38 }
39
40 pub fn update_max_size(&mut self, new_max_size: u64) -> Result<(), ProgramError> {
42 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 pub fn increment_size(&mut self) -> Result<u64, ProgramError> {
52 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#[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 pub mint: Address,
72 pub group: Address,
74 pub member_number: U64,
76}
77impl TokenGroupMember {
78 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 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 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 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: 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 assert_eq!(
193 group.increment_size(),
194 Err(ProgramError::from(TokenGroupError::SizeExceedsMaxSize))
195 );
196 }
197}