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 {
305 if signers_seeds.is_empty() {
306 invoke_unchecked(instruction, accounts)
307 } else {
308 invoke_signed_unchecked(instruction, accounts, signers_seeds)
309 }
310 }
311}
312
313#[inline]
315pub fn invoke_with_bounds<const MAX_ACCOUNTS: usize>(
316 instruction: &InstructionView<'_, '_, '_, '_>,
317 account_views: &[&AccountView<'_>],
318) -> ProgramResult {
319 invoke_signed_with_bounds::<MAX_ACCOUNTS>(instruction, account_views, &[])
320}
321
322#[inline]
327pub fn invoke_signed_with_bounds<const MAX_ACCOUNTS: usize>(
328 instruction: &InstructionView<'_, '_, '_, '_>,
329 account_views: &[&AccountView<'_>],
330 signers_seeds: &[Signer<'_, '_>],
331) -> ProgramResult {
332 if account_views.len() > MAX_ACCOUNTS {
333 return Err(ProgramError::InvalidArgument);
334 }
335
336 let metas_len = instruction.accounts.len();
337 let count = account_views.len();
338 if count < metas_len {
339 return Err(ProgramError::NotEnoughAccountKeys);
340 }
341
342 let mut cpi_accounts: [MaybeUninit<CpiAccount<'_>>; MAX_ACCOUNTS] =
343 unsafe { MaybeUninit::uninit().assume_init() };
348
349 let mut i = 0;
352 while i < metas_len {
353 let actual = account_views[i];
354 let expected = &instruction.accounts[i];
355
356 if !address_eq(actual.address(), expected.address) {
357 return Err(ProgramError::InvalidAccountData);
358 }
359
360 if expected.is_signer && !actual.is_signer() && signers_seeds.is_empty() {
361 return Err(ProgramError::MissingRequiredSignature);
362 }
363
364 if expected.is_writable && !actual.is_writable() {
365 return Err(ProgramError::Immutable);
366 }
367
368 if expected.is_writable {
369 actual.check_borrow_mut()?;
370 } else {
371 actual.check_borrow()?;
372 }
373 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(actual));
374 i += 1;
375 }
376
377 let accounts = unsafe {
380 core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount<'_>, metas_len)
381 };
382
383 unsafe {
385 if signers_seeds.is_empty() {
386 invoke_unchecked(instruction, accounts)
387 } else {
388 invoke_signed_unchecked(instruction, accounts, signers_seeds)
389 }
390 }
391}
392
393#[inline(always)]
397pub fn set_return_data(data: &[u8]) {
398 #[cfg(target_os = "solana")]
399 unsafe {
401 crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64);
402 }
403 #[cfg(not(target_os = "solana"))]
404 {
405 let _ = data;
406 }
407}
408
409#[cfg(test)]
410mod abi_tests {
411 use super::*;
412 use crate::instruction::Seed;
413 use crate::{RuntimeAccount, NOT_BORROWED};
414
415 #[repr(C)]
416 struct Backing {
417 header: RuntimeAccount,
418 data: [u8; 8],
419 }
420
421 fn backing(tag: u8) -> Backing {
422 Backing {
423 header: RuntimeAccount {
424 borrow_state: NOT_BORROWED,
425 is_signer: 0,
426 is_writable: 1,
427 executable: 0,
428 resize_delta: 8,
429 address: Address::new_from_array([tag; 32]),
430 owner: Address::new_from_array([0xA5; 32]),
431 lamports: 5,
432 data_len: 8,
433 },
434 data: [tag; 8],
435 }
436 }
437
438 #[test]
439 fn c_instruction_and_specialized_meta_encoding_match_the_c_abi() {
440 let program_id = Address::new_from_array([9; 32]);
441 let key = Address::new_from_array([3; 32]);
442 let data = [1, 2, 3];
443 let metas = [InstructionAccount::new(&key, true, false)];
444 let view = InstructionView {
445 program_id: &program_id,
446 data: &data,
447 accounts: &metas,
448 };
449 let c = CInstruction::from_view(&view);
450 assert_eq!(c.program_id, &program_id as *const Address);
451 assert_eq!(c.accounts, metas.as_ptr() as *const u8);
452 assert_eq!(c.accounts_len, 1);
453 assert_eq!(c.data, data.as_ptr());
454 assert_eq!(c.data_len, 3);
455
456 let mut first_backing = backing(1);
457 let mut second_backing = backing(2);
458 let mut third_backing = backing(3);
459 let first = unsafe { AccountView::new_unchecked(&mut first_backing.header) };
463 let second = unsafe { AccountView::new_unchecked(&mut second_backing.header) };
465 let third = unsafe { AccountView::new_unchecked(&mut third_backing.header) };
467 let infos = [
468 CpiAccount::from(&first),
469 CpiAccount::from(&second),
470 CpiAccount::from(&third),
471 ];
472 let encoded = specialized_instruction_accounts(&infos, 0b011, 0b100);
473 assert!(encoded[0].is_writable);
474 assert!(encoded[1].is_writable);
475 assert!(!encoded[2].is_writable);
476 assert!(!encoded[0].is_signer);
477 assert!(!encoded[1].is_signer);
478 assert!(encoded[2].is_signer);
479 assert_eq!(encoded[0].address, first.address());
480 assert_eq!(encoded[1].address, second.address());
481 assert_eq!(encoded[2].address, third.address());
482 }
483
484 #[test]
485 fn checked_paths_accept_pda_seeds_and_forward_only_instruction_metas() {
486 let mut signer_backing = backing(4);
487 let mut extra_backing = backing(5);
488 let signer_view = unsafe { AccountView::new_unchecked(&mut signer_backing.header) };
491 let extra_view = unsafe { AccountView::new_unchecked(&mut extra_backing.header) };
492 let metas = [InstructionAccount::readonly_signer(signer_view.address())];
493 let program_id = Address::new_from_array([6; 32]);
494 let instruction = InstructionView {
495 program_id: &program_id,
496 data: &[],
497 accounts: &metas,
498 };
499
500 LAST_HOST_ACCOUNT_INFOS_LEN.store(usize::MAX, core::sync::atomic::Ordering::SeqCst);
501 assert_eq!(
502 invoke::<1>(&instruction, &[&signer_view]),
503 Err(ProgramError::MissingRequiredSignature)
504 );
505 assert_eq!(
506 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
507 usize::MAX,
508 "unsigned signer failure must happen before invoke"
509 );
510
511 let seed = Seed::from(&b"pda"[..]);
512 let signer_seeds = [seed];
513 let signers = [Signer::from(&signer_seeds)];
514 invoke_signed::<1>(&instruction, &[&signer_view], &signers).unwrap();
515 assert_eq!(
516 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
517 1
518 );
519
520 let caller_views = [&signer_view, &extra_view];
521 invoke_signed::<2>(&instruction, &caller_views, &signers).unwrap();
522 assert_eq!(
523 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
524 1,
525 "fixed path must not forward caller views absent from metas"
526 );
527 invoke_signed_with_bounds::<2>(&instruction, &caller_views, &signers).unwrap();
528 assert_eq!(
529 LAST_HOST_ACCOUNT_INFOS_LEN.load(core::sync::atomic::Ordering::SeqCst),
530 1,
531 "bounded path must not forward caller views absent from metas"
532 );
533 }
534}