Skip to content

Commit

Permalink
Add update_metadata_collection_nft
Browse files Browse the repository at this point in the history
  • Loading branch information
danenbm committed Nov 14, 2023
1 parent 32c9330 commit 71a7f3b
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 4 deletions.
4 changes: 4 additions & 0 deletions programs/bubblegum/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ pub enum BubblegumError {
MetadataArgsAmbiguous,
#[msg("MetadataArgs missing")]
MetadataArgsMissing,
#[msg("NFT linked to collection")]
NFTLinkedToCollection,
#[msg("NFT not linked to verified collection")]
NFTNotLinkedToVerifiedCollection,
#[msg("Can only update primary sale to true")]
PrimarySaleCanOnlyBeFlippedToTrue,
#[msg("Creator did not unverify the metadata")]
Expand Down
23 changes: 22 additions & 1 deletion programs/bubblegum/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum InstructionName {
MintToCollectionV1,
SetDecompressibleState,
UpdateMetadata,
UpdateMetadataCollectionNft,
}

pub fn get_instruction_type(full_bytes: &[u8]) -> InstructionName {
Expand Down Expand Up @@ -64,6 +65,7 @@ pub fn get_instruction_type(full_bytes: &[u8]) -> InstructionName {
// `SetDecompressableState` instruction mapped to `SetDecompressibleState` instruction
[18, 135, 238, 168, 246, 195, 61, 115] => InstructionName::SetDecompressibleState,
[170, 182, 43, 239, 97, 78, 225, 186] => InstructionName::UpdateMetadata,
[244, 12, 175, 194, 227, 28, 102, 215] => InstructionName::UpdateMetadataCollectionNft,
_ => InstructionName::Unknown,
}
}
Expand Down Expand Up @@ -265,7 +267,7 @@ pub mod bubblegum {
processor::verify_creator(ctx, root, data_hash, creator_hash, nonce, index, message)
}

/// Verifies a creator for a leaf node.
/// Updates metadata for a leaf node that is not part of a verified collection.
pub fn update_metadata<'info>(
ctx: Context<'_, '_, '_, 'info, UpdateMetadata<'info>>,
root: [u8; 32],
Expand All @@ -276,4 +278,23 @@ pub mod bubblegum {
) -> Result<()> {
processor::update_metadata(ctx, root, nonce, index, current_metadata, update_args)
}

/// Updates metadata for a leaf node that is part of a verified collection.
pub fn update_metadata_collection_nft<'info>(
ctx: Context<'_, '_, '_, 'info, UpdateMetadataCollectionNFT<'info>>,
root: [u8; 32],
nonce: u64,
index: u32,
current_metadata: MetadataArgs,
update_args: UpdateArgs,
) -> Result<()> {
processor::update_metadata_collection_nft(
ctx,
root,
nonce,
index,
current_metadata,
update_args,
)
}
}
136 changes: 133 additions & 3 deletions programs/bubblegum/program/src/processor/update_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use anchor_lang::prelude::*;
use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop};

use crate::{
asserts::assert_metadata_is_mpl_compatible,
asserts::{assert_has_collection_authority, assert_metadata_is_mpl_compatible},
error::BubblegumError,
state::{
leaf_schema::LeafSchema,
metaplex_adapter::{Creator, MetadataArgs, UpdateArgs},
metaplex_anchor::MplTokenMetadata,
metaplex_adapter::{Collection, Creator, MetadataArgs, UpdateArgs},
metaplex_anchor::{MplTokenMetadata, TokenMetadata},
TreeConfig,
},
utils::{get_asset_id, hash_creators, hash_metadata, replace_leaf},
Expand Down Expand Up @@ -36,6 +36,80 @@ pub struct UpdateMetadata<'info> {
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct UpdateMetadataCollectionNFT<'info> {
#[account(
seeds = [merkle_tree.key().as_ref()],
bump,
)]
/// CHECK: This account is neither written to nor read from.
pub tree_authority: Account<'info, TreeConfig>,
pub tree_delegate: Signer<'info>,
pub collection_authority: Signer<'info>,
/// CHECK: This account is checked in the instruction
pub collection_mint: UncheckedAccount<'info>,
pub collection_metadata: Box<Account<'info, TokenMetadata>>,
/// CHECK: This account is checked in the instruction
pub collection_authority_record_pda: Option<UncheckedAccount<'info>>,
/// CHECK: This account is checked in the instruction
pub leaf_owner: UncheckedAccount<'info>,
/// CHECK: This account is checked in the instruction
pub leaf_delegate: UncheckedAccount<'info>,
pub payer: Signer<'info>,
#[account(mut)]
/// CHECK: This account is modified in the downstream program
pub merkle_tree: UncheckedAccount<'info>,
pub log_wrapper: Program<'info, Noop>,
pub compression_program: Program<'info, SplAccountCompression>,
pub token_metadata_program: Program<'info, MplTokenMetadata>,
pub system_program: Program<'info, System>,
}

