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