1use {
4 crate::state::Field,
5 alloc::{string::String, vec, vec::Vec},
6 borsh::{BorshDeserialize, BorshSerialize},
7 solana_address::Address,
8 solana_instruction::{account_meta::AccountMeta, Instruction},
9 solana_nullable::MaybeNull,
10 solana_program_error::ProgramError,
11 spl_discriminator::{discriminator::ArrayDiscriminator, SplDiscriminate},
12};
13
14#[cfg(feature = "serde-traits")]
15use {
16 serde::{Deserialize, Serialize},
17 serde_with::DisplayFromStr,
18};
19
20#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
22#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
23#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
24#[discriminator_hash_input("spl_token_metadata_interface:initialize_account")]
25pub struct Initialize {
26 pub name: String,
28 pub symbol: String,
30 pub uri: String,
32}
33
34#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
37#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
38#[discriminator_hash_input("spl_token_metadata_interface:updating_field")]
39pub struct UpdateField {
40 pub field: Field,
42 pub value: String,
44}
45
46#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
48#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
49#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
50#[discriminator_hash_input("spl_token_metadata_interface:remove_key_ix")]
51pub struct RemoveKey {
52 pub idempotent: bool,
55 pub key: String,
57}
58
59#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
61#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
62#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
63#[discriminator_hash_input("spl_token_metadata_interface:update_the_authority")]
64pub struct UpdateAuthority {
65 #[cfg_attr(
67 feature = "serde-traits",
68 serde(with = "serde_with::As::<Option<DisplayFromStr>>")
69 )]
70 pub new_authority: MaybeNull<Address>,
71}
72
73#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
75#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
76#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, SplDiscriminate)]
77#[discriminator_hash_input("spl_token_metadata_interface:emitter")]
78pub struct Emit {
79 pub start: Option<u64>,
81 pub end: Option<u64>,
83}
84
85#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
87#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
88#[derive(Clone, Debug, PartialEq)]
89pub enum TokenMetadataInstruction {
90 Initialize(Initialize),
105
106 UpdateField(UpdateField),
127
128 RemoveKey(RemoveKey),
144
145 UpdateAuthority(UpdateAuthority),
154
155 Emit(Emit),
169}
170fn check_borsh_string(rest: &[u8], cursor: &mut usize) -> Result<(), ProgramError> {
177 let len_end = cursor
178 .checked_add(core::mem::size_of::<u32>())
179 .ok_or(ProgramError::InvalidInstructionData)?;
180 let len_bytes = rest
181 .get(*cursor..len_end)
182 .ok_or(ProgramError::InvalidInstructionData)?;
183 let len = u32::from_le_bytes(len_bytes.try_into().unwrap()) as usize;
184 let str_end = len_end
185 .checked_add(len)
186 .ok_or(ProgramError::InvalidInstructionData)?;
187 if str_end > rest.len() {
188 return Err(ProgramError::InvalidInstructionData);
189 }
190 *cursor = str_end;
191 Ok(())
192}
193
194impl TokenMetadataInstruction {
195 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
198 if input.len() < ArrayDiscriminator::LENGTH {
199 return Err(ProgramError::InvalidInstructionData);
200 }
201 let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
202 Ok(match discriminator {
203 Initialize::SPL_DISCRIMINATOR_SLICE => {
204 let mut cursor = 0usize;
205 check_borsh_string(rest, &mut cursor)?; check_borsh_string(rest, &mut cursor)?; check_borsh_string(rest, &mut cursor)?; let data = Initialize::try_from_slice(rest)?;
209 Self::Initialize(data)
210 }
211 UpdateField::SPL_DISCRIMINATOR_SLICE => {
212 let mut cursor = 0usize;
213 let tag = *rest
216 .get(cursor)
217 .ok_or(ProgramError::InvalidInstructionData)?;
218 cursor = cursor
219 .checked_add(1)
220 .ok_or(ProgramError::InvalidInstructionData)?;
221 if tag == 3 {
222 check_borsh_string(rest, &mut cursor)?; }
224 check_borsh_string(rest, &mut cursor)?; let data = UpdateField::try_from_slice(rest)?;
226 Self::UpdateField(data)
227 }
228 RemoveKey::SPL_DISCRIMINATOR_SLICE => {
229 let mut cursor = 1usize; check_borsh_string(rest, &mut cursor)?; let data = RemoveKey::try_from_slice(rest)?;
232 Self::RemoveKey(data)
233 }
234 UpdateAuthority::SPL_DISCRIMINATOR_SLICE => {
235 let data = UpdateAuthority::try_from_slice(rest)?;
236 Self::UpdateAuthority(data)
237 }
238 Emit::SPL_DISCRIMINATOR_SLICE => {
239 let data = Emit::try_from_slice(rest)?;
240 Self::Emit(data)
241 }
242 _ => return Err(ProgramError::InvalidInstructionData),
243 })
244 }
245
246 pub fn pack(&self) -> Vec<u8> {
249 let mut buf = vec![];
250 match self {
251 Self::Initialize(data) => {
252 buf.extend_from_slice(Initialize::SPL_DISCRIMINATOR_SLICE);
253 buf.append(&mut borsh::to_vec(data).unwrap());
254 }
255 Self::UpdateField(data) => {
256 buf.extend_from_slice(UpdateField::SPL_DISCRIMINATOR_SLICE);
257 buf.append(&mut borsh::to_vec(data).unwrap());
258 }
259 Self::RemoveKey(data) => {
260 buf.extend_from_slice(RemoveKey::SPL_DISCRIMINATOR_SLICE);
261 buf.append(&mut borsh::to_vec(data).unwrap());
262 }
263 Self::UpdateAuthority(data) => {
264 buf.extend_from_slice(UpdateAuthority::SPL_DISCRIMINATOR_SLICE);
265 buf.append(&mut borsh::to_vec(data).unwrap());
266 }
267 Self::Emit(data) => {
268 buf.extend_from_slice(Emit::SPL_DISCRIMINATOR_SLICE);
269 buf.append(&mut borsh::to_vec(data).unwrap());
270 }
271 };
272 buf
273 }
274}
275
276#[allow(clippy::too_many_arguments)]
278pub fn initialize(
279 program_id: &Address,
280 metadata: &Address,
281 update_authority: &Address,
282 mint: &Address,
283 mint_authority: &Address,
284 name: String,
285 symbol: String,
286 uri: String,
287) -> Instruction {
288 let data = TokenMetadataInstruction::Initialize(Initialize { name, symbol, uri });
289 Instruction {
290 program_id: *program_id,
291 accounts: vec![
292 AccountMeta::new(*metadata, false),
293 AccountMeta::new_readonly(*update_authority, false),
294 AccountMeta::new_readonly(*mint, false),
295 AccountMeta::new_readonly(*mint_authority, true),
296 ],
297 data: data.pack(),
298 }
299}
300
301pub fn update_field(
303 program_id: &Address,
304 metadata: &Address,
305 update_authority: &Address,
306 field: Field,
307 value: String,
308) -> Instruction {
309 let data = TokenMetadataInstruction::UpdateField(UpdateField { field, value });
310 Instruction {
311 program_id: *program_id,
312 accounts: vec![
313 AccountMeta::new(*metadata, false),
314 AccountMeta::new_readonly(*update_authority, true),
315 ],
316 data: data.pack(),
317 }
318}
319
320pub fn remove_key(
322 program_id: &Address,
323 metadata: &Address,
324 update_authority: &Address,
325 key: String,
326 idempotent: bool,
327) -> Instruction {
328 let data = TokenMetadataInstruction::RemoveKey(RemoveKey { key, idempotent });
329 Instruction {
330 program_id: *program_id,
331 accounts: vec![
332 AccountMeta::new(*metadata, false),
333 AccountMeta::new_readonly(*update_authority, true),
334 ],
335 data: data.pack(),
336 }
337}
338
339pub fn update_authority(
341 program_id: &Address,
342 metadata: &Address,
343 current_authority: &Address,
344 new_authority: MaybeNull<Address>,
345) -> Instruction {
346 let data = TokenMetadataInstruction::UpdateAuthority(UpdateAuthority { new_authority });
347 Instruction {
348 program_id: *program_id,
349 accounts: vec![
350 AccountMeta::new(*metadata, false),
351 AccountMeta::new_readonly(*current_authority, true),
352 ],
353 data: data.pack(),
354 }
355}
356
357pub fn emit(
359 program_id: &Address,
360 metadata: &Address,
361 start: Option<u64>,
362 end: Option<u64>,
363) -> Instruction {
364 let data = TokenMetadataInstruction::Emit(Emit { start, end });
365 Instruction {
366 program_id: *program_id,
367 accounts: vec![AccountMeta::new_readonly(*metadata, false)],
368 data: data.pack(),
369 }
370}
371
372#[cfg(test)]
373mod test {
374 #[cfg(feature = "serde-traits")]
375 use core::str::FromStr;
376 use {
377 super::*,
378 crate::NAMESPACE,
379 alloc::{format, string::ToString, vec},
380 solana_sha256_hasher::hashv,
381 };
382
383 fn check_pack_unpack<T: BorshSerialize>(
384 instruction: TokenMetadataInstruction,
385 discriminator: &[u8],
386 data: T,
387 ) {
388 let mut expect = vec![];
389 expect.extend_from_slice(discriminator.as_ref());
390 expect.append(&mut borsh::to_vec(&data).unwrap());
391 let packed = instruction.pack();
392 assert_eq!(packed, expect);
393 let unpacked = TokenMetadataInstruction::unpack(&expect).unwrap();
394 assert_eq!(unpacked, instruction);
395 }
396
397 #[test]
398 fn initialize_pack() {
399 let name = "My test token";
400 let symbol = "TEST";
401 let uri = "http://test.test";
402 let data = Initialize {
403 name: name.to_string(),
404 symbol: symbol.to_string(),
405 uri: uri.to_string(),
406 };
407 let check = TokenMetadataInstruction::Initialize(data.clone());
408 let preimage = hashv(&[format!("{NAMESPACE}:initialize_account").as_bytes()]);
409 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
410 check_pack_unpack(check, discriminator, data);
411 }
412
413 #[test]
414 fn update_field_pack() {
415 let field = "MyTestField";
416 let value = "http://test.uri";
417 let data = UpdateField {
418 field: Field::Key(field.to_string()),
419 value: value.to_string(),
420 };
421 let check = TokenMetadataInstruction::UpdateField(data.clone());
422 let preimage = hashv(&[format!("{NAMESPACE}:updating_field").as_bytes()]);
423 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
424 check_pack_unpack(check, discriminator, data);
425 }
426
427 #[test]
428 fn remove_key_pack() {
429 let data = RemoveKey {
430 key: "MyTestField".to_string(),
431 idempotent: true,
432 };
433 let check = TokenMetadataInstruction::RemoveKey(data.clone());
434 let preimage = hashv(&[format!("{NAMESPACE}:remove_key_ix").as_bytes()]);
435 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
436 check_pack_unpack(check, discriminator, data);
437 }
438
439 #[test]
440 fn update_authority_pack() {
441 let data = UpdateAuthority {
442 new_authority: MaybeNull::default(),
443 };
444 let check = TokenMetadataInstruction::UpdateAuthority(data.clone());
445 let preimage = hashv(&[format!("{NAMESPACE}:update_the_authority").as_bytes()]);
446 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
447 check_pack_unpack(check, discriminator, data);
448 }
449
450 #[test]
451 fn emit_pack() {
452 let data = Emit {
453 start: None,
454 end: Some(10),
455 };
456 let check = TokenMetadataInstruction::Emit(data.clone());
457 let preimage = hashv(&[format!("{NAMESPACE}:emitter").as_bytes()]);
458 let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
459 check_pack_unpack(check, discriminator, data);
460 }
461
462 #[test]
463 fn fail_unpack_initialize_with_forged_name_length() {
464 let mut input = vec![];
465 input.extend_from_slice(Initialize::SPL_DISCRIMINATOR_SLICE);
466 input.extend_from_slice(&u32::MAX.to_le_bytes()); assert_eq!(
468 TokenMetadataInstruction::unpack(&input).unwrap_err(),
469 ProgramError::InvalidInstructionData,
470 );
471 }
472
473 #[test]
474 fn fail_unpack_update_field_with_forged_value_length() {
475 let mut input = vec![];
476 input.extend_from_slice(UpdateField::SPL_DISCRIMINATOR_SLICE);
477 input.push(0u8); input.extend_from_slice(&u32::MAX.to_le_bytes()); assert_eq!(
480 TokenMetadataInstruction::unpack(&input).unwrap_err(),
481 ProgramError::InvalidInstructionData,
482 );
483 }
484
485 #[test]
486 fn fail_unpack_remove_key_with_forged_key_length() {
487 let mut input = vec![];
488 input.extend_from_slice(RemoveKey::SPL_DISCRIMINATOR_SLICE);
489 input.push(0u8); input.extend_from_slice(&u32::MAX.to_le_bytes()); assert_eq!(
492 TokenMetadataInstruction::unpack(&input).unwrap_err(),
493 ProgramError::InvalidInstructionData,
494 );
495 }
496
497 #[cfg(feature = "serde-traits")]
498 #[test]
499 fn initialize_serde() {
500 let data = Initialize {
501 name: "Token Name".to_string(),
502 symbol: "TST".to_string(),
503 uri: "uri.test".to_string(),
504 };
505 let ix = TokenMetadataInstruction::Initialize(data);
506 let serialized = serde_json::to_string(&ix).unwrap();
507 let serialized_expected =
508 "{\"initialize\":{\"name\":\"Token Name\",\"symbol\":\"TST\",\"uri\":\"uri.test\"}}";
509 assert_eq!(&serialized, serialized_expected);
510
511 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
512 assert_eq!(ix, deserialized);
513 }
514
515 #[cfg(feature = "serde-traits")]
516 #[test]
517 fn update_field_serde() {
518 let data = UpdateField {
519 field: Field::Key("MyField".to_string()),
520 value: "my field value".to_string(),
521 };
522 let ix = TokenMetadataInstruction::UpdateField(data);
523 let serialized = serde_json::to_string(&ix).unwrap();
524 let serialized_expected =
525 "{\"updateField\":{\"field\":{\"key\":\"MyField\"},\"value\":\"my field value\"}}";
526 assert_eq!(&serialized, serialized_expected);
527
528 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
529 assert_eq!(ix, deserialized);
530 }
531
532 #[cfg(feature = "serde-traits")]
533 #[test]
534 fn remove_key_serde() {
535 let data = RemoveKey {
536 key: "MyTestField".to_string(),
537 idempotent: true,
538 };
539 let ix = TokenMetadataInstruction::RemoveKey(data);
540 let serialized = serde_json::to_string(&ix).unwrap();
541 let serialized_expected = "{\"removeKey\":{\"idempotent\":true,\"key\":\"MyTestField\"}}";
542 assert_eq!(&serialized, serialized_expected);
543
544 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
545 assert_eq!(ix, deserialized);
546 }
547
548 #[cfg(feature = "serde-traits")]
549 #[test]
550 fn update_authority_serde() {
551 let update_authority_option: Option<Address> =
552 Some(Address::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap());
553 let update_authority: MaybeNull<Address> = update_authority_option.try_into().unwrap();
554 let data = UpdateAuthority {
555 new_authority: update_authority,
556 };
557 let ix = TokenMetadataInstruction::UpdateAuthority(data);
558 let serialized = serde_json::to_string(&ix).unwrap();
559 let serialized_expected = "{\"updateAuthority\":{\"newAuthority\":\"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM\"}}";
560 assert_eq!(&serialized, serialized_expected);
561
562 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
563 assert_eq!(ix, deserialized);
564 }
565
566 #[cfg(feature = "serde-traits")]
567 #[test]
568 fn update_authority_serde_with_none() {
569 let data = UpdateAuthority {
570 new_authority: MaybeNull::default(),
571 };
572 let ix = TokenMetadataInstruction::UpdateAuthority(data);
573 let serialized = serde_json::to_string(&ix).unwrap();
574 let serialized_expected = "{\"updateAuthority\":{\"newAuthority\":null}}";
575 assert_eq!(&serialized, serialized_expected);
576
577 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
578 assert_eq!(ix, deserialized);
579 }
580
581 #[cfg(feature = "serde-traits")]
582 #[test]
583 fn emit_serde() {
584 let data = Emit {
585 start: None,
586 end: Some(10),
587 };
588 let ix = TokenMetadataInstruction::Emit(data);
589 let serialized = serde_json::to_string(&ix).unwrap();
590 let serialized_expected = "{\"emit\":{\"start\":null,\"end\":10}}";
591 assert_eq!(&serialized, serialized_expected);
592
593 let deserialized = serde_json::from_str::<TokenMetadataInstruction>(&serialized).unwrap();
594 assert_eq!(ix, deserialized);
595 }
596}