fn assert_authority_matches_collection<'info>(
collection: &Collection,
collection_authority: &AccountInfo<'info>,
collection_authority_record_pda: &Option<UncheckedAccount<'info>>,
collection_mint: &AccountInfo<'info>,
collection_metadata_account_info: &AccountInfo,
collection_metadata: &TokenMetadata,
token_metadata_program: &Program<'info, MplTokenMetadata>,
) -> Result<()> {
// Mint account must match Collection mint
require!(
collection_mint.key() == collection.key,
BubblegumError::CollectionMismatch
);
// Metadata mint must match Collection mint
require!(
collection_metadata.mint == collection.key,
BubblegumError::CollectionMismatch
);
// Verify correct account ownerships.
require!(
*collection_metadata_account_info.owner == token_metadata_program.key(),
BubblegumError::IncorrectOwner
);
// Collection mint must be owned by SPL token
require!(
*collection_mint.owner == spl_token::id(),
BubblegumError::IncorrectOwner
);

let collection_authority_record = collection_authority_record_pda
.as_ref()
.map(|authority_record_pda| authority_record_pda.to_account_info());

// Assert that the correct Collection Authority was provided using token-metadata
assert_has_collection_authority(
collection_metadata,
collection_mint.key,
collection_authority.key,
collection_authority_record.as_ref(),
)?;

Ok(())
}

fn all_verified_creators_in_a_are_in_b(a: &[Creator], b: &[Creator], exception: Pubkey) -> bool {
a.iter()
.filter(|creator_a| creator_a.verified)
Expand Down Expand Up @@ -178,6 +252,62 @@ pub fn update_metadata<'info>(
BubblegumError::TreeAuthorityIncorrect,
);

// NFTs which are linked to verified collections cannot be updated through this instruction
require!(
current_metadata.collection.is_none()
|| !current_metadata.collection.as_ref().unwrap().verified,
BubblegumError::NFTLinkedToCollection
);

process_update_metadata(
&ctx.accounts.merkle_tree.to_account_info(),
&ctx.accounts.tree_delegate,
&ctx.accounts.leaf_owner,
&ctx.accounts.leaf_delegate,
&ctx.accounts.compression_program.to_account_info(),
&ctx.accounts.tree_authority.to_account_info(),
*ctx.bumps.get("tree_authority").unwrap(),
&ctx.accounts.log_wrapper,
ctx.remaining_accounts,
root,
current_metadata,
update_args,
nonce,
index,
)
}

pub fn update_metadata_collection_nft<'info>(
ctx: Context<'_, '_, '_, 'info, UpdateMetadataCollectionNFT<'info>>,
root: [u8; 32],
nonce: u64,
index: u32,
current_metadata: MetadataArgs,
update_args: UpdateArgs,
) -> Result<()> {
require!(
ctx.accounts.tree_delegate.key() == ctx.accounts.tree_authority.tree_creator
|| ctx.accounts.tree_delegate.key() == ctx.accounts.tree_authority.tree_delegate,
BubblegumError::TreeAuthorityIncorrect,
);

// NFTs updated through this instruction must be linked to a collection,
// so a collection authority for that collection must sign
let collection = current_metadata
.collection
.as_ref()
.ok_or(BubblegumError::NFTNotLinkedToVerifiedCollection)?;

assert_authority_matches_collection(
collection,
&ctx.accounts.collection_authority.to_account_info(),
&ctx.accounts.collection_authority_record_pda,
&ctx.accounts.collection_mint,
&ctx.accounts.collection_metadata.to_account_info(),
&ctx.accounts.collection_metadata,
&ctx.accounts.token_metadata_program,
)?;

process_update_metadata(
&ctx.accounts.merkle_tree.to_account_info(),
&ctx.accounts.tree_delegate,
Expand Down

0 comments on commit 71a7f3b

Please sign in to comment.