1use core::mem::MaybeUninit;
26
27use crate::instruction::{InstructionAccount, InstructionView, Signer};
28use crate::{
29 account::AccountView,
30 address::{address_eq, Address},
31 error::ProgramError,
32 result::ProgramResult,
33};
34
35pub struct DynCpi<'a, const MAX_ACCTS: usize, const MAX_DATA: usize> {
45 program_id: &'a Address,
46 accounts: [MaybeUninit<&'a AccountView<'a>>; MAX_ACCTS],
49 writable: [bool; MAX_ACCTS],
50 signer: [bool; MAX_ACCTS],
51 account_count: usize,
52 info_first: [u16; MAX_ACCTS],
60 info_writable: [bool; MAX_ACCTS],
61 info_signer: [bool; MAX_ACCTS],
62 info_count: usize,
63 data: [MaybeUninit<u8>; MAX_DATA],
64 data_len: usize,
65}
66
67impl<'a, const MAX_ACCTS: usize, const MAX_DATA: usize> DynCpi<'a, MAX_ACCTS, MAX_DATA> {
68 #[inline]
70 pub fn new(program_id: &'a Address) -> Self {
71 Self {
72 program_id,
73 accounts: [const { MaybeUninit::uninit() }; MAX_ACCTS],
74 writable: [false; MAX_ACCTS],
75 signer: [false; MAX_ACCTS],
76 account_count: 0,
77 info_first: [0u16; MAX_ACCTS],
78 info_writable: [false; MAX_ACCTS],
79 info_signer: [false; MAX_ACCTS],
80 info_count: 0,
81 data: [const { MaybeUninit::uninit() }; MAX_DATA],
82 data_len: 0,
83 }
84 }
85
86 #[inline]
102 pub fn push_account(
103 &mut self,
104 account: &'a AccountView<'a>,
105 writable: bool,
106 signer: bool,
107 ) -> ProgramResult {
108 if self.account_count >= MAX_ACCTS {
109 return Err(ProgramError::InvalidArgument);
110 }
111 let idx = self.account_count;
112 self.accounts[idx] = MaybeUninit::new(account);
113 self.writable[idx] = writable;
114 self.signer[idx] = signer;
115
116 let mut k = 0;
118 let mut merged = false;
119 while k < self.info_count {
120 let existing = unsafe { self.accounts[self.info_first[k] as usize].assume_init() };
123 if address_eq(existing.address(), account.address()) {
124 self.info_writable[k] |= writable;
125 self.info_signer[k] |= signer;
126 merged = true;
127 break;
128 }
129 k += 1;
130 }
131 if !merged {
132 let slot = self.info_count;
133 self.info_first[slot] = idx as u16;
134 self.info_writable[slot] = writable;
135 self.info_signer[slot] = signer;
136 self.info_count = self.info_count.wrapping_add(1);
137 }
138
139 self.account_count = self.account_count.wrapping_add(1);
140 Ok(())
141 }
142
143 #[inline]
149 pub fn push_data(&mut self, bytes: &[u8]) -> ProgramResult {
150 if self.data_len.saturating_add(bytes.len()) > MAX_DATA {
151 return Err(ProgramError::InvalidArgument);
152 }
153 let dst = &mut self.data[self.data_len..self.data_len + bytes.len()];
154 for (i, b) in bytes.iter().enumerate() {
155 dst[i] = MaybeUninit::new(*b);
156 }
157 self.data_len = self.data_len.wrapping_add(bytes.len());
158 Ok(())
159 }
160
161 #[inline]
164 pub fn push_byte(&mut self, byte: u8) -> ProgramResult {
165 self.push_data(core::slice::from_ref(&byte))
166 }
167
168 #[inline]
171 pub fn push_u64_le(&mut self, value: u64) -> ProgramResult {
172 self.push_data(&value.to_le_bytes())
173 }
174
175 #[inline]
177 pub fn push_pubkey(&mut self, address: &Address) -> ProgramResult {
178 self.push_data(address.as_array())
179 }
180
181 #[inline(always)]
185 pub const fn account_count(&self) -> usize {
186 self.account_count
187 }
188
189 #[inline(always)]
195 pub const fn info_count(&self) -> usize {
196 self.info_count
197 }
198
199 #[inline]
206 pub fn dedup_info(&self, k: usize) -> Option<(&'a AccountView<'a>, bool, bool)> {
207 if k >= self.info_count {
208 return None;
209 }
210 let view = unsafe { self.accounts[self.info_first[k] as usize].assume_init() };
213 Some((view, self.info_writable[k], self.info_signer[k]))
214 }
215
216 #[inline(always)]
218 pub const fn program_id(&self) -> &Address {
219 self.program_id
220 }
221
222 #[inline(always)]
224 pub const fn data_len(&self) -> usize {
225 self.data_len
226 }
227
228 #[inline]
232 pub fn data(&self) -> &[u8] {
233 unsafe { core::slice::from_raw_parts(self.data.as_ptr() as *const u8, self.data_len) }
235 }
236
237 #[inline]
239 pub fn account_views(&self) -> &[&'a AccountView<'a>] {
240 unsafe {
243 core::slice::from_raw_parts(
244 self.accounts.as_ptr() as *const &'a AccountView<'a>,
245 self.account_count,
246 )
247 }
248 }
249
250 #[inline]
255 pub fn invoke(&self) -> ProgramResult {
256 self.invoke_signed(&[])
257 }
258
259 #[inline]
273 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
274 let count = self.account_count;
275 let views = self.account_views();
276
277 let mut metas: [MaybeUninit<InstructionAccount<'a>>; MAX_ACCTS] =
279 [const { MaybeUninit::uninit() }; MAX_ACCTS];
280 let mut i = 0;
281 while i < count {
282 metas[i] = MaybeUninit::new(InstructionAccount::new(
283 views[i].address(),
284 self.writable[i],
285 self.signer[i],
286 ));
287 i += 1;
288 }
289 let metas_slice = unsafe {
291 core::slice::from_raw_parts(metas.as_ptr() as *const InstructionAccount<'a>, count)
292 };
293 let instruction = InstructionView {
294 program_id: self.program_id,
295 data: self.data(),
296 accounts: metas_slice,
297 };
298
299 let mut infos: [MaybeUninit<&'a AccountView<'a>>; MAX_ACCTS] =
302 [const { MaybeUninit::uninit() }; MAX_ACCTS];
303 let mut k = 0;
304 while k < self.info_count {
305 let view = unsafe { self.accounts[self.info_first[k] as usize].assume_init() };
308 infos[k] = MaybeUninit::new(view);
309 k += 1;
310 }
311 let infos_slice = unsafe {
313 core::slice::from_raw_parts(
314 infos.as_ptr() as *const &'a AccountView<'a>,
315 self.info_count,
316 )
317 };
318
319 crate::cpi::invoke_signed_deduped::<MAX_ACCTS>(&instruction, infos_slice, signers)
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn byte_push_walks_the_buffer() {
329 let program = Address::from([0u8; 32]);
330 let mut cpi: DynCpi<4, 32> = DynCpi::new(&program);
331 cpi.push_byte(0xA1).unwrap();
332 cpi.push_u64_le(0xCAFEBABE_u64).unwrap();
333 assert_eq!(cpi.data_len(), 1 + 8);
334 assert_eq!(cpi.data()[0], 0xA1);
335 assert_eq!(&cpi.data()[1..9], &0xCAFEBABE_u64.to_le_bytes());
336 }
337
338 #[test]
339 fn data_overflow_rejects() {
340 let program = Address::from([0u8; 32]);
341 let mut cpi: DynCpi<0, 4> = DynCpi::new(&program);
342 cpi.push_u64_le(1).expect_err("u64 is 8 bytes, buffer is 4");
343 }
344
345 #[test]
346 fn push_pubkey_fills_32_bytes() {
347 let program = Address::from([0u8; 32]);
348 let mut cpi: DynCpi<0, 64> = DynCpi::new(&program);
349 let pk = Address::from([0x7Au8; 32]);
350 cpi.push_pubkey(&pk).unwrap();
351 assert_eq!(cpi.data_len(), 32);
352 assert!(cpi.data().iter().all(|b| *b == 0x7A));
353 }
354
355 mod invoke {
356 use super::*;
357 use hopper_native::{
358 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
359 NOT_BORROWED,
360 };
361
362 fn make_account(
363 address_byte: u8,
364 is_signer: bool,
365 is_writable: bool,
366 ) -> (std::vec::Vec<u64>, AccountView<'static>) {
367 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + 8).div_ceil(8)];
368 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
369 unsafe {
372 raw.write(RuntimeAccount {
373 borrow_state: NOT_BORROWED,
374 is_signer: is_signer as u8,
375 is_writable: is_writable as u8,
376 executable: 0,
377 resize_delta: 0,
378 address: NativeAddress::new_from_array([address_byte; 32]),
379 owner: NativeAddress::new_from_array([2; 32]),
380 lamports: 1,
381 data_len: 8,
382 });
383 }
384 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
386 (backing, AccountView::from_backend(backend))
387 }
388
389 #[test]
390 fn invoke_validates_and_submits_built_metas() {
391 let program = Address::from([9u8; 32]);
392 let (_b1, signer_acct) = make_account(1, true, false);
393 let (_b2, writable_acct) = make_account(2, false, true);
394
395 let mut cpi: DynCpi<4, 16> = DynCpi::new(&program);
396 cpi.push_account(&signer_acct, false, true).unwrap();
397 cpi.push_account(&writable_acct, true, false).unwrap();
398 cpi.push_byte(3).unwrap();
399 cpi.push_u64_le(42).unwrap();
400
401 assert_eq!(cpi.invoke(), Ok(()));
406 }
407
408 #[test]
409 fn invoke_surfaces_flag_mismatch_from_built_metas() {
410 let program = Address::from([9u8; 32]);
411 let (_b1, not_signer) = make_account(3, false, false);
415
416 let mut cpi: DynCpi<2, 4> = DynCpi::new(&program);
417 cpi.push_account(¬_signer, false, true).unwrap();
418 assert_eq!(cpi.invoke(), Err(ProgramError::MissingRequiredSignature));
419 }
420
421 #[test]
422 fn invoke_rejects_duplicate_writable_accounts() {
423 let program = Address::from([9u8; 32]);
424 let (_b1, first) = make_account(4, false, true);
425 let (_b2, second) = make_account(4, false, true);
426
427 let mut cpi: DynCpi<2, 4> = DynCpi::new(&program);
428 cpi.push_account(&first, true, false).unwrap();
429 cpi.push_account(&second, true, false).unwrap();
430 assert_eq!(cpi.invoke(), Err(ProgramError::AccountBorrowFailed));
431 }
432
433 #[test]
434 fn account_views_exposes_push_order_prefix() {
435 let program = Address::from([9u8; 32]);
436 let (_b1, a) = make_account(5, false, false);
437 let (_b2, b) = make_account(6, false, false);
438
439 let mut cpi: DynCpi<4, 4> = DynCpi::new(&program);
440 cpi.push_account(&a, false, false).unwrap();
441 cpi.push_account(&b, false, false).unwrap();
442 let views = cpi.account_views();
443 assert_eq!(views.len(), 2);
444 assert_eq!(views[0].address(), &Address::from([5u8; 32]));
445 assert_eq!(views[1].address(), &Address::from([6u8; 32]));
446 }
447
448 #[test]
451 fn repeated_address_collapses_to_one_info_with_or_merged_flags() {
452 let program = Address::from([9u8; 32]);
453 let (_b, acct) = make_account(7, true, true);
455
456 let mut cpi: DynCpi<4, 4> = DynCpi::new(&program);
457 cpi.push_account(&acct, true, false).unwrap(); cpi.push_account(&acct, false, true).unwrap(); assert_eq!(cpi.account_count(), 2);
462 assert_eq!(cpi.info_count(), 1);
464
465 let (view, writable, signer) = cpi.dedup_info(0).unwrap();
466 assert_eq!(view.address(), &Address::from([7u8; 32]));
467 assert!(writable, "writable in any meta => info writable");
470 assert!(signer, "signer in any meta => info signer");
471 assert!(cpi.dedup_info(1).is_none());
472 }
473
474 #[test]
475 fn distinct_addresses_stay_distinct_infos() {
476 let program = Address::from([9u8; 32]);
477 let (_b1, a) = make_account(5, false, false);
478 let (_b2, b) = make_account(6, false, false);
479
480 let mut cpi: DynCpi<4, 4> = DynCpi::new(&program);
481 cpi.push_account(&a, false, false).unwrap();
482 cpi.push_account(&b, false, false).unwrap();
483
484 assert_eq!(cpi.account_count(), 2);
485 assert_eq!(cpi.info_count(), 2);
486 assert_eq!(
487 cpi.dedup_info(0).unwrap().0.address(),
488 &Address::from([5u8; 32])
489 );
490 assert_eq!(
491 cpi.dedup_info(1).unwrap().0.address(),
492 &Address::from([6u8; 32])
493 );
494 }
495
496 #[test]
497 fn metas_preserve_order_and_duplicates_while_infos_dedup() {
498 let program = Address::from([9u8; 32]);
499 let (_b1, a) = make_account(5, false, false);
500 let (_b2, b) = make_account(6, false, false);
501
502 let mut cpi: DynCpi<4, 4> = DynCpi::new(&program);
505 cpi.push_account(&a, false, false).unwrap();
506 cpi.push_account(&b, false, false).unwrap();
507 cpi.push_account(&a, false, false).unwrap();
508
509 let metas = cpi.account_views();
511 assert_eq!(metas.len(), 3);
512 assert_eq!(metas[0].address(), &Address::from([5u8; 32]));
513 assert_eq!(metas[1].address(), &Address::from([6u8; 32]));
514 assert_eq!(metas[2].address(), &Address::from([5u8; 32]));
515
516 assert_eq!(cpi.info_count(), 2);
518 assert_eq!(
519 cpi.dedup_info(0).unwrap().0.address(),
520 &Address::from([5u8; 32])
521 );
522 assert_eq!(
523 cpi.dedup_info(1).unwrap().0.address(),
524 &Address::from([6u8; 32])
525 );
526 }
527
528 #[test]
529 fn invoke_submits_deduped_repeated_readonly_account() {
530 let program = Address::from([9u8; 32]);
531 let (_b, acct) = make_account(8, false, false);
532
533 let mut cpi: DynCpi<4, 4> = DynCpi::new(&program);
534 cpi.push_account(&acct, false, false).unwrap();
535 cpi.push_account(&acct, false, false).unwrap();
536 cpi.push_account(&acct, false, false).unwrap();
537
538 assert_eq!(cpi.account_count(), 3);
540 assert_eq!(cpi.info_count(), 1);
541 assert_eq!(cpi.invoke(), Ok(()));
544 }
545
546 #[test]
547 fn wide_dyn_cpi_exceeds_legacy_64_account_ceiling() {
548 let program = Address::from([9u8; 32]);
549 let mut backings: std::vec::Vec<std::vec::Vec<u64>> = std::vec::Vec::new();
552 let mut views: std::vec::Vec<AccountView<'static>> = std::vec::Vec::new();
553 for i in 1..=65u8 {
554 let (b, v) = make_account(i, false, false);
555 backings.push(b);
556 views.push(v);
557 }
558
559 let mut cpi: DynCpi<70, 4> = DynCpi::new(&program);
560 for v in &views {
561 cpi.push_account(v, false, false).unwrap();
562 }
563
564 assert_eq!(cpi.account_count(), 65);
565 assert_eq!(cpi.info_count(), 65);
568 assert_eq!(cpi.invoke(), Ok(()));
569 }
570 }
571}