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