-
Notifications
You must be signed in to change notification settings - Fork 7
Pointer-aware metadata sync #234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
//! Metadata resolution helpers for pointer-aware metadata sync | ||
|
||
use { | ||
crate::{error::TokenWrapError, metaplex::metaplex_to_token_2022_metadata}, | ||
mpl_token_metadata::ID as MPL_TOKEN_METADATA_ID, | ||
solana_account_info::AccountInfo, | ||
solana_cpi::{get_return_data, invoke}, | ||
solana_program_error::ProgramError, | ||
spl_token_2022::{ | ||
extension::{ | ||
metadata_pointer::MetadataPointer, BaseStateWithExtensions, PodStateWithExtensions, | ||
}, | ||
id as token_2022_id, | ||
pod::PodMint, | ||
}, | ||
spl_token_metadata_interface::{instruction::emit, state::TokenMetadata}, | ||
spl_type_length_value::variable_len_pack::VariableLenPack, | ||
}; | ||
|
||
/// Fetches metadata from a third-party program implementing | ||
/// `TokenMetadataInstruction` by invoking its `Emit` instruction and decoding | ||
/// the `TokenMetadata` struct from the return data. | ||
pub fn cpi_emit_and_decode<'a>( | ||
owner_program_info: &AccountInfo<'a>, | ||
metadata_info: &AccountInfo<'a>, | ||
) -> Result<TokenMetadata, ProgramError> { | ||
invoke( | ||
&emit(owner_program_info.key, metadata_info.key, None, None), | ||
&[metadata_info.clone()], | ||
)?; | ||
|
||
if let Some((program_key, data)) = get_return_data() { | ||
// This check ensures this data comes from the program we just called | ||
if program_key == *owner_program_info.key { | ||
return TokenMetadata::unpack_from_slice(&data); | ||
} | ||
} | ||
|
||
Err(TokenWrapError::ExternalProgramReturnedNoData.into()) | ||
} | ||
|
||
/// Resolve the canonical metadata source for an unwrapped Token-2022 mint | ||
/// by following its `MetadataPointer`. | ||
/// | ||
/// Supported pointer targets: | ||
/// - Self | ||
/// - Token-2022 account | ||
/// - `Metaplex` PDA | ||
/// - Third-party program | ||
pub fn resolve_token_2022_source_metadata<'a>( | ||
unwrapped_mint_info: &AccountInfo<'a>, | ||
maybe_source_metadata_info: Option<&AccountInfo<'a>>, | ||
maybe_owner_program_info: Option<&AccountInfo<'a>>, | ||
) -> Result<TokenMetadata, ProgramError> { | ||
let data = unwrapped_mint_info.try_borrow_data()?; | ||
let mint_state = PodStateWithExtensions::<PodMint>::unpack(&data)?; | ||
let pointer = mint_state | ||
.get_extension::<MetadataPointer>() | ||
.map_err(|_| TokenWrapError::MetadataPointerMissing)?; | ||
let metadata_addr = | ||
Option::from(pointer.metadata_address).ok_or(TokenWrapError::MetadataPointerUnset)?; | ||
|
||
// Scenario 1: points to self, read off unwrapped mint | ||
if metadata_addr == *unwrapped_mint_info.key { | ||
return mint_state.get_variable_len_extension::<TokenMetadata>(); | ||
} | ||
|
||
// Metadata account must be passed by this point | ||
let metadata_info = maybe_source_metadata_info.ok_or(ProgramError::NotEnoughAccountKeys)?; | ||
if metadata_info.key != &metadata_addr { | ||
return Err(TokenWrapError::MetadataPointerMismatch.into()); | ||
} | ||
|
||
if metadata_info.owner == &token_2022_id() { | ||
// Scenario 2: points to another token-2022 mint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this actually a possible scenario? I wasn't aware 😅 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The metadata pointer can be updated for any pubkey. However, if you find this scenario is not legitimate, I can follow up and remove it. I'm not super familiar how folks are using the pointer outside the self and PDA case. I was surprised to find the third-party program case myself. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to remove it -- if a mint is pointing its metadata at another mint, I would worry that it's trying to spoof that other mint. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will follow up with a PR! |
||
let data = metadata_info.try_borrow_data()?; | ||
let state = PodStateWithExtensions::<PodMint>::unpack(&data)?; | ||
state.get_variable_len_extension::<TokenMetadata>() | ||
} else if metadata_info.owner == &MPL_TOKEN_METADATA_ID { | ||
// Scenario 3: points to a Metaplex PDA | ||
metaplex_to_token_2022_metadata(unwrapped_mint_info, metadata_info) | ||
} else { | ||
// Scenario 4: points to an external program | ||
let owner_program_info = | ||
maybe_owner_program_info.ok_or(ProgramError::NotEnoughAccountKeys)?; | ||
if owner_program_info.key != metadata_info.owner { | ||
return Err(ProgramError::InvalidAccountOwner); | ||
} | ||
cpi_emit_and_decode(owner_program_info, metadata_info) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love some guidance here. For token-2022's, is their metadata legit only if they have a pointer? Aka, should we support the scenario of a token-2022 with metadata extension with no pointer?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right, the Token Metadata standard in Token-2022 only recognizes metadata explicitly registered with a
MetadataPointer
extension. It's also expected that the metadata adheres to the SPL Token Metadata interface (name
,symbol
,uri
, plus optional additional keys).However, Token-2022 has no way to enforce this interface adherence on the fields specifically. So, maybe we give it our best effort, and default to blank fields for
name
,symbol
, anduri
, while populating all of the additional fields as you do for Metaplex?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what you mean that it adheres to the interface, but not the fields specifically. The section with
update_fields_if_changed()
will ensure the fields will be in sync so if the source data can successfully deserialize then I think we're good.