Skip to content
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

Use token metadata client crate #60

Merged
merged 4 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 5 additions & 33 deletions programs/bubblegum/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion programs/bubblegum/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ default = []
anchor-lang = { version = "0.28.0", features = ["init-if-needed"] }
anchor-spl = "0.28.0"
bytemuck = "1.13.0"
mpl-token-metadata = { version = "2.0.0-beta.1", features = ["no-entrypoint"] }
mpl-token-metadata = "3.2.1"
num-traits = "0.2.15"
solana-program = "~1.16.5"
spl-account-compression = { version="0.2.0", features = ["cpi"] }
Expand Down
55 changes: 46 additions & 9 deletions programs/bubblegum/program/src/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
use crate::{error::BubblegumError, state::metaplex_adapter::MetadataArgs, utils::cmp_pubkeys};
use anchor_lang::prelude::*;
use mpl_token_metadata::{
instruction::MetadataDelegateRole,
pda::{find_collection_authority_account, find_metadata_delegate_record_account},
state::{CollectionAuthorityRecord, Metadata, MetadataDelegateRecord, TokenMetadataAccount},
accounts::{CollectionAuthorityRecord, Metadata, MetadataDelegateRecord},
types::{Collection, MetadataDelegateRole, TokenStandard},
};

/// Assert that the provided MetadataArgs are compatible with MPL `Data`
pub fn assert_metadata_is_mpl_compatible(metadata: &MetadataArgs) -> Result<()> {
if metadata.name.len() > mpl_token_metadata::state::MAX_NAME_LENGTH {
if metadata.name.len() > mpl_token_metadata::MAX_NAME_LENGTH {
return Err(BubblegumError::MetadataNameTooLong.into());
}

if metadata.symbol.len() > mpl_token_metadata::state::MAX_SYMBOL_LENGTH {
if metadata.symbol.len() > mpl_token_metadata::MAX_SYMBOL_LENGTH {
return Err(BubblegumError::MetadataSymbolTooLong.into());
}

if metadata.uri.len() > mpl_token_metadata::state::MAX_URI_LENGTH {
if metadata.uri.len() > mpl_token_metadata::MAX_URI_LENGTH {
return Err(BubblegumError::MetadataUriTooLong.into());
}

if metadata.seller_fee_basis_points > 10000 {
return Err(BubblegumError::MetadataBasisPointsTooHigh.into());
}
if !metadata.creators.is_empty() {
if metadata.creators.len() > mpl_token_metadata::state::MAX_CREATOR_LIMIT {
if metadata.creators.len() > mpl_token_metadata::MAX_CREATOR_LIMIT {
return Err(BubblegumError::CreatorsTooLong.into());
}

Expand Down Expand Up @@ -106,8 +105,8 @@ pub fn assert_has_collection_authority(
}

if let Some(record_info) = delegate_record {
let (ca_pda, ca_bump) = find_collection_authority_account(mint, collection_authority);
let (md_pda, md_bump) = find_metadata_delegate_record_account(
let (ca_pda, ca_bump) = CollectionAuthorityRecord::find_pda(mint, collection_authority);
let (md_pda, md_bump) = MetadataDelegateRecord::find_pda(
mint,
MetadataDelegateRole::Collection,
&collection_data.update_authority,
Expand Down Expand Up @@ -150,3 +149,41 @@ pub fn assert_has_collection_authority(
}
Ok(())
}

pub fn assert_collection_membership(
membership: &Option<Collection>,
collection_metadata: &Metadata,
collection_mint: &Pubkey,
collection_edition: &AccountInfo,
) -> Result<()> {
match membership {
Some(collection) => {
if collection.key != *collection_mint || collection_metadata.mint != *collection_mint {
return Err(BubblegumError::CollectionNotFound.into());
}
}
None => {
return Err(BubblegumError::CollectionNotFound.into());
}
}

let (expected, _) = mpl_token_metadata::accounts::MasterEdition::find_pda(collection_mint);

if collection_edition.key != &expected {
return Err(BubblegumError::CollectionMasterEditionAccountInvalid.into());
}

let edition = mpl_token_metadata::accounts::MasterEdition::try_from(collection_edition)
.map_err(|_err| BubblegumError::CollectionMustBeAUniqueMasterEdition)?;

match collection_metadata.token_standard {
Some(TokenStandard::NonFungible) | Some(TokenStandard::ProgrammableNonFungible) => (),
_ => return Err(BubblegumError::CollectionMustBeAUniqueMasterEdition.into()),
}

if edition.max_supply != Some(0) {
return Err(BubblegumError::CollectionMustBeAUniqueMasterEdition.into());
}

Ok(())
}
8 changes: 4 additions & 4 deletions programs/bubblegum/program/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anchor_lang::prelude::*;
use mpl_token_metadata::error::MetadataError;
use mpl_token_metadata::errors::MplTokenMetadataError;
use num_traits::FromPrimitive;

#[error_code]
Expand Down Expand Up @@ -82,12 +82,12 @@ pub fn metadata_error_into_bubblegum(error: ProgramError) -> BubblegumError {
FromPrimitive::from_u32(e).expect("Unknown error code from token-metadata");

match metadata_error {
MetadataError::CollectionNotFound => BubblegumError::CollectionNotFound,
MetadataError::CollectionMustBeAUniqueMasterEdition => {
MplTokenMetadataError::CollectionNotFound => BubblegumError::CollectionNotFound,
MplTokenMetadataError::CollectionMustBeAUniqueMasterEdition => {
BubblegumError::CollectionMustBeAUniqueMasterEdition
}

MetadataError::CollectionMasterEditionAccountInvalid => {
MplTokenMetadataError::CollectionMasterEditionAccountInvalid => {
BubblegumError::CollectionMasterEditionAccountInvalid
}

Expand Down
111 changes: 44 additions & 67 deletions programs/bubblegum/program/src/processor/decompress.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use anchor_lang::prelude::*;
use anchor_spl::{associated_token::AssociatedToken, token::Token};
use mpl_token_metadata::{
instructions::{CreateMasterEditionV3CpiBuilder, CreateMetadataAccountV3CpiBuilder},
types::DataV2,
};
use solana_program::{
program::{invoke, invoke_signed},
program_pack::Pack,
Expand Down Expand Up @@ -71,12 +75,14 @@ pub struct DecompressV1<'info> {
}

pub(crate) fn decompress_v1(ctx: Context<DecompressV1>, metadata: MetadataArgs) -> Result<()> {
// Allocate and create mint
let incoming_data_hash = hash_metadata(&metadata)?;
// Validate the incoming metadata

match ctx.accounts.voucher.leaf_schema {
LeafSchema::V1 {
owner, data_hash, ..
} => {
let incoming_data_hash = hash_metadata(&metadata)?;

if !cmp_bytes(&data_hash, &incoming_data_hash, 32) {
return Err(BubblegumError::HashingMismatch.into());
}
Expand Down Expand Up @@ -180,82 +186,53 @@ pub(crate) fn decompress_v1(ctx: Context<DecompressV1>, metadata: MetadataArgs)
]],
)?;

let metadata_infos = vec![
ctx.accounts.metadata.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.leaf_owner.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.sysvar_rent.to_account_info(),
];

let master_edition_infos = vec![
ctx.accounts.master_edition.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.leaf_owner.to_account_info(),
ctx.accounts.metadata.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.sysvar_rent.to_account_info(),
];

msg!("Creating metadata!");
invoke_signed(
&mpl_token_metadata::instruction::create_metadata_accounts_v3(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.metadata.key(),
ctx.accounts.mint.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.leaf_owner.key(),
ctx.accounts.mint_authority.key(),
metadata.name.clone(),
metadata.symbol.clone(),
metadata.uri.clone(),
if !metadata.creators.is_empty() {
Some(metadata.creators.iter().map(|c| c.adapt()).collect())
} else {
msg!("Creating metadata");
CreateMetadataAccountV3CpiBuilder::new(&ctx.accounts.token_metadata_program)
.metadata(&ctx.accounts.metadata)
.mint(&ctx.accounts.mint)
.mint_authority(&ctx.accounts.mint_authority)
.payer(&ctx.accounts.leaf_owner)
.update_authority(&ctx.accounts.mint_authority, true)
.system_program(&ctx.accounts.system_program)
.data(DataV2 {
name: metadata.name.clone(),
symbol: metadata.symbol.clone(),
uri: metadata.uri.clone(),
creators: if metadata.creators.is_empty() {
None
} else {
Some(metadata.creators.iter().map(|c| c.adapt()).collect())
},
metadata.seller_fee_basis_points,
true,
metadata.is_mutable,
metadata.collection.map(|c| c.adapt()),
metadata.uses.map(|u| u.adapt()),
None,
),
metadata_infos.as_slice(),
&[&[
collection: metadata.collection.map(|c| c.adapt()),
seller_fee_basis_points: metadata.seller_fee_basis_points,
uses: metadata.uses.map(|u| u.adapt()),
})
.is_mutable(metadata.is_mutable)
.invoke_signed(&[&[
ctx.accounts.mint.key().as_ref(),
&[ctx.bumps["mint_authority"]],
]],
)?;
]])?;

msg!("Creating master edition!");
invoke_signed(
&mpl_token_metadata::instruction::create_master_edition_v3(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.master_edition.key(),
ctx.accounts.mint.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.metadata.key(),
ctx.accounts.leaf_owner.key(),
Some(0),
),
master_edition_infos.as_slice(),
&[&[
msg!("Creating master edition");
CreateMasterEditionV3CpiBuilder::new(&ctx.accounts.token_metadata_program)
.edition(&ctx.accounts.master_edition)
.mint(&ctx.accounts.mint)
.mint_authority(&ctx.accounts.mint_authority)
.update_authority(&ctx.accounts.mint_authority)
.metadata(&ctx.accounts.metadata)
.payer(&ctx.accounts.leaf_owner)
.system_program(&ctx.accounts.system_program)
.token_program(&ctx.accounts.token_program)
.max_supply(0)
.invoke_signed(&[&[
ctx.accounts.mint.key().as_ref(),
&[ctx.bumps["mint_authority"]],
]],
)?;
]])?;

ctx.accounts
.mint_authority
.to_account_info()
.assign(&System::id());

Ok(())
}
Loading
Loading