@@ -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