From e86de64101fc386dd4cc97b6f107da3de258833a Mon Sep 17 00:00:00 2001 From: blockiosaurus <90809591+blockiosaurus@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:07:55 -0400 Subject: [PATCH] =?UTF-8?q?First=20pass=20for=20reducing=20compute=20using?= =?UTF-8?q?=20create=5Fprogram=5Faddress=20when=20pos=E2=80=A6=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First pass for reducing compute using create_program_address when possible. * Finishing PR with published mpl-utils. * Minor cleanup. --- programs/token-metadata/Cargo.lock | 6 +- programs/token-metadata/program/Cargo.toml | 14 ++- .../program/src/assertions/collection.rs | 38 +++++--- .../program/src/assertions/misc.rs | 13 +++ .../program/src/processor/burn/burn.rs | 2 + .../program/src/processor/burn/nonfungible.rs | 6 +- .../src/processor/delegate/delegate.rs | 2 + .../program/src/processor/delegate/revoke.rs | 5 +- .../program/src/processor/metadata/mint.rs | 17 ++-- .../program/src/processor/metadata/print.rs | 1 + .../src/processor/metadata/transfer.rs | 9 +- .../program/src/processor/state/mod.rs | 2 + .../program/src/state/master_edition.rs | 20 ----- .../program/src/utils/master_edition.rs | 2 +- .../program/src/utils/programmable_asset.rs | 87 +++++++++++++++---- 15 files changed, 159 insertions(+), 65 deletions(-) diff --git a/programs/token-metadata/Cargo.lock b/programs/token-metadata/Cargo.lock index be2e004b..0842b8bc 100644 --- a/programs/token-metadata/Cargo.lock +++ b/programs/token-metadata/Cargo.lock @@ -2183,9 +2183,9 @@ dependencies = [ [[package]] name = "mpl-utils" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfbd03696c53e72ae822e9ee8cae3e156031e30e9b4d5b3b33ae3990e79116f3" +checksum = "a82f6f747cb3014adcd33689fbcf09a38564b78915d8893553e7c6eccfaf4aa8" dependencies = [ "arrayref", "solana-program", @@ -5185,7 +5185,7 @@ dependencies = [ "solana-program", "solana-program-test", "solana-sdk", - "spl-associated-token-account 1.1.3", + "spl-associated-token-account 2.1.0", "spl-token 3.5.0", "spl-token-2022 0.8.0", "test-case", diff --git a/programs/token-metadata/program/Cargo.toml b/programs/token-metadata/program/Cargo.toml index a7103b2c..555c68e2 100644 --- a/programs/token-metadata/program/Cargo.toml +++ b/programs/token-metadata/program/Cargo.toml @@ -16,9 +16,11 @@ serde-feature = ["serde", "serde_with"] [dependencies] arrayref = "0.3.6" borsh = "0.9.3" -mpl-token-auth-rules = { version = "=1.4.3-beta.1", features = ["no-entrypoint"] } +mpl-token-auth-rules = { version = "=1.4.3-beta.1", features = [ + "no-entrypoint", +] } mpl-token-metadata-context-derive = { version = "0.3.0", path = "../macro" } -mpl-utils = { version = "0.3.2", features = ["spl-token"] } +mpl-utils = { version = "0.3.4", features = ["spl-token"] } num-derive = "0.3" num-traits = "0.2" serde = { version = "1.0.149", optional = true } @@ -26,13 +28,17 @@ serde_with = { version = "1.14.0", optional = true } shank = { version = "0.3.0" } solana-program = ">= 1.14.13, < 1.17" spl-token-2022 = "0.8.0" -spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = [ + "no-entrypoint", +] } thiserror = "1.0" [dev-dependencies] async-trait = "0.1.64" rmp-serde = "1.1.1" -rooster = { git = "https://github.com/metaplex-foundation/rooster", features = ["no-entrypoint"] } +rooster = { git = "https://github.com/metaplex-foundation/rooster", features = [ + "no-entrypoint", +] } serde = { version = "1.0.147", features = ["derive"] } solana-program-test = ">= 1.14.13, < 1.17" solana-sdk = ">= 1.14.13, < 1.17" diff --git a/programs/token-metadata/program/src/assertions/collection.rs b/programs/token-metadata/program/src/assertions/collection.rs index 2793ed73..222d5d51 100644 --- a/programs/token-metadata/program/src/assertions/collection.rs +++ b/programs/token-metadata/program/src/assertions/collection.rs @@ -10,6 +10,8 @@ use crate::{ }, }; +use super::assert_derivation_with_bump; + /// Checks whether the collection update is allowed or not based on the `verified` status. pub fn assert_collection_update_is_valid( allow_direct_collection_verified_writes: bool, @@ -114,16 +116,32 @@ pub fn assert_collection_verify_is_valid( } } - assert_derivation( - &crate::ID, - edition_account_info, - &[ - PREFIX.as_bytes(), - crate::ID.as_ref(), - collection_metadata.mint.as_ref(), - EDITION.as_bytes(), - ], - ) + match collection_metadata.edition_nonce { + Some(nonce) => assert_derivation_with_bump( + &crate::ID, + edition_account_info, + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + collection_metadata.mint.as_ref(), + EDITION.as_bytes(), + &[nonce], + ], + ), + None => { + let _ = assert_derivation( + &crate::ID, + edition_account_info, + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + collection_metadata.mint.as_ref(), + EDITION.as_bytes(), + ], + )?; + Ok(()) + } + } .map_err(|_| MetadataError::CollectionMasterEditionAccountInvalid)?; assert_master_edition(collection_metadata, edition_account_info)?; diff --git a/programs/token-metadata/program/src/assertions/misc.rs b/programs/token-metadata/program/src/assertions/misc.rs index c7e966ef..5d07d886 100644 --- a/programs/token-metadata/program/src/assertions/misc.rs +++ b/programs/token-metadata/program/src/assertions/misc.rs @@ -118,6 +118,19 @@ pub fn assert_derivation( mpl_utils::assert_derivation(program_id, account, path, MetadataError::DerivedKeyInvalid) } +pub fn assert_derivation_with_bump( + program_id: &Pubkey, + account: &AccountInfo, + path: &[&[u8]], +) -> Result<(), ProgramError> { + mpl_utils::assert_derivation_with_bump( + program_id, + account, + path, + MetadataError::DerivedKeyInvalid, + ) +} + pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult { mpl_utils::assert_owned_by(account, owner, MetadataError::IncorrectOwner) } diff --git a/programs/token-metadata/program/src/processor/burn/burn.rs b/programs/token-metadata/program/src/processor/burn/burn.rs index b7c7378d..665ab631 100644 --- a/programs/token-metadata/program/src/processor/burn/burn.rs +++ b/programs/token-metadata/program/src/processor/burn/burn.rs @@ -211,6 +211,7 @@ fn burn_v1(program_id: &Pubkey, ctx: Context, args: BurnArgs) -> ProgramRe ctx.accounts.token_info.clone(), edition_info.clone(), ctx.accounts.spl_token_program_info.clone(), + metadata.edition_nonce, )?; let mut args = BurnNonFungibleArgs { @@ -269,6 +270,7 @@ fn burn_v1(program_id: &Pubkey, ctx: Context, args: BurnArgs) -> ProgramRe ctx.accounts.token_info.clone(), edition_info.clone(), ctx.accounts.spl_token_program_info.clone(), + metadata.edition_nonce, )?; let mut is_close_auth = false; diff --git a/programs/token-metadata/program/src/processor/burn/nonfungible.rs b/programs/token-metadata/program/src/processor/burn/nonfungible.rs index 4c0b7a6d..a4e77f5a 100644 --- a/programs/token-metadata/program/src/processor/burn/nonfungible.rs +++ b/programs/token-metadata/program/src/processor/burn/nonfungible.rs @@ -48,7 +48,11 @@ pub(crate) fn burn_nonfungible(ctx: &Context, args: BurnNonFungibleArgs) - ctx.accounts.mint_info.key.as_ref(), EDITION.as_bytes(), ]); - let bump = assert_derivation(&crate::ID, edition_info, &edition_info_path)?; + + let bump = match args.metadata.edition_nonce { + Some(bump) => Ok(bump), + None => assert_derivation(&crate::ID, edition_info, &edition_info_path), + }?; let edition_seeds = &[ PREFIX.as_bytes(), diff --git a/programs/token-metadata/program/src/processor/delegate/delegate.rs b/programs/token-metadata/program/src/processor/delegate/delegate.rs index 1e3891f7..0f9989a8 100644 --- a/programs/token-metadata/program/src/processor/delegate/delegate.rs +++ b/programs/token-metadata/program/src/processor/delegate/delegate.rs @@ -437,6 +437,7 @@ fn create_persistent_delegate_v1( token_info.clone(), master_edition_info.clone(), spl_token_program_info.clone(), + metadata.edition_nonce, )?; } else { return Err(MetadataError::MissingEditionAccount.into()); @@ -511,6 +512,7 @@ fn create_persistent_delegate_v1( token_info.clone(), master_edition_info.clone(), spl_token_program_info.clone(), + metadata.edition_nonce, )?; } else { // sanity check: this should not happen at this point since the master diff --git a/programs/token-metadata/program/src/processor/delegate/revoke.rs b/programs/token-metadata/program/src/processor/delegate/revoke.rs index bf29f38e..c2fa5973 100644 --- a/programs/token-metadata/program/src/processor/delegate/revoke.rs +++ b/programs/token-metadata/program/src/processor/delegate/revoke.rs @@ -334,6 +334,7 @@ fn revoke_persistent_delegate_v1( token_info.clone(), master_edition_info.clone(), spl_token_program_info.clone(), + metadata.edition_nonce, )?; // Clear the close authority if it's a Utility Delegate. @@ -341,10 +342,11 @@ fn revoke_persistent_delegate_v1( clear_close_authority(ClearCloseAuthorityParams { token_info, mint_info: ctx.accounts.mint_info, - token, + token: &token, master_edition_info, authority_info: master_edition_info, spl_token_program_info, + edition_bump: metadata.edition_nonce, })?; } } else { @@ -380,6 +382,7 @@ fn revoke_persistent_delegate_v1( token_info.clone(), master_edition_info.clone(), spl_token_program_info.clone(), + metadata.edition_nonce, )?; } else { // sanity check: this should not happen at this point since the master diff --git a/programs/token-metadata/program/src/processor/metadata/mint.rs b/programs/token-metadata/program/src/processor/metadata/mint.rs index 7186f15d..4e0c6358 100644 --- a/programs/token-metadata/program/src/processor/metadata/mint.rs +++ b/programs/token-metadata/program/src/processor/metadata/mint.rs @@ -1,4 +1,4 @@ -use mpl_utils::{assert_signer, cmp_pubkeys}; +use mpl_utils::{assert_derivation_with_bump, assert_signer, cmp_pubkeys}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, @@ -212,17 +212,18 @@ pub fn mint_v1(program_id: &Pubkey, ctx: Context, args: MintArgs) -> Progr .edition_nonce .ok_or(MetadataError::NotAMasterEdition)?], ]; - let master_edition_key = - Pubkey::create_program_address(master_edition_seeds, program_id)?; let master_edition_info = ctx .accounts .master_edition_info .ok_or(MetadataError::MissingMasterEditionAccount)?; - if !cmp_pubkeys(master_edition_info.key, &master_edition_key) { - return Err(MetadataError::InvalidMasterEdition.into()); - } + assert_derivation_with_bump( + &crate::ID, + master_edition_info, + master_edition_seeds, + MetadataError::InvalidMasterEdition, + )?; // thaw the token account for programmable assets; the account // is not frozen if we just initialized it @@ -236,6 +237,7 @@ pub fn mint_v1(program_id: &Pubkey, ctx: Context, args: MintArgs) -> Progr ctx.accounts.token_info.clone(), master_edition_info.clone(), ctx.accounts.spl_token_program_info.clone(), + metadata.edition_nonce, )?; } @@ -244,7 +246,7 @@ pub fn mint_v1(program_id: &Pubkey, ctx: Context, args: MintArgs) -> Progr ctx.accounts.spl_token_program_info.key, ctx.accounts.mint_info.key, ctx.accounts.token_info.key, - &master_edition_key, + master_edition_info.key, &[], amount, )?, @@ -266,6 +268,7 @@ pub fn mint_v1(program_id: &Pubkey, ctx: Context, args: MintArgs) -> Progr ctx.accounts.token_info.clone(), master_edition_info.clone(), ctx.accounts.spl_token_program_info.clone(), + metadata.edition_nonce, )?; } } diff --git a/programs/token-metadata/program/src/processor/metadata/print.rs b/programs/token-metadata/program/src/processor/metadata/print.rs index fa307e17..29cd0e86 100644 --- a/programs/token-metadata/program/src/processor/metadata/print.rs +++ b/programs/token-metadata/program/src/processor/metadata/print.rs @@ -295,6 +295,7 @@ fn print_logic<'a>( edition_token_account_info.clone(), edition_account_info.clone(), token_program.clone(), + None, )?; // for pNFTs, we store the token standard value at the end of the diff --git a/programs/token-metadata/program/src/processor/metadata/transfer.rs b/programs/token-metadata/program/src/processor/metadata/transfer.rs index 3f5d2163..d4700fe5 100644 --- a/programs/token-metadata/program/src/processor/metadata/transfer.rs +++ b/programs/token-metadata/program/src/processor/metadata/transfer.rs @@ -398,7 +398,11 @@ fn transfer_v1(program_id: &Pubkey, ctx: Context, args: TransferArgs) }; auth_rules_validate(auth_rules_validate_params)?; - frozen_transfer(token_transfer_params, ctx.accounts.edition_info)?; + frozen_transfer( + token_transfer_params, + metadata.edition_nonce, + ctx.accounts.edition_info, + )?; let master_edition_info = ctx .accounts @@ -408,10 +412,11 @@ fn transfer_v1(program_id: &Pubkey, ctx: Context, args: TransferArgs) clear_close_authority(ClearCloseAuthorityParams { token_info: ctx.accounts.token_info, mint_info: ctx.accounts.mint_info, - token, + token: &token, master_edition_info, authority_info: master_edition_info, spl_token_program_info: ctx.accounts.spl_token_program_info, + edition_bump: metadata.edition_nonce, })?; // If the token record account for the destination owner doesn't exist, diff --git a/programs/token-metadata/program/src/processor/state/mod.rs b/programs/token-metadata/program/src/processor/state/mod.rs index 08c477db..a34a488b 100644 --- a/programs/token-metadata/program/src/processor/state/mod.rs +++ b/programs/token-metadata/program/src/processor/state/mod.rs @@ -186,6 +186,7 @@ pub(crate) fn toggle_asset_state( accounts.token_info.clone(), edition_info.clone(), spl_token_program_info.clone(), + metadata.edition_nonce, ) } TokenState::Unlocked => { @@ -196,6 +197,7 @@ pub(crate) fn toggle_asset_state( accounts.token_info.clone(), edition_info.clone(), spl_token_program_info.clone(), + metadata.edition_nonce, ) } TokenState::Listed => Err(MetadataError::IncorrectTokenState.into()), diff --git a/programs/token-metadata/program/src/state/master_edition.rs b/programs/token-metadata/program/src/state/master_edition.rs index 83dfdaf0..71c1afe2 100644 --- a/programs/token-metadata/program/src/state/master_edition.rs +++ b/programs/token-metadata/program/src/state/master_edition.rs @@ -41,26 +41,6 @@ pub fn get_master_edition(account: &AccountInfo) -> Result {{ - let path = vec![ - "metadata".as_bytes(), - $crate::ID.as_ref(), - $mint.as_ref(), - "edition".as_bytes(), - ]; - let (_, bump) = Pubkey::find_program_address(&path, &$crate::ID); - &[ - "metadata".as_bytes(), - $crate::ID.as_ref(), - $mint.as_ref(), - "edition".as_bytes(), - &[bump], - ] - }}; -} - #[repr(C)] #[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))] #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, ShankAccount)] diff --git a/programs/token-metadata/program/src/utils/master_edition.rs b/programs/token-metadata/program/src/utils/master_edition.rs index 169d5db9..da73b9d9 100644 --- a/programs/token-metadata/program/src/utils/master_edition.rs +++ b/programs/token-metadata/program/src/utils/master_edition.rs @@ -267,7 +267,7 @@ pub fn extract_edition_number_from_deprecated_reservation_list( let mut offset: Option = None; let mut reservations = reservation_list.reservations(); for i in 0..reservations.len() { - let mut reservation = &mut reservations[i]; + let reservation = &mut reservations[i]; if reservation.address == *mint_authority_info.key { offset = Some( diff --git a/programs/token-metadata/program/src/utils/programmable_asset.rs b/programs/token-metadata/program/src/utils/programmable_asset.rs index 3d680944..04d901d8 100644 --- a/programs/token-metadata/program/src/utils/programmable_asset.rs +++ b/programs/token-metadata/program/src/utils/programmable_asset.rs @@ -13,8 +13,9 @@ use spl_token_2022::{ }; use crate::{ - assertions::{assert_derivation, programmable::assert_valid_authorization}, - edition_seeds, + assertions::{ + assert_derivation, assert_derivation_with_bump, programmable::assert_valid_authorization, + }, error::MetadataError, pda::{EDITION, PREFIX}, processor::{AuthorizationData, TransferScenario}, @@ -75,6 +76,7 @@ pub fn freeze<'a>( token: AccountInfo<'a>, edition: AccountInfo<'a>, spl_token_program: AccountInfo<'a>, + edition_bump: Option, ) -> ProgramResult { let edition_info_path = Vec::from([ PREFIX.as_bytes(), @@ -82,10 +84,26 @@ pub fn freeze<'a>( mint.key.as_ref(), EDITION.as_bytes(), ]); - let edition_info_path_bump_seed = - &[assert_derivation(&crate::ID, &edition, &edition_info_path)?]; - let mut edition_info_seeds = edition_info_path.clone(); - edition_info_seeds.push(edition_info_path_bump_seed); + let bump = match edition_bump { + Some(bump) => { + assert_derivation_with_bump( + &crate::ID, + &edition, + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + mint.key.as_ref(), + EDITION.as_bytes(), + &[bump], + ], + )?; + Ok(bump) + } + None => assert_derivation(&crate::ID, &edition, &edition_info_path), + }?; + let mut edition_info_seeds = edition_info_path; + let binding = [bump]; + edition_info_seeds.push(&binding); invoke_signed( &freeze_account(spl_token_program.key, token.key, mint.key, edition.key, &[]).unwrap(), @@ -100,6 +118,7 @@ pub fn thaw<'a>( token_info: AccountInfo<'a>, edition_info: AccountInfo<'a>, spl_token_program: AccountInfo<'a>, + edition_bump: Option, ) -> ProgramResult { let edition_info_path = Vec::from([ PREFIX.as_bytes(), @@ -107,13 +126,25 @@ pub fn thaw<'a>( mint_info.key.as_ref(), EDITION.as_bytes(), ]); - let edition_info_path_bump_seed = &[assert_derivation( - &crate::ID, - &edition_info, - &edition_info_path, - )?]; - let mut edition_info_seeds = edition_info_path.clone(); - edition_info_seeds.push(edition_info_path_bump_seed); + let bump = match edition_bump { + Some(bump) => { + assert_derivation_with_bump( + &crate::ID, + &edition_info, + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + mint_info.key.as_ref(), + EDITION.as_bytes(), + &[bump], + ], + )?; + Ok(bump) + } + None => assert_derivation(&crate::ID, &edition_info, &edition_info_path), + }?; + let binding = [bump]; + let edition_info_seeds = [edition_info_path, vec![&binding]].concat(); invoke_signed( &thaw_account( @@ -301,6 +332,7 @@ pub fn auth_rules_validate(params: AuthRulesValidateParams) -> ProgramResult { pub fn frozen_transfer<'a>( params: TokenTransferCheckedParams<'a, '_>, + edition_bump: Option, edition_opt_info: Option<&'a AccountInfo<'a>>, ) -> ProgramResult { if edition_opt_info.is_none() { @@ -313,6 +345,7 @@ pub fn frozen_transfer<'a>( params.source.clone(), master_edition_info.clone(), params.token_program.clone(), + edition_bump, )?; let mint_info = params.mint.clone(); @@ -326,18 +359,20 @@ pub fn frozen_transfer<'a>( dest_info.clone(), master_edition_info.clone(), token_program_info.clone(), + edition_bump, )?; Ok(()) } -pub(crate) struct ClearCloseAuthorityParams<'a> { - pub token: Account, +pub(crate) struct ClearCloseAuthorityParams<'a, 'b> { + pub token: &'b Account, pub mint_info: &'a AccountInfo<'a>, pub token_info: &'a AccountInfo<'a>, pub master_edition_info: &'a AccountInfo<'a>, pub authority_info: &'a AccountInfo<'a>, pub spl_token_program_info: &'a AccountInfo<'a>, + pub edition_bump: Option, } pub(crate) fn clear_close_authority(params: ClearCloseAuthorityParams) -> ProgramResult { @@ -348,6 +383,7 @@ pub(crate) fn clear_close_authority(params: ClearCloseAuthorityParams) -> Progra master_edition_info, authority_info, spl_token_program_info, + edition_bump, } = params; // If there's an existing close authority that is not the metadata account, @@ -356,7 +392,26 @@ pub(crate) fn clear_close_authority(params: ClearCloseAuthorityParams) -> Progra if &close_authority != master_edition_info.key { return Err(MetadataError::InvalidCloseAuthority.into()); } - let seeds = edition_seeds!(mint_info.key); + + let bump = edition_bump.unwrap_or( + Pubkey::find_program_address( + &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + mint_info.key.as_ref(), + EDITION.as_bytes(), + ], + &crate::ID, + ) + .1, + ); + let seeds = &[ + PREFIX.as_bytes(), + crate::ID.as_ref(), + mint_info.key.as_ref(), + EDITION.as_bytes(), + &[bump], + ]; invoke_signed( &spl_token_2022::instruction::set_authority(