1use account_compression::StateMerkleTreeAccount;
2use anchor_lang::prelude::*;
3use light_compressed_account::{
4 compressed_account::{CompressedAccount, CompressedAccountData},
5 hash_to_bn254_field_size_be,
6 instruction_data::{
7 compressed_proof::CompressedProof, cpi_context::CompressedCpiContext,
8 data::OutputCompressedAccountWithPackedContext, with_readonly::InAccount,
9 },
10};
11
12use crate::{
13 constants::TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
14 process_transfer::{
15 add_data_hash_to_input_compressed_accounts, cpi_execute_compressed_transaction_transfer,
16 get_input_compressed_accounts_with_merkle_context_and_check_signer,
17 InputTokenDataWithContext, BATCHED_DISCRIMINATOR,
18 },
19 token_data::{AccountState, TokenData},
20 FreezeInstruction,
21};
22
23#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
24pub struct CompressedTokenInstructionDataFreeze {
25 pub proof: CompressedProof,
26 pub owner: Pubkey,
27 pub input_token_data_with_context: Vec<InputTokenDataWithContext>,
28 pub cpi_context: Option<CompressedCpiContext>,
29 pub outputs_merkle_tree_index: u8,
30}
31
32pub fn process_freeze_or_thaw<
33 'a,
34 'b,
35 'c,
36 'info: 'b + 'c,
37 const FROZEN_INPUTS: bool,
38 const FROZEN_OUTPUTS: bool,
39>(
40 ctx: Context<'a, 'b, 'c, 'info, FreezeInstruction<'info>>,
41 inputs: Vec<u8>,
42) -> Result<()> {
43 let inputs: CompressedTokenInstructionDataFreeze =
44 CompressedTokenInstructionDataFreeze::deserialize(&mut inputs.as_slice())?;
45 let (compressed_input_accounts, output_compressed_accounts) =
46 create_input_and_output_accounts_freeze_or_thaw::<FROZEN_INPUTS, FROZEN_OUTPUTS>(
47 &inputs,
48 &ctx.accounts.mint.key(),
49 ctx.remaining_accounts,
50 )?;
51 let proof = if inputs.proof == CompressedProof::default() {
53 None
54 } else {
55 Some(inputs.proof)
56 };
57 cpi_execute_compressed_transaction_transfer(
58 ctx.accounts,
59 compressed_input_accounts,
60 output_compressed_accounts,
61 false,
62 proof,
63 inputs.cpi_context,
64 ctx.accounts.cpi_authority_pda.to_account_info(),
65 ctx.accounts.light_system_program.to_account_info(),
66 ctx.accounts.self_program.to_account_info(),
67 ctx.remaining_accounts,
68 )
69}
70
71pub fn create_input_and_output_accounts_freeze_or_thaw<
72 const FROZEN_INPUTS: bool,
73 const FROZEN_OUTPUTS: bool,
74>(
75 inputs: &CompressedTokenInstructionDataFreeze,
76 mint: &Pubkey,
77 remaining_accounts: &[AccountInfo<'_>],
78) -> Result<(
79 Vec<InAccount>,
80 Vec<OutputCompressedAccountWithPackedContext>,
81)> {
82 if inputs.input_token_data_with_context.is_empty() {
83 return err!(crate::ErrorCode::NoInputTokenAccountsProvided);
84 }
85 let (mut compressed_input_accounts, input_token_data, _) =
86 get_input_compressed_accounts_with_merkle_context_and_check_signer::<FROZEN_INPUTS>(
87 &inputs.owner,
92 &None,
93 remaining_accounts,
94 &inputs.input_token_data_with_context,
95 mint,
96 )?;
97 let output_len = compressed_input_accounts.len();
98 let mut output_compressed_accounts =
99 vec![OutputCompressedAccountWithPackedContext::default(); output_len];
100 let hashed_mint = hash_to_bn254_field_size_be(mint.to_bytes().as_slice());
101 create_token_output_accounts::<FROZEN_OUTPUTS>(
102 inputs.input_token_data_with_context.as_slice(),
103 remaining_accounts,
104 mint,
105 &inputs.owner,
110 &inputs.outputs_merkle_tree_index,
111 &mut output_compressed_accounts,
112 )?;
113
114 add_data_hash_to_input_compressed_accounts::<FROZEN_INPUTS>(
115 &mut compressed_input_accounts,
116 input_token_data.as_slice(),
117 &hashed_mint,
118 remaining_accounts,
119 )?;
120 Ok((compressed_input_accounts, output_compressed_accounts))
121}
122
123fn create_token_output_accounts<const IS_FROZEN: bool>(
127 input_token_data_with_context: &[InputTokenDataWithContext],
128 remaining_accounts: &[AccountInfo],
129 mint: &Pubkey,
130 owner: &Pubkey,
131 outputs_merkle_tree_index: &u8,
132 output_compressed_accounts: &mut [OutputCompressedAccountWithPackedContext],
133) -> Result<()> {
134 for (i, token_data_with_context) in input_token_data_with_context.iter().enumerate() {
135 let capacity = if token_data_with_context.delegate_index.is_some() {
143 107
144 } else {
145 75
146 };
147 let mut token_data_bytes = Vec::with_capacity(capacity);
148 let delegate = token_data_with_context
149 .delegate_index
150 .map(|index| remaining_accounts[index as usize].key());
151 let state = if IS_FROZEN {
152 AccountState::Frozen
153 } else {
154 AccountState::Initialized
155 };
156 let token_data = TokenData {
158 mint: *mint,
159 owner: *owner,
160 amount: token_data_with_context.amount,
161 delegate,
162 state,
163 tlv: None,
164 };
165 token_data.serialize(&mut token_data_bytes)?;
166
167 let discriminator_bytes = &remaining_accounts[token_data_with_context
168 .merkle_context
169 .merkle_tree_pubkey_index
170 as usize]
171 .try_borrow_data()?[0..8];
172 use anchor_lang::Discriminator;
173 let data_hash = match discriminator_bytes {
174 StateMerkleTreeAccount::DISCRIMINATOR => token_data.hash_legacy(),
175 BATCHED_DISCRIMINATOR => token_data.hash(),
176 _ => panic!(),
177 }
178 .map_err(ProgramError::from)?;
179
180 let data: CompressedAccountData = CompressedAccountData {
181 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
182 data: token_data_bytes,
183 data_hash,
184 };
185 output_compressed_accounts[i] = OutputCompressedAccountWithPackedContext {
186 compressed_account: CompressedAccount {
187 owner: crate::ID.into(),
188 lamports: token_data_with_context.lamports.unwrap_or(0),
189 data: Some(data),
190 address: None,
191 },
192 merkle_tree_index: *outputs_merkle_tree_index,
193 };
194 }
195 Ok(())
196}
197
198#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
199pub struct CompressedTokenInstructionDataThaw {
200 pub proof: CompressedProof,
201 pub owner: Pubkey,
202 pub input_token_data_with_context: Vec<InputTokenDataWithContext>,
203 pub cpi_context: Option<CompressedCpiContext>,
204 pub outputs_merkle_tree_index: u8,
205}
206
207#[cfg(not(target_os = "solana"))]
208pub mod sdk {
209
210 use anchor_lang::{AnchorSerialize, InstructionData, ToAccountMetas};
211 use light_compressed_account::{
212 compressed_account::{CompressedAccount, MerkleContext},
213 instruction_data::compressed_proof::CompressedProof,
214 };
215 use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
216
217 use super::CompressedTokenInstructionDataFreeze;
218 use crate::{
219 process_transfer::transfer_sdk::{
220 create_input_output_and_remaining_accounts, to_account_metas, TransferSdkError,
221 },
222 token_data::TokenData,
223 };
224
225 pub struct CreateInstructionInputs {
226 pub fee_payer: Pubkey,
227 pub authority: Pubkey,
228 pub root_indices: Vec<Option<u16>>,
229 pub proof: CompressedProof,
230 pub input_token_data: Vec<TokenData>,
231 pub input_compressed_accounts: Vec<CompressedAccount>,
232 pub input_merkle_contexts: Vec<MerkleContext>,
233 pub outputs_merkle_tree: Pubkey,
234 }
235
236 pub fn create_instruction<const FREEZE: bool>(
237 inputs: CreateInstructionInputs,
238 ) -> Result<Instruction, TransferSdkError> {
239 let (remaining_accounts, input_token_data_with_context, _) =
240 create_input_output_and_remaining_accounts(
241 &[inputs.outputs_merkle_tree],
242 &inputs.input_token_data,
243 &inputs.input_compressed_accounts,
244 &inputs.input_merkle_contexts,
245 &inputs.root_indices,
246 &Vec::new(),
247 );
248 let outputs_merkle_tree_index =
249 remaining_accounts.get(&inputs.outputs_merkle_tree).unwrap();
250
251 let inputs_struct = CompressedTokenInstructionDataFreeze {
252 proof: inputs.proof,
253 input_token_data_with_context,
254 cpi_context: None,
255 outputs_merkle_tree_index: *outputs_merkle_tree_index as u8,
256 owner: inputs.input_token_data[0].owner,
257 };
258 let remaining_accounts = to_account_metas(remaining_accounts);
259 let mut serialized_ix_data = Vec::new();
260 CompressedTokenInstructionDataFreeze::serialize(&inputs_struct, &mut serialized_ix_data)
261 .unwrap();
262
263 let (cpi_authority_pda, _) = crate::process_transfer::get_cpi_authority_pda();
264 let data = if FREEZE {
265 crate::instruction::Freeze {
266 inputs: serialized_ix_data,
267 }
268 .data()
269 } else {
270 crate::instruction::Thaw {
271 inputs: serialized_ix_data,
272 }
273 .data()
274 };
275
276 let accounts = crate::accounts::FreezeInstruction {
277 fee_payer: inputs.fee_payer,
278 authority: inputs.authority,
279 cpi_authority_pda,
280 light_system_program: light_system_program::ID,
281 registered_program_pda: light_system_program::utils::get_registered_program_pda(
282 &light_system_program::ID,
283 ),
284 noop_program: Pubkey::new_from_array(
285 account_compression::utils::constants::NOOP_PUBKEY,
286 ),
287 account_compression_authority: light_system_program::utils::get_cpi_authority_pda(
288 &light_system_program::ID,
289 ),
290 account_compression_program: account_compression::ID,
291 self_program: crate::ID,
292 system_program: solana_sdk::system_program::ID,
293 mint: inputs.input_token_data[0].mint,
294 };
295
296 Ok(Instruction {
297 program_id: crate::ID,
298 accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(),
299
300 data,
301 })
302 }
303
304 pub fn create_freeze_instruction(
305 inputs: CreateInstructionInputs,
306 ) -> Result<Instruction, TransferSdkError> {
307 create_instruction::<true>(inputs)
308 }
309
310 pub fn create_thaw_instruction(
311 inputs: CreateInstructionInputs,
312 ) -> Result<Instruction, TransferSdkError> {
313 create_instruction::<false>(inputs)
314 }
315}
316
317#[cfg(test)]
318pub mod test_freeze {
319 use account_compression::StateMerkleTreeAccount;
320 use anchor_lang::{solana_program::account_info::AccountInfo, Discriminator};
321 use light_compressed_account::compressed_account::PackedMerkleContext;
322 use rand::Rng;
323
324 use super::*;
325 use crate::{
326 constants::TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR, token_data::AccountState, TokenData,
327 };
328
329 #[test]
331 fn test_freeze() {
332 let merkle_tree_pubkey = Pubkey::new_unique();
333 let mut merkle_tree_account_lamports = 0;
334 let mut merkle_tree_account_data = StateMerkleTreeAccount::DISCRIMINATOR.to_vec();
335 let nullifier_queue_pubkey = Pubkey::new_unique();
336 let mut nullifier_queue_account_lamports = 0;
337 let mut nullifier_queue_account_data = Vec::new();
338 let delegate = Pubkey::new_unique();
339 let mut delegate_account_lamports = 0;
340 let mut delegate_account_data = Vec::new();
341 let merkle_tree_pubkey_1 = Pubkey::new_unique();
342 let mut merkle_tree_account_lamports_1 = 0;
343 let mut merkle_tree_account_data_1 = StateMerkleTreeAccount::DISCRIMINATOR.to_vec();
344 let remaining_accounts = vec![
345 AccountInfo::new(
346 &merkle_tree_pubkey,
347 false,
348 false,
349 &mut merkle_tree_account_lamports,
350 &mut merkle_tree_account_data,
351 &account_compression::ID,
352 false,
353 0,
354 ),
355 AccountInfo::new(
356 &nullifier_queue_pubkey,
357 false,
358 false,
359 &mut nullifier_queue_account_lamports,
360 &mut nullifier_queue_account_data,
361 &account_compression::ID,
362 false,
363 0,
364 ),
365 AccountInfo::new(
366 &delegate,
367 false,
368 false,
369 &mut delegate_account_lamports,
370 &mut delegate_account_data,
371 &account_compression::ID,
372 false,
373 0,
374 ),
375 AccountInfo::new(
376 &merkle_tree_pubkey_1,
377 false,
378 false,
379 &mut merkle_tree_account_lamports_1,
380 &mut merkle_tree_account_data_1,
381 &account_compression::ID,
382 false,
383 0,
384 ),
385 ];
386 let owner = Pubkey::new_unique();
387 let mint = Pubkey::new_unique();
388
389 let input_token_data_with_context = vec![
390 InputTokenDataWithContext {
391 amount: 100,
392
393 merkle_context: PackedMerkleContext {
394 merkle_tree_pubkey_index: 0,
395 queue_pubkey_index: 1,
396 leaf_index: 1,
397 prove_by_index: false,
398 },
399 root_index: 0,
400 delegate_index: None,
401 lamports: None,
402 tlv: None,
403 },
404 InputTokenDataWithContext {
405 amount: 101,
406
407 merkle_context: PackedMerkleContext {
408 merkle_tree_pubkey_index: 0,
409 queue_pubkey_index: 1,
410 leaf_index: 2,
411 prove_by_index: false,
412 },
413 root_index: 0,
414 delegate_index: Some(2),
415 lamports: None,
416 tlv: None,
417 },
418 ];
419 {
421 let inputs = CompressedTokenInstructionDataFreeze {
422 proof: CompressedProof::default(),
423 owner,
424 input_token_data_with_context: input_token_data_with_context.clone(),
425 cpi_context: None,
426 outputs_merkle_tree_index: 3,
427 };
428 let (compressed_input_accounts, output_compressed_accounts) =
429 create_input_and_output_accounts_freeze_or_thaw::<false, true>(
430 &inputs,
431 &mint,
432 &remaining_accounts,
433 )
434 .unwrap();
435 assert_eq!(compressed_input_accounts.len(), 2);
436 assert_eq!(output_compressed_accounts.len(), 2);
437 let expected_change_token_data = TokenData {
438 mint,
439 owner,
440 amount: 100,
441 delegate: None,
442 state: AccountState::Frozen,
443 tlv: None,
444 };
445 let expected_delegated_token_data = TokenData {
446 mint,
447 owner,
448 amount: 101,
449 delegate: Some(delegate),
450 state: AccountState::Frozen,
451 tlv: None,
452 };
453
454 let expected_compressed_output_accounts = create_expected_token_output_accounts(
455 vec![expected_change_token_data, expected_delegated_token_data],
456 vec![3u8; 2],
457 );
458 assert_eq!(
459 output_compressed_accounts,
460 expected_compressed_output_accounts
461 );
462 }
463 {
465 let inputs = CompressedTokenInstructionDataFreeze {
466 proof: CompressedProof::default(),
467 owner,
468 input_token_data_with_context,
469 cpi_context: None,
470 outputs_merkle_tree_index: 3,
471 };
472 let (compressed_input_accounts, output_compressed_accounts) =
473 create_input_and_output_accounts_freeze_or_thaw::<true, false>(
474 &inputs,
475 &mint,
476 &remaining_accounts,
477 )
478 .unwrap();
479 assert_eq!(compressed_input_accounts.len(), 2);
480 assert_eq!(output_compressed_accounts.len(), 2);
481 let expected_change_token_data = TokenData {
482 mint,
483 owner,
484 amount: 100,
485 delegate: None,
486 state: AccountState::Initialized,
487 tlv: None,
488 };
489 let expected_delegated_token_data = TokenData {
490 mint,
491 owner,
492 amount: 101,
493 delegate: Some(delegate),
494 state: AccountState::Initialized,
495 tlv: None,
496 };
497
498 let expected_compressed_output_accounts = create_expected_token_output_accounts(
499 vec![expected_change_token_data, expected_delegated_token_data],
500 vec![3u8; 2],
501 );
502 assert_eq!(
503 output_compressed_accounts,
504 expected_compressed_output_accounts
505 );
506 }
507 }
508
509 pub fn create_expected_token_output_accounts(
510 expected_token_data: Vec<TokenData>,
511 merkle_tree_indices: Vec<u8>,
512 ) -> Vec<OutputCompressedAccountWithPackedContext> {
513 let mut expected_compressed_output_accounts = Vec::new();
514 for (token_data, merkle_tree_index) in
515 expected_token_data.iter().zip(merkle_tree_indices.iter())
516 {
517 let serialized_expected_token_data = token_data.try_to_vec().unwrap();
518 let change_data_struct = CompressedAccountData {
519 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
520 data: serialized_expected_token_data.clone(),
521 data_hash: token_data.hash_legacy().unwrap(),
522 };
523 expected_compressed_output_accounts.push(OutputCompressedAccountWithPackedContext {
524 compressed_account: CompressedAccount {
525 owner: crate::ID.into(),
526 lamports: 0,
527 data: Some(change_data_struct),
528 address: None,
529 },
530 merkle_tree_index: *merkle_tree_index,
531 });
532 }
533 expected_compressed_output_accounts
534 }
535 pub fn get_rnd_input_token_data_with_contexts(
536 rng: &mut rand::rngs::ThreadRng,
537 num: usize,
538 ) -> Vec<InputTokenDataWithContext> {
539 let mut vec = Vec::with_capacity(num);
540 for _ in 0..num {
541 let delegate_index = if rng.gen_bool(0.5) { Some(1) } else { None };
542 vec.push(InputTokenDataWithContext {
543 amount: rng.gen_range(0..1_000_000_000),
544 merkle_context: PackedMerkleContext {
545 merkle_tree_pubkey_index: 0,
546 queue_pubkey_index: 1,
547 leaf_index: rng.gen_range(0..1_000_000_000),
548 prove_by_index: false,
549 },
550 root_index: rng.gen_range(0..=65_535),
551 delegate_index,
552 lamports: None,
553 tlv: None,
554 });
555 }
556 vec
557 }
558 pub fn create_expected_input_accounts(
559 input_token_data_with_context: &[InputTokenDataWithContext],
560 mint: &Pubkey,
561 owner: &Pubkey,
562 remaining_accounts: &[Pubkey],
563 ) -> Vec<InAccount> {
564 input_token_data_with_context
565 .iter()
566 .map(|x| {
567 let delegate = x
568 .delegate_index
569 .map(|index| remaining_accounts[index as usize]);
570 let token_data = TokenData {
571 mint: *mint,
572 owner: *owner,
573 amount: x.amount,
574 delegate,
575 state: AccountState::Initialized,
576 tlv: None,
577 };
578 let mut data = Vec::new();
579 token_data.serialize(&mut data).unwrap();
580 let data_hash = token_data.hash_legacy().unwrap();
581 InAccount {
582 lamports: 0,
583 address: None,
584 data_hash,
585 discriminator: TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR,
586 root_index: x.root_index,
587 merkle_context: x.merkle_context,
588 }
589 })
590 .collect()
591 }
592}