1use crate::account_view::AccountView;
6use crate::address::{address_eq, Address};
7use crate::error::ProgramError;
8use crate::instruction::{
9 preflight_cpi_accounts, CpiAccount, InstructionAccount, InstructionView, Signer,
10};
11use crate::ProgramResult;
12use core::mem::MaybeUninit;
13
14#[cfg(all(test, not(target_os = "solana")))]
15static LAST_HOST_ACCOUNT_INFOS_LEN: core::sync::atomic::AtomicUsize =
16 core::sync::atomic::AtomicUsize::new(usize::MAX);
17
18pub const MAX_STATIC_CPI_ACCOUNTS: usize = 64;
28
29pub const MAX_CPI_ACCOUNTS: usize = 255;
37
38pub const MAX_RETURN_DATA: usize = 1024;
40
41#[cfg(any(target_os = "solana", test))]
46#[repr(C)]
47struct CInstruction {
48 program_id: *const Address,
49 accounts: *const u8,
50 accounts_len: u64,
51 data: *const u8,
52 data_len: u64,
53}
54
55#[cfg(any(target_os = "solana", test))]
56impl CInstruction {
57 #[inline(always)]
58 fn from_view(instruction: &InstructionView<'_, '_, '_, '_>) -> Self {
59 Self {
60 program_id: instruction.program_id as *const Address,
61 accounts: instruction.accounts.as_ptr() as *const u8,
62 accounts_len: instruction.accounts.len() as u64,
63 data: instruction.data.as_ptr(),
64 data_len: instruction.data.len() as u64,
65 }
66 }
67}
68
69#[cfg(any(target_os = "solana", test))]
70const _: () = {
71 assert!(core::mem::size_of::<CInstruction>() == 40);
72 assert!(core::mem::align_of::<CInstruction>() == 8);
73 assert!(core::mem::offset_of!(CInstruction, program_id) == 0);
74 assert!(core::mem::offset_of!(CInstruction, accounts) == 8);
75 assert!(core::mem::offset_of!(CInstruction, accounts_len) == 16);
76 assert!(core::mem::offset_of!(CInstruction, data) == 24);
77 assert!(core::mem::offset_of!(CInstruction, data_len) == 32);
78};
79
80#[inline(always)]
81fn specialized_instruction_accounts<'a, const ACCOUNTS: usize>(
82 accounts: &[CpiAccount<'a>; ACCOUNTS],
83 writable_mask: usize,
84 signer_mask: usize,
85) -> [InstructionAccount<'a>; ACCOUNTS] {
86 core::array::from_fn(|index| {
87 accounts[index].instruction_account(
88 writable_mask & (1usize << index) != 0,
89 signer_mask & (1usize << index) != 0,
90 )
91 })
92}
93
94#[inline]
97pub(crate) fn invoke_specialized_signed<'a, const ACCOUNTS: usize>(
98 program_id: &Address,
99 data: &[u8],
100 accounts: &[CpiAccount<'a>; ACCOUNTS],
101 writable_mask: usize,
102 signer_mask: usize,
103 signers_seeds: &[Signer<'_, '_>],
104) -> ProgramResult {
105 preflight_cpi_accounts(accounts, writable_mask)?;
106 let instruction_accounts =
107 specialized_instruction_accounts(accounts, writable_mask, signer_mask);
108 let instruction = InstructionView {
109 program_id,
110 data,
111 accounts: &instruction_accounts,
112 };
113
114 unsafe { invoke_signed_unchecked(&instruction, accounts, signers_seeds) }
120}
121
122#[inline]
168pub unsafe fn invoke_unchecked(
169 instruction: &InstructionView<'_, '_, '_, '_>,
170 accounts: &[CpiAccount<'_>],
171) -> ProgramResult {
172 #[cfg(target_os = "solana")]
173 {
174 let c_instruction = CInstruction::from_view(instruction);
175 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
178 let result = unsafe {
181 crate::syscalls::sol_invoke_signed_c(
182 &c_instruction as *const _ as *const u8,
183 accounts.as_ptr() as *const u8,
184 accounts.len() as u64,
185 core::ptr::null(),
186 0,
187 )
188 };
189 if result == 0 {
190 Ok(())
191 } else {
192 Err(ProgramError::from(result))
193 }
194 }
195 #[cfg(not(target_os = "solana"))]
196 {
197 #[cfg(test)]
198 LAST_HOST_ACCOUNT_INFOS_LEN.store(accounts.len(), core::sync::atomic::Ordering::SeqCst);
199 let _ = (instruction, accounts);
200 Ok(())
201 }
202}
203
204#[inline]
231pub unsafe fn invoke_signed_unchecked(
232 instruction: &InstructionView<'_, '_, '_, '_>,
233 accounts: &[CpiAccount<'_>],
234 signers_seeds: &[Signer<'_, '_>],
235) -> ProgramResult {
236 #[cfg(target_os = "solana")]
237 {
238 let c_instruction = CInstruction::from_view(instruction);
239 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
242 let result = unsafe {
245 crate::syscalls::sol_invoke_signed_c(
246 &c_instruction as *const _ as *const u8,
247 accounts.as_ptr() as *const u8,
248 accounts.len() as u64,
249 signers_seeds.as_ptr() as *const u8,
250 signers_seeds.len() as u64,
251 )
252 };
253 if result == 0 {
254 Ok(())
255 } else {
256 Err(ProgramError::from(result))
257 }
258 }
259 #[cfg(not(target_os = "solana"))]
260 {
261 #[cfg(test)]
262 LAST_HOST_ACCOUNT_INFOS_LEN.store(accounts.len(), core::sync::atomic::Ordering::SeqCst);
263 let _ = (instruction, accounts, signers_seeds);
264 Ok(())
265 }
266}
267
268#[inline]
275pub fn invoke<const ACCOUNTS: usize>(
276 instruction: &InstructionView<'_, '_, '_, '_>,
277 account_views: &[&AccountView<'_>; ACCOUNTS],
278) -> ProgramResult {
279 invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
280}
281
282#[inline]
287pub fn invoke_signed<const ACCOUNTS: usize>(
288 instruction: &InstructionView<'_, '_, '_, '_>,
289 account_views: &[&AccountView<'_>; ACCOUNTS],
290 signers_seeds: &[Signer<'_, '_>],
291) -> ProgramResult {
292 let metas_len = instruction.accounts.len();
293 if ACCOUNTS < metas_len {
294 return Err(ProgramError::NotEnoughAccountKeys);
295 }
296
297 let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; ACCOUNTS] =
302 unsafe { MaybeUninit::uninit().assume_init() };
307
308 let mut i = 0;
309 while i < metas_len {
310 let actual = account_views[i];
311 let expected = &instruction.accounts[i];
312
313 if !address_eq(actual.address(), expected.address) {
314 return Err(ProgramError::InvalidAccountData);
315 }
316
317 if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
320 return Err(ProgramError::MissingRequiredSignature);
321 }
322
323 if expected.is_writable && !actual.is_writable() {
324 return Err(ProgramError::Immutable);
325 }
326
327 if expected.is_writable {
330 actual.check_borrow_mut()?;
331 } else {
332 actual.check_borrow()?;
333 }
334 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
335 i += 1;
336 }
337
338 let accounts = unsafe {
341 core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
342 };
343
344 unsafe {
346 if signers_seeds.is_empty() {
347 invoke_unchecked(instruction, accounts)
348 } else {
349 invoke_signed_unchecked(instruction, accounts, signers_seeds)
350 }
351 }
352}
353
354#[inline]
356pub fn invoke_with_bounds<const MAX_ACCOUNTS: usize>(
357 instruction: &InstructionView<'_, '_, '_, '_>,
358 account_views: &[&AccountView<'_>],
359) -> ProgramResult {
360 invoke_signed_with_bounds::<MAX_ACCOUNTS>(instruction, account_views, &[])
361}
362
363#[inline]
368pub fn invoke_signed_with_bounds<const MAX_ACCOUNTS: usize>(
369 instruction: &InstructionView<'_, '_, '_, '_>,
370 account_views: &[&AccountView<'_>],
371 signers_seeds: &[Signer<'_, '_>],
372) -> ProgramResult {
373 if account_views.len() > MAX_ACCOUNTS {
374 return Err(ProgramError::InvalidArgument);
375 }
376
377 let metas_len = instruction.accounts.len();
378 let count = account_views.len();
379 if count < metas_len {
380 return Err(ProgramError::NotEnoughAccountKeys);
381 }
382
383 let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; MAX_ACCOUNTS] =
384 unsafe { MaybeUninit::uninit().assume_init() };
389
390 let mut i = 0;
393 while i < metas_len {
394 let actual = account_views[i];
395 let expected = &instruction.accounts[i];
396
397 if !address_eq(actual.address(), expected.address) {
398 return Err(ProgramError::InvalidAccountData);
399 }
400
401 if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
402 return Err(ProgramError::MissingRequiredSignature);
403 }
404
405 if expected.is_writable && !actual.is_writable() {
406 return Err(ProgramError::Immutable);
407 }
408
409 if expected.is_writable {
410 actual.check_borrow_mut()?;
411 } else {
412 actual.check_borrow()?;
413 }
414 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
415 i += 1;
416 }
417
418 let accounts = unsafe {
421 core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
422 };
423
424 unsafe {
426 if signers_seeds.is_empty() {
427 invoke_unchecked(instruction, accounts)
428 } else {
429 invoke_signed_unchecked(instruction, accounts, signers_seeds)
430 }
431 }
432}
433
434#[inline(always)]
438pub fn set_return_data(data: &[u8]) {
439 #[cfg(target_os = "solana")]
440 unsafe {
442 crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64);
443 }
444 #[cfg(not(target_os = "solana"))]
445 {
446 let _ = data;
447 }
448}
449
450#[cfg(test)]
451mod abi_tests {
452 use super::*;
453 use crate::instruction::Seed;
454 use crate::{RuntimeAccount, NOT_BORROWED};
455
456 #[repr(C)]
457 struct Backing {
458 header: RuntimeAccount,
459 data: [u8; 8],
460 }
461
462 fn backing(tag: u8) -> Backing {
463 Backing {
464 header: RuntimeAccount {
465 borrow_state: NOT_BORROWED,
466 is_signer: 0,
467 is_writable: 1,
468 executable: 0,
469 resize_delta: 8,
470 address: Address::new_from_array([tag; 32]),
471 owner: Address::new_from_array([0xA5; 32]),
472 lamports: 5,
473 data_len: 8,
474 },
475 data: [tag; 8],
476 }
477 }
478
479 #[test]
480 fn c_instruction_and_specialized_meta_encoding_match_the_c_abi() {
481 let program_id = Address::new_from_array([9; 32]);
482 let key = Address::new_from_array([3; 32]);
483 let data = [1, 2, 3];
484 let metas = [InstructionAccount::new(&key, true, false)];
485 let view = InstructionView {
486 program_id: &program_id,
487 data: &data,
488 accounts: &metas,
489 };
490 let c = CInstruction::from_view(&view);
491 assert_eq!(c.program_id, &program_id as *const Address);
492 assert_eq!(c.accounts, metas.as_ptr() as *const u8);
493 assert_eq!(c.accounts_len, 1);
494 assert_eq!(c.data, data.as_ptr());
495 assert_eq!(c.data_len, 3);
496
497 let mut first_backing = backing(1);
498 let mut second_backing = backing(2);
499 let mut third_backing = backing(3);
500 let first = unsafe { AccountView::new_unchecked(&mut first_backing.header) };
504 let second = unsafe { AccountView::new_unchecked(&mut second_backing.header) };
506 let third = unsafe { AccountView::new_unchecked(&mut third_backing.header) };
508 let infos = [
509 CpiAccount::from(&first),
510 CpiAccount::from(&second),
511 CpiAccount::from(&third),
512 ];
513 let encoded = specialized_instruction_accounts(&infos, 0b011, 0b100);
514 assert!(encoded[0].is_writable);
515 assert!(encoded[1].is_writable);
516 assert!(!encoded[2].is_writable);
517 assert!(!encoded[0].is_signer);
518 assert!(!encoded[1].is_signer);
519 assert!(encoded[2].is_signer);
520 assert_eq!(encoded[0].address, first.address());
521 assert_eq!(encoded[1].address, second.address());
522 assert_eq!(encoded[2].address, third.address());
523 }
524
525 #[test]
526 fn checked_paths_accept_pda_seeds_and_forward_only_instruction_metas() {
527 let mut signer_backing = backing(4);
528 let mut extra_backing = backing(5);
529 let signer_view = unsafe { AccountView::new_unchecked(&mut signer_backing.header) };
532 let extra_view = unsafe { AccountView::new_unchecked(&mut extra_backing.header) };
533 let metas = [InstructionAccount::readonly_signer(signer_view.address())];
534 let program_id = Address::new_from_array([6; 32]);
535 let instruction = InstructionView {
536 program_id: &program_id,
537 data: &[],
538 accounts: &metas,
539 };
540
541 LAST_HOST_ACCOUNT_INFOS_LEN.store(usize::MAX, core::sync::atomic::Ordering::SeqCst);
542 assert_eq!(
543 invoke::<1>(&instruction, &[&signer_view]),
544 Err(ProgramError::MissingRequiredSignature)
545 );
546 assert_eq!(
547 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
548 usize::MAX,
549 "unsigned signer failure must happen before invoke"
550 );
551
552 let seed = Seed::from(&b"pda"[..]);
553 let signer_seeds = [seed];
554 let signers = [Signer::from(&signer_seeds)];
555 invoke_signed::<1>(&instruction, &[&signer_view], &signers).unwrap();
556 assert_eq!(
557 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
558 1
559 );
560
561 let caller_views = [&signer_view, &extra_view];
562 invoke_signed::<2>(&instruction, &caller_views, &signers).unwrap();
563 assert_eq!(
564 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
565 1,
566 "fixed path must not forward caller views absent from metas"
567 );
568 invoke_signed_with_bounds::<2>(&instruction, &caller_views, &signers).unwrap();
569 assert_eq!(
570 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
571 1,
572 "bounded path must not forward caller views absent from metas"
573 );
574 }
575}