atlas_program_runtime/
serialization.rs

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