solana_program_runtime/
serialization.rs

1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4    crate::invoke_context::SerializedAccountMetadata,
5    solana_instruction::error::InstructionError,
6    solana_program_entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, NON_DUP_MARKER},
7    solana_pubkey::Pubkey,
8    solana_sbpf::{
9        aligned_memory::{AlignedMemory, Pod},
10        ebpf::{HOST_ALIGN, MM_INPUT_START},
11        memory_region::MemoryRegion,
12    },
13    solana_sdk_ids::bpf_loader_deprecated,
14    solana_system_interface::MAX_PERMITTED_DATA_LENGTH,
15    solana_transaction_context::{
16        BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
17    },
18    std::mem::{self, size_of},
19};
20
21/// Maximum number of instruction accounts that can be serialized into the
22/// SBF VM.
23const MAX_INSTRUCTION_ACCOUNTS: u8 = NON_DUP_MARKER;
24
25#[allow(dead_code)]
26enum SerializeAccount<'a> {
27    Account(IndexOfAccount, BorrowedAccount<'a>),
28    Duplicate(IndexOfAccount),
29}
30
31struct Serializer {
32    buffer: AlignedMemory<HOST_ALIGN>,
33    regions: Vec<MemoryRegion>,
34    vaddr: u64,
35    region_start: usize,
36    aligned: bool,
37    copy_account_data: bool,
38}
39
40impl Serializer {
41    fn new(size: usize, start_addr: u64, aligned: bool, copy_account_data: bool) -> Serializer {
42        Serializer {
43            buffer: AlignedMemory::with_capacity(size),
44            regions: Vec::new(),
45            region_start: 0,
46            vaddr: start_addr,
47            aligned,
48            copy_account_data,
49        }
50    }
51
52    fn fill_write(&mut self, num: usize, value: u8) -> std::io::Result<()> {
53        self.buffer.fill_write(num, value)
54    }
55
56    fn write<T: Pod>(&mut self, value: T) -> u64 {
57        self.debug_assert_alignment::<T>();
58        let vaddr = self
59            .vaddr
60            .saturating_add(self.buffer.len() as u64)
61            .saturating_sub(self.region_start as u64);
62        // Safety:
63        // in serialize_parameters_(aligned|unaligned) first we compute the
64        // required size then we write into the newly allocated buffer. There's
65        // no need to check bounds at every write.
66        //
67        // AlignedMemory::write_unchecked _does_ debug_assert!() that the capacity
68        // is enough, so in the unlikely case we introduce a bug in the size
69        // computation, tests will abort.
70        unsafe {
71            self.buffer.write_unchecked(value);
72        }
73
74        vaddr
75    }
76
77    fn write_all(&mut self, value: &[u8]) -> u64 {
78        let vaddr = self
79            .vaddr
80            .saturating_add(self.buffer.len() as u64)
81            .saturating_sub(self.region_start as u64);
82        // Safety:
83        // see write() - the buffer is guaranteed to be large enough
84        unsafe {
85            self.buffer.write_all_unchecked(value);
86        }
87
88        vaddr
89    }
90
91    fn write_account(
92        &mut self,
93        account: &mut BorrowedAccount<'_>,
94    ) -> Result<u64, InstructionError> {
95        let vm_data_addr = if self.copy_account_data {
96            let vm_data_addr = self.vaddr.saturating_add(self.buffer.len() as u64);
97            self.write_all(account.get_data());
98            vm_data_addr
99        } else {
100            self.push_region(true);
101            let vaddr = self.vaddr;
102            if !account.get_data().is_empty() {
103                let writable = account.can_data_be_changed().is_ok();
104                let shared = account.is_shared();
105                let mut new_region = if writable && !shared {
106                    MemoryRegion::new_writable(account.get_data_mut()?, self.vaddr)
107                } else {
108                    MemoryRegion::new_readonly(account.get_data(), self.vaddr)
109                };
110                if writable && shared {
111                    new_region.cow_callback_payload = account.get_index_in_transaction() as u32;
112                }
113                self.vaddr += new_region.len;
114                self.regions.push(new_region);
115            }
116            vaddr
117        };
118
119        if self.aligned {
120            let align_offset =
121                (account.get_data().len() as *const u8).align_offset(BPF_ALIGN_OF_U128);
122            if self.copy_account_data {
123                self.fill_write(MAX_PERMITTED_DATA_INCREASE + align_offset, 0)
124                    .map_err(|_| InstructionError::InvalidArgument)?;
125            } else {
126                // The deserialization code is going to align the vm_addr to
127                // BPF_ALIGN_OF_U128. Always add one BPF_ALIGN_OF_U128 worth of
128                // padding and shift the start of the next region, so that once
129                // vm_addr is aligned, the corresponding host_addr is aligned
130                // too.
131                self.fill_write(MAX_PERMITTED_DATA_INCREASE + BPF_ALIGN_OF_U128, 0)
132                    .map_err(|_| InstructionError::InvalidArgument)?;
133                self.region_start += BPF_ALIGN_OF_U128.saturating_sub(align_offset);
134                // put the realloc padding in its own region
135                self.push_region(account.can_data_be_changed().is_ok());
136            }
137        }
138
139        Ok(vm_data_addr)
140    }
141
142    fn push_region(&mut self, writable: bool) {
143        let range = self.region_start..self.buffer.len();
144        let region = if writable {
145            MemoryRegion::new_writable(
146                self.buffer.as_slice_mut().get_mut(range.clone()).unwrap(),
147                self.vaddr,
148            )
149        } else {
150            MemoryRegion::new_readonly(
151                self.buffer.as_slice().get(range.clone()).unwrap(),
152                self.vaddr,
153            )
154        };
155        self.regions.push(region);
156        self.region_start = range.end;
157        self.vaddr += range.len() as u64;
158    }
159
160    fn finish(mut self) -> (AlignedMemory<HOST_ALIGN>, Vec<MemoryRegion>) {
161        self.push_region(true);
162        debug_assert_eq!(self.region_start, self.buffer.len());
163        (self.buffer, self.regions)
164    }
165
166    fn debug_assert_alignment<T>(&self) {
167        debug_assert!(
168            !self.aligned
169                || self
170                    .buffer
171                    .as_slice()
172                    .as_ptr_range()
173                    .end
174                    .align_offset(mem::align_of::<T>())
175                    == 0
176        );
177    }
178}
179
180pub fn serialize_parameters(
181    transaction_context: &TransactionContext,
182    instruction_context: &InstructionContext,
183    copy_account_data: bool,
184    mask_out_rent_epoch_in_vm_serialization: bool,
185) -> Result<
186    (
187        AlignedMemory<HOST_ALIGN>,
188        Vec<MemoryRegion>,
189        Vec<SerializedAccountMetadata>,
190    ),
191    InstructionError,
192> {
193    let num_ix_accounts = instruction_context.get_number_of_instruction_accounts();
194    if num_ix_accounts > MAX_INSTRUCTION_ACCOUNTS as IndexOfAccount {
195        return Err(InstructionError::MaxAccountsExceeded);
196    }
197
198    let (program_id, is_loader_deprecated) = {
199        let program_account =
200            instruction_context.try_borrow_last_program_account(transaction_context)?;
201        (
202            *program_account.get_key(),
203            *program_account.get_owner() == bpf_loader_deprecated::id(),
204        )
205    };
206
207    let accounts = (0..instruction_context.get_number_of_instruction_accounts())
208        .map(|instruction_account_index| {
209            if let Some(index) = instruction_context
210                .is_instruction_account_duplicate(instruction_account_index)
211                .unwrap()
212            {
213                SerializeAccount::Duplicate(index)
214            } else {
215                let account = instruction_context
216                    .try_borrow_instruction_account(transaction_context, instruction_account_index)
217                    .unwrap();
218                SerializeAccount::Account(instruction_account_index, account)
219            }
220        })
221        // fun fact: jemalloc is good at caching tiny allocations like this one,
222        // so collecting here is actually faster than passing the iterator
223        // around, since the iterator does the work to produce its items each
224        // time it's iterated on.
225        .collect::<Vec<_>>();
226
227    if is_loader_deprecated {
228        serialize_parameters_unaligned(
229            accounts,
230            instruction_context.get_instruction_data(),
231            &program_id,
232            copy_account_data,
233            mask_out_rent_epoch_in_vm_serialization,
234        )
235    } else {
236        serialize_parameters_aligned(
237            accounts,
238            instruction_context.get_instruction_data(),
239            &program_id,
240            copy_account_data,
241            mask_out_rent_epoch_in_vm_serialization,
242        )
243    }
244}
245
246pub fn deserialize_parameters(
247    transaction_context: &TransactionContext,
248    instruction_context: &InstructionContext,
249    copy_account_data: bool,
250    buffer: &[u8],
251    accounts_metadata: &[SerializedAccountMetadata],
252) -> Result<(), InstructionError> {
253    let is_loader_deprecated = *instruction_context
254        .try_borrow_last_program_account(transaction_context)?
255        .get_owner()
256        == bpf_loader_deprecated::id();
257    let account_lengths = accounts_metadata.iter().map(|a| a.original_data_len);
258    if is_loader_deprecated {
259        deserialize_parameters_unaligned(
260            transaction_context,
261            instruction_context,
262            copy_account_data,
263            buffer,
264            account_lengths,
265        )
266    } else {
267        deserialize_parameters_aligned(
268            transaction_context,
269            instruction_context,
270            copy_account_data,
271            buffer,
272            account_lengths,
273        )
274    }
275}
276
277fn serialize_parameters_unaligned(
278    accounts: Vec<SerializeAccount>,
279    instruction_data: &[u8],
280    program_id: &Pubkey,
281    copy_account_data: bool,
282    mask_out_rent_epoch_in_vm_serialization: bool,
283) -> Result<
284    (
285        AlignedMemory<HOST_ALIGN>,
286        Vec<MemoryRegion>,
287        Vec<SerializedAccountMetadata>,
288    ),
289    InstructionError,
290> {
291    // Calculate size in order to alloc once
292    let mut size = size_of::<u64>();
293    for account in &accounts {
294        size += 1; // dup
295        match account {
296            SerializeAccount::Duplicate(_) => {}
297            SerializeAccount::Account(_, account) => {
298                size += size_of::<u8>() // is_signer
299                + size_of::<u8>() // is_writable
300                + size_of::<Pubkey>() // key
301                + size_of::<u64>()  // lamports
302                + size_of::<u64>()  // data len
303                + size_of::<Pubkey>() // owner
304                + size_of::<u8>() // executable
305                + size_of::<u64>(); // rent_epoch
306                if copy_account_data {
307                    size += account.get_data().len();
308                }
309            }
310        }
311    }
312    size += size_of::<u64>() // instruction data len
313         + instruction_data.len() // instruction data
314         + size_of::<Pubkey>(); // program id
315
316    let mut s = Serializer::new(size, MM_INPUT_START, false, copy_account_data);
317
318    let mut accounts_metadata: Vec<SerializedAccountMetadata> = Vec::with_capacity(accounts.len());
319    s.write::<u64>((accounts.len() as u64).to_le());
320    for account in accounts {
321        match account {
322            SerializeAccount::Duplicate(position) => {
323                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
324                s.write(position as u8);
325            }
326            SerializeAccount::Account(_, mut account) => {
327                s.write::<u8>(NON_DUP_MARKER);
328                s.write::<u8>(account.is_signer() as u8);
329                s.write::<u8>(account.is_writable() as u8);
330                let vm_key_addr = s.write_all(account.get_key().as_ref());
331                let vm_lamports_addr = s.write::<u64>(account.get_lamports().to_le());
332                s.write::<u64>((account.get_data().len() as u64).to_le());
333                let vm_data_addr = s.write_account(&mut account)?;
334                let vm_owner_addr = s.write_all(account.get_owner().as_ref());
335                #[allow(deprecated)]
336                s.write::<u8>(account.is_executable() as u8);
337                let rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
338                    u64::MAX
339                } else {
340                    account.get_rent_epoch()
341                };
342                s.write::<u64>(rent_epoch.to_le());
343                accounts_metadata.push(SerializedAccountMetadata {
344                    original_data_len: account.get_data().len(),
345                    vm_key_addr,
346                    vm_lamports_addr,
347                    vm_owner_addr,
348                    vm_data_addr,
349                });
350            }
351        };
352    }
353    s.write::<u64>((instruction_data.len() as u64).to_le());
354    s.write_all(instruction_data);
355    s.write_all(program_id.as_ref());
356
357    let (mem, regions) = s.finish();
358    Ok((mem, regions, accounts_metadata))
359}
360
361fn deserialize_parameters_unaligned<I: IntoIterator<Item = usize>>(
362    transaction_context: &TransactionContext,
363    instruction_context: &InstructionContext,
364    copy_account_data: bool,
365    buffer: &[u8],
366    account_lengths: I,
367) -> Result<(), InstructionError> {
368    let mut start = size_of::<u64>(); // number of accounts
369    for (instruction_account_index, pre_len) in (0..instruction_context
370        .get_number_of_instruction_accounts())
371        .zip(account_lengths.into_iter())
372    {
373        let duplicate =
374            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
375        start += 1; // is_dup
376        if duplicate.is_none() {
377            let mut borrowed_account = instruction_context
378                .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
379            start += size_of::<u8>(); // is_signer
380            start += size_of::<u8>(); // is_writable
381            start += size_of::<Pubkey>(); // key
382            let lamports = buffer
383                .get(start..start.saturating_add(8))
384                .map(<[u8; 8]>::try_from)
385                .and_then(Result::ok)
386                .map(u64::from_le_bytes)
387                .ok_or(InstructionError::InvalidArgument)?;
388            if borrowed_account.get_lamports() != lamports {
389                borrowed_account.set_lamports(lamports)?;
390            }
391            start += size_of::<u64>() // lamports
392                + size_of::<u64>(); // data length
393            if copy_account_data {
394                let data = buffer
395                    .get(start..start + pre_len)
396                    .ok_or(InstructionError::InvalidArgument)?;
397                // The redundant check helps to avoid the expensive data comparison if we can
398                match borrowed_account.can_data_be_resized(data.len()) {
399                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
400                    Err(err) if borrowed_account.get_data() != data => return Err(err),
401                    _ => {}
402                }
403                start += pre_len; // data
404            }
405            start += size_of::<Pubkey>() // owner
406                + size_of::<u8>() // executable
407                + size_of::<u64>(); // rent_epoch
408        }
409    }
410    Ok(())
411}
412
413fn serialize_parameters_aligned(
414    accounts: Vec<SerializeAccount>,
415    instruction_data: &[u8],
416    program_id: &Pubkey,
417    copy_account_data: bool,
418    mask_out_rent_epoch_in_vm_serialization: bool,
419) -> Result<
420    (
421        AlignedMemory<HOST_ALIGN>,
422        Vec<MemoryRegion>,
423        Vec<SerializedAccountMetadata>,
424    ),
425    InstructionError,
426> {
427    let mut accounts_metadata = Vec::with_capacity(accounts.len());
428    // Calculate size in order to alloc once
429    let mut size = size_of::<u64>();
430    for account in &accounts {
431        size += 1; // dup
432        match account {
433            SerializeAccount::Duplicate(_) => size += 7, // padding to 64-bit aligned
434            SerializeAccount::Account(_, account) => {
435                let data_len = account.get_data().len();
436                size += size_of::<u8>() // is_signer
437                + size_of::<u8>() // is_writable
438                + size_of::<u8>() // executable
439                + size_of::<u32>() // original_data_len
440                + size_of::<Pubkey>()  // key
441                + size_of::<Pubkey>() // owner
442                + size_of::<u64>()  // lamports
443                + size_of::<u64>()  // data len
444                + MAX_PERMITTED_DATA_INCREASE
445                + size_of::<u64>(); // rent epoch
446                if copy_account_data {
447                    size += data_len + (data_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
448                } else {
449                    size += BPF_ALIGN_OF_U128;
450                }
451            }
452        }
453    }
454    size += size_of::<u64>() // data len
455    + instruction_data.len()
456    + size_of::<Pubkey>(); // program id;
457
458    let mut s = Serializer::new(size, MM_INPUT_START, true, copy_account_data);
459
460    // Serialize into the buffer
461    s.write::<u64>((accounts.len() as u64).to_le());
462    for account in accounts {
463        match account {
464            SerializeAccount::Account(_, mut borrowed_account) => {
465                s.write::<u8>(NON_DUP_MARKER);
466                s.write::<u8>(borrowed_account.is_signer() as u8);
467                s.write::<u8>(borrowed_account.is_writable() as u8);
468                #[allow(deprecated)]
469                s.write::<u8>(borrowed_account.is_executable() as u8);
470                s.write_all(&[0u8, 0, 0, 0]);
471                let vm_key_addr = s.write_all(borrowed_account.get_key().as_ref());
472                let vm_owner_addr = s.write_all(borrowed_account.get_owner().as_ref());
473                let vm_lamports_addr = s.write::<u64>(borrowed_account.get_lamports().to_le());
474                s.write::<u64>((borrowed_account.get_data().len() as u64).to_le());
475                let vm_data_addr = s.write_account(&mut borrowed_account)?;
476                let rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
477                    u64::MAX
478                } else {
479                    borrowed_account.get_rent_epoch()
480                };
481                s.write::<u64>(rent_epoch.to_le());
482                accounts_metadata.push(SerializedAccountMetadata {
483                    original_data_len: borrowed_account.get_data().len(),
484                    vm_key_addr,
485                    vm_owner_addr,
486                    vm_lamports_addr,
487                    vm_data_addr,
488                });
489            }
490            SerializeAccount::Duplicate(position) => {
491                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
492                s.write::<u8>(position as u8);
493                s.write_all(&[0u8, 0, 0, 0, 0, 0, 0]);
494            }
495        };
496    }
497    s.write::<u64>((instruction_data.len() as u64).to_le());
498    s.write_all(instruction_data);
499    s.write_all(program_id.as_ref());
500
501    let (mem, regions) = s.finish();
502    Ok((mem, regions, accounts_metadata))
503}
504
505fn deserialize_parameters_aligned<I: IntoIterator<Item = usize>>(
506    transaction_context: &TransactionContext,
507    instruction_context: &InstructionContext,
508    copy_account_data: bool,
509    buffer: &[u8],
510    account_lengths: I,
511) -> Result<(), InstructionError> {
512    let mut start = size_of::<u64>(); // number of accounts
513    for (instruction_account_index, pre_len) in (0..instruction_context
514        .get_number_of_instruction_accounts())
515        .zip(account_lengths.into_iter())
516    {
517        let duplicate =
518            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
519        start += size_of::<u8>(); // position
520        if duplicate.is_some() {
521            start += 7; // padding to 64-bit aligned
522        } else {
523            let mut borrowed_account = instruction_context
524                .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
525            start += size_of::<u8>() // is_signer
526                + size_of::<u8>() // is_writable
527                + size_of::<u8>() // executable
528                + size_of::<u32>() // original_data_len
529                + size_of::<Pubkey>(); // key
530            let owner = buffer
531                .get(start..start + size_of::<Pubkey>())
532                .ok_or(InstructionError::InvalidArgument)?;
533            start += size_of::<Pubkey>(); // owner
534            let lamports = buffer
535                .get(start..start.saturating_add(8))
536                .map(<[u8; 8]>::try_from)
537                .and_then(Result::ok)
538                .map(u64::from_le_bytes)
539                .ok_or(InstructionError::InvalidArgument)?;
540            if borrowed_account.get_lamports() != lamports {
541                borrowed_account.set_lamports(lamports)?;
542            }
543            start += size_of::<u64>(); // lamports
544            let post_len = buffer
545                .get(start..start.saturating_add(8))
546                .map(<[u8; 8]>::try_from)
547                .and_then(Result::ok)
548                .map(u64::from_le_bytes)
549                .ok_or(InstructionError::InvalidArgument)? as usize;
550            start += size_of::<u64>(); // data length
551            if post_len.saturating_sub(pre_len) > MAX_PERMITTED_DATA_INCREASE
552                || post_len > MAX_PERMITTED_DATA_LENGTH as usize
553            {
554                return Err(InstructionError::InvalidRealloc);
555            }
556            // The redundant check helps to avoid the expensive data comparison if we can
557            let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
558            if copy_account_data {
559                let data = buffer
560                    .get(start..start + post_len)
561                    .ok_or(InstructionError::InvalidArgument)?;
562                match borrowed_account.can_data_be_resized(post_len) {
563                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
564                    Err(err) if borrowed_account.get_data() != data => return Err(err),
565                    _ => {}
566                }
567                start += pre_len; // data
568            } else {
569                // See Serializer::write_account() as to why we have this
570                // padding before the realloc region here.
571                start += BPF_ALIGN_OF_U128.saturating_sub(alignment_offset);
572                let data = buffer
573                    .get(start..start + MAX_PERMITTED_DATA_INCREASE)
574                    .ok_or(InstructionError::InvalidArgument)?;
575                match borrowed_account.can_data_be_resized(post_len) {
576                    Ok(()) => {
577                        borrowed_account.set_data_length(post_len)?;
578                        let allocated_bytes = post_len.saturating_sub(pre_len);
579                        if allocated_bytes > 0 {
580                            borrowed_account
581                                .get_data_mut()?
582                                .get_mut(pre_len..pre_len.saturating_add(allocated_bytes))
583                                .ok_or(InstructionError::InvalidArgument)?
584                                .copy_from_slice(
585                                    data.get(0..allocated_bytes)
586                                        .ok_or(InstructionError::InvalidArgument)?,
587                                );
588                        }
589                    }
590                    Err(err) if borrowed_account.get_data().len() != post_len => return Err(err),
591                    _ => {}
592                }
593            }
594            start += MAX_PERMITTED_DATA_INCREASE;
595            start += alignment_offset;
596            start += size_of::<u64>(); // rent_epoch
597            if borrowed_account.get_owner().to_bytes() != owner {
598                // Change the owner at the end so that we are allowed to change the lamports and data before
599                borrowed_account.set_owner(owner)?;
600            }
601        }
602    }
603    Ok(())
604}
605
606#[cfg(test)]
607#[allow(clippy::indexing_slicing)]
608mod tests {
609    use {
610        super::*,
611        crate::with_mock_invoke_context,
612        solana_account::{Account, AccountSharedData, WritableAccount},
613        solana_account_info::AccountInfo,
614        solana_program_entrypoint::deserialize,
615        solana_sdk_ids::bpf_loader,
616        solana_transaction_context::InstructionAccount,
617        std::{
618            cell::RefCell,
619            mem::transmute,
620            rc::Rc,
621            slice::{self, from_raw_parts, from_raw_parts_mut},
622        },
623    };
624
625    fn deduplicated_instruction_accounts(
626        transaction_indexes: &[IndexOfAccount],
627        is_writable: fn(usize) -> bool,
628    ) -> Vec<InstructionAccount> {
629        transaction_indexes
630            .iter()
631            .enumerate()
632            .map(|(index_in_instruction, index_in_transaction)| {
633                let index_in_callee = transaction_indexes
634                    .get(0..index_in_instruction)
635                    .unwrap()
636                    .iter()
637                    .position(|account_index| account_index == index_in_transaction)
638                    .unwrap_or(index_in_instruction);
639                InstructionAccount {
640                    index_in_transaction: *index_in_transaction,
641                    index_in_caller: *index_in_transaction,
642                    index_in_callee: index_in_callee as IndexOfAccount,
643                    is_signer: false,
644                    is_writable: is_writable(index_in_instruction),
645                }
646            })
647            .collect()
648    }
649
650    #[test]
651    fn test_serialize_parameters_with_many_accounts() {
652        struct TestCase {
653            num_ix_accounts: usize,
654            append_dup_account: bool,
655            expected_err: Option<InstructionError>,
656            name: &'static str,
657        }
658
659        for copy_account_data in [true] {
660            for TestCase {
661                num_ix_accounts,
662                append_dup_account,
663                expected_err,
664                name,
665            } in [
666                TestCase {
667                    name: "serialize max accounts with cap",
668                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
669                    append_dup_account: false,
670                    expected_err: None,
671                },
672                TestCase {
673                    name: "serialize too many accounts with cap",
674                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS) + 1,
675                    append_dup_account: false,
676                    expected_err: Some(InstructionError::MaxAccountsExceeded),
677                },
678                TestCase {
679                    name: "serialize too many accounts and append dup with cap",
680                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
681                    append_dup_account: true,
682                    expected_err: Some(InstructionError::MaxAccountsExceeded),
683                },
684            ] {
685                let program_id = solana_pubkey::new_rand();
686                let mut transaction_accounts = vec![(
687                    program_id,
688                    AccountSharedData::from(Account {
689                        lamports: 0,
690                        data: vec![],
691                        owner: bpf_loader::id(),
692                        executable: true,
693                        rent_epoch: 0,
694                    }),
695                )];
696                for _ in 0..num_ix_accounts {
697                    transaction_accounts.push((
698                        Pubkey::new_unique(),
699                        AccountSharedData::from(Account {
700                            lamports: 0,
701                            data: vec![],
702                            owner: program_id,
703                            executable: false,
704                            rent_epoch: 0,
705                        }),
706                    ));
707                }
708
709                let transaction_accounts_indexes: Vec<IndexOfAccount> =
710                    (1..(num_ix_accounts + 1) as u16).collect();
711                let mut instruction_accounts =
712                    deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false);
713                if append_dup_account {
714                    instruction_accounts.push(instruction_accounts.last().cloned().unwrap());
715                }
716                let program_indices = [0];
717                let instruction_data = vec![];
718
719                with_mock_invoke_context!(
720                    invoke_context,
721                    transaction_context,
722                    transaction_accounts
723                );
724                invoke_context
725                    .transaction_context
726                    .get_next_instruction_context()
727                    .unwrap()
728                    .configure(&program_indices, &instruction_accounts, &instruction_data);
729                invoke_context.push().unwrap();
730                let instruction_context = invoke_context
731                    .transaction_context
732                    .get_current_instruction_context()
733                    .unwrap();
734
735                let serialization_result = serialize_parameters(
736                    invoke_context.transaction_context,
737                    instruction_context,
738                    copy_account_data,
739                    true, // mask_out_rent_epoch_in_vm_serialization
740                );
741                assert_eq!(
742                    serialization_result.as_ref().err(),
743                    expected_err.as_ref(),
744                    "{name} test case failed",
745                );
746                if expected_err.is_some() {
747                    continue;
748                }
749
750                let (mut serialized, regions, _account_lengths) = serialization_result.unwrap();
751                let mut serialized_regions = concat_regions(&regions);
752                let (de_program_id, de_accounts, de_instruction_data) = unsafe {
753                    deserialize(
754                        if copy_account_data {
755                            serialized.as_slice_mut()
756                        } else {
757                            serialized_regions.as_slice_mut()
758                        }
759                        .first_mut()
760                        .unwrap() as *mut u8,
761                    )
762                };
763                assert_eq!(de_program_id, &program_id);
764                assert_eq!(de_instruction_data, &instruction_data);
765                for account_info in de_accounts {
766                    let index_in_transaction = invoke_context
767                        .transaction_context
768                        .find_index_of_account(account_info.key)
769                        .unwrap();
770                    let account = invoke_context
771                        .transaction_context
772                        .accounts()
773                        .try_borrow(index_in_transaction)
774                        .unwrap();
775                    assert_eq!(account.lamports(), account_info.lamports());
776                    assert_eq!(account.data(), &account_info.data.borrow()[..]);
777                    assert_eq!(account.owner(), account_info.owner);
778                    assert_eq!(account.executable(), account_info.executable);
779                    assert_eq!(u64::MAX, account_info.rent_epoch);
780                }
781            }
782        }
783    }
784
785    #[test]
786    fn test_serialize_parameters() {
787        for copy_account_data in [false, true] {
788            let program_id = solana_pubkey::new_rand();
789            let transaction_accounts = vec![
790                (
791                    program_id,
792                    AccountSharedData::from(Account {
793                        lamports: 0,
794                        data: vec![],
795                        owner: bpf_loader::id(),
796                        executable: true,
797                        rent_epoch: 0,
798                    }),
799                ),
800                (
801                    solana_pubkey::new_rand(),
802                    AccountSharedData::from(Account {
803                        lamports: 1,
804                        data: vec![1u8, 2, 3, 4, 5],
805                        owner: bpf_loader::id(),
806                        executable: false,
807                        rent_epoch: 100,
808                    }),
809                ),
810                (
811                    solana_pubkey::new_rand(),
812                    AccountSharedData::from(Account {
813                        lamports: 2,
814                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
815                        owner: bpf_loader::id(),
816                        executable: true,
817                        rent_epoch: 200,
818                    }),
819                ),
820                (
821                    solana_pubkey::new_rand(),
822                    AccountSharedData::from(Account {
823                        lamports: 3,
824                        data: vec![],
825                        owner: bpf_loader::id(),
826                        executable: false,
827                        rent_epoch: 3100,
828                    }),
829                ),
830                (
831                    solana_pubkey::new_rand(),
832                    AccountSharedData::from(Account {
833                        lamports: 4,
834                        data: vec![1u8, 2, 3, 4, 5],
835                        owner: bpf_loader::id(),
836                        executable: false,
837                        rent_epoch: 100,
838                    }),
839                ),
840                (
841                    solana_pubkey::new_rand(),
842                    AccountSharedData::from(Account {
843                        lamports: 5,
844                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
845                        owner: bpf_loader::id(),
846                        executable: true,
847                        rent_epoch: 200,
848                    }),
849                ),
850                (
851                    solana_pubkey::new_rand(),
852                    AccountSharedData::from(Account {
853                        lamports: 6,
854                        data: vec![],
855                        owner: bpf_loader::id(),
856                        executable: false,
857                        rent_epoch: 3100,
858                    }),
859                ),
860            ];
861            let instruction_accounts =
862                deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
863            let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
864            let program_indices = [0];
865            let mut original_accounts = transaction_accounts.clone();
866            with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
867            invoke_context
868                .transaction_context
869                .get_next_instruction_context()
870                .unwrap()
871                .configure(&program_indices, &instruction_accounts, &instruction_data);
872            invoke_context.push().unwrap();
873            let instruction_context = invoke_context
874                .transaction_context
875                .get_current_instruction_context()
876                .unwrap();
877
878            // check serialize_parameters_aligned
879            let (mut serialized, regions, accounts_metadata) = serialize_parameters(
880                invoke_context.transaction_context,
881                instruction_context,
882                copy_account_data,
883                true, // mask_out_rent_epoch_in_vm_serialization
884            )
885            .unwrap();
886
887            let mut serialized_regions = concat_regions(&regions);
888            if copy_account_data {
889                assert_eq!(serialized.as_slice(), serialized_regions.as_slice());
890            }
891            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
892                deserialize(
893                    if copy_account_data {
894                        serialized.as_slice_mut()
895                    } else {
896                        serialized_regions.as_slice_mut()
897                    }
898                    .first_mut()
899                    .unwrap() as *mut u8,
900                )
901            };
902
903            assert_eq!(&program_id, de_program_id);
904            assert_eq!(instruction_data, de_instruction_data);
905            assert_eq!(
906                (de_instruction_data.first().unwrap() as *const u8).align_offset(BPF_ALIGN_OF_U128),
907                0
908            );
909            for account_info in de_accounts {
910                let index_in_transaction = invoke_context
911                    .transaction_context
912                    .find_index_of_account(account_info.key)
913                    .unwrap();
914                let account = invoke_context
915                    .transaction_context
916                    .accounts()
917                    .try_borrow(index_in_transaction)
918                    .unwrap();
919                assert_eq!(account.lamports(), account_info.lamports());
920                assert_eq!(account.data(), &account_info.data.borrow()[..]);
921                assert_eq!(account.owner(), account_info.owner);
922                assert_eq!(account.executable(), account_info.executable);
923                assert_eq!(u64::MAX, account_info.rent_epoch);
924
925                assert_eq!(
926                    (*account_info.lamports.borrow() as *const u64).align_offset(BPF_ALIGN_OF_U128),
927                    0
928                );
929                assert_eq!(
930                    account_info
931                        .data
932                        .borrow()
933                        .as_ptr()
934                        .align_offset(BPF_ALIGN_OF_U128),
935                    0
936                );
937            }
938
939            deserialize_parameters(
940                invoke_context.transaction_context,
941                instruction_context,
942                copy_account_data,
943                serialized.as_slice(),
944                &accounts_metadata,
945            )
946            .unwrap();
947            for (index_in_transaction, (_key, original_account)) in
948                original_accounts.iter().enumerate()
949            {
950                let account = invoke_context
951                    .transaction_context
952                    .accounts()
953                    .try_borrow(index_in_transaction as IndexOfAccount)
954                    .unwrap();
955                assert_eq!(&*account, original_account);
956            }
957
958            // check serialize_parameters_unaligned
959            original_accounts
960                .first_mut()
961                .unwrap()
962                .1
963                .set_owner(bpf_loader_deprecated::id());
964            invoke_context
965                .transaction_context
966                .get_account_at_index(0)
967                .unwrap()
968                .try_borrow_mut()
969                .unwrap()
970                .set_owner(bpf_loader_deprecated::id());
971
972            let (mut serialized, regions, account_lengths) = serialize_parameters(
973                invoke_context.transaction_context,
974                instruction_context,
975                copy_account_data,
976                true, // mask_out_rent_epoch_in_vm_serialization
977            )
978            .unwrap();
979            let mut serialized_regions = concat_regions(&regions);
980
981            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
982                deserialize_unaligned(
983                    if copy_account_data {
984                        serialized.as_slice_mut()
985                    } else {
986                        serialized_regions.as_slice_mut()
987                    }
988                    .first_mut()
989                    .unwrap() as *mut u8,
990                )
991            };
992            assert_eq!(&program_id, de_program_id);
993            assert_eq!(instruction_data, de_instruction_data);
994            for account_info in de_accounts {
995                let index_in_transaction = invoke_context
996                    .transaction_context
997                    .find_index_of_account(account_info.key)
998                    .unwrap();
999                let account = invoke_context
1000                    .transaction_context
1001                    .accounts()
1002                    .try_borrow(index_in_transaction)
1003                    .unwrap();
1004                assert_eq!(account.lamports(), account_info.lamports());
1005                assert_eq!(account.data(), &account_info.data.borrow()[..]);
1006                assert_eq!(account.owner(), account_info.owner);
1007                assert_eq!(account.executable(), account_info.executable);
1008                assert_eq!(u64::MAX, account_info.rent_epoch);
1009            }
1010
1011            deserialize_parameters(
1012                invoke_context.transaction_context,
1013                instruction_context,
1014                copy_account_data,
1015                serialized.as_slice(),
1016                &account_lengths,
1017            )
1018            .unwrap();
1019            for (index_in_transaction, (_key, original_account)) in
1020                original_accounts.iter().enumerate()
1021            {
1022                let account = invoke_context
1023                    .transaction_context
1024                    .accounts()
1025                    .try_borrow(index_in_transaction as IndexOfAccount)
1026                    .unwrap();
1027                assert_eq!(&*account, original_account);
1028            }
1029        }
1030    }
1031
1032    #[test]
1033    fn test_serialize_parameters_mask_out_rent_epoch_in_vm_serialization() {
1034        for mask_out_rent_epoch_in_vm_serialization in [false, true] {
1035            let transaction_accounts = vec![
1036                (
1037                    solana_pubkey::new_rand(),
1038                    AccountSharedData::from(Account {
1039                        lamports: 0,
1040                        data: vec![],
1041                        owner: bpf_loader::id(),
1042                        executable: true,
1043                        rent_epoch: 0,
1044                    }),
1045                ),
1046                (
1047                    solana_pubkey::new_rand(),
1048                    AccountSharedData::from(Account {
1049                        lamports: 1,
1050                        data: vec![1u8, 2, 3, 4, 5],
1051                        owner: bpf_loader::id(),
1052                        executable: false,
1053                        rent_epoch: 100,
1054                    }),
1055                ),
1056                (
1057                    solana_pubkey::new_rand(),
1058                    AccountSharedData::from(Account {
1059                        lamports: 2,
1060                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1061                        owner: bpf_loader::id(),
1062                        executable: true,
1063                        rent_epoch: 200,
1064                    }),
1065                ),
1066                (
1067                    solana_pubkey::new_rand(),
1068                    AccountSharedData::from(Account {
1069                        lamports: 3,
1070                        data: vec![],
1071                        owner: bpf_loader::id(),
1072                        executable: false,
1073                        rent_epoch: 300,
1074                    }),
1075                ),
1076                (
1077                    solana_pubkey::new_rand(),
1078                    AccountSharedData::from(Account {
1079                        lamports: 4,
1080                        data: vec![1u8, 2, 3, 4, 5],
1081                        owner: bpf_loader::id(),
1082                        executable: false,
1083                        rent_epoch: 100,
1084                    }),
1085                ),
1086                (
1087                    solana_pubkey::new_rand(),
1088                    AccountSharedData::from(Account {
1089                        lamports: 5,
1090                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1091                        owner: bpf_loader::id(),
1092                        executable: true,
1093                        rent_epoch: 200,
1094                    }),
1095                ),
1096                (
1097                    solana_pubkey::new_rand(),
1098                    AccountSharedData::from(Account {
1099                        lamports: 6,
1100                        data: vec![],
1101                        owner: bpf_loader::id(),
1102                        executable: false,
1103                        rent_epoch: 3100,
1104                    }),
1105                ),
1106            ];
1107            let instruction_accounts =
1108                deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
1109            let instruction_data = vec![];
1110            let program_indices = [0];
1111            let mut original_accounts = transaction_accounts.clone();
1112            with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1113            invoke_context
1114                .transaction_context
1115                .get_next_instruction_context()
1116                .unwrap()
1117                .configure(&program_indices, &instruction_accounts, &instruction_data);
1118            invoke_context.push().unwrap();
1119            let instruction_context = invoke_context
1120                .transaction_context
1121                .get_current_instruction_context()
1122                .unwrap();
1123
1124            // check serialize_parameters_aligned
1125            let (_serialized, regions, _accounts_metadata) = serialize_parameters(
1126                invoke_context.transaction_context,
1127                instruction_context,
1128                true,
1129                mask_out_rent_epoch_in_vm_serialization,
1130            )
1131            .unwrap();
1132
1133            let mut serialized_regions = concat_regions(&regions);
1134            let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1135                deserialize(serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8)
1136            };
1137
1138            for account_info in de_accounts {
1139                let index_in_transaction = invoke_context
1140                    .transaction_context
1141                    .find_index_of_account(account_info.key)
1142                    .unwrap();
1143                let account = invoke_context
1144                    .transaction_context
1145                    .accounts()
1146                    .try_borrow(index_in_transaction)
1147                    .unwrap();
1148                let expected_rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
1149                    u64::MAX
1150                } else {
1151                    account.rent_epoch()
1152                };
1153                assert_eq!(expected_rent_epoch, account_info.rent_epoch);
1154            }
1155
1156            // check serialize_parameters_unaligned
1157            original_accounts
1158                .first_mut()
1159                .unwrap()
1160                .1
1161                .set_owner(bpf_loader_deprecated::id());
1162            invoke_context
1163                .transaction_context
1164                .get_account_at_index(0)
1165                .unwrap()
1166                .try_borrow_mut()
1167                .unwrap()
1168                .set_owner(bpf_loader_deprecated::id());
1169
1170            let (_serialized, regions, _account_lengths) = serialize_parameters(
1171                invoke_context.transaction_context,
1172                instruction_context,
1173                true,
1174                mask_out_rent_epoch_in_vm_serialization,
1175            )
1176            .unwrap();
1177            let mut serialized_regions = concat_regions(&regions);
1178
1179            let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1180                deserialize_unaligned(
1181                    serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8
1182                )
1183            };
1184            for account_info in de_accounts {
1185                let index_in_transaction = invoke_context
1186                    .transaction_context
1187                    .find_index_of_account(account_info.key)
1188                    .unwrap();
1189                let account = invoke_context
1190                    .transaction_context
1191                    .accounts()
1192                    .try_borrow(index_in_transaction)
1193                    .unwrap();
1194                let expected_rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
1195                    u64::MAX
1196                } else {
1197                    account.rent_epoch()
1198                };
1199                assert_eq!(expected_rent_epoch, account_info.rent_epoch);
1200            }
1201        }
1202    }
1203
1204    // the old bpf_loader in-program deserializer bpf_loader::id()
1205    #[deny(unsafe_op_in_unsafe_fn)]
1206    unsafe fn deserialize_unaligned<'a>(
1207        input: *mut u8,
1208    ) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
1209        // this boring boilerplate struct is needed until inline const...
1210        struct Ptr<T>(std::marker::PhantomData<T>);
1211        impl<T> Ptr<T> {
1212            const COULD_BE_UNALIGNED: bool = std::mem::align_of::<T>() > 1;
1213
1214            #[inline(always)]
1215            fn read_possibly_unaligned(input: *mut u8, offset: usize) -> T {
1216                unsafe {
1217                    let src = input.add(offset) as *const T;
1218                    if Self::COULD_BE_UNALIGNED {
1219                        src.read_unaligned()
1220                    } else {
1221                        src.read()
1222                    }
1223                }
1224            }
1225
1226            // rustc inserts debug_assert! for misaligned pointer dereferences when
1227            // deserializing, starting from [1]. so, use std::mem::transmute as the last resort
1228            // while preventing clippy from complaining to suggest not to use it.
1229            // [1]: https://github.com/rust-lang/rust/commit/22a7a19f9333bc1fcba97ce444a3515cb5fb33e6
1230            // as for the ub nature of the misaligned pointer dereference, this is
1231            // acceptable in this code, given that this is cfg(test) and it's cared only with
1232            // x86-64 and the target only incurs some performance penalty, not like segfaults
1233            // in other targets.
1234            #[inline(always)]
1235            fn ref_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a T {
1236                #[allow(clippy::transmute_ptr_to_ref)]
1237                unsafe {
1238                    transmute(input.add(offset) as *const T)
1239                }
1240            }
1241
1242            // See ref_possibly_unaligned's comment
1243            #[inline(always)]
1244            fn mut_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a mut T {
1245                #[allow(clippy::transmute_ptr_to_ref)]
1246                unsafe {
1247                    transmute(input.add(offset) as *mut T)
1248                }
1249            }
1250        }
1251
1252        let mut offset: usize = 0;
1253
1254        // number of accounts present
1255
1256        let num_accounts = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1257        offset += size_of::<u64>();
1258
1259        // account Infos
1260
1261        let mut accounts = Vec::with_capacity(num_accounts);
1262        for _ in 0..num_accounts {
1263            let dup_info = Ptr::<u8>::read_possibly_unaligned(input, offset);
1264            offset += size_of::<u8>();
1265            if dup_info == NON_DUP_MARKER {
1266                let is_signer = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1267                offset += size_of::<u8>();
1268
1269                let is_writable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1270                offset += size_of::<u8>();
1271
1272                let key = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1273                offset += size_of::<Pubkey>();
1274
1275                let lamports = Rc::new(RefCell::new(Ptr::mut_possibly_unaligned(input, offset)));
1276                offset += size_of::<u64>();
1277
1278                let data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1279                offset += size_of::<u64>();
1280
1281                let data = Rc::new(RefCell::new(unsafe {
1282                    from_raw_parts_mut(input.add(offset), data_len)
1283                }));
1284                offset += data_len;
1285
1286                let owner: &Pubkey = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1287                offset += size_of::<Pubkey>();
1288
1289                let executable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1290                offset += size_of::<u8>();
1291
1292                let rent_epoch = Ptr::<u64>::read_possibly_unaligned(input, offset);
1293                offset += size_of::<u64>();
1294
1295                accounts.push(AccountInfo {
1296                    key,
1297                    is_signer,
1298                    is_writable,
1299                    lamports,
1300                    data,
1301                    owner,
1302                    executable,
1303                    rent_epoch,
1304                });
1305            } else {
1306                // duplicate account, clone the original
1307                accounts.push(accounts.get(dup_info as usize).unwrap().clone());
1308            }
1309        }
1310
1311        // instruction data
1312
1313        let instruction_data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1314        offset += size_of::<u64>();
1315
1316        let instruction_data = unsafe { from_raw_parts(input.add(offset), instruction_data_len) };
1317        offset += instruction_data_len;
1318
1319        // program Id
1320
1321        let program_id = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1322
1323        (program_id, accounts, instruction_data)
1324    }
1325
1326    fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
1327        let len = regions.iter().fold(0, |len, region| len + region.len) as usize;
1328        let mut mem = AlignedMemory::zero_filled(len);
1329        for region in regions {
1330            let host_slice = unsafe {
1331                slice::from_raw_parts(region.host_addr.get() as *const u8, region.len as usize)
1332            };
1333            mem.as_slice_mut()[(region.vm_addr - MM_INPUT_START) as usize..][..region.len as usize]
1334                .copy_from_slice(host_slice)
1335        }
1336        mem
1337    }
1338}