1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4 clone_solana_instruction::error::InstructionError,
5 clone_solana_program_entrypoint::{
6 BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, NON_DUP_MARKER,
7 },
8 clone_solana_program_runtime::invoke_context::SerializedAccountMetadata,
9 clone_solana_pubkey::Pubkey,
10 clone_solana_sdk_ids::bpf_loader_deprecated,
11 clone_solana_system_interface::MAX_PERMITTED_DATA_LENGTH,
12 clone_solana_transaction_context::{
13 BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
14 },
15 solana_sbpf::{
16 aligned_memory::{AlignedMemory, Pod},
17 ebpf::{HOST_ALIGN, MM_INPUT_START},
18 memory_region::{MemoryRegion, MemoryState},
19 },
20 std::mem::{self, size_of},
21};
22
23const MAX_INSTRUCTION_ACCOUNTS: u8 = NON_DUP_MARKER;
26
27#[allow(dead_code)]
28enum SerializeAccount<'a> {
29 Account(IndexOfAccount, BorrowedAccount<'a>),
30 Duplicate(IndexOfAccount),
31}
32
33struct Serializer {
34 buffer: AlignedMemory<HOST_ALIGN>,
35 regions: Vec<MemoryRegion>,
36 vaddr: u64,
37 region_start: usize,
38 aligned: bool,
39 copy_account_data: bool,
40}
41
42impl Serializer {
43 fn new(size: usize, start_addr: u64, aligned: bool, copy_account_data: bool) -> Serializer {
44 Serializer {
45 buffer: AlignedMemory::with_capacity(size),
46 regions: Vec::new(),
47 region_start: 0,
48 vaddr: start_addr,
49 aligned,
50 copy_account_data,
51 }
52 }
53
54 fn fill_write(&mut self, num: usize, value: u8) -> std::io::Result<()> {
55 self.buffer.fill_write(num, value)
56 }
57
58 fn write<T: Pod>(&mut self, value: T) -> u64 {
59 self.debug_assert_alignment::<T>();
60 let vaddr = self
61 .vaddr
62 .saturating_add(self.buffer.len() as u64)
63 .saturating_sub(self.region_start as u64);
64 unsafe {
73 self.buffer.write_unchecked(value);
74 }
75
76 vaddr
77 }
78
79 fn write_all(&mut self, value: &[u8]) -> u64 {
80 let vaddr = self
81 .vaddr
82 .saturating_add(self.buffer.len() as u64)
83 .saturating_sub(self.region_start as u64);
84 unsafe {
87 self.buffer.write_all_unchecked(value);
88 }
89
90 vaddr
91 }
92
93 fn write_account(
94 &mut self,
95 account: &mut BorrowedAccount<'_>,
96 ) -> Result<u64, InstructionError> {
97 let vm_data_addr = if self.copy_account_data {
98 let vm_data_addr = self.vaddr.saturating_add(self.buffer.len() as u64);
99 self.write_all(account.get_data());
100 vm_data_addr
101 } else {
102 self.push_region(true);
103 let vaddr = self.vaddr;
104 self.push_account_data_region(account)?;
105 vaddr
106 };
107
108 if self.aligned {
109 let align_offset =
110 (account.get_data().len() as *const u8).align_offset(BPF_ALIGN_OF_U128);
111 if self.copy_account_data {
112 self.fill_write(MAX_PERMITTED_DATA_INCREASE + align_offset, 0)
113 .map_err(|_| InstructionError::InvalidArgument)?;
114 } else {
115 self.fill_write(MAX_PERMITTED_DATA_INCREASE + BPF_ALIGN_OF_U128, 0)
121 .map_err(|_| InstructionError::InvalidArgument)?;
122 self.region_start += BPF_ALIGN_OF_U128.saturating_sub(align_offset);
123 self.push_region(account.can_data_be_changed().is_ok());
125 }
126 }
127
128 Ok(vm_data_addr)
129 }
130
131 fn push_account_data_region(
132 &mut self,
133 account: &mut BorrowedAccount<'_>,
134 ) -> Result<(), InstructionError> {
135 if !account.get_data().is_empty() {
136 let region = match account_data_region_memory_state(account) {
137 MemoryState::Readable => MemoryRegion::new_readonly(account.get_data(), self.vaddr),
138 MemoryState::Writable => {
139 MemoryRegion::new_writable(account.get_data_mut()?, self.vaddr)
140 }
141 MemoryState::Cow(index_in_transaction) => {
142 MemoryRegion::new_cow(account.get_data(), self.vaddr, index_in_transaction)
143 }
144 };
145 self.vaddr += region.len;
146 self.regions.push(region);
147 }
148
149 Ok(())
150 }
151
152 fn push_region(&mut self, writable: bool) {
153 let range = self.region_start..self.buffer.len();
154 let region = if writable {
155 MemoryRegion::new_writable(
156 self.buffer.as_slice_mut().get_mut(range.clone()).unwrap(),
157 self.vaddr,
158 )
159 } else {
160 MemoryRegion::new_readonly(
161 self.buffer.as_slice().get(range.clone()).unwrap(),
162 self.vaddr,
163 )
164 };
165 self.regions.push(region);
166 self.region_start = range.end;
167 self.vaddr += range.len() as u64;
168 }
169
170 fn finish(mut self) -> (AlignedMemory<HOST_ALIGN>, Vec<MemoryRegion>) {
171 self.push_region(true);
172 debug_assert_eq!(self.region_start, self.buffer.len());
173 (self.buffer, self.regions)
174 }
175
176 fn debug_assert_alignment<T>(&self) {
177 debug_assert!(
178 !self.aligned
179 || self
180 .buffer
181 .as_slice()
182 .as_ptr_range()
183 .end
184 .align_offset(mem::align_of::<T>())
185 == 0
186 );
187 }
188}
189
190pub fn serialize_parameters(
191 transaction_context: &TransactionContext,
192 instruction_context: &InstructionContext,
193 copy_account_data: bool,
194 mask_out_rent_epoch_in_vm_serialization: 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 .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 mask_out_rent_epoch_in_vm_serialization,
244 )
245 } else {
246 serialize_parameters_aligned(
247 accounts,
248 instruction_context.get_instruction_data(),
249 &program_id,
250 copy_account_data,
251 mask_out_rent_epoch_in_vm_serialization,
252 )
253 }
254}
255
256pub(crate) fn deserialize_parameters(
257 transaction_context: &TransactionContext,
258 instruction_context: &InstructionContext,
259 copy_account_data: bool,
260 buffer: &[u8],
261 accounts_metadata: &[SerializedAccountMetadata],
262) -> Result<(), InstructionError> {
263 let is_loader_deprecated = *instruction_context
264 .try_borrow_last_program_account(transaction_context)?
265 .get_owner()
266 == bpf_loader_deprecated::id();
267 let account_lengths = accounts_metadata.iter().map(|a| a.original_data_len);
268 if is_loader_deprecated {
269 deserialize_parameters_unaligned(
270 transaction_context,
271 instruction_context,
272 copy_account_data,
273 buffer,
274 account_lengths,
275 )
276 } else {
277 deserialize_parameters_aligned(
278 transaction_context,
279 instruction_context,
280 copy_account_data,
281 buffer,
282 account_lengths,
283 )
284 }
285}
286
287fn serialize_parameters_unaligned(
288 accounts: Vec<SerializeAccount>,
289 instruction_data: &[u8],
290 program_id: &Pubkey,
291 copy_account_data: bool,
292 mask_out_rent_epoch_in_vm_serialization: bool,
293) -> Result<
294 (
295 AlignedMemory<HOST_ALIGN>,
296 Vec<MemoryRegion>,
297 Vec<SerializedAccountMetadata>,
298 ),
299 InstructionError,
300> {
301 let mut size = size_of::<u64>();
303 for account in &accounts {
304 size += 1; match account {
306 SerializeAccount::Duplicate(_) => {}
307 SerializeAccount::Account(_, account) => {
308 size += size_of::<u8>() + size_of::<u8>() + size_of::<Pubkey>() + size_of::<u64>() + size_of::<u64>() + size_of::<Pubkey>() + size_of::<u8>() + size_of::<u64>(); if copy_account_data {
317 size += account.get_data().len();
318 }
319 }
320 }
321 }
322 size += size_of::<u64>() + instruction_data.len() + size_of::<Pubkey>(); let mut s = Serializer::new(size, MM_INPUT_START, false, copy_account_data);
327
328 let mut accounts_metadata: Vec<SerializedAccountMetadata> = Vec::with_capacity(accounts.len());
329 s.write::<u64>((accounts.len() as u64).to_le());
330 for account in accounts {
331 match account {
332 SerializeAccount::Duplicate(position) => {
333 accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
334 s.write(position as u8);
335 }
336 SerializeAccount::Account(_, mut account) => {
337 s.write::<u8>(NON_DUP_MARKER);
338 s.write::<u8>(account.is_signer() as u8);
339 s.write::<u8>(account.is_writable() as u8);
340 let vm_key_addr = s.write_all(account.get_key().as_ref());
341 let vm_lamports_addr = s.write::<u64>(account.get_lamports().to_le());
342 s.write::<u64>((account.get_data().len() as u64).to_le());
343 let vm_data_addr = s.write_account(&mut account)?;
344 let vm_owner_addr = s.write_all(account.get_owner().as_ref());
345 #[allow(deprecated)]
346 s.write::<u8>(account.is_executable() as u8);
347 let rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
348 u64::MAX
349 } else {
350 account.get_rent_epoch()
351 };
352 s.write::<u64>(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
371fn 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) -> Result<(), InstructionError> {
378 let mut start = size_of::<u64>(); for (instruction_account_index, pre_len) in (0..instruction_context
380 .get_number_of_instruction_accounts())
381 .zip(account_lengths.into_iter())
382 {
383 let duplicate =
384 instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
385 start += 1; if duplicate.is_none() {
387 let mut borrowed_account = instruction_context
388 .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
389 start += size_of::<u8>(); start += size_of::<u8>(); start += size_of::<Pubkey>(); let lamports = buffer
393 .get(start..start.saturating_add(8))
394 .map(<[u8; 8]>::try_from)
395 .and_then(Result::ok)
396 .map(u64::from_le_bytes)
397 .ok_or(InstructionError::InvalidArgument)?;
398 if borrowed_account.get_lamports() != lamports {
399 borrowed_account.set_lamports(lamports)?;
400 }
401 start += size_of::<u64>() + size_of::<u64>(); if copy_account_data {
404 let data = buffer
405 .get(start..start + pre_len)
406 .ok_or(InstructionError::InvalidArgument)?;
407 match borrowed_account
409 .can_data_be_resized(data.len())
410 .and_then(|_| borrowed_account.can_data_be_changed())
411 {
412 Ok(()) => borrowed_account.set_data_from_slice(data)?,
413 Err(err) if borrowed_account.get_data() != data => return Err(err),
414 _ => {}
415 }
416 start += pre_len; }
418 start += size_of::<Pubkey>() + size_of::<u8>() + size_of::<u64>(); }
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 mask_out_rent_epoch_in_vm_serialization: bool,
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 let mut size = size_of::<u64>();
443 for account in &accounts {
444 size += 1; match account {
446 SerializeAccount::Duplicate(_) => size += 7, SerializeAccount::Account(_, account) => {
448 let data_len = account.get_data().len();
449 size += size_of::<u8>() + size_of::<u8>() + size_of::<u8>() + size_of::<u32>() + size_of::<Pubkey>() + size_of::<Pubkey>() + size_of::<u64>() + size_of::<u64>() + MAX_PERMITTED_DATA_INCREASE
458 + size_of::<u64>(); 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>() + instruction_data.len()
469 + size_of::<Pubkey>(); let mut s = Serializer::new(size, MM_INPUT_START, true, copy_account_data);
472
473 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 #[allow(deprecated)]
482 s.write::<u8>(borrowed_account.is_executable() as u8);
483 s.write_all(&[0u8, 0, 0, 0]);
484 let vm_key_addr = s.write_all(borrowed_account.get_key().as_ref());
485 let vm_owner_addr = s.write_all(borrowed_account.get_owner().as_ref());
486 let vm_lamports_addr = s.write::<u64>(borrowed_account.get_lamports().to_le());
487 s.write::<u64>((borrowed_account.get_data().len() as u64).to_le());
488 let vm_data_addr = s.write_account(&mut borrowed_account)?;
489 let rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
490 u64::MAX
491 } else {
492 borrowed_account.get_rent_epoch()
493 };
494 s.write::<u64>(rent_epoch.to_le());
495 accounts_metadata.push(SerializedAccountMetadata {
496 original_data_len: borrowed_account.get_data().len(),
497 vm_key_addr,
498 vm_owner_addr,
499 vm_lamports_addr,
500 vm_data_addr,
501 });
502 }
503 SerializeAccount::Duplicate(position) => {
504 accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
505 s.write::<u8>(position as u8);
506 s.write_all(&[0u8, 0, 0, 0, 0, 0, 0]);
507 }
508 };
509 }
510 s.write::<u64>((instruction_data.len() as u64).to_le());
511 s.write_all(instruction_data);
512 s.write_all(program_id.as_ref());
513
514 let (mem, regions) = s.finish();
515 Ok((mem, regions, accounts_metadata))
516}
517
518fn deserialize_parameters_aligned<I: IntoIterator<Item = usize>>(
519 transaction_context: &TransactionContext,
520 instruction_context: &InstructionContext,
521 copy_account_data: bool,
522 buffer: &[u8],
523 account_lengths: I,
524) -> Result<(), InstructionError> {
525 let mut start = size_of::<u64>(); for (instruction_account_index, pre_len) in (0..instruction_context
527 .get_number_of_instruction_accounts())
528 .zip(account_lengths.into_iter())
529 {
530 let duplicate =
531 instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
532 start += size_of::<u8>(); if duplicate.is_some() {
534 start += 7; } else {
536 let mut borrowed_account = instruction_context
537 .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
538 start += size_of::<u8>() + size_of::<u8>() + size_of::<u8>() + size_of::<u32>() + size_of::<Pubkey>(); let owner = buffer
544 .get(start..start + size_of::<Pubkey>())
545 .ok_or(InstructionError::InvalidArgument)?;
546 start += size_of::<Pubkey>(); let lamports = buffer
548 .get(start..start.saturating_add(8))
549 .map(<[u8; 8]>::try_from)
550 .and_then(Result::ok)
551 .map(u64::from_le_bytes)
552 .ok_or(InstructionError::InvalidArgument)?;
553 if borrowed_account.get_lamports() != lamports {
554 borrowed_account.set_lamports(lamports)?;
555 }
556 start += size_of::<u64>(); let post_len = buffer
558 .get(start..start.saturating_add(8))
559 .map(<[u8; 8]>::try_from)
560 .and_then(Result::ok)
561 .map(u64::from_le_bytes)
562 .ok_or(InstructionError::InvalidArgument)? as usize;
563 start += size_of::<u64>(); if post_len.saturating_sub(pre_len) > MAX_PERMITTED_DATA_INCREASE
565 || post_len > MAX_PERMITTED_DATA_LENGTH as usize
566 {
567 return Err(InstructionError::InvalidRealloc);
568 }
569 let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
571 if copy_account_data {
572 let data = buffer
573 .get(start..start + post_len)
574 .ok_or(InstructionError::InvalidArgument)?;
575 match borrowed_account
576 .can_data_be_resized(post_len)
577 .and_then(|_| borrowed_account.can_data_be_changed())
578 {
579 Ok(()) => borrowed_account.set_data_from_slice(data)?,
580 Err(err) if borrowed_account.get_data() != data => return Err(err),
581 _ => {}
582 }
583 start += pre_len; } else {
585 start += BPF_ALIGN_OF_U128.saturating_sub(alignment_offset);
588 let data = buffer
589 .get(start..start + MAX_PERMITTED_DATA_INCREASE)
590 .ok_or(InstructionError::InvalidArgument)?;
591 match borrowed_account
592 .can_data_be_resized(post_len)
593 .and_then(|_| borrowed_account.can_data_be_changed())
594 {
595 Ok(()) => {
596 borrowed_account.set_data_length(post_len)?;
597 let allocated_bytes = post_len.saturating_sub(pre_len);
598 if allocated_bytes > 0 {
599 borrowed_account
600 .get_data_mut()?
601 .get_mut(pre_len..pre_len.saturating_add(allocated_bytes))
602 .ok_or(InstructionError::InvalidArgument)?
603 .copy_from_slice(
604 data.get(0..allocated_bytes)
605 .ok_or(InstructionError::InvalidArgument)?,
606 );
607 }
608 }
609 Err(err) if borrowed_account.get_data().len() != post_len => return Err(err),
610 _ => {}
611 }
612 }
613 start += MAX_PERMITTED_DATA_INCREASE;
614 start += alignment_offset;
615 start += size_of::<u64>(); if borrowed_account.get_owner().to_bytes() != owner {
617 borrowed_account.set_owner(owner)?;
619 }
620 }
621 }
622 Ok(())
623}
624
625pub(crate) fn account_data_region_memory_state(account: &BorrowedAccount<'_>) -> MemoryState {
626 if account.can_data_be_changed().is_ok() {
627 if account.is_shared() {
628 MemoryState::Cow(account.get_index_in_transaction() as u64)
629 } else {
630 MemoryState::Writable
631 }
632 } else {
633 MemoryState::Readable
634 }
635}
636
637#[cfg(test)]
638#[allow(clippy::indexing_slicing)]
639mod tests {
640 use {
641 super::*,
642 clone_solana_account::{Account, AccountSharedData, WritableAccount},
643 clone_solana_account_info::AccountInfo,
644 clone_solana_program_entrypoint::deserialize,
645 clone_solana_program_runtime::with_mock_invoke_context,
646 clone_solana_sdk_ids::bpf_loader,
647 clone_solana_transaction_context::InstructionAccount,
648 std::{
649 cell::RefCell,
650 mem::transmute,
651 rc::Rc,
652 slice::{self, from_raw_parts, from_raw_parts_mut},
653 },
654 };
655
656 fn deduplicated_instruction_accounts(
657 transaction_indexes: &[IndexOfAccount],
658 is_writable: fn(usize) -> bool,
659 ) -> Vec<InstructionAccount> {
660 transaction_indexes
661 .iter()
662 .enumerate()
663 .map(|(index_in_instruction, index_in_transaction)| {
664 let index_in_callee = transaction_indexes
665 .get(0..index_in_instruction)
666 .unwrap()
667 .iter()
668 .position(|account_index| account_index == index_in_transaction)
669 .unwrap_or(index_in_instruction);
670 InstructionAccount {
671 index_in_transaction: *index_in_transaction,
672 index_in_caller: *index_in_transaction,
673 index_in_callee: index_in_callee as IndexOfAccount,
674 is_signer: false,
675 is_writable: is_writable(index_in_instruction),
676 }
677 })
678 .collect()
679 }
680
681 #[test]
682 fn test_serialize_parameters_with_many_accounts() {
683 struct TestCase {
684 num_ix_accounts: usize,
685 append_dup_account: bool,
686 expected_err: Option<InstructionError>,
687 name: &'static str,
688 }
689
690 for copy_account_data in [true] {
691 for TestCase {
692 num_ix_accounts,
693 append_dup_account,
694 expected_err,
695 name,
696 } in [
697 TestCase {
698 name: "serialize max accounts with cap",
699 num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
700 append_dup_account: false,
701 expected_err: None,
702 },
703 TestCase {
704 name: "serialize too many accounts with cap",
705 num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS) + 1,
706 append_dup_account: false,
707 expected_err: Some(InstructionError::MaxAccountsExceeded),
708 },
709 TestCase {
710 name: "serialize too many accounts and append dup with cap",
711 num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
712 append_dup_account: true,
713 expected_err: Some(InstructionError::MaxAccountsExceeded),
714 },
715 ] {
716 let program_id = clone_solana_pubkey::new_rand();
717 let mut transaction_accounts = vec![(
718 program_id,
719 AccountSharedData::from(Account {
720 lamports: 0,
721 data: vec![],
722 owner: bpf_loader::id(),
723 executable: true,
724 rent_epoch: 0,
725 }),
726 )];
727 for _ in 0..num_ix_accounts {
728 transaction_accounts.push((
729 Pubkey::new_unique(),
730 AccountSharedData::from(Account {
731 lamports: 0,
732 data: vec![],
733 owner: program_id,
734 executable: false,
735 rent_epoch: 0,
736 }),
737 ));
738 }
739
740 let transaction_accounts_indexes: Vec<IndexOfAccount> =
741 (1..(num_ix_accounts + 1) as u16).collect();
742 let mut instruction_accounts =
743 deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false);
744 if append_dup_account {
745 instruction_accounts.push(instruction_accounts.last().cloned().unwrap());
746 }
747 let program_indices = [0];
748 let instruction_data = vec![];
749
750 with_mock_invoke_context!(
751 invoke_context,
752 transaction_context,
753 transaction_accounts
754 );
755 invoke_context
756 .transaction_context
757 .get_next_instruction_context()
758 .unwrap()
759 .configure(&program_indices, &instruction_accounts, &instruction_data);
760 invoke_context.push().unwrap();
761 let instruction_context = invoke_context
762 .transaction_context
763 .get_current_instruction_context()
764 .unwrap();
765
766 let serialization_result = serialize_parameters(
767 invoke_context.transaction_context,
768 instruction_context,
769 copy_account_data,
770 true, );
772 assert_eq!(
773 serialization_result.as_ref().err(),
774 expected_err.as_ref(),
775 "{name} test case failed",
776 );
777 if expected_err.is_some() {
778 continue;
779 }
780
781 let (mut serialized, regions, _account_lengths) = serialization_result.unwrap();
782 let mut serialized_regions = concat_regions(®ions);
783 let (de_program_id, de_accounts, de_instruction_data) = unsafe {
784 deserialize(
785 if copy_account_data {
786 serialized.as_slice_mut()
787 } else {
788 serialized_regions.as_slice_mut()
789 }
790 .first_mut()
791 .unwrap() as *mut u8,
792 )
793 };
794 assert_eq!(de_program_id, &program_id);
795 assert_eq!(de_instruction_data, &instruction_data);
796 for account_info in de_accounts {
797 let index_in_transaction = invoke_context
798 .transaction_context
799 .find_index_of_account(account_info.key)
800 .unwrap();
801 let account = invoke_context
802 .transaction_context
803 .accounts()
804 .try_borrow(index_in_transaction)
805 .unwrap();
806 assert_eq!(account.lamports(), account_info.lamports());
807 assert_eq!(account.data(), &account_info.data.borrow()[..]);
808 assert_eq!(account.owner(), account_info.owner);
809 assert_eq!(account.executable(), account_info.executable);
810 assert_eq!(u64::MAX, account_info.rent_epoch);
811 }
812 }
813 }
814 }
815
816 #[test]
817 fn test_serialize_parameters() {
818 for copy_account_data in [false, true] {
819 let program_id = clone_solana_pubkey::new_rand();
820 let transaction_accounts = vec![
821 (
822 program_id,
823 AccountSharedData::from(Account {
824 lamports: 0,
825 data: vec![],
826 owner: bpf_loader::id(),
827 executable: true,
828 rent_epoch: 0,
829 }),
830 ),
831 (
832 clone_solana_pubkey::new_rand(),
833 AccountSharedData::from(Account {
834 lamports: 1,
835 data: vec![1u8, 2, 3, 4, 5],
836 owner: bpf_loader::id(),
837 executable: false,
838 rent_epoch: 100,
839 }),
840 ),
841 (
842 clone_solana_pubkey::new_rand(),
843 AccountSharedData::from(Account {
844 lamports: 2,
845 data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
846 owner: bpf_loader::id(),
847 executable: true,
848 rent_epoch: 200,
849 }),
850 ),
851 (
852 clone_solana_pubkey::new_rand(),
853 AccountSharedData::from(Account {
854 lamports: 3,
855 data: vec![],
856 owner: bpf_loader::id(),
857 executable: false,
858 rent_epoch: 3100,
859 }),
860 ),
861 (
862 clone_solana_pubkey::new_rand(),
863 AccountSharedData::from(Account {
864 lamports: 4,
865 data: vec![1u8, 2, 3, 4, 5],
866 owner: bpf_loader::id(),
867 executable: false,
868 rent_epoch: 100,
869 }),
870 ),
871 (
872 clone_solana_pubkey::new_rand(),
873 AccountSharedData::from(Account {
874 lamports: 5,
875 data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
876 owner: bpf_loader::id(),
877 executable: true,
878 rent_epoch: 200,
879 }),
880 ),
881 (
882 clone_solana_pubkey::new_rand(),
883 AccountSharedData::from(Account {
884 lamports: 6,
885 data: vec![],
886 owner: bpf_loader::id(),
887 executable: false,
888 rent_epoch: 3100,
889 }),
890 ),
891 ];
892 let instruction_accounts =
893 deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
894 let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
895 let program_indices = [0];
896 let mut original_accounts = transaction_accounts.clone();
897 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
898 invoke_context
899 .transaction_context
900 .get_next_instruction_context()
901 .unwrap()
902 .configure(&program_indices, &instruction_accounts, &instruction_data);
903 invoke_context.push().unwrap();
904 let instruction_context = invoke_context
905 .transaction_context
906 .get_current_instruction_context()
907 .unwrap();
908
909 let (mut serialized, regions, accounts_metadata) = serialize_parameters(
911 invoke_context.transaction_context,
912 instruction_context,
913 copy_account_data,
914 true, )
916 .unwrap();
917
918 let mut serialized_regions = concat_regions(®ions);
919 if copy_account_data {
920 assert_eq!(serialized.as_slice(), serialized_regions.as_slice());
921 }
922 let (de_program_id, de_accounts, de_instruction_data) = unsafe {
923 deserialize(
924 if copy_account_data {
925 serialized.as_slice_mut()
926 } else {
927 serialized_regions.as_slice_mut()
928 }
929 .first_mut()
930 .unwrap() as *mut u8,
931 )
932 };
933
934 assert_eq!(&program_id, de_program_id);
935 assert_eq!(instruction_data, de_instruction_data);
936 assert_eq!(
937 (de_instruction_data.first().unwrap() as *const u8).align_offset(BPF_ALIGN_OF_U128),
938 0
939 );
940 for account_info in de_accounts {
941 let index_in_transaction = invoke_context
942 .transaction_context
943 .find_index_of_account(account_info.key)
944 .unwrap();
945 let account = invoke_context
946 .transaction_context
947 .accounts()
948 .try_borrow(index_in_transaction)
949 .unwrap();
950 assert_eq!(account.lamports(), account_info.lamports());
951 assert_eq!(account.data(), &account_info.data.borrow()[..]);
952 assert_eq!(account.owner(), account_info.owner);
953 assert_eq!(account.executable(), account_info.executable);
954 assert_eq!(u64::MAX, account_info.rent_epoch);
955
956 assert_eq!(
957 (*account_info.lamports.borrow() as *const u64).align_offset(BPF_ALIGN_OF_U128),
958 0
959 );
960 assert_eq!(
961 account_info
962 .data
963 .borrow()
964 .as_ptr()
965 .align_offset(BPF_ALIGN_OF_U128),
966 0
967 );
968 }
969
970 deserialize_parameters(
971 invoke_context.transaction_context,
972 instruction_context,
973 copy_account_data,
974 serialized.as_slice(),
975 &accounts_metadata,
976 )
977 .unwrap();
978 for (index_in_transaction, (_key, original_account)) in
979 original_accounts.iter().enumerate()
980 {
981 let account = invoke_context
982 .transaction_context
983 .accounts()
984 .try_borrow(index_in_transaction as IndexOfAccount)
985 .unwrap();
986 assert_eq!(&*account, original_account);
987 }
988
989 original_accounts
991 .first_mut()
992 .unwrap()
993 .1
994 .set_owner(bpf_loader_deprecated::id());
995 invoke_context
996 .transaction_context
997 .get_account_at_index(0)
998 .unwrap()
999 .try_borrow_mut()
1000 .unwrap()
1001 .set_owner(bpf_loader_deprecated::id());
1002
1003 let (mut serialized, regions, account_lengths) = serialize_parameters(
1004 invoke_context.transaction_context,
1005 instruction_context,
1006 copy_account_data,
1007 true, )
1009 .unwrap();
1010 let mut serialized_regions = concat_regions(®ions);
1011
1012 let (de_program_id, de_accounts, de_instruction_data) = unsafe {
1013 deserialize_unaligned(
1014 if copy_account_data {
1015 serialized.as_slice_mut()
1016 } else {
1017 serialized_regions.as_slice_mut()
1018 }
1019 .first_mut()
1020 .unwrap() as *mut u8,
1021 )
1022 };
1023 assert_eq!(&program_id, de_program_id);
1024 assert_eq!(instruction_data, de_instruction_data);
1025 for account_info in de_accounts {
1026 let index_in_transaction = invoke_context
1027 .transaction_context
1028 .find_index_of_account(account_info.key)
1029 .unwrap();
1030 let account = invoke_context
1031 .transaction_context
1032 .accounts()
1033 .try_borrow(index_in_transaction)
1034 .unwrap();
1035 assert_eq!(account.lamports(), account_info.lamports());
1036 assert_eq!(account.data(), &account_info.data.borrow()[..]);
1037 assert_eq!(account.owner(), account_info.owner);
1038 assert_eq!(account.executable(), account_info.executable);
1039 assert_eq!(u64::MAX, account_info.rent_epoch);
1040 }
1041
1042 deserialize_parameters(
1043 invoke_context.transaction_context,
1044 instruction_context,
1045 copy_account_data,
1046 serialized.as_slice(),
1047 &account_lengths,
1048 )
1049 .unwrap();
1050 for (index_in_transaction, (_key, original_account)) in
1051 original_accounts.iter().enumerate()
1052 {
1053 let account = invoke_context
1054 .transaction_context
1055 .accounts()
1056 .try_borrow(index_in_transaction as IndexOfAccount)
1057 .unwrap();
1058 assert_eq!(&*account, original_account);
1059 }
1060 }
1061 }
1062
1063 #[test]
1064 fn test_serialize_parameters_mask_out_rent_epoch_in_vm_serialization() {
1065 for mask_out_rent_epoch_in_vm_serialization in [false, true] {
1066 let transaction_accounts = vec![
1067 (
1068 clone_solana_pubkey::new_rand(),
1069 AccountSharedData::from(Account {
1070 lamports: 0,
1071 data: vec![],
1072 owner: bpf_loader::id(),
1073 executable: true,
1074 rent_epoch: 0,
1075 }),
1076 ),
1077 (
1078 clone_solana_pubkey::new_rand(),
1079 AccountSharedData::from(Account {
1080 lamports: 1,
1081 data: vec![1u8, 2, 3, 4, 5],
1082 owner: bpf_loader::id(),
1083 executable: false,
1084 rent_epoch: 100,
1085 }),
1086 ),
1087 (
1088 clone_solana_pubkey::new_rand(),
1089 AccountSharedData::from(Account {
1090 lamports: 2,
1091 data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1092 owner: bpf_loader::id(),
1093 executable: true,
1094 rent_epoch: 200,
1095 }),
1096 ),
1097 (
1098 clone_solana_pubkey::new_rand(),
1099 AccountSharedData::from(Account {
1100 lamports: 3,
1101 data: vec![],
1102 owner: bpf_loader::id(),
1103 executable: false,
1104 rent_epoch: 300,
1105 }),
1106 ),
1107 (
1108 clone_solana_pubkey::new_rand(),
1109 AccountSharedData::from(Account {
1110 lamports: 4,
1111 data: vec![1u8, 2, 3, 4, 5],
1112 owner: bpf_loader::id(),
1113 executable: false,
1114 rent_epoch: 100,
1115 }),
1116 ),
1117 (
1118 clone_solana_pubkey::new_rand(),
1119 AccountSharedData::from(Account {
1120 lamports: 5,
1121 data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1122 owner: bpf_loader::id(),
1123 executable: true,
1124 rent_epoch: 200,
1125 }),
1126 ),
1127 (
1128 clone_solana_pubkey::new_rand(),
1129 AccountSharedData::from(Account {
1130 lamports: 6,
1131 data: vec![],
1132 owner: bpf_loader::id(),
1133 executable: false,
1134 rent_epoch: 3100,
1135 }),
1136 ),
1137 ];
1138 let instruction_accounts =
1139 deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
1140 let instruction_data = vec![];
1141 let program_indices = [0];
1142 let mut original_accounts = transaction_accounts.clone();
1143 with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1144 invoke_context
1145 .transaction_context
1146 .get_next_instruction_context()
1147 .unwrap()
1148 .configure(&program_indices, &instruction_accounts, &instruction_data);
1149 invoke_context.push().unwrap();
1150 let instruction_context = invoke_context
1151 .transaction_context
1152 .get_current_instruction_context()
1153 .unwrap();
1154
1155 let (_serialized, regions, _accounts_metadata) = serialize_parameters(
1157 invoke_context.transaction_context,
1158 instruction_context,
1159 true,
1160 mask_out_rent_epoch_in_vm_serialization,
1161 )
1162 .unwrap();
1163
1164 let mut serialized_regions = concat_regions(®ions);
1165 let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1166 deserialize(serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8)
1167 };
1168
1169 for account_info in de_accounts {
1170 let index_in_transaction = invoke_context
1171 .transaction_context
1172 .find_index_of_account(account_info.key)
1173 .unwrap();
1174 let account = invoke_context
1175 .transaction_context
1176 .accounts()
1177 .try_borrow(index_in_transaction)
1178 .unwrap();
1179 let expected_rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
1180 u64::MAX
1181 } else {
1182 account.rent_epoch()
1183 };
1184 assert_eq!(expected_rent_epoch, account_info.rent_epoch);
1185 }
1186
1187 original_accounts
1189 .first_mut()
1190 .unwrap()
1191 .1
1192 .set_owner(bpf_loader_deprecated::id());
1193 invoke_context
1194 .transaction_context
1195 .get_account_at_index(0)
1196 .unwrap()
1197 .try_borrow_mut()
1198 .unwrap()
1199 .set_owner(bpf_loader_deprecated::id());
1200
1201 let (_serialized, regions, _account_lengths) = serialize_parameters(
1202 invoke_context.transaction_context,
1203 instruction_context,
1204 true,
1205 mask_out_rent_epoch_in_vm_serialization,
1206 )
1207 .unwrap();
1208 let mut serialized_regions = concat_regions(®ions);
1209
1210 let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1211 deserialize_unaligned(
1212 serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8
1213 )
1214 };
1215 for account_info in de_accounts {
1216 let index_in_transaction = invoke_context
1217 .transaction_context
1218 .find_index_of_account(account_info.key)
1219 .unwrap();
1220 let account = invoke_context
1221 .transaction_context
1222 .accounts()
1223 .try_borrow(index_in_transaction)
1224 .unwrap();
1225 let expected_rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
1226 u64::MAX
1227 } else {
1228 account.rent_epoch()
1229 };
1230 assert_eq!(expected_rent_epoch, account_info.rent_epoch);
1231 }
1232 }
1233 }
1234
1235 #[deny(unsafe_op_in_unsafe_fn)]
1237 unsafe fn deserialize_unaligned<'a>(
1238 input: *mut u8,
1239 ) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
1240 struct Ptr<T>(std::marker::PhantomData<T>);
1242 impl<T> Ptr<T> {
1243 const COULD_BE_UNALIGNED: bool = std::mem::align_of::<T>() > 1;
1244
1245 #[inline(always)]
1246 fn read_possibly_unaligned(input: *mut u8, offset: usize) -> T {
1247 unsafe {
1248 let src = input.add(offset) as *const T;
1249 if Self::COULD_BE_UNALIGNED {
1250 src.read_unaligned()
1251 } else {
1252 src.read()
1253 }
1254 }
1255 }
1256
1257 #[inline(always)]
1266 fn ref_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a T {
1267 #[allow(clippy::transmute_ptr_to_ref)]
1268 unsafe {
1269 transmute(input.add(offset) as *const T)
1270 }
1271 }
1272
1273 #[inline(always)]
1275 fn mut_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a mut T {
1276 #[allow(clippy::transmute_ptr_to_ref)]
1277 unsafe {
1278 transmute(input.add(offset) as *mut T)
1279 }
1280 }
1281 }
1282
1283 let mut offset: usize = 0;
1284
1285 let num_accounts = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1288 offset += size_of::<u64>();
1289
1290 let mut accounts = Vec::with_capacity(num_accounts);
1293 for _ in 0..num_accounts {
1294 let dup_info = Ptr::<u8>::read_possibly_unaligned(input, offset);
1295 offset += size_of::<u8>();
1296 if dup_info == NON_DUP_MARKER {
1297 let is_signer = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1298 offset += size_of::<u8>();
1299
1300 let is_writable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1301 offset += size_of::<u8>();
1302
1303 let key = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1304 offset += size_of::<Pubkey>();
1305
1306 let lamports = Rc::new(RefCell::new(Ptr::mut_possibly_unaligned(input, offset)));
1307 offset += size_of::<u64>();
1308
1309 let data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1310 offset += size_of::<u64>();
1311
1312 let data = Rc::new(RefCell::new(unsafe {
1313 from_raw_parts_mut(input.add(offset), data_len)
1314 }));
1315 offset += data_len;
1316
1317 let owner: &Pubkey = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1318 offset += size_of::<Pubkey>();
1319
1320 let executable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1321 offset += size_of::<u8>();
1322
1323 let rent_epoch = Ptr::<u64>::read_possibly_unaligned(input, offset);
1324 offset += size_of::<u64>();
1325
1326 accounts.push(AccountInfo {
1327 key,
1328 is_signer,
1329 is_writable,
1330 lamports,
1331 data,
1332 owner,
1333 executable,
1334 rent_epoch,
1335 });
1336 } else {
1337 accounts.push(accounts.get(dup_info as usize).unwrap().clone());
1339 }
1340 }
1341
1342 let instruction_data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1345 offset += size_of::<u64>();
1346
1347 let instruction_data = unsafe { from_raw_parts(input.add(offset), instruction_data_len) };
1348 offset += instruction_data_len;
1349
1350 let program_id = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1353
1354 (program_id, accounts, instruction_data)
1355 }
1356
1357 fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
1358 let len = regions.iter().fold(0, |len, region| len + region.len) as usize;
1359 let mut mem = AlignedMemory::zero_filled(len);
1360 for region in regions {
1361 let host_slice = unsafe {
1362 slice::from_raw_parts(region.host_addr.get() as *const u8, region.len as usize)
1363 };
1364 mem.as_slice_mut()[(region.vm_addr - MM_INPUT_START) as usize..][..region.len as usize]
1365 .copy_from_slice(host_slice)
1366 }
1367 mem
1368 }
1369}