Skip to main content

solana_program_runtime/
serialization.rs

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