Skip to main content

rialo_s_bpf_loader_program/
serialization.rs

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