1use core::mem::MaybeUninit;
23use hopper_runtime::error::ProgramError;
24use hopper_runtime::ProgramResult;
25
26pub struct HopperCpi<'a, const ACCTS: usize, const DATA: usize> {
32 #[allow(dead_code)]
34 program_id: &'a hopper_runtime::Address,
35 account_keys: [&'a hopper_runtime::Address; ACCTS],
37 account_flags: [(bool, bool); ACCTS], account_views: [MaybeUninit<&'a hopper_runtime::AccountView>; ACCTS],
42 data: [u8; DATA],
44 acct_cursor: usize,
46}
47
48impl<'a, const ACCTS: usize, const DATA: usize> HopperCpi<'a, ACCTS, DATA> {
49 #[inline(always)]
51 pub fn new(program_id: &'a hopper_runtime::Address) -> Self {
52 Self {
53 program_id,
54 account_keys: [program_id; ACCTS], account_flags: [(false, false); ACCTS],
56 account_views: unsafe { MaybeUninit::uninit().assume_init() },
59 data: [0u8; DATA],
60 acct_cursor: 0,
61 }
62 }
63
64 #[inline(always)]
68 pub fn add_account(
69 mut self,
70 view: &'a hopper_runtime::AccountView,
71 is_writable: bool,
72 is_signer: bool,
73 ) -> Self {
74 let idx = self.acct_cursor;
75 debug_assert!(idx < ACCTS, "Too many accounts added to CPI");
76 self.account_keys[idx] = view.address();
77 self.account_flags[idx] = (is_writable, is_signer);
78 self.account_views[idx] = MaybeUninit::new(view);
79 self.acct_cursor += 1;
80 self
81 }
82
83 #[inline(always)]
85 pub fn set_data(mut self, src: &[u8; DATA]) -> Self {
86 self.data = *src;
87 self
88 }
89
90 #[inline(always)]
92 pub fn set_data_from_slice(mut self, src: &[u8]) -> Result<Self, ProgramError> {
93 if src.len() != DATA {
94 return Err(ProgramError::InvalidInstructionData);
95 }
96 self.data.copy_from_slice(src);
97 Ok(self)
98 }
99
100 #[inline]
102 pub fn invoke(&self) -> ProgramResult {
103 debug_assert_eq!(self.acct_cursor, ACCTS, "Not all accounts added to CPI");
104 self.invoke_signed(&[])
105 }
106
107 #[inline]
109 pub fn invoke_signed(&self, seeds: &[&[&[u8]]]) -> ProgramResult {
110 #[cfg(target_os = "solana")]
111 {
112 use hopper_runtime::instruction::{InstructionAccount, InstructionView, Seed, Signer};
113
114 debug_assert_eq!(self.acct_cursor, ACCTS, "Not all accounts added to CPI");
115
116 let views: &[&hopper_runtime::AccountView; ACCTS] = unsafe {
120 &*(&self.account_views as *const [MaybeUninit<&hopper_runtime::AccountView>; ACCTS]
121 as *const [&hopper_runtime::AccountView; ACCTS])
122 };
123
124 let mut ix_accounts: [InstructionAccount; ACCTS] = unsafe { core::mem::zeroed() };
127 let mut i = 0;
128 while i < ACCTS {
129 ix_accounts[i] = InstructionAccount {
130 address: self.account_keys[i],
131 is_writable: self.account_flags[i].0,
132 is_signer: self.account_flags[i].1,
133 };
134 i += 1;
135 }
136
137 let ix = InstructionView {
138 program_id: self.program_id,
139 accounts: &ix_accounts,
140 data: &self.data,
141 };
142
143 if seeds.is_empty() {
144 hopper_runtime::cpi::invoke(&ix, views)
145 } else {
146 let mut signers_buf: [Signer; 4] = unsafe { core::mem::zeroed() };
148 let signer_count = seeds.len().min(4);
149 let mut seed_bufs: [[Seed; 16]; 4] = unsafe { core::mem::zeroed() };
150 let mut seed_lens = [0usize; 4];
151
152 let mut s = 0;
153 while s < signer_count {
154 let signer_seeds = seeds[s];
155 let num_seeds = signer_seeds.len().min(16);
156 let mut sd = 0;
157 while sd < num_seeds {
158 seed_bufs[s][sd] = Seed::from(signer_seeds[sd]);
159 sd += 1;
160 }
161 seed_lens[s] = num_seeds;
162 s += 1;
163 }
164
165 let mut s = 0;
166 while s < signer_count {
167 signers_buf[s] = Signer::from(&seed_bufs[s][..seed_lens[s]]);
168 s += 1;
169 }
170
171 hopper_runtime::cpi::invoke_signed(&ix, views, &signers_buf[..signer_count])
172 }
173 }
174 #[cfg(not(target_os = "solana"))]
175 {
176 let _ = seeds;
177 Ok(())
178 }
179 }
180}
181
182pub struct HopperCpiBuf<'a, const ACCTS: usize, const MAX: usize> {
187 #[allow(dead_code)]
188 program_id: &'a hopper_runtime::Address,
189 account_keys: [&'a hopper_runtime::Address; ACCTS],
190 account_flags: [(bool, bool); ACCTS],
191 account_views: [MaybeUninit<&'a hopper_runtime::AccountView>; ACCTS],
192 data: [u8; MAX],
193 data_len: usize,
194 acct_cursor: usize,
195}
196
197impl<'a, const ACCTS: usize, const MAX: usize> HopperCpiBuf<'a, ACCTS, MAX> {
198 #[inline(always)]
200 pub fn new(program_id: &'a hopper_runtime::Address) -> Self {
201 Self {
202 program_id,
203 account_keys: [program_id; ACCTS],
204 account_flags: [(false, false); ACCTS],
205 account_views: unsafe { MaybeUninit::uninit().assume_init() },
207 data: [0u8; MAX],
208 data_len: 0,
209 acct_cursor: 0,
210 }
211 }
212
213 #[inline(always)]
215 pub fn add_account(
216 mut self,
217 view: &'a hopper_runtime::AccountView,
218 is_writable: bool,
219 is_signer: bool,
220 ) -> Self {
221 let idx = self.acct_cursor;
222 debug_assert!(idx < ACCTS);
223 self.account_keys[idx] = view.address();
224 self.account_flags[idx] = (is_writable, is_signer);
225 self.account_views[idx] = MaybeUninit::new(view);
226 self.acct_cursor += 1;
227 self
228 }
229
230 #[inline]
232 pub fn write_data(mut self, src: &[u8]) -> Result<Self, ProgramError> {
233 if src.len() > MAX {
234 return Err(ProgramError::InvalidInstructionData);
235 }
236 self.data[..src.len()].copy_from_slice(src);
237 self.data_len = src.len();
238 Ok(self)
239 }
240
241 #[inline]
243 pub fn invoke(&self) -> ProgramResult {
244 self.invoke_signed(&[])
245 }
246
247 #[inline]
249 pub fn invoke_signed(&self, seeds: &[&[&[u8]]]) -> ProgramResult {
250 #[cfg(target_os = "solana")]
251 {
252 use hopper_runtime::instruction::{InstructionAccount, InstructionView, Seed, Signer};
253
254 debug_assert_eq!(self.acct_cursor, ACCTS, "Not all accounts added to CPI");
255
256 let views: &[&hopper_runtime::AccountView; ACCTS] = unsafe {
258 &*(&self.account_views as *const [MaybeUninit<&hopper_runtime::AccountView>; ACCTS]
259 as *const [&hopper_runtime::AccountView; ACCTS])
260 };
261
262 let mut ix_accounts: [InstructionAccount; ACCTS] = unsafe { core::mem::zeroed() };
264 let mut i = 0;
265 while i < ACCTS {
266 ix_accounts[i] = InstructionAccount {
267 address: self.account_keys[i],
268 is_writable: self.account_flags[i].0,
269 is_signer: self.account_flags[i].1,
270 };
271 i += 1;
272 }
273
274 let ix = InstructionView {
275 program_id: self.program_id,
276 accounts: &ix_accounts,
277 data: &self.data[..self.data_len],
278 };
279
280 if seeds.is_empty() {
281 hopper_runtime::cpi::invoke(&ix, views)
282 } else {
283 let mut signers_buf: [Signer; 4] = unsafe { core::mem::zeroed() };
285 let signer_count = seeds.len().min(4);
286 let mut seed_bufs: [[Seed; 16]; 4] = unsafe { core::mem::zeroed() };
287 let mut seed_lens = [0usize; 4];
288
289 let mut s = 0;
290 while s < signer_count {
291 let signer_seeds = seeds[s];
292 let num_seeds = signer_seeds.len().min(16);
293 let mut sd = 0;
294 while sd < num_seeds {
295 seed_bufs[s][sd] = Seed::from(signer_seeds[sd]);
296 sd += 1;
297 }
298 seed_lens[s] = num_seeds;
299 s += 1;
300 }
301
302 let mut s = 0;
303 while s < signer_count {
304 signers_buf[s] = Signer::from(&seed_bufs[s][..seed_lens[s]]);
305 s += 1;
306 }
307
308 hopper_runtime::cpi::invoke_signed(&ix, views, &signers_buf[..signer_count])
309 }
310 }
311 #[cfg(not(target_os = "solana"))]
312 {
313 let _ = seeds;
314 Ok(())
315 }
316 }
317}