1use crate::address::{address_eq, Address};
8use crate::error::ProgramError;
9use crate::ProgramResult;
10use crate::instruction::InstructionView;
11use crate::account::AccountView;
12
13#[cfg(all(feature = "hopper-native-backend", target_os = "solana"))]
14use crate::instruction::InstructionAccount;
15
16pub use crate::instruction::{Signer, Seed};
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 {
71 hopper_native::syscalls::sol_invoke_signed_c(
72 &c_instruction as *const _ as *const u8,
73 accounts.as_ptr() as *const u8,
74 accounts.len() as u64,
75 core::ptr::null(),
76 0,
77 )
78 };
79 if result == 0 { Ok(()) } else { Err(ProgramError::from(result)) }
80 }
81 #[cfg(not(target_os = "solana"))]
82 {
83 let _ = (instruction, accounts);
84 Ok(())
85 }
86}
87
88#[cfg(feature = "hopper-native-backend")]
94#[inline]
95pub unsafe fn invoke_signed_unchecked(
96 instruction: &InstructionView,
97 accounts: &[CpiAccount],
98 signers_seeds: &[Signer],
99) -> ProgramResult {
100 #[cfg(target_os = "solana")]
101 {
102 let c_instruction = CInstruction {
103 program_id: instruction.program_id as *const Address,
104 accounts: instruction.accounts.as_ptr(),
105 accounts_len: instruction.accounts.len() as u64,
106 data: instruction.data.as_ptr(),
107 data_len: instruction.data.len() as u64,
108 };
109
110 let result = unsafe {
111 hopper_native::syscalls::sol_invoke_signed_c(
112 &c_instruction as *const _ as *const u8,
113 accounts.as_ptr() as *const u8,
114 accounts.len() as u64,
115 signers_seeds.as_ptr() as *const u8,
116 signers_seeds.len() as u64,
117 )
118 };
119 if result == 0 { Ok(()) } else { Err(ProgramError::from(result)) }
120 }
121 #[cfg(not(target_os = "solana"))]
122 {
123 let _ = (instruction, accounts, signers_seeds);
124 Ok(())
125 }
126}
127
128#[inline]
132fn validate_no_duplicate_writable(
133 instruction: &InstructionView,
134 account_views: &[&AccountView],
135) -> ProgramResult {
136 let mut i = 0;
137 while i < instruction.accounts.len() {
138 if instruction.accounts[i].is_writable {
139 let mut j = i + 1;
140 while j < instruction.accounts.len() {
141 if instruction.accounts[j].is_writable
142 && address_eq(account_views[i].address(), account_views[j].address())
143 {
144 return Err(ProgramError::AccountBorrowFailed);
145 }
146 j += 1;
147 }
148 }
149 i += 1;
150 }
151 Ok(())
152}
153
154#[inline]
155fn signer_matches_pda(program_id: &Address, account: &Address, signers_seeds: &[Signer]) -> bool {
156 let mut i = 0;
157 while i < signers_seeds.len() {
158 let signer = &signers_seeds[i];
159 let seeds = unsafe {
160 core::slice::from_raw_parts(signer.seeds, signer.len as usize)
161 };
162
163 if seeds.len() <= crate::address::MAX_SEEDS {
164 let mut seed_refs: [&[u8]; crate::address::MAX_SEEDS] = [&[]; crate::address::MAX_SEEDS];
165 let mut j = 0;
166 while j < seeds.len() {
167 seed_refs[j] = unsafe {
168 core::slice::from_raw_parts(seeds[j].seed, seeds[j].len as usize)
169 };
170 j += 1;
171 }
172
173 if let Ok(derived) = crate::compat::create_program_address(&seed_refs[..seeds.len()], program_id) {
174 if address_eq(&derived, account) {
175 return true;
176 }
177 }
178 }
179
180 i += 1;
181 }
182
183 false
184}
185
186#[inline]
188fn validate_cpi_accounts(
189 instruction: &InstructionView,
190 account_views: &[&AccountView],
191 signers_seeds: &[Signer],
192) -> ProgramResult {
193 if account_views.len() < instruction.accounts.len() {
194 return Err(ProgramError::NotEnoughAccountKeys);
195 }
196
197 let mut i = 0;
198 while i < instruction.accounts.len() {
199 let expected = &instruction.accounts[i];
200 let actual = account_views[i];
201
202 if !address_eq(actual.address(), expected.address) {
203 return Err(ProgramError::InvalidAccountData);
204 }
205
206 if expected.is_signer
207 && !actual.is_signer()
208 && !signer_matches_pda(instruction.program_id, actual.address(), signers_seeds)
209 {
210 return Err(ProgramError::MissingRequiredSignature);
211 }
212
213 if expected.is_writable && !actual.is_writable() {
214 return Err(ProgramError::Immutable);
215 }
216
217 if expected.is_writable {
218 actual.check_borrow_mut()?;
219 } else {
220 actual.check_borrow()?;
221 }
222
223 i += 1;
224 }
225
226 validate_no_duplicate_writable(instruction, account_views)?;
227
228 Ok(())
229}
230
231#[cfg(feature = "hopper-native-backend")]
235#[inline]
236pub fn invoke<const ACCOUNTS: usize>(
237 instruction: &InstructionView,
238 account_views: &[&AccountView; ACCOUNTS],
239) -> ProgramResult {
240 invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
241}
242
243#[cfg(feature = "hopper-native-backend")]
245#[inline]
246pub fn invoke_signed<const ACCOUNTS: usize>(
247 instruction: &InstructionView,
248 account_views: &[&AccountView; ACCOUNTS],
249 signers_seeds: &[Signer],
250) -> ProgramResult {
251 validate_cpi_accounts(instruction, &account_views[..], signers_seeds)?;
252
253 let mut cpi_accounts: [MaybeUninit<CpiAccount>; ACCOUNTS] =
254 unsafe { MaybeUninit::uninit().assume_init() };
255
256 let mut i = 0;
257 while i < ACCOUNTS {
258 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
259 i += 1;
260 }
261
262 let accounts: &[CpiAccount; ACCOUNTS] = unsafe {
263 &*(cpi_accounts.as_ptr() as *const [CpiAccount; ACCOUNTS])
264 };
265
266 unsafe {
267 if signers_seeds.is_empty() {
268 invoke_unchecked(instruction, accounts.as_slice())
269 } else {
270 invoke_signed_unchecked(instruction, accounts.as_slice(), signers_seeds)
271 }
272 }
273}
274
275#[cfg(feature = "hopper-native-backend")]
277#[inline]
278pub fn invoke_with_bounds<const MAX_ACCOUNTS: usize>(
279 instruction: &InstructionView,
280 account_views: &[&AccountView],
281) -> ProgramResult {
282 invoke_signed_with_bounds::<MAX_ACCOUNTS>(instruction, account_views, &[])
283}
284
285#[cfg(feature = "hopper-native-backend")]
287#[inline]
288pub fn invoke_signed_with_bounds<const MAX_ACCOUNTS: usize>(
289 instruction: &InstructionView,
290 account_views: &[&AccountView],
291 signers_seeds: &[Signer],
292) -> ProgramResult {
293 if account_views.len() > MAX_ACCOUNTS {
294 return Err(ProgramError::InvalidArgument);
295 }
296
297 validate_cpi_accounts(instruction, account_views, signers_seeds)?;
298
299 let mut cpi_accounts: [MaybeUninit<CpiAccount>; MAX_ACCOUNTS] =
300 unsafe { MaybeUninit::uninit().assume_init() };
301
302 let count = account_views.len();
303 let mut i = 0;
304 while i < count {
305 cpi_accounts[i] = MaybeUninit::new(CpiAccount::from(account_views[i]));
306 i += 1;
307 }
308
309 let accounts = unsafe {
310 core::slice::from_raw_parts(cpi_accounts.as_ptr() as *const CpiAccount, count)
311 };
312
313 unsafe {
314 if signers_seeds.is_empty() {
315 invoke_unchecked(instruction, accounts)
316 } else {
317 invoke_signed_unchecked(instruction, accounts, signers_seeds)
318 }
319 }
320}
321
322#[cfg(any(feature = "legacy-pinocchio-compat", feature = "solana-program-backend"))]
328#[inline]
329pub fn invoke<const ACCOUNTS: usize>(
330 instruction: &InstructionView,
331 account_views: &[&AccountView; ACCOUNTS],
332) -> ProgramResult {
333 invoke_signed::<ACCOUNTS>(instruction, account_views, &[])
334}
335
336#[cfg(any(feature = "legacy-pinocchio-compat", feature = "solana-program-backend"))]
338#[inline]
339pub fn invoke_signed<const ACCOUNTS: usize>(
340 instruction: &InstructionView,
341 account_views: &[&AccountView; ACCOUNTS],
342 signers_seeds: &[Signer],
343) -> ProgramResult {
344 validate_cpi_accounts(instruction, &account_views[..], signers_seeds)?;
345 crate::compat::invoke_signed(instruction, account_views, signers_seeds)
346}
347
348#[inline(always)]
352pub fn set_return_data(data: &[u8]) {
353 crate::compat::set_return_data(data)
354}
355
356#[cfg(all(test, feature = "hopper-native-backend"))]
357mod tests {
358 use super::*;
359
360 use crate::InstructionAccount;
361 use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
362
363 fn make_account(address: [u8; 32]) -> (std::vec::Vec<u8>, AccountView) {
364 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + 16];
365 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
366 unsafe {
367 raw.write(RuntimeAccount {
368 borrow_state: NOT_BORROWED,
369 is_signer: 0,
370 is_writable: 1,
371 executable: 0,
372 resize_delta: 0,
373 address: NativeAddress::new_from_array(address),
374 owner: NativeAddress::new_from_array([9; 32]),
375 lamports: 1,
376 data_len: 16,
377 });
378 }
379 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
380 (backing, AccountView::from_backend(backend))
381 }
382
383 #[test]
384 fn duplicate_writable_accounts_are_rejected_before_cpi() {
385 let (_first_backing, first) = make_account([3; 32]);
386 let (_second_backing, second) = make_account([3; 32]);
387
388 let instruction_accounts = [
389 InstructionAccount::writable(first.address()),
390 InstructionAccount::writable(second.address()),
391 ];
392 let program_id = Address::new_from_array([7; 32]);
393 let instruction = InstructionView {
394 program_id: &program_id,
395 data: &[0u8],
396 accounts: &instruction_accounts,
397 };
398
399 let err = validate_no_duplicate_writable(&instruction, &[&first, &second]).unwrap_err();
400 assert_eq!(err, ProgramError::AccountBorrowFailed);
401 }
402}