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