diff --git a/libraries/type-length-value/src/pod.rs b/libraries/type-length-value/src/pod.rs index e75437ae785..c5927a7f8ab 100644 --- a/libraries/type-length-value/src/pod.rs +++ b/libraries/type-length-value/src/pod.rs @@ -22,6 +22,10 @@ pub fn pod_slice_from_bytes(bytes: &[u8]) -> Result<&[T], ProgramError> pub fn pod_slice_from_bytes_mut(bytes: &mut [u8]) -> Result<&mut [T], ProgramError> { bytemuck::try_cast_slice_mut(bytes).map_err(|_| ProgramError::InvalidArgument) } +/// Convert a pod slice into its raw bytes +pub fn pod_slice_to_bytes(slice: &[T]) -> &[u8] { + bytemuck::cast_slice(slice) +} /// Simple macro for implementing conversion functions between Pod* ints and /// standard ints. diff --git a/token/transfer-hook-example/src/processor.rs b/token/transfer-hook-example/src/processor.rs index 5fff669c7cd..84dfbef7ad9 100644 --- a/token/transfer-hook-example/src/processor.rs +++ b/token/transfer-hook-example/src/processor.rs @@ -90,9 +90,10 @@ pub fn process_execute( } /// Processes a [InitializeExtraAccountMetaList](enum.TransferHookInstruction.html) instruction. -pub fn process_initialize_extra_account_metas( +pub fn process_initialize_extra_account_meta_list( program_id: &Pubkey, accounts: &[AccountInfo], + extra_account_metas: &[ExtraAccountMeta], ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -127,8 +128,7 @@ pub fn process_initialize_extra_account_metas( // Create the account let bump_seed = [bump_seed]; let signer_seeds = collect_extra_account_metas_signer_seeds(mint_info.key, &bump_seed); - let extra_account_infos = account_info_iter.as_slice(); - let length = extra_account_infos.len(); + let length = extra_account_metas.len(); let account_size = ExtraAccountMetaList::size_of(length)?; invoke_signed( &system_instruction::allocate(extra_account_metas_info.key, account_size as u64), @@ -143,13 +143,7 @@ pub fn process_initialize_extra_account_metas( // Write the data let mut data = extra_account_metas_info.try_borrow_mut_data()?; - ExtraAccountMetaList::init::( - &mut data, - &extra_account_infos - .iter() - .map(ExtraAccountMeta::from) - .collect::>(), - )?; + ExtraAccountMetaList::init::(&mut data, extra_account_metas)?; Ok(()) } @@ -163,9 +157,11 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> P msg!("Instruction: Execute"); process_execute(program_id, accounts, amount) } - TransferHookInstruction::InitializeExtraAccountMetaList => { + TransferHookInstruction::InitializeExtraAccountMetaList { + extra_account_metas, + } => { msg!("Instruction: InitializeExtraAccountMetaList"); - process_initialize_extra_account_metas(program_id, accounts) + process_initialize_extra_account_meta_list(program_id, accounts, &extra_account_metas) } } } diff --git a/token/transfer-hook-example/tests/functional.rs b/token/transfer-hook-example/tests/functional.rs index 0c1216b88e9..a8539f4485e 100644 --- a/token/transfer-hook-example/tests/functional.rs +++ b/token/transfer-hook-example/tests/functional.rs @@ -16,7 +16,7 @@ use { system_instruction, sysvar, transaction::{Transaction, TransactionError}, }, - spl_tlv_account_resolution::state::ExtraAccountMetaList, + spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList}, spl_token_2022::{ extension::{transfer_hook::TransferHookAccount, ExtensionType, StateWithExtensionsMut}, state::{Account, AccountState, Mint}, @@ -24,7 +24,7 @@ use { spl_transfer_hook_interface::{ error::TransferHookError, get_extra_account_metas_address, - instruction::{execute_with_extra_account_metas, initialize_extra_account_metas}, + instruction::{execute_with_extra_account_metas, initialize_extra_account_meta_list}, onchain, }, }; @@ -151,30 +151,35 @@ async fn success_execute() { true, ); - let extra_account_metas = get_extra_account_metas_address(&mint_address, &program_id); + let extra_account_metas_address = get_extra_account_metas_address(&mint_address, &program_id); - let extra_account_pubkeys = [ + let extra_account_metas = [ AccountMeta::new_readonly(sysvar::instructions::id(), false), AccountMeta::new_readonly(mint_authority_pubkey, true), - AccountMeta::new(extra_account_metas, false), + AccountMeta::new(extra_account_metas_address, false), ]; + let init_extra_account_metas = extra_account_metas + .iter() + .map(ExtraAccountMeta::from) + .collect::>(); + let mut context = program_test.start_with_context().await; let rent = context.banks_client.get_rent().await.unwrap(); - let rent_lamports = - rent.minimum_balance(ExtraAccountMetaList::size_of(extra_account_pubkeys.len()).unwrap()); + let rent_lamports = rent + .minimum_balance(ExtraAccountMetaList::size_of(init_extra_account_metas.len()).unwrap()); let transaction = Transaction::new_signed_with_payer( &[ system_instruction::transfer( &context.payer.pubkey(), - &extra_account_metas, + &extra_account_metas_address, rent_lamports, ), - initialize_extra_account_metas( + initialize_extra_account_meta_list( &program_id, - &extra_account_metas, + &extra_account_metas_address, &mint_address, &mint_authority_pubkey, - &extra_account_pubkeys, + &init_extra_account_metas, ), ], Some(&context.payer.pubkey()), @@ -197,8 +202,8 @@ async fn success_execute() { &mint_address, &destination, &wallet.pubkey(), - &extra_account_metas, - &extra_account_pubkeys[..2], + &extra_account_metas_address, + &extra_account_metas[..2], 0, )], Some(&context.payer.pubkey()), @@ -222,7 +227,7 @@ async fn success_execute() { // fail with wrong account { - let extra_account_pubkeys = [ + let extra_account_metas = [ AccountMeta::new_readonly(sysvar::instructions::id(), false), AccountMeta::new_readonly(mint_authority_pubkey, true), AccountMeta::new(wallet.pubkey(), false), @@ -234,8 +239,8 @@ async fn success_execute() { &mint_address, &destination, &wallet.pubkey(), + &extra_account_metas_address, &extra_account_metas, - &extra_account_pubkeys, 0, )], Some(&context.payer.pubkey()), @@ -259,10 +264,10 @@ async fn success_execute() { // fail with not signer { - let extra_account_pubkeys = [ + let extra_account_metas = [ AccountMeta::new_readonly(sysvar::instructions::id(), false), AccountMeta::new_readonly(mint_authority_pubkey, false), - AccountMeta::new(extra_account_metas, false), + AccountMeta::new(extra_account_metas_address, false), ]; let transaction = Transaction::new_signed_with_payer( &[execute_with_extra_account_metas( @@ -271,8 +276,8 @@ async fn success_execute() { &mint_address, &destination, &wallet.pubkey(), + &extra_account_metas_address, &extra_account_metas, - &extra_account_pubkeys, 0, )], Some(&context.payer.pubkey()), @@ -303,8 +308,8 @@ async fn success_execute() { &mint_address, &destination, &wallet.pubkey(), + &extra_account_metas_address, &extra_account_metas, - &extra_account_pubkeys, 0, )], Some(&context.payer.pubkey()), @@ -358,7 +363,7 @@ async fn fail_incorrect_derivation() { &extra_account_metas, rent_lamports, ), - initialize_extra_account_metas( + initialize_extra_account_meta_list( &program_id, &extra_account_metas, &mint_address, @@ -431,31 +436,37 @@ async fn success_on_chain_invoke() { true, ); - let extra_account_metas = get_extra_account_metas_address(&mint_address, &hook_program_id); + let extra_account_metas_address = + get_extra_account_metas_address(&mint_address, &hook_program_id); let writable_pubkey = Pubkey::new_unique(); - let extra_account_pubkeys = [ + let extra_account_metas = [ AccountMeta::new_readonly(sysvar::instructions::id(), false), AccountMeta::new_readonly(mint_authority_pubkey, true), AccountMeta::new(writable_pubkey, false), ]; + let init_extra_account_metas = extra_account_metas + .iter() + .map(ExtraAccountMeta::from) + .collect::>(); + let mut context = program_test.start_with_context().await; let rent = context.banks_client.get_rent().await.unwrap(); - let rent_lamports = - rent.minimum_balance(ExtraAccountMetaList::size_of(extra_account_pubkeys.len()).unwrap()); + let rent_lamports = rent + .minimum_balance(ExtraAccountMetaList::size_of(init_extra_account_metas.len()).unwrap()); let transaction = Transaction::new_signed_with_payer( &[ system_instruction::transfer( &context.payer.pubkey(), - &extra_account_metas, + &extra_account_metas_address, rent_lamports, ), - initialize_extra_account_metas( + initialize_extra_account_meta_list( &hook_program_id, - &extra_account_metas, + &extra_account_metas_address, &mint_address, &mint_authority_pubkey, - &extra_account_pubkeys, + &init_extra_account_metas, ), ], Some(&context.payer.pubkey()), @@ -476,8 +487,8 @@ async fn success_on_chain_invoke() { &mint_address, &destination, &wallet.pubkey(), + &extra_account_metas_address, &extra_account_metas, - &extra_account_pubkeys, 0, ); test_instruction @@ -522,25 +533,26 @@ async fn fail_without_transferring_flag() { false, ); - let extra_account_metas = get_extra_account_metas_address(&mint_address, &program_id); - let extra_account_pubkeys = []; + let extra_account_metas_address = get_extra_account_metas_address(&mint_address, &program_id); + let extra_account_metas = []; + let init_extra_account_metas = []; let mut context = program_test.start_with_context().await; let rent = context.banks_client.get_rent().await.unwrap(); - let rent_lamports = - rent.minimum_balance(ExtraAccountMetaList::size_of(extra_account_pubkeys.len()).unwrap()); + let rent_lamports = rent + .minimum_balance(ExtraAccountMetaList::size_of(init_extra_account_metas.len()).unwrap()); let transaction = Transaction::new_signed_with_payer( &[ system_instruction::transfer( &context.payer.pubkey(), - &extra_account_metas, + &extra_account_metas_address, rent_lamports, ), - initialize_extra_account_metas( + initialize_extra_account_meta_list( &program_id, - &extra_account_metas, + &extra_account_metas_address, &mint_address, &mint_authority_pubkey, - &extra_account_pubkeys, + &init_extra_account_metas, ), ], Some(&context.payer.pubkey()), @@ -560,8 +572,8 @@ async fn fail_without_transferring_flag() { &mint_address, &destination, &wallet.pubkey(), + &extra_account_metas_address, &extra_account_metas, - &extra_account_pubkeys, 0, )], Some(&context.payer.pubkey()), diff --git a/token/transfer-hook-interface/src/instruction.rs b/token/transfer-hook-interface/src/instruction.rs index 330f2a6eb7e..6322712552d 100644 --- a/token/transfer-hook-interface/src/instruction.rs +++ b/token/transfer-hook-interface/src/instruction.rs @@ -8,6 +8,8 @@ use { system_program, }, spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, + spl_tlv_account_resolution::account::ExtraAccountMeta, + spl_type_length_value::pod::{pod_slice_to_bytes, PodSlice}, std::convert::TryInto, }; @@ -39,9 +41,11 @@ pub enum TransferHookInstruction { /// 1. `[]` Mint /// 2. `[s]` Mint authority /// 3. `[]` System program - /// 4..4+M `[]` `M` additional accounts, to be written to validation data /// - InitializeExtraAccountMetaList, + InitializeExtraAccountMetaList { + /// List of `ExtraAccountMeta`s to write into the account + extra_account_metas: Vec, + }, } /// TLV instruction type only used to define the discriminator. The actual data /// is entirely managed by `ExtraAccountMetaList`, and it is the only data contained @@ -73,7 +77,11 @@ impl TransferHookInstruction { Self::Execute { amount } } InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => { - Self::InitializeExtraAccountMetaList + let pod_slice = PodSlice::::unpack(rest)?; + let extra_account_metas = pod_slice.data().to_vec(); + Self::InitializeExtraAccountMetaList { + extra_account_metas, + } } _ => return Err(ProgramError::InvalidInstructionData), }) @@ -87,10 +95,14 @@ impl TransferHookInstruction { buf.extend_from_slice(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE); buf.extend_from_slice(&amount.to_le_bytes()); } - Self::InitializeExtraAccountMetaList => { + Self::InitializeExtraAccountMetaList { + extra_account_metas, + } => { buf.extend_from_slice( InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE, ); + buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes()); + buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas)); } }; buf @@ -150,22 +162,24 @@ pub fn execute( } /// Creates a `InitializeExtraAccountMetaList` instruction. -pub fn initialize_extra_account_metas( +pub fn initialize_extra_account_meta_list( program_id: &Pubkey, extra_account_metas_pubkey: &Pubkey, mint_pubkey: &Pubkey, authority_pubkey: &Pubkey, - additional_accounts: &[AccountMeta], + extra_account_metas: &[ExtraAccountMeta], ) -> Instruction { - let data = TransferHookInstruction::InitializeExtraAccountMetaList.pack(); + let data = TransferHookInstruction::InitializeExtraAccountMetaList { + extra_account_metas: extra_account_metas.to_vec(), + } + .pack(); - let mut accounts = vec![ + let accounts = vec![ AccountMeta::new(*extra_account_metas_pubkey, false), AccountMeta::new_readonly(*mint_pubkey, false), AccountMeta::new_readonly(*authority_pubkey, true), AccountMeta::new_readonly(system_program::id(), false), ]; - accounts.extend_from_slice(additional_accounts); Instruction { program_id: *program_id, @@ -176,7 +190,10 @@ pub fn initialize_extra_account_metas( #[cfg(test)] mod test { - use {super::*, crate::NAMESPACE, solana_program::hash}; + use { + super::*, crate::NAMESPACE, solana_program::hash, + spl_type_length_value::pod::pod_from_bytes, + }; #[test] fn validate_packing() { @@ -197,7 +214,21 @@ mod test { #[test] fn initialize_validation_pubkeys_packing() { - let check = TransferHookInstruction::InitializeExtraAccountMetaList; + let extra_meta_len_bytes = &[ + 1, 0, 0, 0, // `1u32` + ]; + let extra_meta_bytes = &[ + 0, // `AccountMeta` + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, // pubkey + 0, // is_signer + 0, // is_writable + ]; + let extra_account_metas = + vec![*pod_from_bytes::(extra_meta_bytes).unwrap()]; + let check = TransferHookInstruction::InitializeExtraAccountMetaList { + extra_account_metas, + }; let packed = check.pack(); // Please use INITIALIZE_EXTRA_ACCOUNT_METAS_DISCRIMINATOR in your program, // the following is just for test purposes @@ -206,6 +237,8 @@ mod test { let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH]; let mut expect = vec![]; expect.extend_from_slice(discriminator.as_ref()); + expect.extend_from_slice(extra_meta_len_bytes); + expect.extend_from_slice(extra_meta_bytes); assert_eq!(packed, expect); let unpacked = TransferHookInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check);