Skip to content

Commit f5088a7

Browse files
authored
Merge branch 'anza-xyz:main' into main
2 parents 4d1d45f + ed37b7c commit f5088a7

File tree

1 file changed

+181
-3
lines changed
  • sdk/pinocchio/src/entrypoint

1 file changed

+181
-3
lines changed

sdk/pinocchio/src/entrypoint/mod.rs

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,7 @@ macro_rules! process_n_accounts {
225225
$input = $input.add(size_of::<u64>());
226226

227227
if (*account).borrow_state != NON_DUP_MARKER {
228-
$accounts.write(AccountInfo {
229-
raw: $accounts_slice.add((*account).borrow_state as usize) as *mut Account,
230-
});
228+
clone_account_info($accounts, $accounts_slice, (*account).borrow_state);
231229
} else {
232230
$accounts.write(AccountInfo { raw: account });
233231

@@ -258,6 +256,33 @@ macro_rules! process_accounts {
258256
};
259257
}
260258

259+
/// Create an `AccountInfo` referencing the same account referenced
260+
/// by the `AccountInfo` at the specified `index`.
261+
///
262+
/// # Safety
263+
///
264+
/// The caller must ensure that:
265+
/// - `accounts` pointer must point to an array of `AccountInfo`s where
266+
/// the new `AccountInfo` will be written.
267+
/// - `accounts_slice` pointer must point to a slice of `AccountInfo`s
268+
/// already initialized.
269+
/// - `index` is a valid index in the `accounts_slice`.
270+
//
271+
// Note: The function is marked as `cold` to stop the compiler from optimizing the
272+
// parsing of duplicated accounts, which leads to an overall increase in CU
273+
// consumption.
274+
#[cold]
275+
#[inline(always)]
276+
unsafe fn clone_account_info(
277+
accounts: *mut AccountInfo,
278+
accounts_slice: *const AccountInfo,
279+
index: u8,
280+
) {
281+
accounts.write(AccountInfo {
282+
raw: (*accounts_slice.add(index as usize)).raw,
283+
});
284+
}
285+
261286
/// Parse the arguments from the runtime input buffer.
262287
///
263288
/// This function parses the `accounts`, `instruction_data` and `program_id` from
@@ -757,6 +782,72 @@ mod tests {
757782
input
758783
}
759784

785+
/// Creates an input buffer with a specified number of accounts, including
786+
/// duplicated accounts, and instruction data.
787+
///
788+
/// This function differs from `create_input` in that it creates accounts
789+
/// with a marker indicating that they are duplicated. There will be
790+
/// `accounts - duplicated` unique accounts, and the remaining `duplicated`
791+
/// accounts will be duplicates of the last unique account.
792+
///
793+
/// This function mimics the input buffer created by the SVM loader.
794+
/// Each account created has zeroed data, apart from the `data_len`
795+
/// field, which is set to the index of the account.
796+
///
797+
/// # Safety
798+
///
799+
/// The returned `AlignedMemory` should only be used within the test
800+
/// context.
801+
unsafe fn create_input_with_duplicates(
802+
accounts: usize,
803+
instruction_data: &[u8],
804+
duplicated: usize,
805+
) -> AlignedMemory {
806+
let mut input = AlignedMemory::new(1_000_000_000);
807+
// Number of accounts.
808+
input.write(&(accounts as u64).to_le_bytes(), 0);
809+
let mut offset = size_of::<u64>();
810+
811+
if accounts > 0 {
812+
assert!(
813+
duplicated < accounts,
814+
"Duplicated accounts must be less than total accounts"
815+
);
816+
let unique = accounts - duplicated;
817+
818+
for i in 0..unique {
819+
// Account data.
820+
let mut account = [0u8; STATIC_ACCOUNT_DATA + size_of::<u64>()];
821+
account[0] = NON_DUP_MARKER;
822+
// Set the accounts data length. The actual account data is zeroed.
823+
account[80..88].copy_from_slice(&i.to_le_bytes());
824+
input.write(&account, offset);
825+
offset += account.len();
826+
// Padding for the account data to align to `BPF_ALIGN_OF_U128`.
827+
let padding_for_data = (i + (BPF_ALIGN_OF_U128 - 1)) & !(BPF_ALIGN_OF_U128 - 1);
828+
input.write(&vec![0u8; padding_for_data], offset);
829+
offset += padding_for_data;
830+
}
831+
832+
// Remaining accounts are duplicated of the last unique account.
833+
for _ in unique..accounts {
834+
input.write(&[(unique - 1) as u8, 0, 0, 0, 0, 0, 0, 0], offset);
835+
offset += size_of::<u64>();
836+
}
837+
}
838+
839+
// Instruction data length.
840+
input.write(&instruction_data.len().to_le_bytes(), offset);
841+
offset += size_of::<u64>();
842+
// Instruction data.
843+
input.write(instruction_data, offset);
844+
offset += instruction_data.len();
845+
// Program ID (mock).
846+
input.write(&MOCK_PROGRAM_ID, offset);
847+
848+
input
849+
}
850+
760851
/// Asserts that the accounts slice contains the expected number of accounts
761852
/// and that each account's data length matches its index.
762853
fn assert_accounts(accounts: &[MaybeUninit<AccountInfo>]) {
@@ -766,6 +857,44 @@ mod tests {
766857
}
767858
}
768859

860+
/// Asserts that the accounts slice contains the expected number of accounts
861+
/// and all accounts are duplicated, apart from the first one.
862+
fn assert_duplicated_accounts(accounts: &[MaybeUninit<AccountInfo>], duplicated: usize) {
863+
assert!(accounts.len() > duplicated);
864+
865+
let unique = accounts.len() - duplicated;
866+
867+
// Unique accounts should have `data_len` equal to their index.
868+
for (i, account) in accounts[..unique].iter().enumerate() {
869+
let account_info = unsafe { account.assume_init_ref() };
870+
assert_eq!(account_info.data_len(), i);
871+
}
872+
873+
// Last unique account.
874+
let duplicated = unsafe { accounts[unique - 1].assume_init_ref() };
875+
// No mutable borrow active at this point.
876+
assert!(duplicated.try_borrow_mut_data().is_ok());
877+
878+
// Duplicated accounts should reference (share) the account pointer
879+
// to the last unique account.
880+
for account in accounts[unique..].iter() {
881+
let account_info = unsafe { account.assume_init_ref() };
882+
883+
assert_eq!(account_info.raw, duplicated.raw);
884+
assert_eq!(account_info.data_len(), duplicated.data_len());
885+
886+
let borrowed = account_info.try_borrow_mut_data().unwrap();
887+
// Only one mutable borrow at the same time should be allowed
888+
// on the duplicated account.
889+
assert!(duplicated.try_borrow_mut_data().is_err());
890+
drop(borrowed);
891+
}
892+
893+
// There should not be any mutable borrow on the duplicated account
894+
// at this point.
895+
assert!(duplicated.try_borrow_mut_data().is_ok());
896+
}
897+
769898
#[test]
770899
fn test_deserialize() {
771900
let ix_data = [3u8; 100];
@@ -810,4 +939,53 @@ mod tests {
810939
assert_eq!(&ix_data, parsed_ix_data);
811940
assert_accounts(&accounts);
812941
}
942+
943+
#[test]
944+
fn test_deserialize_duplicated() {
945+
let ix_data = [3u8; 100];
946+
947+
// Input with 0 accounts.
948+
949+
let mut input = unsafe { create_input_with_duplicates(0, &ix_data, 0) };
950+
let mut accounts = [UNINIT; 1];
951+
952+
let (program_id, count, parsed_ix_data) =
953+
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
954+
955+
assert_eq!(count, 0);
956+
assert_eq!(program_id, &MOCK_PROGRAM_ID);
957+
assert_eq!(&ix_data, parsed_ix_data);
958+
959+
// Input with 3 (1 + 2 duplicated) accounts but the accounts array has only
960+
// space for 2. The assert checks that the second account is a duplicate of
961+
// the first one and the first one is unique.
962+
963+
let mut input = unsafe { create_input_with_duplicates(3, &ix_data, 2) };
964+
let mut accounts = [UNINIT; 2];
965+
966+
let (program_id, count, parsed_ix_data) =
967+
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
968+
969+
assert_eq!(count, 2);
970+
assert_eq!(program_id, &MOCK_PROGRAM_ID);
971+
assert_eq!(&ix_data, parsed_ix_data);
972+
assert_duplicated_accounts(&accounts[..count], 1);
973+
974+
// Input with `MAX_TX_ACCOUNTS` accounts (only 32 unique ones) but accounts
975+
// array has only space for 64. The assert checks that the first 32 accounts
976+
// are unique and the rest are duplicates of the account at index 31.
977+
978+
let mut input = unsafe {
979+
create_input_with_duplicates(MAX_TX_ACCOUNTS, &ix_data, MAX_TX_ACCOUNTS - 32)
980+
};
981+
let mut accounts = [UNINIT; 64];
982+
983+
let (program_id, count, parsed_ix_data) =
984+
unsafe { deserialize(input.as_mut_ptr(), &mut accounts) };
985+
986+
assert_eq!(count, 64);
987+
assert_eq!(program_id, &MOCK_PROGRAM_ID);
988+
assert_eq!(&ix_data, parsed_ix_data);
989+
assert_duplicated_accounts(&accounts, 32);
990+
}
813991
}

0 commit comments

Comments
 (0)