additional_accounts_request/
lib.rs1use anchor_lang::prelude::*;
4use anchor_lang::solana_program::log::sol_log_compute_units;
5use anchor_lang::solana_program::program::MAX_RETURN_DATA;
6use anchor_lang::solana_program::{
7 hash,
8 program::{get_return_data, invoke, invoke_signed, set_return_data},
9};
10
11#[derive(Debug, Copy, Clone, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)]
12#[repr(C)]
13pub struct IAccountMeta {
14 pub pubkey: Pubkey,
15 pub writable: u8,
16}
17
18pub const MAX_ACCOUNTS: usize = 30;
19
20#[zero_copy]
21#[derive(Debug, AnchorDeserialize, AnchorSerialize)]
22pub struct AdditionalAccounts {
23 pub protocol_version: u8,
24 pub has_more: u8,
25 pub _padding_1: [u8; 2],
26 pub num_accounts: u32,
27 pub accounts: [Pubkey; MAX_ACCOUNTS],
28 pub writable_bits: [u8; MAX_ACCOUNTS],
29 pub _padding_2: [u8; 26],
30}
31
32impl Default for AdditionalAccounts {
33 fn default() -> Self {
34 Self {
35 protocol_version: 0,
36 has_more: 0,
37 _padding_1: [0u8; 2],
38 num_accounts: 0u32,
39 accounts: [Pubkey::default(); MAX_ACCOUNTS],
40 writable_bits: [0u8; MAX_ACCOUNTS],
41 _padding_2: [0u8; 26],
42 }
43 }
44}
45
46impl AdditionalAccounts {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn has_space_available(&self) -> bool {
52 MAX_ACCOUNTS - self.num_accounts as usize > 0
53 }
54
55 pub fn set_has_more(&mut self, has_more: bool) {
56 self.has_more = match has_more {
57 true => 1,
58 false => 0,
59 };
60 }
61
62 pub fn add_account(&mut self, pubkey: &Pubkey, writable: bool) -> Result<()> {
63 if self.num_accounts >= MAX_ACCOUNTS as u32 {
64 msg!("Cannot write another account");
65 return Err(ProgramError::InvalidInstructionData.into());
66 }
67
68 self.accounts[self.num_accounts as usize] = *pubkey;
69 self.writable_bits[self.num_accounts as usize] = match writable {
70 true => 1,
71 false => 0,
72 };
73 self.num_accounts += 1;
74 Ok(())
75 }
76
77 pub fn iter(&self) -> impl DoubleEndedIterator<Item = (&Pubkey, bool)> {
78 let num_accounts = self.num_accounts as usize;
79 self.accounts[0..num_accounts]
80 .iter()
81 .zip(self.writable_bits[0..num_accounts].iter())
82 .map(|(pubkey, writable)| {
83 (
84 pubkey,
85 match writable {
86 0 => false,
87 1 => true,
88 _ => panic!("Invalid writable bit"),
89 },
90 )
91 })
92 }
93
94 pub fn iter_from(&self, start: usize) -> impl DoubleEndedIterator<Item = (&Pubkey, bool)> {
95 let num_accounts = self.num_accounts as usize;
96 self.accounts[start..num_accounts]
97 .iter()
98 .zip(self.writable_bits[0..num_accounts].iter())
99 .map(|(pubkey, writable)| {
100 (
101 pubkey,
102 match writable {
103 0 => false,
104 1 => true,
105 _ => panic!("Invalid writable bit"),
106 },
107 )
108 })
109 }
110
111 pub fn from_return_data(data: &[u8]) -> Result<&Self> {
112 if data.len() != MAX_RETURN_DATA {
113 msg!("Invalid return data length");
114 return Err(ProgramError::InvalidAccountData.into());
115 }
116 Ok(bytemuck::from_bytes::<AdditionalAccounts>(&data))
117 }
118}
119
120#[inline(never)]
122pub fn resolve_additional_accounts<'info, C1: ToAccountInfos<'info> + ToAccountMetas>(
123 ix_name: String,
124 ctx: &CpiContext<'_, '_, '_, 'info, C1>,
125 args: &[u8],
126 log_info: bool,
127) -> Result<AdditionalAccounts> {
128 call_preflight_interface_function(ix_name.clone(), &ctx, &args)?;
129
130 let program_key = ctx.program.key();
131 let (key, program_data) = get_return_data().unwrap();
132 assert_eq!(key, program_key);
133
134 let program_data = program_data.as_slice();
135 if log_info {
136 msg!("Return data length: {}", program_data.len());
137 }
138
139 let accs: AdditionalAccounts = bytemuck::pod_read_unaligned::<AdditionalAccounts>(program_data);
142 if log_info {
143 msg!(
144 "Accounts has more: {} {}",
145 accs.has_more,
146 accs.accounts.len()
147 );
148 }
149 Ok(accs)
150}
151
152pub fn identify_additional_accounts<'info, C1: ToAccountInfos<'info> + ToAccountMetas>(
155 ix_name: String,
156 ctx: &CpiContext<'_, '_, '_, 'info, C1>,
157 args: &[u8],
158 log_info: bool,
159) -> Result<Vec<AdditionalAccounts>> {
160 if log_info {
161 msg!("Preflight {}", &ix_name);
162 }
163
164 let mut additional_accounts: Vec<AdditionalAccounts> = vec![];
165
166 let mut has_more = true;
169 while has_more {
170 let accs = resolve_additional_accounts(ix_name.clone(), ctx, args, log_info)?;
171
172 additional_accounts.push(accs);
173
174 let mut should_exit = false;
176 accs.iter().rev().for_each(|(acc, _writable)| {
177 let mut found = false;
178 ctx.remaining_accounts.iter().rev().for_each(|account| {
179 if account.key == acc {
180 found = true;
181 }
182 });
183 if !found {
184 should_exit = true;
185 }
186 });
187 if should_exit {
188 msg!("Missing account(s)");
189 break;
190 }
191
192 has_more = accs.has_more == 1;
193 }
194
195 Ok(additional_accounts)
196}
197
198pub fn call_preflight_interface_function<'info, T: ToAccountInfos<'info> + ToAccountMetas>(
200 function_name: String,
201 ctx: &CpiContext<'_, '_, '_, 'info, T>,
202 args: &[u8],
203) -> Result<()> {
204 sol_log_compute_units();
206 let mut ix_data: Vec<u8> =
207 hash::hash(format!("global:preflight_{}", &function_name).as_bytes()).to_bytes()[..8]
208 .to_vec();
209
210 ix_data.extend_from_slice(args);
211
212 let mut ix_account_metas = ctx.accounts.to_account_metas(Some(false));
213 ix_account_metas.extend(ctx.remaining_accounts.to_account_metas(None));
214
215 let ix = anchor_lang::solana_program::instruction::Instruction {
216 program_id: ctx.program.key(),
217 accounts: ix_account_metas,
218 data: ix_data,
219 };
220 sol_log_compute_units();
221 msg!("Preflighted...");
222
223 let mut ix_ais = ctx.accounts.to_account_infos();
225 ix_ais.extend(ctx.remaining_accounts.to_account_infos());
226 invoke(&ix, &ix_ais)?;
227 Ok(())
228}
229
230pub fn call_interface_function_raw(
231 program_key: &Pubkey,
232 function_name: String,
233 args: &[u8],
234 metas: Vec<AccountMeta>,
235 accounts: &[AccountInfo],
236 signer_seeds: &[&[&[u8]]],
237 log_info: bool,
238) -> Result<()> {
239 let mut ix_data: Vec<u8> =
240 hash::hash(format!("global:{}", &function_name).as_bytes()).to_bytes()[..8].to_vec();
241 ix_data.extend_from_slice(&args);
242
243 if log_info {
244 msg!("Account Metas creation...");
245 sol_log_compute_units();
246 }
247 if log_info {
248 sol_log_compute_units();
249 msg!("Account Metas created...");
250 }
251
252 let ix = anchor_lang::solana_program::instruction::Instruction {
253 program_id: *program_key,
254 accounts: metas,
255 data: ix_data,
256 };
257
258 if log_info {
262 msg!("Finished creating context...");
263 sol_log_compute_units();
264 }
265
266 invoke_signed(&ix, &accounts, &signer_seeds)?;
267 Ok(())
268}
269
270pub fn call_interface_function<'info, T: ToAccountInfos<'info> + ToAccountMetas>(
273 function_name: String,
274 ctx: CpiContext<'_, '_, '_, 'info, T>,
275 args: &[u8],
276 additional_accounts: &mut dyn Iterator<Item = (&Pubkey, bool)>,
277 log_info: bool,
278) -> Result<()> {
279 if log_info {
280 msg!("Creating interface context...");
281 sol_log_compute_units();
282 }
283 let remaining_accounts = ctx.remaining_accounts.to_vec();
285
286 let mut ix_data: Vec<u8> =
287 hash::hash(format!("global:{}", &function_name).as_bytes()).to_bytes()[..8].to_vec();
288 ix_data.extend_from_slice(&args);
289
290 if log_info {
291 msg!("Account Metas creation...");
292 sol_log_compute_units();
293 }
294 let mut ix_account_metas = ctx.accounts.to_account_metas(None);
295 ix_account_metas.append(
296 additional_accounts
297 .map(|(acc, writable)| {
298 if writable {
299 AccountMeta::new(*acc, false)
300 } else {
301 AccountMeta::new_readonly(*acc, false)
302 }
303 })
304 .collect::<Vec<AccountMeta>>()
305 .as_mut(),
306 );
307 if log_info {
308 sol_log_compute_units();
309 msg!("Account Metas created...");
310 }
311
312 let ix = anchor_lang::solana_program::instruction::Instruction {
313 program_id: ctx.program.key(),
314 accounts: ix_account_metas,
315 data: ix_data,
316 };
317
318 let mut ix_ais: Vec<AccountInfo> = ctx.accounts.to_account_infos();
319 if log_info {
320 msg!("IX accounts: {:?}", &ix_ais.len());
321 msg!("Account Info creation...");
322 sol_log_compute_units();
323 }
324 ix_ais.extend_from_slice(&remaining_accounts);
328 if log_info {
329 sol_log_compute_units();
330 msg!("Account Infos created...");
331 }
332
333 if log_info {
334 msg!("IX accounts: {:?}", &ix_ais.len());
335 }
346
347 if log_info {
348 msg!("Finished creating context...");
349 sol_log_compute_units();
350 }
351
352 invoke_signed(&ix, &ix_ais, &ctx.signer_seeds)?;
354 Ok(())
355}
356
357pub fn get_delimiter(program_id: &Pubkey) -> Pubkey {
358 Pubkey::find_program_address(&["DELIMITER".as_ref()], program_id).0
359}
360
361#[inline(never)]
366pub fn call<'info, C1: ToAccountInfos<'info> + ToAccountMetas>(
367 ix_name: String,
368 ctx: CpiContext<'_, '_, '_, 'info, C1>,
369 args: Vec<u8>,
370 delimiter: Pubkey,
371 num_accounts_consumed: u8,
372 log_info: bool,
373) -> Result<u8> {
374 let mut accounts = ctx.accounts.to_account_infos();
376 let mut metas = ctx.accounts.to_account_metas(None);
377
378 if log_info {
379 msg!("Identifying additional accounts...");
380 sol_log_compute_units();
381 }
382 let mut used_accounts = 0;
383 for acc in ctx.remaining_accounts[num_accounts_consumed as usize..].iter() {
384 used_accounts += 1;
385 if *acc.key != delimiter {
386 accounts.push(acc.clone());
387 metas.push(AccountMeta {
388 pubkey: *acc.key,
389 is_signer: acc.is_signer,
390 is_writable: acc.is_writable,
391 });
392 } else {
393 if log_info {
394 msg!("Found delimiter");
395 }
396 break;
397 }
398 }
399
400 if log_info {
402 sol_log_compute_units();
403 msg!("Execute {}", &ix_name);
404 }
405 call_interface_function_raw(
406 ctx.program.key,
407 ix_name.clone(),
408 &args,
409 metas,
410 &accounts,
411 ctx.signer_seeds,
412 log_info,
413 )?;
414 Ok(num_accounts_consumed + used_accounts)
415}
416
417pub fn forward_return_data(expected_program_key: &Pubkey) {
418 let (key, return_data) = get_return_data().unwrap();
419 assert_eq!(key, *expected_program_key);
420 set_return_data(&return_data);
421}
422
423pub trait InterfaceInstruction {
424 fn instruction_name() -> String;
425}