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