1use crate::account::AccountView;
8use crate::address::{address_eq, Address};
9use crate::error::ProgramError;
10use crate::instruction::InstructionView;
11use crate::ProgramResult;
12
13#[cfg(all(feature = "hopper-native-backend", target_os = "solana"))]
14use crate::instruction::InstructionAccount;
15
16pub use crate::instruction::{Seed, Signer};
18
19pub const MAX_STATIC_CPI_ACCOUNTS: usize = 64;
21
22pub const MAX_CPI_ACCOUNTS: usize = 128;
24
25pub const MAX_RETURN_DATA: usize = 1024;
27
28#[cfg(feature = "hopper-native-backend")]
33use crate::instruction::CpiAccount;
34#[cfg(feature = "hopper-native-backend")]
35use core::mem::MaybeUninit;
36
37#[cfg(all(feature = "hopper-native-backend", target_os = "solana"))]
38#[repr(C)]
39struct CInstruction<'a> {
40 program_id: *const Address,
41 accounts: *const InstructionAccount<'a>,
42 accounts_len: u64,
43 data: *const u8,
44 data_len: u64,
45}
46
47#[cfg(feature = "hopper-native-backend")]
55#[inline]
56pub unsafe fn invoke_unchecked(
57 instruction: &InstructionView,
58 accounts: &[CpiAccount],
59) -> ProgramResult {
60 #[cfg(target_os = "solana")]
61 {
62 let c_instruction = CInstruction {
63 program_id: instruction.program_id as *const Address,
64 accounts: instruction.accounts.as_ptr(),
65 accounts_len: instruction.accounts.len() as u64,
66 data: instruction.data.as_ptr(),
67 data_len: instruction.data.len() as u64,
68 };
69
70 let result = unsafe {
72 hopper_native::syscalls::sol_invoke_signed_c(
73 &c_instruction as *const _ as *const u8,
74 accounts.as_ptr() as *const u8,
75 accounts.len() as u64,
76 core::ptr::null(),
77 0,
78 )
79 };
80 if result == 0 {
81 Ok(())
82 } else {
83 Err(ProgramError::from(result))
84 }
85 }
86 #[cfg(not(target_os = "solana"))]
87 {
88 let _ = (instruction, accounts);
89 Ok(())
90 }
91}
92
93#[cfg(feature = "hopper-native-backend")]
99#[inline]
100pub unsafe fn invoke_signed_unchecked(
101 instruction: &InstructionView,
102 accounts: &[CpiAccount],
103 signers_seeds: &[Signer],
104) -> ProgramResult {
105 #[cfg(target_os = "solana")]
106 {
107 let c_instruction = CInstruction {
108 program_id: instruction.program_id as *const Address,
109 accounts: instruction.accounts.as_ptr(),
110 accounts_len: instruction.accounts.len() as u64,
111 data: instruction.data.as_ptr(),
112 data_len: instruction.data.len() as u64,
113 };
114
115 let result = unsafe {
117 hopper_native::syscalls::sol_invoke_signed_c(
118 &c_instruction as *const _ as *const u8,
119 accounts.as_ptr() as *const u8,
120 accounts.len() as u64,
121 signers_seeds.as_ptr() as *const u8,
122 signers_seeds.len() as u64,
123 )
124 };
125 if result == 0 {
126 Ok(())
127 } else {
128 Err(ProgramError::from(result))
129 }
130 }
131 #[cfg(not(target_os = "solana"))]
132 {
133 let _ = (instruction, accounts, signers_seeds);
134 Ok(())
135 }
136}
137
138#[inline]
142fn validate_no_duplicate_writable(
143 instruction: &InstructionView,
144 account_views: &[&AccountView],
145) -> ProgramResult {
146 let mut i = 0;
147 while i < instruction.accounts.len() {
148 if instruction.accounts[i].is_writable {
149 let mut j = i + 1;
150 while j < instruction.accounts.len() {
151 if instruction.accounts[j].is_writable
152 && address_eq(account_views[i].address(), account_views[j].address())
153 {
154 return Err(ProgramError::AccountBorrowFailed);
155 }
156 j += 1;
157 }
158 }
159 i += 1;
160 }
161 Ok(())
162}
163
164#[inline]
165fn signer_matches_pda(program_id: &Address, account: &Address, signers_seeds: &[Signer]) -> bool {
166 let mut i = 0;
167 while i < signers_seeds.len() {
168 let signer = &signers_seeds[i];
169 let seeds = unsafe { core::slice::from_raw_parts(signer.seeds, signer.len as usize) };
171
172 if seeds.len() <= crate::address::MAX_SEEDS {
173 let mut seed_refs: [&[u8]; crate::address::MAX_SEEDS] =
174 [&[]; crate::address::MAX_SEEDS];
175 let mut j = 0;
176 while j < seeds.len() {
177 seed_refs[j] =
179 unsafe { core::slice::from_raw_parts(seeds[j].seed, seeds[j].len as usize) };
180 j += 1;
181 }
182
183 if let Ok(derived) =
184 crate::compat::create_program_address(&seed_refs[..seeds.len()], program_id)
185 {
186 if address_eq(&derived, account) {
187 return true;
188 }
189 }
190 }
191
192 i += 1;
193 }
194
195 false
196}
197
198#[inline]
200fn validate_cpi_accounts(
201 instruction: &InstructionView,
202 account_views: &[&AccountView],
203 signers_seeds: &[Signer],
204) -> ProgramResult {
205 if account_views.len() < instruction.accounts.len() {
206 return Err(ProgramError::NotEnoughAccountKeys);
207 }
208
209 let mut i = 0;
210 while i < instruction.accounts.len() {
211 let expected = &instruction.accounts[i];
212 let actual = account_views[i];
213
214 if !address_eq(actual.address(), expected.address) {
215 return Err(ProgramError::InvalidAccountData);
216 }
217
218 if expected.is_signer
219 && !actual.is_signer()
220 && !signer_matches_pda(instruction.program_id, actual.address(), signers_seeds)
221 {
222 return Err(ProgramError::MissingRequiredSignature);
223 }
224
225 if expected.is_writable && !actual.is_writable() {
226 return Err(ProgramError::Immutable);
227 }
228
229 if expected.is_writable {
230 actual.check_borrow_mut()?;
231 } else {
232 actual.check_borrow()?;
233 }
234
235 i += 1;
236 }
237
238 validate_no_duplicate_writable(instruction, account_views)?;
239
240 Ok(())
241}
242
243#[cfg(feature = "hopper-native-backend")]
247#[inline]
248pub fn invoke<const ACCOUNTS: usize>(
249 instruction: &InstructionView,
250 account_views: &[&AccountView; ACCOUNTS],
251) -> ProgramResult {
252 invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
253}
254
255#[cfg(feature = "hopper-native-backend")]
257#[inline]
258pub fn invoke_signed<const ACCOUNTS: usize>(
259 instruction: &InstructionView,
260 account_views: &[&AccountView; ACCOUNTS],
261 signers_seeds: &[Signer],
262) -> ProgramResult {
263 validate_cpi_accounts(instruction, &account_views[..], signers_seeds)?;
264
265 let mut cpi_accounts: [MaybeUninit<CpiAccount>; ACCOUNTS] =
266 unsafe { MaybeUninit::uninit().assume_init() };
268
269 let mut i = 0;
270 while i < ACCOUNTS {
271 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
272 i += 1;
273 }
274
275 let accounts: &[CpiAccount; ACCOUNTS] =
277 unsafe { &*(cpi_accounts.as_ptr() as *const [CpiAccount; ACCOUNTS]) };
278
279 unsafe {
281 if signers_seeds.is_empty() {
282 invoke_unchecked(instruction, accounts.as_slice())
283 } else {
284 invoke_signed_unchecked(instruction, accounts.as_slice(), signers_seeds)
285 }
286 }
287}
288
289#[cfg(feature = "hopper-native-backend")]
291#[inline]
292pub fn invoke_with_bounds<const MAX_ACCOUNTS: usize>(
293 instruction: &InstructionView,
294 account_views: &[&AccountView],
295) -> ProgramResult {
296 invoke_signed_with_bounds::<MAX_ACCOUNTS>(instruction, account_views, &[])
297}
298
299#[cfg(feature = "hopper-native-backend")]
301#[inline]
302pub fn invoke_signed_with_bounds<const MAX_ACCOUNTS: usize>(
303 instruction: &InstructionView,
304 account_views: &[&AccountView],
305 signers_seeds: &[Signer],
306) -> ProgramResult {
307 if account_views.len() > MAX_ACCOUNTS {
308 return Err(ProgramError::InvalidArgument);
309 }
310
311 validate_cpi_accounts(instruction, account_views, signers_seeds)?;
312
313 let mut cpi_accounts: [MaybeUninit<CpiAccount>; MAX_ACCOUNTS] =
314 unsafe { MaybeUninit::uninit().assume_init() };
316
317 let count = account_views.len();
318 let mut i = 0;
319 while i < count {
320 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
321 i += 1;
322 }
323
324 let accounts =
326 unsafe { core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount, count) };
327
328 unsafe {
330 if signers_seeds.is_empty() {
331 invoke_unchecked(instruction, accounts)
332 } else {
333 invoke_signed_unchecked(instruction, accounts, signers_seeds)
334 }
335 }
336}
337
338#[cfg(any(
344 feature = "legacy-pinocchio-compat",
345 feature = "solana-program-backend"
346))]
347#[inline]
348pub fn invoke<const ACCOUNTS: usize>(
349 instruction: &InstructionView,
350 account_views: &[&AccountView; ACCOUNTS],
351) -> ProgramResult {
352 invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
353}
354
355#[cfg(any(
357 feature = "legacy-pinocchio-compat",
358 feature = "solana-program-backend"
359))]
360#[inline]
361pub fn invoke_signed<const ACCOUNTS: usize>(
362 instruction: &InstructionView,
363 account_views: &[&AccountView; ACCOUNTS],
364 signers_seeds: &[Signer],
365) -> ProgramResult {
366 validate_cpi_accounts(instruction, &account_views[..], signers_seeds)?;
367 crate::compat::invoke_signed(instruction, account_views, signers_seeds)
368}
369
370#[inline(always)]
374pub fn set_return_data(data: &[u8]) {
375 crate::compat::set_return_data(data)
376}
377
378#[cfg(all(test, feature = "hopper-native-backend"))]
379mod tests {
380 use super::*;
381
382 use crate::InstructionAccount;
383 use hopper_native::{
384 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
385 };
386
387 fn make_account(address: [u8; 32]) -> (std::vec::Vec<u8>, AccountView) {
388 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + 16];
389 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
390 unsafe {
392 raw.write(RuntimeAccount {
393 borrow_state: NOT_BORROWED,
394 is_signer: 0,
395 is_writable: 1,
396 executable: 0,
397 resize_delta: 0,
398 address: NativeAddress::new_from_array(address),
399 owner: NativeAddress::new_from_array([9; 32]),
400 lamports: 1,
401 data_len: 16,
402 });
403 }
404 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
406 (backing, AccountView::from_backend(backend))
407 }
408
409 #[test]
410 fn duplicate_writable_accounts_are_rejected_before_cpi() {
411 let (_first_backing, first) = make_account([3; 32]);
412 let (_second_backing, second) = make_account([3; 32]);
413
414 let instruction_accounts = [
415 InstructionAccount::writable(first.address()),
416 InstructionAccount::writable(second.address()),
417 ];
418 let program_id = Address::new_from_array([7; 32]);
419 let instruction = InstructionView {
420 program_id: &program_id,
421 data: &[0u8],
422 accounts: &instruction_accounts,
423 };
424
425 let err = validate_no_duplicate_writable(&instruction, &[&first, &second]).unwrap_err();
426 assert_eq!(err, ProgramError::AccountBorrowFailed);
427 }
428}