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

Merge main into update-metadata-staging #61

Merged
merged 2 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
227 changes: 100 additions & 127 deletions programs/bubblegum/Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions programs/bubblegum/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ 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.3"
num-traits = "0.2.15"
solana-program = "~1.16.5"
solana-program = "~1.16.8"
spl-account-compression = { version="0.2.0", features = ["cpi"] }
spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] }
spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] }
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 @@ -94,12 +94,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(())
}
50 changes: 17 additions & 33 deletions programs/bubblegum/program/src/processor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use anchor_lang::prelude::*;
use mpl_token_metadata::{
assertions::collection::assert_collection_verify_is_valid, state::CollectionDetails,
instructions::BubblegumSetCollectionSizeCpiBuilder,
types::{CollectionDetails, SetCollectionSizeArgs},
};
use solana_program::{account_info::AccountInfo, program::invoke_signed, pubkey::Pubkey};
use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
use spl_account_compression::wrap_application_data_v1;

use crate::{
asserts::assert_has_collection_authority,
error::{metadata_error_into_bubblegum, BubblegumError},
asserts::{assert_collection_membership, assert_has_collection_authority},
error::BubblegumError,
state::{
leaf_schema::LeafSchema,
metaplex_adapter::{self, Creator, MetadataArgs},
Expand Down Expand Up @@ -192,14 +193,12 @@ fn process_collection_verification_mpl_only<'info>(

// If the NFT has collection data, we set it to the correct value after doing some validation.
if let Some(collection) = &mut message.collection {
// Collection verify assert from token-metadata program.
assert_collection_verify_is_valid(
assert_collection_membership(
&Some(collection.adapt()),
collection_metadata,
collection_mint,
collection_mint.key,
edition_account,
)
.map_err(metadata_error_into_bubblegum)?;
)?;

assert_has_collection_authority(
collection_metadata,
Expand Down Expand Up @@ -230,31 +229,16 @@ fn process_collection_verification_mpl_only<'info>(
}
};

// CPI into to token-metadata program to change the collection size.
let mut bubblegum_set_collection_size_infos = vec![
collection_metadata.to_account_info(),
collection_authority.clone(),
collection_mint.clone(),
bubblegum_signer.clone(),
];
// CPI into Token Metadata program to change the collection size.

if let Some(record) = collection_authority_record {
bubblegum_set_collection_size_infos.push(record.clone());
}

invoke_signed(
&mpl_token_metadata::instruction::bubblegum_set_collection_size(
token_metadata_program.key(),
collection_metadata.to_account_info().key(),
collection_authority.key(),
collection_mint.key(),
bubblegum_signer.key(),
collection_authority_record.map(|r| r.key()),
new_size,
),
bubblegum_set_collection_size_infos.as_slice(),
&[&[COLLECTION_CPI_PREFIX.as_bytes(), &[bubblegum_bump]]],
)?;
BubblegumSetCollectionSizeCpiBuilder::new(token_metadata_program)
.collection_metadata(&collection_metadata.to_account_info())
.collection_authority(collection_authority)
.collection_mint(collection_mint)
.bubblegum_signer(bubblegum_signer)
.collection_authority_record(collection_authority_record)
.set_collection_size_args(SetCollectionSizeArgs { size: new_size })
.invoke_signed(&[&[COLLECTION_CPI_PREFIX.as_bytes(), &[bubblegum_bump]]])?;
} else {
return Err(BubblegumError::CollectionMustBeSized.into());
}
Expand Down
18 changes: 9 additions & 9 deletions programs/bubblegum/program/src/state/metaplex_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub struct Creator {
}

impl Creator {
pub fn adapt(&self) -> mpl_token_metadata::state::Creator {
mpl_token_metadata::state::Creator {
pub fn adapt(&self) -> mpl_token_metadata::types::Creator {
mpl_token_metadata::types::Creator {
address: self.address,
verified: self.verified,
share: self.share,
Expand Down Expand Up @@ -50,12 +50,12 @@ pub struct Uses {
}

impl Uses {
pub fn adapt(&self) -> mpl_token_metadata::state::Uses {
mpl_token_metadata::state::Uses {
pub fn adapt(&self) -> mpl_token_metadata::types::Uses {
mpl_token_metadata::types::Uses {
use_method: match self.use_method {
UseMethod::Burn => mpl_token_metadata::state::UseMethod::Burn,
UseMethod::Multiple => mpl_token_metadata::state::UseMethod::Multiple,
UseMethod::Single => mpl_token_metadata::state::UseMethod::Single,
UseMethod::Burn => mpl_token_metadata::types::UseMethod::Burn,
UseMethod::Multiple => mpl_token_metadata::types::UseMethod::Multiple,
UseMethod::Single => mpl_token_metadata::types::UseMethod::Single,
},
remaining: self.remaining,
total: self.total,
Expand All @@ -71,8 +71,8 @@ pub struct Collection {
}

impl Collection {
pub fn adapt(&self) -> mpl_token_metadata::state::Collection {
mpl_token_metadata::state::Collection {
pub fn adapt(&self) -> mpl_token_metadata::types::Collection {
mpl_token_metadata::types::Collection {
verified: self.verified,
key: self.key,
}
Expand Down
Loading
Loading