1use crate::account::AccountView;
22use crate::address::Address;
23use crate::error::ProgramError;
24use crate::instruction::{InstructionAccount, Signer};
25use crate::token::{Invoke, TokenInstruction, TokenSink, TOKEN_2022_PROGRAM_ID};
26use crate::ProgramResult;
27
28pub const MAX_METADATA_INSTRUCTION_DATA: usize = 512;
30
31pub const DISC_METADATA_INITIALIZE: [u8; 8] = [210, 225, 30, 162, 88, 184, 77, 141];
33pub const DISC_METADATA_UPDATE_FIELD: [u8; 8] = [221, 233, 49, 45, 181, 202, 220, 200];
35pub const DISC_METADATA_REMOVE_KEY: [u8; 8] = [234, 18, 32, 56, 89, 141, 37, 181];
37pub const DISC_METADATA_UPDATE_AUTHORITY: [u8; 8] = [215, 228, 166, 228, 84, 100, 86, 123];
39pub const DISC_METADATA_EMIT: [u8; 8] = [250, 166, 180, 250, 13, 12, 184, 70];
41pub const DISC_GROUP_INITIALIZE: [u8; 8] = [121, 113, 108, 39, 54, 51, 0, 4];
43pub const DISC_GROUP_UPDATE_MAX_SIZE: [u8; 8] = [108, 37, 171, 143, 248, 30, 18, 110];
45pub const DISC_GROUP_UPDATE_AUTHORITY: [u8; 8] = [161, 105, 88, 1, 237, 221, 216, 203];
47pub const DISC_GROUP_INITIALIZE_MEMBER: [u8; 8] = [152, 32, 222, 176, 223, 237, 116, 134];
49
50pub struct Data {
53 buf: [u8; MAX_METADATA_INSTRUCTION_DATA],
54 len: usize,
55}
56
57impl Data {
58 #[inline(always)]
59 fn new(discriminator: &[u8; 8]) -> Self {
60 let mut buf = [0u8; MAX_METADATA_INSTRUCTION_DATA];
61 buf[..8].copy_from_slice(discriminator);
62 Self { buf, len: 8 }
63 }
64
65 #[inline(always)]
66 fn bytes(&mut self, bytes: &[u8]) -> ProgramResult {
67 let end = self
68 .len
69 .checked_add(bytes.len())
70 .ok_or(ProgramError::InvalidArgument)?;
71 if end > MAX_METADATA_INSTRUCTION_DATA {
72 return Err(ProgramError::InvalidArgument);
73 }
74 self.buf[self.len..end].copy_from_slice(bytes);
75 self.len = end;
76 Ok(())
77 }
78
79 #[inline(always)]
81 fn string(&mut self, text: &str) -> ProgramResult {
82 let len = u32::try_from(text.len()).map_err(|_| ProgramError::InvalidArgument)?;
83 self.bytes(&len.to_le_bytes())?;
84 self.bytes(text.as_bytes())
85 }
86
87 #[inline(always)]
90 fn nullable(&mut self, address: Option<&Address>) -> ProgramResult {
91 match address {
92 Some(address) if address.as_array() == &[0u8; 32] => Err(ProgramError::InvalidArgument),
93 Some(address) => self.bytes(address.as_array()),
94 None => self.bytes(&[0u8; 32]),
95 }
96 }
97
98 #[inline(always)]
100 fn option_u64(&mut self, value: Option<u64>) -> ProgramResult {
101 match value {
102 Some(value) => {
103 self.bytes(&[1])?;
104 self.bytes(&value.to_le_bytes())
105 }
106 None => self.bytes(&[0]),
107 }
108 }
109
110 #[inline(always)]
112 pub fn as_slice(&self) -> &[u8] {
113 &self.buf[..self.len]
114 }
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub enum MetadataField<'a> {
121 Name,
122 Symbol,
123 Uri,
124 Key(&'a str),
125}
126
127pub mod encoders {
129 use super::*;
130
131 #[inline(always)]
133 pub fn encode_initialize(name: &str, symbol: &str, uri: &str) -> Result<Data, ProgramError> {
134 let mut data = Data::new(&DISC_METADATA_INITIALIZE);
135 data.string(name)?;
136 data.string(symbol)?;
137 data.string(uri)?;
138 Ok(data)
139 }
140
141 #[inline(always)]
144 pub fn encode_update_field(
145 field: MetadataField<'_>,
146 value: &str,
147 ) -> Result<Data, ProgramError> {
148 let mut data = Data::new(&DISC_METADATA_UPDATE_FIELD);
149 match field {
150 MetadataField::Name => data.bytes(&[0])?,
151 MetadataField::Symbol => data.bytes(&[1])?,
152 MetadataField::Uri => data.bytes(&[2])?,
153 MetadataField::Key(key) => {
154 data.bytes(&[3])?;
155 data.string(key)?;
156 }
157 }
158 data.string(value)?;
159 Ok(data)
160 }
161
162 #[inline(always)]
164 pub fn encode_remove_key(idempotent: bool, key: &str) -> Result<Data, ProgramError> {
165 let mut data = Data::new(&DISC_METADATA_REMOVE_KEY);
166 data.bytes(&[u8::from(idempotent)])?;
167 data.string(key)?;
168 Ok(data)
169 }
170
171 #[inline(always)]
175 pub fn encode_update_authority(
176 discriminator: &[u8; 8],
177 new_authority: Option<&Address>,
178 ) -> Result<Data, ProgramError> {
179 let mut data = Data::new(discriminator);
180 data.nullable(new_authority)?;
181 Ok(data)
182 }
183
184 #[inline(always)]
186 pub fn encode_emit(start: Option<u64>, end: Option<u64>) -> Result<Data, ProgramError> {
187 let mut data = Data::new(&DISC_METADATA_EMIT);
188 data.option_u64(start)?;
189 data.option_u64(end)?;
190 Ok(data)
191 }
192
193 #[inline(always)]
195 pub fn encode_initialize_group(
196 update_authority: Option<&Address>,
197 max_size: u64,
198 ) -> Result<Data, ProgramError> {
199 let mut data = Data::new(&DISC_GROUP_INITIALIZE);
200 data.nullable(update_authority)?;
201 data.bytes(&max_size.to_le_bytes())?;
202 Ok(data)
203 }
204
205 #[inline(always)]
207 pub fn encode_update_group_max_size(max_size: u64) -> Result<Data, ProgramError> {
208 let mut data = Data::new(&DISC_GROUP_UPDATE_MAX_SIZE);
209 data.bytes(&max_size.to_le_bytes())?;
210 Ok(data)
211 }
212
213 #[inline(always)]
215 pub fn encode_initialize_member() -> Data {
216 Data::new(&DISC_GROUP_INITIALIZE_MEMBER)
217 }
218}
219
220macro_rules! interface_methods {
223 ($name:ident $(, signer = $signer:ident)?) => {
224 impl $name<'_> {
225 #[inline]
227 pub fn invoke(&self) -> ProgramResult {
228 self.invoke_on_program(&TOKEN_2022_PROGRAM_ID, &[])
229 }
230
231 #[inline]
233 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
234 self.invoke_on_program(&TOKEN_2022_PROGRAM_ID, signers)
235 }
236
237 #[inline]
241 pub fn invoke_on_program(
242 &self,
243 program: &Address,
244 signers: &[Signer<'_, '_>],
245 ) -> ProgramResult {
246 $(
247 if signers.is_empty() && !self.$signer.is_signer() {
248 return Err(ProgramError::MissingRequiredSignature);
249 }
250 )?
251 self.emit(&[], &mut Invoke { program, signers })
252 }
253 }
254 };
255}
256
257pub struct InitializeTokenMetadata<'a> {
264 pub metadata: &'a AccountView<'a>,
265 pub update_authority: &'a AccountView<'a>,
266 pub mint: &'a AccountView<'a>,
267 pub mint_authority: &'a AccountView<'a>,
268 pub name: &'a str,
269 pub symbol: &'a str,
270 pub uri: &'a str,
271}
272
273impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeTokenMetadata<'x> {
274 #[inline(always)]
275 fn emit(
276 &self,
277 _multisig_signers: &[&'a AccountView<'a>],
278 sink: &mut impl TokenSink<'a>,
279 ) -> ProgramResult {
280 let data = encoders::encode_initialize(self.name, self.symbol, self.uri)?;
281 let accounts = [
282 InstructionAccount::writable(self.metadata.address()),
283 InstructionAccount::readonly(self.update_authority.address()),
284 InstructionAccount::readonly(self.mint.address()),
285 InstructionAccount::readonly_signer(self.mint_authority.address()),
286 ];
287 let views = [
288 self.metadata,
289 self.update_authority,
290 self.mint,
291 self.mint_authority,
292 ];
293 sink.emit(data.as_slice(), accounts, views, &[])
294 }
295}
296
297interface_methods!(InitializeTokenMetadata, signer = mint_authority);
298
299macro_rules! authority_instruction {
301 ($(#[$doc:meta])* $name:ident { target = $target:ident, authority = $auth:ident $(, $field:ident : $ty:ty)* $(,)? } data = |$s:ident| $data:expr;) => {
302 $(#[$doc])*
303 pub struct $name<'a> {
304 pub $target: &'a AccountView<'a>,
305 pub $auth: &'a AccountView<'a>,
306 $(pub $field: $ty,)*
307 }
308
309 impl<'a, 'x: 'a> TokenInstruction<'a> for $name<'x> {
310 #[inline(always)]
311 fn emit(
312 &self,
313 _multisig_signers: &[&'a AccountView<'a>],
314 sink: &mut impl TokenSink<'a>,
315 ) -> ProgramResult {
316 let $s = self;
317 let data: Data = $data;
318 let accounts = [
319 InstructionAccount::writable(self.$target.address()),
320 InstructionAccount::readonly_signer(self.$auth.address()),
321 ];
322 let views = [self.$target, self.$auth];
323 sink.emit(data.as_slice(), accounts, views, &[])
324 }
325 }
326
327 interface_methods!($name, signer = $auth);
328 };
329}
330
331authority_instruction! {
332 UpdateMetadataField { target = metadata, authority = update_authority, field: MetadataField<'a>, value: &'a str }
336 data = |s| encoders::encode_update_field(s.field, s.value)?;
337}
338
339authority_instruction! {
340 RemoveMetadataKey { target = metadata, authority = update_authority, idempotent: bool, key: &'a str }
343 data = |s| encoders::encode_remove_key(s.idempotent, s.key)?;
344}
345
346authority_instruction! {
347 UpdateMetadataAuthority { target = metadata, authority = current_authority, new_authority: Option<&'a Address> }
350 data = |s| encoders::encode_update_authority(&DISC_METADATA_UPDATE_AUTHORITY, s.new_authority)?;
351}
352
353pub struct EmitTokenMetadata<'a> {
356 pub metadata: &'a AccountView<'a>,
357 pub start: Option<u64>,
358 pub end: Option<u64>,
359}
360
361impl<'a, 'x: 'a> TokenInstruction<'a> for EmitTokenMetadata<'x> {
362 #[inline(always)]
363 fn emit(
364 &self,
365 _multisig_signers: &[&'a AccountView<'a>],
366 sink: &mut impl TokenSink<'a>,
367 ) -> ProgramResult {
368 let data = encoders::encode_emit(self.start, self.end)?;
369 let accounts = [InstructionAccount::readonly(self.metadata.address())];
370 let views = [self.metadata];
371 sink.emit(data.as_slice(), accounts, views, &[])
372 }
373}
374
375interface_methods!(EmitTokenMetadata);
376
377pub struct InitializeTokenGroup<'a> {
383 pub group: &'a AccountView<'a>,
384 pub mint: &'a AccountView<'a>,
385 pub mint_authority: &'a AccountView<'a>,
386 pub update_authority: Option<&'a Address>,
387 pub max_size: u64,
388}
389
390impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeTokenGroup<'x> {
391 #[inline(always)]
392 fn emit(
393 &self,
394 _multisig_signers: &[&'a AccountView<'a>],
395 sink: &mut impl TokenSink<'a>,
396 ) -> ProgramResult {
397 let data = encoders::encode_initialize_group(self.update_authority, self.max_size)?;
398 let accounts = [
399 InstructionAccount::writable(self.group.address()),
400 InstructionAccount::readonly(self.mint.address()),
401 InstructionAccount::readonly_signer(self.mint_authority.address()),
402 ];
403 let views = [self.group, self.mint, self.mint_authority];
404 sink.emit(data.as_slice(), accounts, views, &[])
405 }
406}
407
408interface_methods!(InitializeTokenGroup, signer = mint_authority);
409
410authority_instruction! {
411 UpdateTokenGroupMaxSize { target = group, authority = update_authority, max_size: u64 }
413 data = |s| encoders::encode_update_group_max_size(s.max_size)?;
414}
415
416authority_instruction! {
417 UpdateTokenGroupAuthority { target = group, authority = current_authority, new_authority: Option<&'a Address> }
420 data = |s| encoders::encode_update_authority(&DISC_GROUP_UPDATE_AUTHORITY, s.new_authority)?;
421}
422
423pub struct InitializeTokenGroupMember<'a> {
427 pub member: &'a AccountView<'a>,
428 pub member_mint: &'a AccountView<'a>,
429 pub member_mint_authority: &'a AccountView<'a>,
430 pub group: &'a AccountView<'a>,
431 pub group_update_authority: &'a AccountView<'a>,
432}
433
434impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeTokenGroupMember<'x> {
435 #[inline(always)]
436 fn emit(
437 &self,
438 _multisig_signers: &[&'a AccountView<'a>],
439 sink: &mut impl TokenSink<'a>,
440 ) -> ProgramResult {
441 let data = encoders::encode_initialize_member();
442 let accounts = [
443 InstructionAccount::writable(self.member.address()),
444 InstructionAccount::readonly(self.member_mint.address()),
445 InstructionAccount::readonly_signer(self.member_mint_authority.address()),
446 InstructionAccount::writable(self.group.address()),
447 InstructionAccount::readonly_signer(self.group_update_authority.address()),
448 ];
449 let views = [
450 self.member,
451 self.member_mint,
452 self.member_mint_authority,
453 self.group,
454 self.group_update_authority,
455 ];
456 sink.emit(data.as_slice(), accounts, views, &[])
457 }
458}
459
460impl InitializeTokenGroupMember<'_> {
461 #[inline]
463 pub fn invoke(&self) -> ProgramResult {
464 self.invoke_on_program(&TOKEN_2022_PROGRAM_ID, &[])
465 }
466
467 #[inline]
469 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
470 self.invoke_on_program(&TOKEN_2022_PROGRAM_ID, signers)
471 }
472
473 #[inline]
476 pub fn invoke_on_program(
477 &self,
478 program: &Address,
479 signers: &[Signer<'_, '_>],
480 ) -> ProgramResult {
481 if signers.is_empty()
482 && !(self.member_mint_authority.is_signer() && self.group_update_authority.is_signer())
483 {
484 return Err(ProgramError::MissingRequiredSignature);
485 }
486 self.emit(&[], &mut Invoke { program, signers })
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::encoders::*;
493 use super::*;
494
495 #[test]
496 fn discriminators_are_the_hash_prefixes_of_the_interface_inputs() {
497 for (input, expected) in [
498 (
499 "spl_token_metadata_interface:initialize_account",
500 DISC_METADATA_INITIALIZE,
501 ),
502 (
503 "spl_token_metadata_interface:updating_field",
504 DISC_METADATA_UPDATE_FIELD,
505 ),
506 (
507 "spl_token_metadata_interface:remove_key_ix",
508 DISC_METADATA_REMOVE_KEY,
509 ),
510 (
511 "spl_token_metadata_interface:update_the_authority",
512 DISC_METADATA_UPDATE_AUTHORITY,
513 ),
514 ("spl_token_metadata_interface:emitter", DISC_METADATA_EMIT),
515 (
516 "spl_token_group_interface:initialize_token_group",
517 DISC_GROUP_INITIALIZE,
518 ),
519 (
520 "spl_token_group_interface:update_group_max_size",
521 DISC_GROUP_UPDATE_MAX_SIZE,
522 ),
523 (
524 "spl_token_group_interface:update_authority",
525 DISC_GROUP_UPDATE_AUTHORITY,
526 ),
527 (
528 "spl_token_group_interface:initialize_member",
529 DISC_GROUP_INITIALIZE_MEMBER,
530 ),
531 ] {
532 let digest = hopper_native::hash::sha256(&[input.as_bytes()]).unwrap();
533 assert_eq!(&digest[..8], &expected, "{input}");
534 }
535 }
536
537 #[test]
538 fn strings_are_length_prefixed_and_bounded() {
539 let data = encode_initialize("Hopper", "HOP", "https://hopperzero.dev/t.json").unwrap();
540 let bytes = data.as_slice();
541 assert_eq!(&bytes[..8], &DISC_METADATA_INITIALIZE);
542 assert_eq!(&bytes[8..12], &6u32.to_le_bytes());
543 assert_eq!(&bytes[12..18], b"Hopper");
544 assert_eq!(&bytes[18..22], &3u32.to_le_bytes());
545 assert_eq!(&bytes[22..25], b"HOP");
546 assert_eq!(bytes.len(), 8 + 4 + 6 + 4 + 3 + 4 + 29);
547
548 let long = "x".repeat(MAX_METADATA_INSTRUCTION_DATA);
549 assert_eq!(
550 encode_initialize(&long, "", "").err(),
551 Some(ProgramError::InvalidArgument)
552 );
553 let fits = "x".repeat(MAX_METADATA_INSTRUCTION_DATA - 8 - 12);
555 assert_eq!(
556 encode_initialize(&fits, "", "").unwrap().as_slice().len(),
557 MAX_METADATA_INSTRUCTION_DATA
558 );
559 }
560
561 #[test]
562 fn fields_options_and_authorities_encode_as_borsh() {
563 assert_eq!(
564 &encode_update_field(MetadataField::Uri, "u")
565 .unwrap()
566 .as_slice()[8..],
567 &[2, 1, 0, 0, 0, b'u']
568 );
569 assert_eq!(
570 &encode_update_field(MetadataField::Key("k"), "v")
571 .unwrap()
572 .as_slice()[8..],
573 &[3, 1, 0, 0, 0, b'k', 1, 0, 0, 0, b'v']
574 );
575 assert_eq!(
576 &encode_remove_key(true, "k").unwrap().as_slice()[8..],
577 &[1, 1, 0, 0, 0, b'k']
578 );
579 assert_eq!(&encode_emit(None, None).unwrap().as_slice()[8..], &[0, 0]);
580 assert_eq!(
581 &encode_emit(Some(2), None).unwrap().as_slice()[8..],
582 &[1, 2, 0, 0, 0, 0, 0, 0, 0, 0]
583 );
584 let authority = Address::new_from_array([7; 32]);
585 assert_eq!(
586 &encode_update_authority(&DISC_GROUP_UPDATE_AUTHORITY, Some(&authority))
587 .unwrap()
588 .as_slice()[8..],
589 &[7u8; 32]
590 );
591 assert_eq!(
592 &encode_update_authority(&DISC_METADATA_UPDATE_AUTHORITY, None)
593 .unwrap()
594 .as_slice()[8..],
595 &[0u8; 32]
596 );
597 assert!(encode_update_authority(
598 &DISC_METADATA_UPDATE_AUTHORITY,
599 Some(&Address::new_from_array([0; 32]))
600 )
601 .is_err());
602 assert_eq!(
603 &encode_initialize_group(None, 9).unwrap().as_slice()[40..],
604 &9u64.to_le_bytes()
605 );
606 assert_eq!(encode_initialize_member().as_slice().len(), 8);
607 }
608}