1mod account_meta;
2mod instruction;
3
4pub use account_meta::*;
5pub use instruction::*;
6use pinocchio::Address;
7
8use crate::args::{
9 EncryptedBuffer, MaybeEncryptedInstruction, MaybeEncryptedIxData,
10 MaybeEncryptedPubkey, PostDelegationActions,
11};
12
13pub trait ClearText: Sized {
14 type Output;
15
16 fn cleartext(self) -> Self::Output;
17}
18
19pub trait ClearTextWithInsertable: Sized {
20 type Output;
21
22 fn cleartext_with_insertable(
23 self,
24 insertable: PostDelegationActions,
25 insert_before_index: usize,
26 ) -> Self::Output;
27}
28
29impl ClearText for Vec<u8> {
30 type Output = MaybeEncryptedIxData;
31
32 fn cleartext(self) -> Self::Output {
33 MaybeEncryptedIxData {
34 prefix: self,
35 suffix: EncryptedBuffer::default(),
36 }
37 }
38}
39
40impl ClearText for Vec<solana_instruction::Instruction> {
41 type Output = PostDelegationActions;
42
43 fn cleartext(self) -> Self::Output {
44 let mut signers: Vec<solana_instruction::AccountMeta> = Vec::new();
45 let mut non_signers: Vec<solana_instruction::AccountMeta> = Vec::new();
46
47 let mut add_to_signers = |meta: &solana_instruction::AccountMeta| {
48 assert!(meta.is_signer, "AccountMeta must be a signer");
49 let Some(found) =
50 signers.iter_mut().find(|m| m.pubkey == meta.pubkey)
51 else {
52 signers.push(meta.clone());
53 return;
54 };
55
56 found.is_signer |= meta.is_signer;
57 found.is_writable |= meta.is_writable;
58 };
59
60 let mut add_to_non_signers =
61 |meta: &solana_instruction::AccountMeta| {
62 assert!(!meta.is_signer, "AccountMeta must not be a signer");
63 let Some(found) =
64 non_signers.iter_mut().find(|m| m.pubkey == meta.pubkey)
65 else {
66 non_signers.push(meta.clone());
67 return;
68 };
69
70 found.is_writable |= meta.is_writable;
71 };
72
73 for meta in self
74 .iter()
75 .flat_map(|ix| ix.accounts.iter())
76 .filter(|meta| meta.is_signer)
77 {
78 add_to_signers(meta);
79 }
80
81 for ix in self.iter() {
82 add_to_non_signers(&solana_instruction::AccountMeta::new_readonly(
83 ix.program_id,
84 false,
85 ));
86 for meta in ix.accounts.iter().filter(|meta| !meta.is_signer) {
87 let Some(found) =
88 signers.iter_mut().find(|m| m.pubkey == meta.pubkey)
89 else {
90 add_to_non_signers(meta);
91 continue;
92 };
93
94 found.is_writable |= meta.is_writable;
95 }
96 }
97
98 if signers.len() + non_signers.len()
99 > crate::compact::MAX_PUBKEYS as usize
100 {
101 panic!(
102 "delegate_with_actions supports at most {} unique pubkeys",
103 crate::compact::MAX_PUBKEYS
104 );
105 }
106
107 let index_of = |pk: &solana_address::Address| -> u8 {
108 if let Some(index) = signers.iter().position(|s| &s.pubkey == pk) {
109 return index as u8;
110 }
111 signers.len() as u8
112 + non_signers
113 .iter()
114 .position(|ns| &ns.pubkey == pk)
115 .expect("pubkey must exist in signers or non_signers")
116 as u8
117 };
118
119 let compact_instructions: Vec<MaybeEncryptedInstruction> = self
120 .into_iter()
121 .map(|ix| MaybeEncryptedInstruction {
122 program_id: index_of(&ix.program_id),
123
124 accounts: ix
125 .accounts
126 .into_iter()
127 .map(|meta| {
128 let index = index_of(&meta.pubkey);
129 crate::compact::AccountMeta::try_new(
130 index,
131 meta.is_signer,
132 meta.is_writable,
133 )
134 .expect("compact account index must fit in 6 bits")
135 .cleartext()
136 })
137 .collect(),
138
139 data: ix.data.cleartext(),
140 })
141 .collect();
142
143 PostDelegationActions {
144 inserted_signers: 0,
145 inserted_non_signers: 0,
146
147 signers: signers.iter().map(|s| s.pubkey.to_bytes()).collect(),
148
149 non_signers: non_signers
150 .into_iter()
151 .map(|ns| MaybeEncryptedPubkey::ClearText(ns.pubkey.to_bytes()))
152 .collect(),
153
154 instructions: compact_instructions,
155 }
156 }
157}
158
159impl ClearTextWithInsertable for Vec<solana_instruction::Instruction> {
160 type Output = PostDelegationActions;
161 fn cleartext_with_insertable(
162 self,
163 insertable: PostDelegationActions,
164 insert_before_index: usize,
165 ) -> Self::Output {
166 assert!(
167 insertable.inserted_signers == 0,
168 "PostDelegationActions does not support multiple merge/insert"
169 );
170 assert!(
171 insertable.inserted_non_signers == 0,
172 "PostDelegationActions does not support multiple merge/insert"
173 );
174
175 let mut skipable_pubkeys: Vec<Option<Address>> = vec![];
177 {
178 for signer in insertable.signers.iter() {
179 skipable_pubkeys.push(Some((*signer).into()));
180 }
181 for non_signer in insertable.non_signers.iter() {
182 if let MaybeEncryptedPubkey::ClearText(non_signer) = non_signer
183 {
184 skipable_pubkeys.push(Some((*non_signer).into()));
185 } else {
186 skipable_pubkeys.push(None);
190 }
191 }
192 }
193
194 let mut signers: Vec<solana_instruction::AccountMeta> = Vec::new();
195 let mut non_signers: Vec<solana_instruction::AccountMeta> = Vec::new();
196
197 let mut add_to_signers = |meta: &solana_instruction::AccountMeta| {
198 if skipable_pubkeys.contains(&Some(meta.pubkey)) {
199 return;
200 }
201
202 assert!(meta.is_signer, "AccountMeta must be a signer");
203 let Some(found) =
204 signers.iter_mut().find(|m| m.pubkey == meta.pubkey)
205 else {
206 signers.push(meta.clone());
207 return;
208 };
209
210 found.is_writable |= meta.is_writable;
211 };
212
213 let mut add_to_non_signers =
214 |meta: &solana_instruction::AccountMeta| {
215 if skipable_pubkeys.contains(&Some(meta.pubkey)) {
216 return;
217 }
218
219 assert!(!meta.is_signer, "AccountMeta must not be a signer");
220 let Some(found) =
221 non_signers.iter_mut().find(|m| m.pubkey == meta.pubkey)
222 else {
223 non_signers.push(meta.clone());
224 return;
225 };
226
227 found.is_writable |= meta.is_writable;
228 };
229
230 for meta in self
231 .iter()
232 .flat_map(|ix| ix.accounts.iter())
233 .filter(|meta| meta.is_signer)
234 {
235 add_to_signers(meta);
236 }
237
238 for ix in self.iter() {
239 add_to_non_signers(&solana_instruction::AccountMeta::new_readonly(
240 ix.program_id,
241 false,
242 ));
243 for meta in ix.accounts.iter().filter(|meta| !meta.is_signer) {
244 let Some(found) =
245 signers.iter_mut().find(|m| m.pubkey == meta.pubkey)
246 else {
247 add_to_non_signers(meta);
248 continue;
249 };
250
251 found.is_writable |= meta.is_writable;
252 }
253 }
254
255 if signers.len() + non_signers.len()
256 > crate::compact::MAX_PUBKEYS as usize
257 {
258 panic!(
259 "delegate_with_actions supports at most {} unique pubkeys",
260 crate::compact::MAX_PUBKEYS
261 );
262 }
263
264 let old_signers_len = insertable.signers.len();
265 let old_non_signers_len = insertable.non_signers.len();
266 let old_total = old_signers_len + old_non_signers_len;
267
268 let index_of = |pk: &solana_address::Address| -> u8 {
269 if let Some(index) = skipable_pubkeys
287 .iter()
288 .position(|pubkey| pubkey == &Some(*pk))
289 {
290 return index as u8;
291 }
292
293 if let Some(index) = signers.iter().position(|s| &s.pubkey == pk) {
294 return (old_total + index) as u8;
295 }
296 (old_total
297 + signers.len()
298 + non_signers.iter().position(|ns| &ns.pubkey == pk).unwrap())
299 as u8
300 };
301
302 let mut compact_instructions: Vec<MaybeEncryptedInstruction> = self
303 .into_iter()
304 .map(|ix| MaybeEncryptedInstruction {
305 program_id: index_of(&ix.program_id),
306
307 accounts: ix
308 .accounts
309 .into_iter()
310 .map(|meta| {
311 let index = index_of(&meta.pubkey);
312 crate::compact::AccountMeta::try_new(
313 index,
314 meta.is_signer,
315 meta.is_writable,
316 )
317 .expect("compact account index must fit in 6 bits")
318 .cleartext()
319 })
320 .collect(),
321
322 data: ix.data.cleartext(),
323 })
324 .collect();
325
326 let mut rv = insertable;
328 rv.inserted_signers = old_signers_len as u8;
329 rv.inserted_non_signers = old_non_signers_len as u8;
330
331 rv.signers.extend_from_slice(
332 &signers
333 .iter()
334 .map(|s| s.pubkey.to_bytes())
335 .collect::<Vec<_>>(),
336 );
337 rv.non_signers.extend_from_slice(
338 &non_signers
339 .iter()
340 .map(|ns| MaybeEncryptedPubkey::ClearText(ns.pubkey.to_bytes()))
341 .collect::<Vec<_>>(),
342 );
343
344 if insert_before_index <= compact_instructions.len() {
345 compact_instructions.splice(
346 insert_before_index..insert_before_index,
347 rv.instructions,
348 );
349 } else {
350 compact_instructions.extend_from_slice(&rv.instructions);
351 }
352
353 rv.instructions = compact_instructions;
354
355 rv
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use solana_instruction::{AccountMeta, Instruction};
362 use solana_pubkey::Pubkey;
363
364 use super::*;
365 use crate::args::MaybeEncryptedAccountMeta;
366
367 fn pk(byte: u8) -> Pubkey {
368 Pubkey::new_from_array([byte; 32])
369 }
370
371 fn assert_cleartext_meta(
372 meta: &MaybeEncryptedAccountMeta,
373 expected_index: u8,
374 expected_signer: bool,
375 ) {
376 let MaybeEncryptedAccountMeta::ClearText(meta) = meta else {
377 panic!("expected cleartext account meta");
378 };
379 assert_eq!(meta.key(), expected_index);
380 assert_eq!(meta.is_signer(), expected_signer);
381 }
382
383 #[test]
384 fn test_cleartext_with_insertable_indices() {
385 let s1 = pk(1);
386 let s2 = pk(2);
387 let n1 = pk(3);
388 let n2 = pk(4);
389 let n3 = pk(5);
390
391 let insertable = PostDelegationActions {
392 inserted_signers: 0,
393 inserted_non_signers: 0,
394 signers: vec![s1.to_bytes(), s2.to_bytes()],
395 non_signers: vec![
396 MaybeEncryptedPubkey::ClearText(n1.to_bytes()),
397 MaybeEncryptedPubkey::ClearText(n2.to_bytes()),
398 MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new(
399 n3.to_bytes().into(),
400 )),
401 ],
402 instructions: vec![MaybeEncryptedInstruction {
403 program_id: 0,
404 accounts: vec![],
405 data: MaybeEncryptedIxData {
406 prefix: vec![],
407 suffix: EncryptedBuffer::new(vec![]),
408 },
409 }],
410 };
411
412 let ns1 = pk(6);
413 let nn1 = pk(7);
414 let program_id = pk(8);
415
416 let ix = Instruction {
417 program_id,
418 accounts: vec![
419 AccountMeta::new_readonly(s1, true), AccountMeta::new_readonly(ns1, true),
421 AccountMeta::new_readonly(nn1, false),
422 AccountMeta::new_readonly(n3, false), ],
424 data: vec![1, 2, 3],
425 };
426
427 let actions = vec![ix].cleartext_with_insertable(insertable, 1);
428
429 assert_eq!(actions.inserted_signers, 2);
430 assert_eq!(actions.inserted_non_signers, 3); assert_eq!(actions.signers.len(), 3);
433 assert_eq!(actions.non_signers.len(), 5 + 1); assert_eq!(
436 actions.signers,
437 vec![s1.to_bytes(), s2.to_bytes(), ns1.to_bytes()]
438 );
439 assert_eq!(
440 actions.non_signers,
441 vec![
442 MaybeEncryptedPubkey::ClearText(n1.to_bytes()),
443 MaybeEncryptedPubkey::ClearText(n2.to_bytes()),
444 MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new(
445 n3.to_bytes().into(),
446 )),
447 MaybeEncryptedPubkey::ClearText(program_id.to_bytes()),
448 MaybeEncryptedPubkey::ClearText(nn1.to_bytes()),
449 MaybeEncryptedPubkey::ClearText(n3.to_bytes()),
450 ]
451 );
452
453 assert_eq!(actions.instructions.len(), 2);
454 let new_ix = &actions.instructions[0];
455 assert_eq!(new_ix.program_id, 6);
456 assert_eq!(new_ix.accounts.len(), 4);
457
458 assert_cleartext_meta(&new_ix.accounts[0], 0, true);
459 assert_cleartext_meta(&new_ix.accounts[1], 5, true);
460 assert_cleartext_meta(&new_ix.accounts[2], 7, false);
461 assert_cleartext_meta(&new_ix.accounts[3], 8, false);
462 }
463}