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(
106 accounts,
107 writable_mask,
108 signer_mask,
109 !signers_seeds.is_empty(),
110 )?;
111 let instruction_accounts =
112 specialized_instruction_accounts(accounts, writable_mask, signer_mask);
113 let instruction = InstructionView {
114 program_id,
115 data,
116 accounts: &instruction_accounts,
117 };
118
119 unsafe { invoke_signed_unchecked(&instruction, accounts, signers_seeds) }
125}
126
127#[inline]
145pub unsafe fn invoke_unchecked(
146 instruction: &InstructionView<'_, '_, '_, '_>,
147 accounts: &[CpiAccount<'_>],
148) -> ProgramResult {
149 #[cfg(target_os = "solana")]
150 {
151 let c_instruction = CInstruction::from_view(instruction);
152 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
155 let result = unsafe {
158 crate::syscalls::sol_invoke_signed_c(
159 &c_instruction as *const _ as *const u8,
160 accounts.as_ptr() as *const u8,
161 accounts.len() as u64,
162 core::ptr::null(),
163 0,
164 )
165 };
166 if result == 0 {
167 Ok(())
168 } else {
169 Err(ProgramError::from(result))
170 }
171 }
172 #[cfg(not(target_os = "solana"))]
173 {
174 #[cfg(test)]
175 LAST_HOST_ACCOUNT_INFOS_LEN.store(accounts.len(), core::sync::atomic::Ordering::SeqCst);
176 let _ = (instruction, accounts);
177 Ok(())
178 }
179}
180
181#[inline]
190pub unsafe fn invoke_signed_unchecked(
191 instruction: &InstructionView<'_, '_, '_, '_>,
192 accounts: &[CpiAccount<'_>],
193 signers_seeds: &[Signer<'_, '_>],
194) -> ProgramResult {
195 #[cfg(target_os = "solana")]
196 {
197 let c_instruction = CInstruction::from_view(instruction);
198 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
201 let result = unsafe {
204 crate::syscalls::sol_invoke_signed_c(
205 &c_instruction as *const _ as *const u8,
206 accounts.as_ptr() as *const u8,
207 accounts.len() as u64,
208 signers_seeds.as_ptr() as *const u8,
209 signers_seeds.len() as u64,
210 )
211 };
212 if result == 0 {
213 Ok(())
214 } else {
215 Err(ProgramError::from(result))
216 }
217 }
218 #[cfg(not(target_os = "solana"))]
219 {
220 #[cfg(test)]
221 LAST_HOST_ACCOUNT_INFOS_LEN.store(accounts.len(), core::sync::atomic::Ordering::SeqCst);
222 let _ = (instruction, accounts, signers_seeds);
223 Ok(())
224 }
225}
226
227#[inline]
234pub fn invoke<const ACCOUNTS: usize>(
235 instruction: &InstructionView<'_, '_, '_, '_>,
236 account_views: &[&AccountView<'_>; ACCOUNTS],
237) -> ProgramResult {
238 invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
239}
240
241#[inline]
246pub fn invoke_signed<const ACCOUNTS: usize>(
247 instruction: &InstructionView<'_, '_, '_, '_>,
248 account_views: &[&AccountView<'_>; ACCOUNTS],
249 signers_seeds: &[Signer<'_, '_>],
250) -> ProgramResult {
251 let metas_len = instruction.accounts.len();
252 if ACCOUNTS < metas_len {
253 return Err(ProgramError::NotEnoughAccountKeys);
254 }
255
256 let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; ACCOUNTS] =
261 unsafe { MaybeUninit::uninit().assume_init() };
266
267 let mut i = 0;
268 while i < metas_len {
269 let actual = account_views[i];
270 let expected = &instruction.accounts[i];
271
272 if !address_eq(actual.address(), expected.address) {
273 return Err(ProgramError::InvalidAccountData);
274 }
275
276 if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
279 return Err(ProgramError::MissingRequiredSignature);
280 }
281
282 if expected.is_writable && !actual.is_writable() {
283 return Err(ProgramError::Immutable);
284 }
285
286 if expected.is_writable {
289 actual.check_borrow_mut()?;
290 } else {
291 actual.check_borrow()?;
292 }
293 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
294 i += 1;
295 }
296
297 let accounts = unsafe {
300 core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
301 };
302
303 unsafe {
307 if signers_seeds.is_empty() {
308 invoke_unchecked(instruction, accounts)
309 } else {
310 invoke_signed_unchecked(instruction, accounts, signers_seeds)
311 }
312 }
313}
314
315#[inline]
317pub fn invoke_with_bounds<const MAX_ACCOUNTS: usize>(
318 instruction: &InstructionView<'_, '_, '_, '_>,
319 account_views: &[&AccountView<'_>],
320) -> ProgramResult {
321 invoke_signed_with_bounds::<MAX_ACCOUNTS>(instruction, account_views, &[])
322}
323
324#[inline]
329pub fn invoke_signed_with_bounds<const MAX_ACCOUNTS: usize>(
330 instruction: &InstructionView<'_, '_, '_, '_>,
331 account_views: &[&AccountView<'_>],
332 signers_seeds: &[Signer<'_, '_>],
333) -> ProgramResult {
334 if account_views.len() > MAX_ACCOUNTS {
335 return Err(ProgramError::InvalidArgument);
336 }
337
338 let metas_len = instruction.accounts.len();
339 let count = account_views.len();
340 if count < metas_len {
341 return Err(ProgramError::NotEnoughAccountKeys);
342 }
343
344 let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; MAX_ACCOUNTS] =
345 unsafe { MaybeUninit::uninit().assume_init() };
350
351 let mut i = 0;
354 while i < metas_len {
355 let actual = account_views[i];
356 let expected = &instruction.accounts[i];
357
358 if !address_eq(actual.address(), expected.address) {
359 return Err(ProgramError::InvalidAccountData);
360 }
361
362 if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
363 return Err(ProgramError::MissingRequiredSignature);
364 }
365
366 if expected.is_writable && !actual.is_writable() {
367 return Err(ProgramError::Immutable);
368 }
369
370 if expected.is_writable {
371 actual.check_borrow_mut()?;
372 } else {
373 actual.check_borrow()?;
374 }
375 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
376 i += 1;
377 }
378
379 let accounts = unsafe {
382 core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
383 };
384
385 unsafe {
389 if signers_seeds.is_empty() {
390 invoke_unchecked(instruction, accounts)
391 } else {
392 invoke_signed_unchecked(instruction, accounts, signers_seeds)
393 }
394 }
395}
396
397#[inline(always)]
401pub fn set_return_data(data: &[u8]) {
402 #[cfg(target_os = "solana")]
403 unsafe {
406 crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64);
407 }
408 #[cfg(not(target_os = "solana"))]
409 {
410 let _ = data;
411 }
412}
413
414#[cfg(test)]
415mod abi_tests {
416 use super::*;
417 use crate::instruction::Seed;
418 use crate::{RuntimeAccount, NOT_BORROWED};
419
420 #[repr(C)]
421 struct Backing {
422 header: RuntimeAccount,
423 data: [u8; 8],
424 }
425
426 fn backing(tag: u8) -> Backing {
427 Backing {
428 header: RuntimeAccount {
429 borrow_state: NOT_BORROWED,
430 is_signer: 0,
431 is_writable: 1,
432 executable: 0,
433 resize_delta: 8,
434 address: Address::new_from_array([tag; 32]),
435 owner: Address::new_from_array([0xA5; 32]),
436 lamports: 5,
437 data_len: 8,
438 },
439 data: [tag; 8],
440 }
441 }
442
443 #[test]
444 fn c_instruction_and_specialized_meta_encoding_match_the_c_abi() {
445 let program_id = Address::new_from_array([9; 32]);
446 let key = Address::new_from_array([3; 32]);
447 let data = [1, 2, 3];
448 let metas = [InstructionAccount::new(&key, true, false)];
449 let view = InstructionView {
450 program_id: &program_id,
451 data: &data,
452 accounts: &metas,
453 };
454 let c = CInstruction::from_view(&view);
455 assert_eq!(c.program_id, &program_id as *const Address);
456 assert_eq!(c.accounts, metas.as_ptr() as *const u8);
457 assert_eq!(c.accounts_len, 1);
458 assert_eq!(c.data, data.as_ptr());
459 assert_eq!(c.data_len, 3);
460
461 let mut first_backing = backing(1);
462 let mut second_backing = backing(2);
463 let mut third_backing = backing(3);
464 let first = unsafe { AccountView::new_unchecked(&mut first_backing.header) };
468 let second = unsafe { AccountView::new_unchecked(&mut second_backing.header) };
470 let third = unsafe { AccountView::new_unchecked(&mut third_backing.header) };
472 let infos = [
473 CpiAccount::from(&first),
474 CpiAccount::from(&second),
475 CpiAccount::from(&third),
476 ];
477 let encoded = specialized_instruction_accounts(&infos, 0b011, 0b100);
478 assert!(encoded[0].is_writable);
479 assert!(encoded[1].is_writable);
480 assert!(!encoded[2].is_writable);
481 assert!(!encoded[0].is_signer);
482 assert!(!encoded[1].is_signer);
483 assert!(encoded[2].is_signer);
484 assert_eq!(encoded[0].address, first.address());
485 assert_eq!(encoded[1].address, second.address());
486 assert_eq!(encoded[2].address, third.address());
487 }
488
489 #[test]
490 fn checked_paths_accept_pda_seeds_and_forward_only_instruction_metas() {
491 let mut signer_backing = backing(4);
492 let mut extra_backing = backing(5);
493 let signer_view = unsafe { AccountView::new_unchecked(&mut signer_backing.header) };
496 let extra_view = unsafe { AccountView::new_unchecked(&mut extra_backing.header) };
497 let metas = [InstructionAccount::readonly_signer(signer_view.address())];
498 let program_id = Address::new_from_array([6; 32]);
499 let instruction = InstructionView {
500 program_id: &program_id,
501 data: &[],
502 accounts: &metas,
503 };
504
505 LAST_HOST_ACCOUNT_INFOS_LEN.store(usize::MAX, core::sync::atomic::Ordering::SeqCst);
506 assert_eq!(
507 invoke::<1>(&instruction, &[&signer_view]),
508 Err(ProgramError::MissingRequiredSignature)
509 );
510 assert_eq!(
511 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
512 usize::MAX,
513 "unsigned signer failure must happen before invoke"
514 );
515
516 let seed = Seed::from(&b"pda"[..]);
517 let signer_seeds = [seed];
518 let signers = [Signer::from(&signer_seeds)];
519 invoke_signed::<1>(&instruction, &[&signer_view], &signers).unwrap();
520 assert_eq!(
521 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
522 1
523 );
524
525 let caller_views = [&signer_view, &extra_view];
526 invoke_signed::<2>(&instruction, &caller_views, &signers).unwrap();
527 assert_eq!(
528 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
529 1,
530 "fixed path must not forward caller views absent from metas"
531 );
532 invoke_signed_with_bounds::<2>(&instruction, &caller_views, &signers).unwrap();
533 assert_eq!(
534 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
535 1,
536 "bounded path must not forward caller views absent from metas"
537 );
538 }
539}