miraland_bpf_loader_program/
serialization.rs

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