From cb091c369571541f2918175e8fe813b71007bb50 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Tue, 25 Jul 2023 10:06:32 -0500 Subject: [PATCH 1/4] feat: Add ability to add creators to cNFTs --- programs/bubblegum/program/src/lib.rs | 100 +++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 07fd3188..0d132aed 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -165,6 +165,28 @@ pub struct CreatorVerification<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct AddCreator<'info> { + #[account( + seeds = [merkle_tree.key().as_ref()], + bump, + has_one = tree_delegate + )] + pub tree_authority: Account<'info, TreeConfig>, + pub tree_delegate: Signer<'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>, + #[account(mut)] + /// CHECK: This account is modified in the downstream program + pub merkle_tree: UncheckedAccount<'info>, + pub payer: Signer<'info>, + pub log_wrapper: Program<'info, Noop>, + pub compression_program: Program<'info, SplAccountCompression>, + pub system_program: Program<'info, System>, +} + #[derive(Accounts)] pub struct CollectionVerification<'info> { #[account( @@ -468,6 +490,7 @@ pub enum InstructionName { UnverifyCollection, SetAndVerifyCollection, MintToCollectionV1, + AddCreator } pub fn get_instruction_type(full_bytes: &[u8]) -> InstructionName { @@ -1012,7 +1035,7 @@ pub mod bubblegum { // Create a HashSet to store signers to use with creator validation. Any signer can be // counted as a validated creator. - let mut metadata_auth = HashSet::::new(); + let mut metadata_auth = HashSet::::new(); metadata_auth.insert(payer); metadata_auth.insert(tree_delegate); @@ -1078,6 +1101,81 @@ pub mod bubblegum { ) } + pub fn add_creator<'info>( + ctx: Context<'_, '_, '_, 'info, AddCreator<'info>>, + root: [u8; 32], + metadata: MetadataArgs, + new_creator: Creator, + nonce: u64, + index: u32, + ) -> Result<()> { + let creator_opt = ctx.remaining_accounts.get(0); + let owner = ctx.accounts.leaf_owner.to_account_info(); + let delegate = ctx.accounts.leaf_delegate.to_account_info(); + let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); + let mut new_metadata = metadata.clone(); + + let old_creators = new_metadata.creators; + + // Calculate new creator Vec with `verified` set to true for signing creator. + let mut updated_creator_vec = old_creators.clone(); + let verified = match creator_opt { + Some(creator) if creator.key() == new_creator.address && creator.is_signer => + true, + _ => false, + }; + updated_creator_vec.push( + Creator { + address: new_creator.address, + verified, + share: new_creator.share, + } + ); + + new_metadata.creators = updated_creator_vec; + + // Calculate new creator hash. + let updated_creator_hash = hash_creators(&new_metadata.creators)?; + + // Calculate new data hash. + let updated_data_hash = hash_metadata(&new_metadata)?; + + // Build previous leaf struct, new leaf struct, and replace the leaf in the tree. + let asset_id = get_asset_id(&merkle_tree.key(), nonce); + let previous_leaf = LeafSchema::new_v0( + asset_id, + owner.key(), + delegate.key(), + nonce, + hash_metadata(&metadata)?, + hash_creators(&old_creators)?, + ); + let new_leaf = LeafSchema::new_v0( + asset_id, + owner.key(), + delegate.key(), + nonce, + updated_data_hash, + updated_creator_hash, + ); + + wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; + + replace_leaf( + &merkle_tree.key(), + *ctx.bumps.get("tree_authority").unwrap(), + &ctx.accounts.compression_program.to_account_info(), + &ctx.accounts.tree_authority.to_account_info(), + &ctx.accounts.merkle_tree.to_account_info(), + &ctx.accounts.log_wrapper.to_account_info(), + ctx.remaining_accounts, + root, + previous_leaf.to_node(), + new_leaf.to_node(), + index, + ) + } + pub fn unverify_creator<'info>( ctx: Context<'_, '_, '_, 'info, CreatorVerification<'info>>, root: [u8; 32], From ea9705ac7b4b47da9dfa0b7f7a02db898865f9e7 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Tue, 25 Jul 2023 10:08:07 -0500 Subject: [PATCH 2/4] fix weird space --- programs/bubblegum/program/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 0d132aed..4198609f 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -1035,7 +1035,7 @@ pub mod bubblegum { // Create a HashSet to store signers to use with creator validation. Any signer can be // counted as a validated creator. - let mut metadata_auth = HashSet::::new(); + let mut metadata_auth = HashSet::::new(); metadata_auth.insert(payer); metadata_auth.insert(tree_delegate); From 32e762385e33e04e33f258e272e5e755d8fab377 Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Tue, 25 Jul 2023 10:15:55 -0500 Subject: [PATCH 3/4] Fix --- programs/bubblegum/program/src/error.rs | 2 ++ programs/bubblegum/program/src/lib.rs | 19 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/programs/bubblegum/program/src/error.rs b/programs/bubblegum/program/src/error.rs index 0ec6da49..ff982073 100644 --- a/programs/bubblegum/program/src/error.rs +++ b/programs/bubblegum/program/src/error.rs @@ -70,6 +70,8 @@ pub enum BubblegumError { CollectionMustBeAUniqueMasterEdition, #[msg("Could not convert external error to BubblegumError")] UnknownExternalError, + #[msg("Metadata must be mutable to make this change")] + MetadataMustBeMutable, } // Converts certain Token Metadata errors into Bubblegum equivalents diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 4198609f..a5a1c18a 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -174,6 +174,8 @@ pub struct AddCreator<'info> { )] pub tree_authority: Account<'info, TreeConfig>, pub tree_delegate: Signer<'info>, + /// CHECK: The new creator being added + pub creator: AccountInfo<'info>, /// CHECK: This account is checked in the instruction pub leaf_owner: UncheckedAccount<'info>, /// CHECK: This account is checked in the instruction @@ -1105,11 +1107,11 @@ pub mod bubblegum { ctx: Context<'_, '_, '_, 'info, AddCreator<'info>>, root: [u8; 32], metadata: MetadataArgs, - new_creator: Creator, nonce: u64, index: u32, ) -> Result<()> { - let creator_opt = ctx.remaining_accounts.get(0); + require!(metadata.is_mutable, BubblegumError::MetadataMustBeMutable); + let owner = ctx.accounts.leaf_owner.to_account_info(); let delegate = ctx.accounts.leaf_delegate.to_account_info(); let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); @@ -1119,16 +1121,13 @@ pub mod bubblegum { // Calculate new creator Vec with `verified` set to true for signing creator. let mut updated_creator_vec = old_creators.clone(); - let verified = match creator_opt { - Some(creator) if creator.key() == new_creator.address && creator.is_signer => - true, - _ => false, - }; updated_creator_vec.push( Creator { - address: new_creator.address, - verified, - share: new_creator.share, + address: ctx.accounts.creator.key(), + verified: ctx.accounts.creator.is_signer, + // TODO: Need to add a rebalance shares endpoint to allow setting these shares, + // and ensuring they all balance to 100 + share: 0, } ); From 2c4c245e6c27a0801b3aa91ef04896a44835408d Mon Sep 17 00:00:00 2001 From: Chewing Glass Date: Tue, 25 Jul 2023 18:11:57 -0500 Subject: [PATCH 4/4] Format --- programs/bubblegum/program/src/lib.rs | 110 +++++++++++++------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index a5a1c18a..504accf1 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -492,7 +492,7 @@ pub enum InstructionName { UnverifyCollection, SetAndVerifyCollection, MintToCollectionV1, - AddCreator + AddCreator, } pub fn get_instruction_type(full_bytes: &[u8]) -> InstructionName { @@ -1110,69 +1110,67 @@ pub mod bubblegum { nonce: u64, index: u32, ) -> Result<()> { - require!(metadata.is_mutable, BubblegumError::MetadataMustBeMutable); + require!(metadata.is_mutable, BubblegumError::MetadataMustBeMutable); - let owner = ctx.accounts.leaf_owner.to_account_info(); - let delegate = ctx.accounts.leaf_delegate.to_account_info(); - let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); - let mut new_metadata = metadata.clone(); + let owner = ctx.accounts.leaf_owner.to_account_info(); + let delegate = ctx.accounts.leaf_delegate.to_account_info(); + let merkle_tree = ctx.accounts.merkle_tree.to_account_info(); + let mut new_metadata = metadata.clone(); - let old_creators = new_metadata.creators; + let old_creators = new_metadata.creators; - // Calculate new creator Vec with `verified` set to true for signing creator. - let mut updated_creator_vec = old_creators.clone(); - updated_creator_vec.push( - Creator { + // Calculate new creator Vec with `verified` set to true for signing creator. + let mut updated_creator_vec = old_creators.clone(); + updated_creator_vec.push(Creator { address: ctx.accounts.creator.key(), verified: ctx.accounts.creator.is_signer, // TODO: Need to add a rebalance shares endpoint to allow setting these shares, // and ensuring they all balance to 100 share: 0, - } - ); - - new_metadata.creators = updated_creator_vec; - - // Calculate new creator hash. - let updated_creator_hash = hash_creators(&new_metadata.creators)?; - - // Calculate new data hash. - let updated_data_hash = hash_metadata(&new_metadata)?; - - // Build previous leaf struct, new leaf struct, and replace the leaf in the tree. - let asset_id = get_asset_id(&merkle_tree.key(), nonce); - let previous_leaf = LeafSchema::new_v0( - asset_id, - owner.key(), - delegate.key(), - nonce, - hash_metadata(&metadata)?, - hash_creators(&old_creators)?, - ); - let new_leaf = LeafSchema::new_v0( - asset_id, - owner.key(), - delegate.key(), - nonce, - updated_data_hash, - updated_creator_hash, - ); - - wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; - - replace_leaf( - &merkle_tree.key(), - *ctx.bumps.get("tree_authority").unwrap(), - &ctx.accounts.compression_program.to_account_info(), - &ctx.accounts.tree_authority.to_account_info(), - &ctx.accounts.merkle_tree.to_account_info(), - &ctx.accounts.log_wrapper.to_account_info(), - ctx.remaining_accounts, - root, - previous_leaf.to_node(), - new_leaf.to_node(), - index, - ) + }); + + new_metadata.creators = updated_creator_vec; + + // Calculate new creator hash. + let updated_creator_hash = hash_creators(&new_metadata.creators)?; + + // Calculate new data hash. + let updated_data_hash = hash_metadata(&new_metadata)?; + + // Build previous leaf struct, new leaf struct, and replace the leaf in the tree. + let asset_id = get_asset_id(&merkle_tree.key(), nonce); + let previous_leaf = LeafSchema::new_v0( + asset_id, + owner.key(), + delegate.key(), + nonce, + hash_metadata(&metadata)?, + hash_creators(&old_creators)?, + ); + let new_leaf = LeafSchema::new_v0( + asset_id, + owner.key(), + delegate.key(), + nonce, + updated_data_hash, + updated_creator_hash, + ); + + wrap_application_data_v1(new_leaf.to_event().try_to_vec()?, &ctx.accounts.log_wrapper)?; + + replace_leaf( + &merkle_tree.key(), + *ctx.bumps.get("tree_authority").unwrap(), + &ctx.accounts.compression_program.to_account_info(), + &ctx.accounts.tree_authority.to_account_info(), + &ctx.accounts.merkle_tree.to_account_info(), + &ctx.accounts.log_wrapper.to_account_info(), + ctx.remaining_accounts, + root, + previous_leaf.to_node(), + new_leaf.to_node(), + index, + ) } pub fn unverify_creator<'info>(