From 6af24e1a1c20f318e8cecca90399d2ff3cc5e227 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Tue, 27 Jun 2023 12:11:18 -0400 Subject: [PATCH] Adding delegate for pNFT editions. --- .../src/processor/delegate/delegate.rs | 4 +- token-metadata/program/tests/delegate.rs | 77 ++++++++- .../program/tests/utils/edition_marker.rs | 146 +++++++++++++++++- 3 files changed, 220 insertions(+), 7 deletions(-) diff --git a/token-metadata/program/src/processor/delegate/delegate.rs b/token-metadata/program/src/processor/delegate/delegate.rs index 4c13f34284..45b612b760 100644 --- a/token-metadata/program/src/processor/delegate/delegate.rs +++ b/token-metadata/program/src/processor/delegate/delegate.rs @@ -283,7 +283,8 @@ fn create_persistent_delegate_v1( // programmables assets can have delegates from any role apart from `Standard` match metadata.token_standard { - Some(TokenStandard::ProgrammableNonFungible) => { + Some(TokenStandard::ProgrammableNonFungible) + | Some(TokenStandard::ProgrammableNonFungibleEdition) => { if matches!(role, TokenDelegateRole::Standard) { return Err(MetadataError::InvalidDelegateRole.into()); } @@ -462,6 +463,7 @@ fn create_persistent_delegate_v1( if matches!( metadata.token_standard, Some(TokenStandard::ProgrammableNonFungible) + | Some(TokenStandard::ProgrammableNonFungibleEdition) ) { if let Some(master_edition_info) = ctx.accounts.master_edition_info { freeze( diff --git a/token-metadata/program/tests/delegate.rs b/token-metadata/program/tests/delegate.rs index 8a0deef196..49f83480ad 100644 --- a/token-metadata/program/tests/delegate.rs +++ b/token-metadata/program/tests/delegate.rs @@ -17,7 +17,8 @@ mod delegate { instruction::{DelegateArgs, MetadataDelegateRole}, pda::{find_metadata_delegate_record_account, find_token_record_account}, state::{ - Key, Metadata, MetadataDelegateRecord, TokenDelegateRole, TokenRecord, TokenStandard, + Key, Metadata, MetadataDelegateRecord, PrintSupply, TokenDelegateRole, TokenRecord, + TokenStandard, }, }; use num_traits::FromPrimitive; @@ -93,6 +94,80 @@ mod delegate { } } + #[tokio::test] + async fn set_transfer_delegate_programmable_nonfungible_edition() { + let mut context = program_test().start_with_context().await; + + // asset + + let mut master_asset = DigitalAsset::default(); + master_asset + .create_and_mint_with_supply( + &mut context, + TokenStandard::ProgrammableNonFungible, + None, + None, + 1, + PrintSupply::Unlimited, + ) + .await + .unwrap(); + + assert!(master_asset.token.is_some()); + + let test_master_edition = MasterEditionV2::new_from_asset(&master_asset); + let mut test_edition_marker = + EditionMarker::new_from_asset(&master_asset, &test_master_edition, 1); + + test_edition_marker + .create_from_asset(&mut context) + .await + .unwrap(); + + // delegates the asset for transfer + + let user = Keypair::new(); + let user_pubkey = user.pubkey(); + let payer = Keypair::from_bytes(&context.payer.to_bytes()).unwrap(); + + test_edition_marker + .delegate_asset( + &mut context, + payer, + user_pubkey, + DelegateArgs::TransferV1 { + amount: 1, + authorization_data: None, + }, + ) + .await + .unwrap(); + + // asserts + + let (pda_key, _) = find_token_record_account( + &test_edition_marker.mint.pubkey(), + &test_edition_marker.token.pubkey(), + ); + + let pda = get_account(&mut context, &pda_key).await; + let token_record: TokenRecord = try_from_slice_unchecked(&pda.data).unwrap(); + + assert_eq!(token_record.key, Key::TokenRecord); + assert_eq!(token_record.delegate, Some(user_pubkey)); + assert_eq!( + token_record.delegate_role, + Some(TokenDelegateRole::Transfer) + ); + + let account = get_account(&mut context, &test_edition_marker.token.pubkey()).await; + let token_account = Account::unpack(&account.data).unwrap(); + + assert!(token_account.is_frozen()); + assert_eq!(token_account.delegate, COption::Some(user_pubkey)); + assert_eq!(token_account.delegated_amount, 1); + } + #[tokio::test] async fn set_collection_delegate_programmable_nonfungible() { let mut context = program_test().start_with_context().await; diff --git a/token-metadata/program/tests/utils/edition_marker.rs b/token-metadata/program/tests/utils/edition_marker.rs index a816155e23..7950561988 100644 --- a/token-metadata/program/tests/utils/edition_marker.rs +++ b/token-metadata/program/tests/utils/edition_marker.rs @@ -2,12 +2,12 @@ use borsh::BorshSerialize; use mpl_token_metadata::{ instruction::{ self, - builders::{BurnBuilder, PrintBuilder, TransferBuilder}, - BurnArgs, InstructionBuilder, MetadataInstruction, + builders::{BurnBuilder, DelegateBuilder, PrintBuilder, TransferBuilder}, + BurnArgs, DelegateArgs, InstructionBuilder, MetadataDelegateRole, MetadataInstruction, MintNewEditionFromMasterEditionViaTokenArgs, PrintArgs, TransferArgs, }, - pda::{find_token_record_account, MARKER}, - state::{TokenMetadataAccount, EDITION, EDITION_MARKER_BIT_SIZE, PREFIX}, + pda::{find_metadata_delegate_record_account, find_token_record_account, MARKER}, + state::{ProgrammableConfig, TokenMetadataAccount, EDITION, EDITION_MARKER_BIT_SIZE, PREFIX}, ID, }; use solana_program::{ @@ -17,7 +17,8 @@ use solana_program::{ }; use solana_program_test::BanksClientError; use solana_sdk::{ - pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, transaction::Transaction, + compute_budget::ComputeBudgetInstruction, pubkey::Pubkey, signature::Signer, + signer::keypair::Keypair, transaction::Transaction, }; use spl_associated_token_account::{ get_associated_token_address, instruction::create_associated_token_account, @@ -640,4 +641,139 @@ impl EditionMarker { md_account.is_some() && token_account.is_some() && print_edition_account.is_some() } + + pub async fn delegate_asset( + &mut self, + context: &mut ProgramTestContext, + payer: Keypair, + delegate: Pubkey, + args: DelegateArgs, + ) -> Result, BanksClientError> { + let mut builder = DelegateBuilder::new(); + builder + .delegate(delegate) + .mint(self.mint.pubkey()) + .metadata(self.new_metadata_pubkey) + .payer(payer.pubkey()) + .authority(payer.pubkey()) + .spl_token_program(spl_token::ID) + .master_edition(self.new_edition_pubkey) + .token(self.token.pubkey()); + + let mut delegate_or_token_record = None; + + match args { + // Token delegates. + DelegateArgs::SaleV1 { .. } + | DelegateArgs::TransferV1 { .. } + | DelegateArgs::UtilityV1 { .. } + | DelegateArgs::StakingV1 { .. } + | DelegateArgs::LockedTransferV1 { .. } => { + let (token_record, _) = + find_token_record_account(&self.mint.pubkey(), &self.token.pubkey()); + builder.token_record(token_record); + delegate_or_token_record = Some(token_record); + } + DelegateArgs::StandardV1 { .. } => { /* nothing to add */ } + + // Metadata delegates. + DelegateArgs::CollectionV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::Collection, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + DelegateArgs::DataV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::Data, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + DelegateArgs::ProgrammableConfigV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::ProgrammableConfig, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + DelegateArgs::AuthorityItemV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::AuthorityItem, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + DelegateArgs::DataItemV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::DataItem, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + DelegateArgs::CollectionItemV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::CollectionItem, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + DelegateArgs::ProgrammableConfigItemV1 { .. } => { + let (delegate_record, _) = find_metadata_delegate_record_account( + &self.mint.pubkey(), + MetadataDelegateRole::ProgrammableConfigItem, + &payer.pubkey(), + &delegate, + ); + builder.delegate_record(delegate_record); + delegate_or_token_record = Some(delegate_record); + } + } + + // determines if we need to set the rule set + let metadata_account = get_account(context, &self.metadata_pubkey).await; + let metadata: mpl_token_metadata::state::Metadata = + try_from_slice_unchecked(&metadata_account.data).unwrap(); + + if let Some(ProgrammableConfig::V1 { + rule_set: Some(rule_set), + }) = metadata.programmable_config + { + builder.authorization_rules(rule_set); + builder.authorization_rules_program(mpl_token_auth_rules::ID); + } + + let compute_ix = ComputeBudgetInstruction::set_compute_unit_limit(400_000); + + let delegate_ix = builder.build(args.clone()).unwrap().instruction(); + + let tx = Transaction::new_signed_with_payer( + &[compute_ix, delegate_ix], + Some(&payer.pubkey()), + &[&payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await?; + Ok(delegate_or_token_record) + } }