light_system_program/sdk/
invoke.rs

1#![cfg(not(target_os = "solana"))]
2use std::collections::HashMap;
3
4use anchor_lang::{AnchorSerialize, InstructionData, ToAccountMetas};
5use solana_sdk::{
6    instruction::{AccountMeta, Instruction},
7    pubkey::Pubkey,
8};
9
10use super::compressed_account::{
11    CompressedAccount, MerkleContext, PackedCompressedAccountWithMerkleContext, PackedMerkleContext,
12};
13use crate::{
14    invoke::{processor::CompressedProof, sol_compression::SOL_POOL_PDA_SEED},
15    utils::{get_cpi_authority_pda, get_registered_program_pda},
16    InstructionDataInvoke, NewAddressParams, NewAddressParamsPacked,
17    OutputCompressedAccountWithPackedContext,
18};
19
20pub fn get_sol_pool_pda() -> Pubkey {
21    Pubkey::find_program_address(&[SOL_POOL_PDA_SEED], &crate::ID).0
22}
23
24#[allow(clippy::too_many_arguments)]
25pub fn create_invoke_instruction(
26    fee_payer: &Pubkey,
27    payer: &Pubkey,
28    input_compressed_accounts: &[CompressedAccount],
29    output_compressed_accounts: &[CompressedAccount],
30    merkle_context: &[MerkleContext],
31    output_compressed_account_merkle_tree_pubkeys: &[Pubkey],
32    input_root_indices: &[u16],
33    new_address_params: &[NewAddressParams],
34    proof: Option<CompressedProof>,
35    compress_or_decompress_lamports: Option<u64>,
36    is_compress: bool,
37    decompression_recipient: Option<Pubkey>,
38    sort: bool,
39) -> Instruction {
40    let (remaining_accounts, mut inputs_struct) =
41        create_invoke_instruction_data_and_remaining_accounts(
42            new_address_params,
43            merkle_context,
44            input_compressed_accounts,
45            input_root_indices,
46            output_compressed_account_merkle_tree_pubkeys,
47            output_compressed_accounts,
48            proof,
49            compress_or_decompress_lamports,
50            is_compress,
51        );
52    if sort {
53        inputs_struct
54            .output_compressed_accounts
55            .sort_by(|a, b| a.merkle_tree_index.cmp(&b.merkle_tree_index));
56    }
57    let mut inputs = Vec::new();
58
59    InstructionDataInvoke::serialize(&inputs_struct, &mut inputs).unwrap();
60
61    let instruction_data = crate::instruction::Invoke { inputs };
62
63    let sol_pool_pda = compress_or_decompress_lamports.map(|_| get_sol_pool_pda());
64
65    let accounts = crate::accounts::InvokeInstruction {
66        fee_payer: *fee_payer,
67        authority: *payer,
68        registered_program_pda: get_registered_program_pda(&crate::ID),
69        noop_program: Pubkey::new_from_array(account_compression::utils::constants::NOOP_PUBKEY),
70        account_compression_program: account_compression::ID,
71        account_compression_authority: get_cpi_authority_pda(&crate::ID),
72        sol_pool_pda,
73        decompression_recipient,
74        system_program: solana_sdk::system_program::ID,
75    };
76    Instruction {
77        program_id: crate::ID,
78        accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(),
79        data: instruction_data.data(),
80    }
81}
82
83#[allow(clippy::too_many_arguments)]
84pub fn create_invoke_instruction_data_and_remaining_accounts(
85    new_address_params: &[NewAddressParams],
86    merkle_context: &[MerkleContext],
87    input_compressed_accounts: &[CompressedAccount],
88    input_root_indices: &[u16],
89    output_compressed_account_merkle_tree_pubkeys: &[Pubkey],
90    output_compressed_accounts: &[CompressedAccount],
91    proof: Option<CompressedProof>,
92    compress_or_decompress_lamports: Option<u64>,
93    is_compress: bool,
94) -> (Vec<AccountMeta>, InstructionDataInvoke) {
95    let mut remaining_accounts = HashMap::<Pubkey, usize>::new();
96    let mut _input_compressed_accounts: Vec<PackedCompressedAccountWithMerkleContext> =
97        Vec::<PackedCompressedAccountWithMerkleContext>::new();
98    let mut index = 0;
99    let mut new_address_params_packed = new_address_params
100        .iter()
101        .map(|x| NewAddressParamsPacked {
102            seed: x.seed,
103            address_merkle_tree_root_index: x.address_merkle_tree_root_index,
104            address_merkle_tree_account_index: 0, // will be assigned later
105            address_queue_account_index: 0,       // will be assigned later
106        })
107        .collect::<Vec<NewAddressParamsPacked>>();
108    for (i, context) in merkle_context.iter().enumerate() {
109        match remaining_accounts.get(&context.merkle_tree_pubkey) {
110            Some(_) => {}
111            None => {
112                remaining_accounts.insert(context.merkle_tree_pubkey, index);
113                index += 1;
114            }
115        };
116        _input_compressed_accounts.push(PackedCompressedAccountWithMerkleContext {
117            compressed_account: input_compressed_accounts[i].clone(),
118            merkle_context: PackedMerkleContext {
119                merkle_tree_pubkey_index: *remaining_accounts
120                    .get(&context.merkle_tree_pubkey)
121                    .unwrap() as u8,
122                nullifier_queue_pubkey_index: 0,
123                leaf_index: context.leaf_index,
124                queue_index: None,
125            },
126            read_only: false,
127            root_index: input_root_indices[i],
128        });
129    }
130
131    for (i, context) in merkle_context.iter().enumerate() {
132        match remaining_accounts.get(&context.nullifier_queue_pubkey) {
133            Some(_) => {}
134            None => {
135                remaining_accounts.insert(context.nullifier_queue_pubkey, index);
136                index += 1;
137            }
138        };
139        _input_compressed_accounts[i]
140            .merkle_context
141            .nullifier_queue_pubkey_index = *remaining_accounts
142            .get(&context.nullifier_queue_pubkey)
143            .unwrap() as u8;
144    }
145
146    let mut output_compressed_accounts_with_context: Vec<OutputCompressedAccountWithPackedContext> =
147        Vec::<OutputCompressedAccountWithPackedContext>::new();
148
149    for (i, mt) in output_compressed_account_merkle_tree_pubkeys
150        .iter()
151        .enumerate()
152    {
153        match remaining_accounts.get(mt) {
154            Some(_) => {}
155            None => {
156                remaining_accounts.insert(*mt, index);
157                index += 1;
158            }
159        };
160
161        output_compressed_accounts_with_context.push(OutputCompressedAccountWithPackedContext {
162            compressed_account: output_compressed_accounts[i].clone(),
163            merkle_tree_index: *remaining_accounts.get(mt).unwrap() as u8,
164        });
165    }
166
167    for (i, params) in new_address_params.iter().enumerate() {
168        match remaining_accounts.get(&params.address_merkle_tree_pubkey) {
169            Some(_) => {}
170            None => {
171                remaining_accounts.insert(params.address_merkle_tree_pubkey, index);
172                index += 1;
173            }
174        };
175        new_address_params_packed[i].address_merkle_tree_account_index = *remaining_accounts
176            .get(&params.address_merkle_tree_pubkey)
177            .unwrap()
178            as u8;
179    }
180
181    for (i, params) in new_address_params.iter().enumerate() {
182        match remaining_accounts.get(&params.address_queue_pubkey) {
183            Some(_) => {}
184            None => {
185                remaining_accounts.insert(params.address_queue_pubkey, index);
186                index += 1;
187            }
188        };
189        new_address_params_packed[i].address_queue_account_index = *remaining_accounts
190            .get(&params.address_queue_pubkey)
191            .unwrap() as u8;
192    }
193    let mut remaining_accounts = remaining_accounts
194        .iter()
195        .map(|(k, i)| (AccountMeta::new(*k, false), *i))
196        .collect::<Vec<(AccountMeta, usize)>>();
197    // hash maps are not sorted so we need to sort manually and collect into a vector again
198    remaining_accounts.sort_by(|a, b| a.1.cmp(&b.1));
199    let remaining_accounts = remaining_accounts
200        .iter()
201        .map(|(k, _)| k.clone())
202        .collect::<Vec<AccountMeta>>();
203
204    let inputs_struct = InstructionDataInvoke {
205        relay_fee: None,
206        input_compressed_accounts_with_merkle_context: _input_compressed_accounts,
207        output_compressed_accounts: output_compressed_accounts_with_context,
208        proof,
209        new_address_params: new_address_params_packed,
210        compress_or_decompress_lamports,
211        is_compress,
212    };
213    (remaining_accounts, inputs_struct)
214}
215
216#[cfg(test)]
217mod test {
218    use anchor_lang::AnchorDeserialize;
219    use solana_sdk::{signature::Keypair, signer::Signer};
220
221    use super::*;
222
223    #[test]
224    fn test_create_execute_compressed_transaction() {
225        let payer = Keypair::new().pubkey();
226        let recipient = Keypair::new().pubkey();
227        let input_compressed_accounts = vec![
228            CompressedAccount {
229                lamports: 100,
230                owner: payer,
231                address: None,
232                data: None,
233            },
234            CompressedAccount {
235                lamports: 100,
236                owner: payer,
237                address: None,
238                data: None,
239            },
240        ];
241        let output_compressed_accounts = vec![
242            CompressedAccount {
243                lamports: 50,
244                owner: payer,
245                address: None,
246                data: None,
247            },
248            CompressedAccount {
249                lamports: 150,
250                owner: recipient,
251                address: None,
252                data: None,
253            },
254        ];
255        let merkle_tree_indices = vec![0, 2];
256        let merkle_tree_pubkey = Keypair::new().pubkey();
257        let merkle_tree_pubkey_1 = Keypair::new().pubkey();
258
259        let nullifier_array_pubkey = Keypair::new().pubkey();
260        let input_merkle_context = vec![
261            MerkleContext {
262                merkle_tree_pubkey,
263                nullifier_queue_pubkey: nullifier_array_pubkey,
264                leaf_index: 0,
265                queue_index: None,
266            },
267            MerkleContext {
268                merkle_tree_pubkey,
269                nullifier_queue_pubkey: nullifier_array_pubkey,
270                leaf_index: 1,
271                queue_index: None,
272            },
273        ];
274
275        let output_compressed_account_merkle_tree_pubkeys =
276            vec![merkle_tree_pubkey, merkle_tree_pubkey_1];
277        let input_root_indices = vec![0, 1];
278        let proof = CompressedProof {
279            a: [0u8; 32],
280            b: [1u8; 64],
281            c: [0u8; 32],
282        };
283        let instruction = create_invoke_instruction(
284            &payer,
285            &payer,
286            &input_compressed_accounts.clone(),
287            &output_compressed_accounts.clone(),
288            &input_merkle_context,
289            &output_compressed_account_merkle_tree_pubkeys,
290            &input_root_indices.clone(),
291            Vec::<NewAddressParams>::new().as_slice(),
292            Some(proof.clone()),
293            Some(100),
294            true,
295            None,
296            true,
297        );
298        assert_eq!(instruction.program_id, crate::ID);
299
300        let deserialized_instruction_data: InstructionDataInvoke =
301            InstructionDataInvoke::deserialize(&mut instruction.data[12..].as_ref()).unwrap();
302        deserialized_instruction_data
303            .input_compressed_accounts_with_merkle_context
304            .iter()
305            .enumerate()
306            .for_each(|(i, compressed_account_with_context)| {
307                assert_eq!(
308                    input_compressed_accounts[i],
309                    compressed_account_with_context.compressed_account
310                );
311            });
312        deserialized_instruction_data
313            .output_compressed_accounts
314            .iter()
315            .enumerate()
316            .for_each(|(i, compressed_account)| {
317                assert_eq!(
318                    OutputCompressedAccountWithPackedContext {
319                        compressed_account: output_compressed_accounts[i].clone(),
320                        merkle_tree_index: merkle_tree_indices[i] as u8
321                    },
322                    *compressed_account
323                );
324            });
325        assert_eq!(
326            deserialized_instruction_data
327                .input_compressed_accounts_with_merkle_context
328                .len(),
329            2
330        );
331        assert_eq!(
332            deserialized_instruction_data
333                .output_compressed_accounts
334                .len(),
335            2
336        );
337        assert_eq!(
338            deserialized_instruction_data.proof.clone().unwrap().a,
339            proof.a
340        );
341        assert_eq!(
342            deserialized_instruction_data.proof.clone().unwrap().b,
343            proof.b
344        );
345        assert_eq!(
346            deserialized_instruction_data.proof.clone().unwrap().c,
347            proof.c
348        );
349        assert_eq!(
350            deserialized_instruction_data
351                .compress_or_decompress_lamports
352                .unwrap(),
353            100
354        );
355        assert_eq!(deserialized_instruction_data.is_compress, true);
356        let ref_account_meta = AccountMeta::new(payer, true);
357        assert_eq!(instruction.accounts[0], ref_account_meta);
358        assert_eq!(
359            deserialized_instruction_data.input_compressed_accounts_with_merkle_context[0]
360                .merkle_context
361                .nullifier_queue_pubkey_index,
362            1
363        );
364        assert_eq!(
365            deserialized_instruction_data.input_compressed_accounts_with_merkle_context[1]
366                .merkle_context
367                .nullifier_queue_pubkey_index,
368            1
369        );
370        assert_eq!(
371            instruction.accounts[9 + deserialized_instruction_data
372                .input_compressed_accounts_with_merkle_context[0]
373                .merkle_context
374                .merkle_tree_pubkey_index as usize],
375            AccountMeta::new(merkle_tree_pubkey, false)
376        );
377        assert_eq!(
378            instruction.accounts[9 + deserialized_instruction_data
379                .input_compressed_accounts_with_merkle_context[1]
380                .merkle_context
381                .merkle_tree_pubkey_index as usize],
382            AccountMeta::new(merkle_tree_pubkey, false)
383        );
384        assert_eq!(
385            instruction.accounts[9 + deserialized_instruction_data
386                .input_compressed_accounts_with_merkle_context[0]
387                .merkle_context
388                .nullifier_queue_pubkey_index as usize],
389            AccountMeta::new(nullifier_array_pubkey, false)
390        );
391        assert_eq!(
392            instruction.accounts[9 + deserialized_instruction_data
393                .input_compressed_accounts_with_merkle_context[1]
394                .merkle_context
395                .nullifier_queue_pubkey_index as usize],
396            AccountMeta::new(nullifier_array_pubkey, false)
397        );
398        assert_eq!(
399            instruction.accounts[9 + deserialized_instruction_data.output_compressed_accounts[0]
400                .merkle_tree_index as usize],
401            AccountMeta::new(merkle_tree_pubkey, false)
402        );
403        assert_eq!(
404            instruction.accounts[9 + deserialized_instruction_data.output_compressed_accounts[1]
405                .merkle_tree_index as usize],
406            AccountMeta::new(merkle_tree_pubkey_1, false)
407        );
408    }
409}