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