From 7e04983fd93754a121eeb12b00f0099cf5a0cfe5 Mon Sep 17 00:00:00 2001 From: Joe C Date: Fri, 14 Jun 2024 09:56:24 -0500 Subject: [PATCH] TLV Account Resolution: Add support for keys in data (#6847) * tlv-account-resolution: add `KeyData` configuration * tlv-account-resolution: add `KeyData` support to `ExtraAccountMeta` * tlv-account-resolution: add tests for `KeyData` * rename to `PubkeyData` * update docs --- .../tlv-account-resolution/src/account.rs | 86 +++++++- libraries/tlv-account-resolution/src/error.rs | 9 + libraries/tlv-account-resolution/src/lib.rs | 1 + .../tlv-account-resolution/src/pubkey_data.rs | 185 ++++++++++++++++++ libraries/tlv-account-resolution/src/state.rs | 185 ++++++++++++++++-- 5 files changed, 445 insertions(+), 21 deletions(-) create mode 100644 libraries/tlv-account-resolution/src/pubkey_data.rs diff --git a/libraries/tlv-account-resolution/src/account.rs b/libraries/tlv-account-resolution/src/account.rs index 83580abe5d1..86575793a95 100644 --- a/libraries/tlv-account-resolution/src/account.rs +++ b/libraries/tlv-account-resolution/src/account.rs @@ -4,11 +4,13 @@ //! collection of seeds use { - crate::{error::AccountResolutionError, seeds::Seed}, + crate::{error::AccountResolutionError, pubkey_data::PubkeyData, seeds::Seed}, bytemuck::{Pod, Zeroable}, solana_program::{ - account_info::AccountInfo, instruction::AccountMeta, program_error::ProgramError, - pubkey::Pubkey, + account_info::AccountInfo, + instruction::AccountMeta, + program_error::ProgramError, + pubkey::{Pubkey, PUBKEY_BYTES}, }, spl_pod::primitives::PodBool, }; @@ -66,18 +68,66 @@ where Ok(Pubkey::find_program_address(&pda_seeds, program_id).0) } +/// Resolve a pubkey from a pubkey data configuration. +fn resolve_key_data<'a, F>( + key_data: &PubkeyData, + instruction_data: &[u8], + get_account_key_data_fn: F, +) -> Result +where + F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>, +{ + match key_data { + PubkeyData::Uninitialized => Err(ProgramError::InvalidAccountData), + PubkeyData::InstructionData { index } => { + let key_start = *index as usize; + let key_end = key_start + PUBKEY_BYTES; + if key_end > instruction_data.len() { + return Err(AccountResolutionError::InstructionDataTooSmall.into()); + } + Ok(Pubkey::new_from_array( + instruction_data[key_start..key_end].try_into().unwrap(), + )) + } + PubkeyData::AccountData { + account_index, + data_index, + } => { + let account_index = *account_index as usize; + let account_data = get_account_key_data_fn(account_index) + .ok_or::(AccountResolutionError::AccountNotFound.into())? + .1 + .ok_or::(AccountResolutionError::AccountDataNotFound.into())?; + let arg_start = *data_index as usize; + let arg_end = arg_start + PUBKEY_BYTES; + if account_data.len() < arg_end { + return Err(AccountResolutionError::AccountDataTooSmall.into()); + } + Ok(Pubkey::new_from_array( + account_data[arg_start..arg_end].try_into().unwrap(), + )) + } + } +} + /// `Pod` type for defining a required account in a validation account. /// -/// This can either be a standard `AccountMeta` or a PDA. +/// This can be any of the following: +/// +/// * A standard `AccountMeta` +/// * A PDA (with seed configurations) +/// * A pubkey stored in some data (account or instruction data) +/// /// Can be used in TLV-encoded data. #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] pub struct ExtraAccountMeta { /// Discriminator to tell whether this represents a standard - /// `AccountMeta` or a PDA + /// `AccountMeta`, PDA, or pubkey data. pub discriminator: u8, - /// This `address_config` field can either be the pubkey of the account - /// or the seeds used to derive the pubkey from provided inputs + /// This `address_config` field can either be the pubkey of the account, + /// the seeds used to derive the pubkey from provided inputs (PDA), or the + /// data used to derive the pubkey (account or instruction data). pub address_config: [u8; 32], /// Whether the account should sign pub is_signer: PodBool, @@ -118,6 +168,20 @@ impl ExtraAccountMeta { }) } + /// Create a `ExtraAccountMeta` from a pubkey data configuration. + pub fn new_with_pubkey_data( + key_data: &PubkeyData, + is_signer: bool, + is_writable: bool, + ) -> Result { + Ok(Self { + discriminator: 2, + address_config: PubkeyData::pack_into_address_config(key_data)?, + is_signer: is_signer.into(), + is_writable: is_writable.into(), + }) + } + /// Create a `ExtraAccountMeta` from a list of seed configurations, /// representing a PDA for an external program /// @@ -173,6 +237,14 @@ impl ExtraAccountMeta { is_writable: self.is_writable.into(), }) } + 2 => { + let key_data = PubkeyData::unpack(&self.address_config)?; + Ok(AccountMeta { + pubkey: resolve_key_data(&key_data, instruction_data, get_account_key_data_fn)?, + is_signer: self.is_signer.into(), + is_writable: self.is_writable.into(), + }) + } _ => Err(ProgramError::InvalidAccountData), } } diff --git a/libraries/tlv-account-resolution/src/error.rs b/libraries/tlv-account-resolution/src/error.rs index d86ee6ba00c..03aeaeba709 100644 --- a/libraries/tlv-account-resolution/src/error.rs +++ b/libraries/tlv-account-resolution/src/error.rs @@ -60,4 +60,13 @@ pub enum AccountResolutionError { /// Failed to fetch account #[error("Failed to fetch account")] AccountFetchFailed, + /// Not enough bytes available to pack pubkey data configuration. + #[error("Not enough bytes available to pack pubkey data configuration")] + NotEnoughBytesForPubkeyData, + /// The provided bytes are not valid for a pubkey data configuration + #[error("The provided bytes are not valid for a pubkey data configuration")] + InvalidBytesForPubkeyData, + /// Tried to pack an invalid pubkey data configuration + #[error("Tried to pack an invalid pubkey data configuration")] + InvalidPubkeyDataConfig, } diff --git a/libraries/tlv-account-resolution/src/lib.rs b/libraries/tlv-account-resolution/src/lib.rs index 1f961ce8b6b..5fbe38d1f70 100644 --- a/libraries/tlv-account-resolution/src/lib.rs +++ b/libraries/tlv-account-resolution/src/lib.rs @@ -9,6 +9,7 @@ pub mod account; pub mod error; +pub mod pubkey_data; pub mod seeds; pub mod state; diff --git a/libraries/tlv-account-resolution/src/pubkey_data.rs b/libraries/tlv-account-resolution/src/pubkey_data.rs new file mode 100644 index 00000000000..6585ba5c38a --- /dev/null +++ b/libraries/tlv-account-resolution/src/pubkey_data.rs @@ -0,0 +1,185 @@ +//! Types for managing extra account meta keys that may be extracted from some +//! data. +//! +//! This can be either account data from some account in the list of accounts +//! or from the instruction data itself. + +#[cfg(feature = "serde-traits")] +use serde::{Deserialize, Serialize}; +use {crate::error::AccountResolutionError, solana_program::program_error::ProgramError}; + +/// Enum to describe a required key stored in some data. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))] +pub enum PubkeyData { + /// Uninitialized configuration byte space. + Uninitialized, + /// A pubkey to be resolved from the instruction data. + /// + /// Packed as: + /// * 1 - Discriminator + /// * 1 - Start index of instruction data + /// + /// Note: Length is always 32 bytes. + InstructionData { + /// The index where the address bytes begin in the instruction data. + index: u8, + }, + /// A pubkey to be resolved from the inner data of some account. + /// + /// Packed as: + /// * 1 - Discriminator + /// * 1 - Index of account in accounts list + /// * 1 - Start index of account data + /// + /// Note: Length is always 32 bytes. + AccountData { + /// The index of the account in the entire accounts list. + account_index: u8, + /// The index where the address bytes begin in the account data. + data_index: u8, + }, +} +impl PubkeyData { + /// Get the size of a pubkey data configuration. + pub fn tlv_size(&self) -> u8 { + match self { + Self::Uninitialized => 0, + // 1 byte for the discriminator, 1 byte for the index. + Self::InstructionData { .. } => 1 + 1, + // 1 byte for the discriminator, 1 byte for the account index, + // 1 byte for the data index. + Self::AccountData { .. } => 1 + 1 + 1, + } + } + + /// Packs a pubkey data configuration into a slice. + pub fn pack(&self, dst: &mut [u8]) -> Result<(), ProgramError> { + // Because no `PubkeyData` variant is larger than 3 bytes, this check + // is sufficient for the data length. + if dst.len() != self.tlv_size() as usize { + return Err(AccountResolutionError::NotEnoughBytesForPubkeyData.into()); + } + match &self { + Self::Uninitialized => { + return Err(AccountResolutionError::InvalidPubkeyDataConfig.into()) + } + Self::InstructionData { index } => { + dst[0] = 1; + dst[1] = *index; + } + Self::AccountData { + account_index, + data_index, + } => { + dst[0] = 2; + dst[1] = *account_index; + dst[2] = *data_index; + } + } + Ok(()) + } + + /// Packs a pubkey data configuration into a 32-byte array, filling the + /// rest with 0s. + pub fn pack_into_address_config(key_data: &Self) -> Result<[u8; 32], ProgramError> { + let mut packed = [0u8; 32]; + let tlv_size = key_data.tlv_size() as usize; + key_data.pack(&mut packed[..tlv_size])?; + Ok(packed) + } + + /// Unpacks a pubkey data configuration from a slice. + pub fn unpack(bytes: &[u8]) -> Result { + let (discrim, rest) = bytes + .split_first() + .ok_or::(ProgramError::InvalidAccountData)?; + match discrim { + 0 => Ok(Self::Uninitialized), + 1 => { + if rest.is_empty() { + return Err(AccountResolutionError::InvalidBytesForPubkeyData.into()); + } + Ok(Self::InstructionData { index: rest[0] }) + } + 2 => { + if rest.len() < 2 { + return Err(AccountResolutionError::InvalidBytesForPubkeyData.into()); + } + Ok(Self::AccountData { + account_index: rest[0], + data_index: rest[1], + }) + } + _ => Err(ProgramError::InvalidAccountData), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pack() { + // Should fail if the length is too short. + let key = PubkeyData::InstructionData { index: 0 }; + let mut packed = vec![0u8; key.tlv_size() as usize - 1]; + assert_eq!( + key.pack(&mut packed).unwrap_err(), + AccountResolutionError::NotEnoughBytesForPubkeyData.into(), + ); + + // Should fail if the length is too long. + let key = PubkeyData::InstructionData { index: 0 }; + let mut packed = vec![0u8; key.tlv_size() as usize + 1]; + assert_eq!( + key.pack(&mut packed).unwrap_err(), + AccountResolutionError::NotEnoughBytesForPubkeyData.into(), + ); + + // Can't pack a `PubkeyData::Uninitialized`. + let key = PubkeyData::Uninitialized; + let mut packed = vec![0u8; key.tlv_size() as usize]; + assert_eq!( + key.pack(&mut packed).unwrap_err(), + AccountResolutionError::InvalidPubkeyDataConfig.into(), + ); + } + + #[test] + fn test_unpack() { + // Can unpack zeroes. + let zeroes = [0u8; 32]; + let key = PubkeyData::unpack(&zeroes).unwrap(); + assert_eq!(key, PubkeyData::Uninitialized); + + // Should fail for empty bytes. + let bytes = []; + assert_eq!( + PubkeyData::unpack(&bytes).unwrap_err(), + ProgramError::InvalidAccountData + ); + } + + fn test_pack_unpack_key(key: PubkeyData) { + let tlv_size = key.tlv_size() as usize; + let mut packed = vec![0u8; tlv_size]; + key.pack(&mut packed).unwrap(); + let unpacked = PubkeyData::unpack(&packed).unwrap(); + assert_eq!(key, unpacked); + } + + #[test] + fn test_pack_unpack() { + // Instruction data. + test_pack_unpack_key(PubkeyData::InstructionData { index: 0 }); + + // Account data. + test_pack_unpack_key(PubkeyData::AccountData { + account_index: 0, + data_index: 0, + }); + } +} diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 7d8234e6338..16230521251 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -373,7 +373,7 @@ impl ExtraAccountMetaList { mod tests { use { super::*, - crate::seeds::Seed, + crate::{pubkey_data::PubkeyData, seeds::Seed}, solana_program::{clock::Epoch, instruction::AccountMeta, pubkey::Pubkey}, solana_program_test::tokio, spl_discriminator::{ArrayDiscriminator, SplDiscriminate}, @@ -581,14 +581,24 @@ mod tests { true, ) .unwrap(); + let extra_meta4 = ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::InstructionData { index: 4 }, + false, + true, + ) + .unwrap(); let metas = [ ExtraAccountMeta::from(&extra_meta1), ExtraAccountMeta::from(&extra_meta2), extra_meta3, + extra_meta4, ]; - let ix_data = vec![1, 2, 3, 4]; + let mut ix_data = vec![1, 2, 3, 4]; + let check_extra_meta4_pubkey = Pubkey::new_unique(); + ix_data.extend_from_slice(check_extra_meta4_pubkey.as_ref()); + let ix_accounts = vec![ix_account1.clone(), ix_account2.clone()]; let mut instruction = Instruction::new_with_bytes(program_id, &ix_data, ix_accounts); @@ -624,12 +634,17 @@ mod tests { extra_meta1, extra_meta2, AccountMeta::new(check_extra_meta3_pubkey, false), + AccountMeta::new(check_extra_meta4_pubkey, false), ]; assert_eq!( instruction.accounts.get(4).unwrap().pubkey, check_extra_meta3_pubkey, ); + assert_eq!( + instruction.accounts.get(5).unwrap().pubkey, + check_extra_meta4_pubkey, + ); assert_eq!(instruction.accounts, check_metas,); } @@ -661,6 +676,12 @@ mod tests { true, ) .unwrap(); + let extra_meta6 = ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::InstructionData { index: 8 }, + false, + true, + ) + .unwrap(); let other_meta1 = AccountMeta::new(Pubkey::new_unique(), false); let other_meta2 = ExtraAccountMeta::new_with_seeds( @@ -678,6 +699,12 @@ mod tests { true, ) .unwrap(); + let other_meta3 = ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::InstructionData { index: 7 }, + false, + true, + ) + .unwrap(); let metas = [ ExtraAccountMeta::from(&extra_meta1), @@ -685,8 +712,13 @@ mod tests { ExtraAccountMeta::from(&extra_meta3), ExtraAccountMeta::from(&extra_meta4), extra_meta5, + extra_meta6, + ]; + let other_metas = [ + ExtraAccountMeta::from(&other_meta1), + other_meta2, + other_meta3, ]; - let other_metas = [ExtraAccountMeta::from(&other_meta1), other_meta2]; let account_size = ExtraAccountMetaList::size_of(metas.len()).unwrap() + ExtraAccountMetaList::size_of(other_metas.len()).unwrap(); @@ -698,8 +730,13 @@ mod tests { let mock_rpc = MockRpc::setup(&[]); let program_id = Pubkey::new_unique(); - let ix_data = vec![0, 0, 0, 0, 0, 7, 0, 0]; + + let mut ix_data = vec![0, 0, 0, 0, 0, 7, 0, 0]; + let check_extra_meta6_pubkey = Pubkey::new_unique(); + ix_data.extend_from_slice(check_extra_meta6_pubkey.as_ref()); + let ix_accounts = vec![]; + let mut instruction = Instruction::new_with_bytes(program_id, &ix_data, ix_accounts); ExtraAccountMetaList::add_to_instruction::( &mut instruction, @@ -726,19 +763,29 @@ mod tests { extra_meta3, extra_meta4, AccountMeta::new(check_extra_meta5_pubkey, false), + AccountMeta::new(check_extra_meta6_pubkey, false), ]; assert_eq!( instruction.accounts.get(4).unwrap().pubkey, check_extra_meta5_pubkey, ); + assert_eq!( + instruction.accounts.get(5).unwrap().pubkey, + check_extra_meta6_pubkey, + ); assert_eq!(instruction.accounts, check_metas,); let program_id = Pubkey::new_unique(); + let ix_account1 = AccountMeta::new(Pubkey::new_unique(), false); let ix_account2 = AccountMeta::new(Pubkey::new_unique(), true); let ix_accounts = vec![ix_account1.clone(), ix_account2.clone()]; - let ix_data = vec![0, 26, 0, 0, 0, 0, 0]; + + let mut ix_data = vec![0, 26, 0, 0, 0, 0, 0]; + let check_other_meta3_pubkey = Pubkey::new_unique(); + ix_data.extend_from_slice(check_other_meta3_pubkey.as_ref()); + let mut instruction = Instruction::new_with_bytes(program_id, &ix_data, ix_accounts); ExtraAccountMetaList::add_to_instruction::( &mut instruction, @@ -763,12 +810,17 @@ mod tests { ix_account2, other_meta1, AccountMeta::new(check_other_meta2_pubkey, false), + AccountMeta::new(check_other_meta3_pubkey, false), ]; assert_eq!( instruction.accounts.get(3).unwrap().pubkey, check_other_meta2_pubkey, ); + assert_eq!( + instruction.accounts.get(4).unwrap().pubkey, + check_other_meta3_pubkey, + ); assert_eq!(instruction.accounts, check_other_metas,); } @@ -855,6 +907,12 @@ mod tests { true, ) .unwrap(); + let extra_meta7 = ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::InstructionData { index: 41 }, // After the other pubkey arg. + false, + true, + ) + .unwrap(); let test_ix_required_extra_accounts = account_infos .iter() @@ -867,6 +925,7 @@ mod tests { ExtraAccountMeta::from(&extra_meta4), extra_meta5, extra_meta6, + extra_meta7, ]; let account_size = ExtraAccountMetaList::size_of(test_ix_required_extra_accounts.len()) @@ -904,11 +963,16 @@ mod tests { assert_eq!(instruction.accounts, test_ix_check_metas,); let program_id = Pubkey::new_unique(); + let instruction_u8array_arg = [1, 2, 3, 4, 5, 6, 7, 8]; let instruction_pubkey_arg = Pubkey::new_unique(); + let instruction_key_data_pubkey_arg = Pubkey::new_unique(); + let mut instruction_data = vec![0]; instruction_data.extend_from_slice(&instruction_u8array_arg); instruction_data.extend_from_slice(instruction_pubkey_arg.as_ref()); + instruction_data.extend_from_slice(instruction_key_data_pubkey_arg.as_ref()); + let mut instruction = Instruction::new_with_bytes(program_id, &instruction_data, vec![]); ExtraAccountMetaList::add_to_instruction::( &mut instruction, @@ -946,6 +1010,7 @@ mod tests { extra_meta4, AccountMeta::new(check_extra_meta5_pubkey, false), AccountMeta::new(check_extra_meta6_pubkey, false), + AccountMeta::new(instruction_key_data_pubkey_arg, false), ]; assert_eq!( @@ -956,6 +1021,10 @@ mod tests { instruction.accounts.get(5).unwrap().pubkey, check_extra_meta6_pubkey, ); + assert_eq!( + instruction.accounts.get(6).unwrap().pubkey, + instruction_key_data_pubkey_arg, + ); assert_eq!(instruction.accounts, test_other_ix_check_metas,); } @@ -972,6 +1041,7 @@ mod tests { // Some seeds used by the program for PDAs let required_pda1_literal_string = "required_pda1"; let required_pda2_literal_u32 = 4u32; + let required_key_data_instruction_data = Pubkey::new_unique(); // Define instruction data // - 0: u8 @@ -982,6 +1052,7 @@ mod tests { let mut instruction_data = vec![0]; instruction_data.extend_from_slice(&instruction_u8array_arg); instruction_data.extend_from_slice(instruction_u64_arg.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(required_key_data_instruction_data.as_ref()); // Define known instruction accounts let ix_accounts = vec![ @@ -1055,6 +1126,30 @@ mod tests { true, ) .unwrap(), + ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::InstructionData { index: 17 }, + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::AccountData { + account_index: 6, + data_index: 0, + }, + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::AccountData { + account_index: 7, + data_index: 8, + }, + false, + true, + ) + .unwrap(), ]; // Now here we're going to build the list of account infos @@ -1098,9 +1193,12 @@ mod tests { &program_id, ) .0; + let check_key_data1_pubkey = required_key_data_instruction_data; + let check_key_data2_pubkey = Pubkey::new_from_array([8; 32]); + let check_key_data3_pubkey = Pubkey::new_from_array([9; 32]); // The instruction account infos for the program to CPI to - let pubkey_ix_1 = ix_accounts.get(0).unwrap().pubkey; + let pubkey_ix_1 = ix_accounts.first().unwrap().pubkey; let mut lamports_ix_1 = 0; let mut data_ix_1 = []; let pubkey_ix_2 = ix_accounts.get(1).unwrap().pubkey; @@ -1117,11 +1215,18 @@ mod tests { let mut lamports_pda1 = 0; let mut data_pda1 = [7; 12]; let mut lamports_pda2 = 0; - let mut data_pda2 = []; + let mut data_pda2 = [8; 32]; let mut lamports_pda3 = 0; - let mut data_pda3 = []; + let mut data_pda3 = [0; 40]; + data_pda3[8..].copy_from_slice(&[9; 32]); // Add pubkey data for pubkey data pubkey 3. let mut lamports_pda4 = 0; let mut data_pda4 = []; + let mut data_key_data1 = []; + let mut lamports_key_data1 = 0; + let mut data_key_data2 = []; + let mut lamports_key_data2 = 0; + let mut data_key_data3 = []; + let mut lamports_key_data3 = 0; // Some other arbitrary account infos our program may use let pubkey_arb_1 = Pubkey::new_unique(); @@ -1134,8 +1239,8 @@ mod tests { let all_account_infos = [ AccountInfo::new( &pubkey_ix_1, - ix_accounts.get(0).unwrap().is_signer, - ix_accounts.get(0).unwrap().is_writable, + ix_accounts.first().unwrap().is_signer, + ix_accounts.first().unwrap().is_writable, &mut lamports_ix_1, &mut data_ix_1, &owner, @@ -1154,8 +1259,8 @@ mod tests { ), AccountInfo::new( &extra_meta1.pubkey, - required_accounts.get(0).unwrap().is_signer.into(), - required_accounts.get(0).unwrap().is_writable.into(), + required_accounts.first().unwrap().is_signer.into(), + required_accounts.first().unwrap().is_writable.into(), &mut lamports1, &mut data1, &owner, @@ -1222,6 +1327,36 @@ mod tests { false, Epoch::default(), ), + AccountInfo::new( + &check_key_data1_pubkey, + required_accounts.get(7).unwrap().is_signer.into(), + required_accounts.get(7).unwrap().is_writable.into(), + &mut lamports_key_data1, + &mut data_key_data1, + &owner, + false, + Epoch::default(), + ), + AccountInfo::new( + &check_key_data2_pubkey, + required_accounts.get(8).unwrap().is_signer.into(), + required_accounts.get(8).unwrap().is_writable.into(), + &mut lamports_key_data2, + &mut data_key_data2, + &owner, + false, + Epoch::default(), + ), + AccountInfo::new( + &check_key_data3_pubkey, + required_accounts.get(9).unwrap().is_signer.into(), + required_accounts.get(9).unwrap().is_writable.into(), + &mut lamports_key_data3, + &mut data_key_data3, + &owner, + false, + Epoch::default(), + ), AccountInfo::new( &pubkey_arb_1, false, @@ -1299,7 +1434,7 @@ mod tests { // Note: The two additional arbitrary account infos for the currently // executing program won't be present in the CPI instruction's account // infos, so we will omit them (hence the `..9`). - let check_account_infos = &all_account_infos[..9]; + let check_account_infos = &all_account_infos[..12]; assert_eq!(cpi_account_infos.len(), check_account_infos.len()); for (a, b) in std::iter::zip(cpi_account_infos, check_account_infos) { assert_eq!(a.key, b.key); @@ -1452,6 +1587,12 @@ mod tests { true, ) .unwrap(), + ExtraAccountMeta::new_with_pubkey_data( + &PubkeyData::InstructionData { index: 8 }, + false, + true, + ) + .unwrap(), ]; // Create the validation data @@ -1460,7 +1601,9 @@ mod tests { ExtraAccountMetaList::init::(&mut buffer, &required_accounts).unwrap(); // Create the instruction data - let instruction_data = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let mut instruction_data = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let key_data_pubkey = Pubkey::new_unique(); + instruction_data.extend_from_slice(key_data_pubkey.as_ref()); // Set up a list of the required accounts as account infos, // with two instruction accounts @@ -1476,6 +1619,8 @@ mod tests { let mut data2 = []; let mut lamports3 = 0; let mut data3 = []; + let mut lamports4 = 0; + let mut data4 = []; let pda = Pubkey::find_program_address( &[b"lit_seed", &instruction_data[..4], pubkey_ix_1.as_ref()], &program_id, @@ -1537,6 +1682,17 @@ mod tests { false, Epoch::default(), ), + // Required account 4 (pubkey data) + AccountInfo::new( + &key_data_pubkey, + false, + true, + &mut lamports4, + &mut data4, + &owner, + false, + Epoch::default(), + ), ]; // Create another list of account infos to intentionally mess up @@ -1544,6 +1700,7 @@ mod tests { messed_account_infos.swap(0, 2); messed_account_infos.swap(1, 4); messed_account_infos.swap(3, 2); + messed_account_infos.swap(5, 4); // Account info check should fail for the messed list assert_eq!(