spl_token_metadata_interface/
state.rs1#[cfg(feature = "serde-traits")]
4use serde::{Deserialize, Serialize};
5use {
6 borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
7 miraland_program::{
8 borsh1::{get_instance_packed_len, try_from_slice_unchecked},
9 program_error::ProgramError,
10 pubkey::Pubkey,
11 },
12 spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
13 spl_pod::optional_keys::OptionalNonZeroPubkey,
14 spl_type_length_value::{
15 state::{TlvState, TlvStateBorrowed},
16 variable_len_pack::VariableLenPack,
17 },
18};
19
20#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
25pub struct TokenMetadata {
26 pub update_authority: OptionalNonZeroPubkey,
28 pub mint: Pubkey,
31 pub name: String,
33 pub symbol: String,
35 pub uri: String,
37 pub additional_metadata: Vec<(String, String)>,
40}
41impl SplDiscriminate for TokenMetadata {
42 const SPL_DISCRIMINATOR: ArrayDiscriminator =
44 ArrayDiscriminator::new([112, 132, 90, 90, 11, 88, 157, 87]);
45}
46impl TokenMetadata {
47 pub fn tlv_size_of(&self) -> Result<usize, ProgramError> {
49 TlvStateBorrowed::get_base_len()
50 .checked_add(get_instance_packed_len(self)?)
51 .ok_or(ProgramError::InvalidAccountData)
52 }
53
54 pub fn update(&mut self, field: Field, value: String) {
56 match field {
57 Field::Name => self.name = value,
58 Field::Symbol => self.symbol = value,
59 Field::Uri => self.uri = value,
60 Field::Key(key) => self.set_key_value(key, value),
61 }
62 }
63
64 pub fn set_key_value(&mut self, new_key: String, new_value: String) {
69 for (key, value) in self.additional_metadata.iter_mut() {
70 if *key == new_key {
71 value.replace_range(.., &new_value);
72 return;
73 }
74 }
75 self.additional_metadata.push((new_key, new_value));
76 }
77
78 pub fn remove_key(&mut self, key: &str) -> bool {
81 let mut found_key = false;
82 self.additional_metadata.retain(|x| {
83 let should_retain = x.0 != key;
84 if !should_retain {
85 found_key = true;
86 }
87 should_retain
88 });
89 found_key
90 }
91
92 pub fn get_slice(data: &[u8], start: Option<u64>, end: Option<u64>) -> Option<&[u8]> {
94 let start = start.unwrap_or(0) as usize;
95 let end = end.map(|x| x as usize).unwrap_or(data.len());
96 data.get(start..end)
97 }
98}
99impl VariableLenPack for TokenMetadata {
100 fn pack_into_slice(&self, dst: &mut [u8]) -> Result<(), ProgramError> {
101 borsh::to_writer(&mut dst[..], self).map_err(Into::into)
102 }
103 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
104 try_from_slice_unchecked(src).map_err(Into::into)
105 }
106 fn get_packed_len(&self) -> Result<usize, ProgramError> {
107 get_instance_packed_len(self).map_err(Into::into)
108 }
109}
110
111#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
113#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
114#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
115pub enum Field {
116 Name,
118 Symbol,
120 Uri,
122 Key(String),
124}
125
126#[cfg(test)]
127mod tests {
128 use {super::*, crate::NAMESPACE, miraland_program::hash};
129
130 #[test]
131 fn discriminator() {
132 let preimage = hash::hashv(&[format!("{NAMESPACE}:token_metadata").as_bytes()]);
133 let discriminator =
134 ArrayDiscriminator::try_from(&preimage.as_ref()[..ArrayDiscriminator::LENGTH]).unwrap();
135 assert_eq!(TokenMetadata::SPL_DISCRIMINATOR, discriminator);
136 }
137
138 #[test]
139 fn update() {
140 let name = "name".to_string();
141 let symbol = "symbol".to_string();
142 let uri = "uri".to_string();
143 let mut token_metadata = TokenMetadata {
144 name,
145 symbol,
146 uri,
147 ..Default::default()
148 };
149
150 let new_name = "new_name".to_string();
152 token_metadata.update(Field::Name, new_name.clone());
153 assert_eq!(token_metadata.name, new_name);
154
155 let new_symbol = "new_symbol".to_string();
156 token_metadata.update(Field::Symbol, new_symbol.clone());
157 assert_eq!(token_metadata.symbol, new_symbol);
158
159 let new_uri = "new_uri".to_string();
160 token_metadata.update(Field::Uri, new_uri.clone());
161 assert_eq!(token_metadata.uri, new_uri);
162
163 let key1 = "key1".to_string();
165 let value1 = "value1".to_string();
166 token_metadata.update(Field::Key(key1.clone()), value1.clone());
167 assert_eq!(token_metadata.additional_metadata.len(), 1);
168 assert_eq!(
169 token_metadata.additional_metadata[0],
170 (key1.clone(), value1.clone())
171 );
172
173 let key2 = "key2".to_string();
174 let value2 = "value2".to_string();
175 token_metadata.update(Field::Key(key2.clone()), value2.clone());
176 assert_eq!(token_metadata.additional_metadata.len(), 2);
177 assert_eq!(
178 token_metadata.additional_metadata[0],
179 (key1.clone(), value1)
180 );
181 assert_eq!(
182 token_metadata.additional_metadata[1],
183 (key2.clone(), value2.clone())
184 );
185
186 let new_value1 = "new_value1".to_string();
188 token_metadata.update(Field::Key(key1.clone()), new_value1.clone());
189 assert_eq!(token_metadata.additional_metadata.len(), 2);
190 assert_eq!(token_metadata.additional_metadata[0], (key1, new_value1));
191 assert_eq!(token_metadata.additional_metadata[1], (key2, value2));
192 }
193
194 #[test]
195 fn remove_key() {
196 let name = "name".to_string();
197 let symbol = "symbol".to_string();
198 let uri = "uri".to_string();
199 let mut token_metadata = TokenMetadata {
200 name,
201 symbol,
202 uri,
203 ..Default::default()
204 };
205
206 let key = "key".to_string();
208 let value = "value".to_string();
209 token_metadata.update(Field::Key(key.clone()), value.clone());
210 assert_eq!(token_metadata.additional_metadata.len(), 1);
211 assert_eq!(token_metadata.additional_metadata[0], (key.clone(), value));
212
213 assert!(token_metadata.remove_key(&key));
215 assert_eq!(token_metadata.additional_metadata.len(), 0);
216
217 assert!(!token_metadata.remove_key(&key));
219 assert_eq!(token_metadata.additional_metadata.len(), 0);
220 }
221}