|
| 1 | +//! Metadata resolution helpers for pointer-aware metadata sync |
| 2 | +
|
| 3 | +use { |
| 4 | + crate::{error::TokenWrapError, metaplex::metaplex_to_token_2022_metadata}, |
| 5 | + mpl_token_metadata::ID as MPL_TOKEN_METADATA_ID, |
| 6 | + solana_account_info::AccountInfo, |
| 7 | + solana_cpi::{get_return_data, invoke}, |
| 8 | + solana_program_error::ProgramError, |
| 9 | + spl_token_2022::{ |
| 10 | + extension::{ |
| 11 | + metadata_pointer::MetadataPointer, BaseStateWithExtensions, PodStateWithExtensions, |
| 12 | + }, |
| 13 | + id as token_2022_id, |
| 14 | + pod::PodMint, |
| 15 | + }, |
| 16 | + spl_token_metadata_interface::{instruction::emit, state::TokenMetadata}, |
| 17 | + spl_type_length_value::variable_len_pack::VariableLenPack, |
| 18 | +}; |
| 19 | + |
| 20 | +/// Fetches metadata from a third-party program implementing |
| 21 | +/// `TokenMetadataInstruction` by invoking its `Emit` instruction and decoding |
| 22 | +/// the `TokenMetadata` struct from the return data. |
| 23 | +pub fn cpi_emit_and_decode<'a>( |
| 24 | + owner_program_info: &AccountInfo<'a>, |
| 25 | + metadata_info: &AccountInfo<'a>, |
| 26 | +) -> Result<TokenMetadata, ProgramError> { |
| 27 | + invoke( |
| 28 | + &emit(owner_program_info.key, metadata_info.key, None, None), |
| 29 | + &[metadata_info.clone()], |
| 30 | + )?; |
| 31 | + |
| 32 | + if let Some((program_key, data)) = get_return_data() { |
| 33 | + // This check ensures this data comes from the program we just called |
| 34 | + if program_key == *owner_program_info.key { |
| 35 | + return TokenMetadata::unpack_from_slice(&data); |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + Err(TokenWrapError::ExternalProgramReturnedNoData.into()) |
| 40 | +} |
| 41 | + |
| 42 | +/// Resolve the canonical metadata source for an unwrapped Token-2022 mint |
| 43 | +/// by following its `MetadataPointer`. |
| 44 | +/// |
| 45 | +/// Supported pointer targets: |
| 46 | +/// - Self |
| 47 | +/// - Token-2022 account |
| 48 | +/// - `Metaplex` PDA |
| 49 | +/// - Third-party program |
| 50 | +pub fn resolve_token_2022_source_metadata<'a>( |
| 51 | + unwrapped_mint_info: &AccountInfo<'a>, |
| 52 | + maybe_source_metadata_info: Option<&AccountInfo<'a>>, |
| 53 | + maybe_owner_program_info: Option<&AccountInfo<'a>>, |
| 54 | +) -> Result<TokenMetadata, ProgramError> { |
| 55 | + let data = unwrapped_mint_info.try_borrow_data()?; |
| 56 | + let mint_state = PodStateWithExtensions::<PodMint>::unpack(&data)?; |
| 57 | + let pointer = mint_state |
| 58 | + .get_extension::<MetadataPointer>() |
| 59 | + .map_err(|_| TokenWrapError::MetadataPointerMissing)?; |
| 60 | + let metadata_addr = |
| 61 | + Option::from(pointer.metadata_address).ok_or(TokenWrapError::MetadataPointerUnset)?; |
| 62 | + |
| 63 | + // Scenario 1: points to self, read off unwrapped mint |
| 64 | + if metadata_addr == *unwrapped_mint_info.key { |
| 65 | + return mint_state.get_variable_len_extension::<TokenMetadata>(); |
| 66 | + } |
| 67 | + |
| 68 | + // Metadata account must be passed by this point |
| 69 | + let metadata_info = maybe_source_metadata_info.ok_or(ProgramError::NotEnoughAccountKeys)?; |
| 70 | + if metadata_info.key != &metadata_addr { |
| 71 | + return Err(TokenWrapError::MetadataPointerMismatch.into()); |
| 72 | + } |
| 73 | + |
| 74 | + if metadata_info.owner == &token_2022_id() { |
| 75 | + // Scenario 2: points to another token-2022 mint |
| 76 | + let data = metadata_info.try_borrow_data()?; |
| 77 | + let state = PodStateWithExtensions::<PodMint>::unpack(&data)?; |
| 78 | + state.get_variable_len_extension::<TokenMetadata>() |
| 79 | + } else if metadata_info.owner == &MPL_TOKEN_METADATA_ID { |
| 80 | + // Scenario 3: points to a Metaplex PDA |
| 81 | + metaplex_to_token_2022_metadata(unwrapped_mint_info, metadata_info) |
| 82 | + } else { |
| 83 | + // Scenario 4: points to an external program |
| 84 | + let owner_program_info = |
| 85 | + maybe_owner_program_info.ok_or(ProgramError::NotEnoughAccountKeys)?; |
| 86 | + if owner_program_info.key != metadata_info.owner { |
| 87 | + return Err(ProgramError::InvalidAccountOwner); |
| 88 | + } |
| 89 | + cpi_emit_and_decode(owner_program_info, metadata_info) |
| 90 | + } |
| 91 | +} |
0 commit comments