light_system_program/invoke/
append_state.rs

1use account_compression::utils::constants::CPI_AUTHORITY_PDA_SEED;
2use anchor_lang::{
3    prelude::*,
4    solana_program::{program::invoke_signed, pubkey::Pubkey},
5    Bumps,
6};
7use light_hasher::Poseidon;
8use light_heap::{bench_sbf_end, bench_sbf_start};
9use light_macros::heap_neutral;
10use light_utils::hash_to_bn254_field_size_be;
11
12use crate::{
13    constants::CPI_AUTHORITY_PDA_BUMP,
14    errors::SystemProgramError,
15    invoke_cpi::verify_signer::check_program_owner_state_merkle_tree,
16    sdk::{
17        accounts::{InvokeAccounts, SignerAccounts},
18        event::MerkleTreeSequenceNumber,
19    },
20    OutputCompressedAccountWithPackedContext,
21};
22
23#[allow(clippy::too_many_arguments)]
24#[heap_neutral]
25pub fn insert_output_compressed_accounts_into_state_merkle_tree<
26    'a,
27    'b,
28    'c: 'info,
29    'info,
30    A: InvokeAccounts<'info> + SignerAccounts<'info> + Bumps,
31>(
32    output_compressed_accounts: &mut [OutputCompressedAccountWithPackedContext],
33    ctx: &'a Context<'a, 'b, 'c, 'info, A>,
34    output_compressed_account_indices: &'a mut [u32],
35    output_compressed_account_hashes: &'a mut [[u8; 32]],
36    compressed_account_addresses: &'a mut Vec<Option<[u8; 32]>>,
37    invoking_program: &Option<Pubkey>,
38    hashed_pubkeys: &'a mut Vec<(Pubkey, [u8; 32])>,
39    sequence_numbers: &'a mut Vec<MerkleTreeSequenceNumber>,
40) -> Result<()> {
41    bench_sbf_start!("cpda_append_data_init");
42    let mut account_infos = vec![
43        ctx.accounts.get_fee_payer().to_account_info(), // fee payer
44        ctx.accounts
45            .get_account_compression_authority() // authority
46            .to_account_info(),
47        ctx.accounts.get_registered_program_pda().to_account_info(),
48        ctx.accounts.get_system_program().to_account_info(),
49    ];
50    let mut accounts = vec![
51        AccountMeta {
52            pubkey: account_infos[0].key(),
53            is_signer: true,
54            is_writable: true,
55        },
56        AccountMeta {
57            pubkey: account_infos[1].key(),
58            is_signer: true,
59            is_writable: false,
60        },
61        AccountMeta::new_readonly(account_infos[2].key(), false),
62        AccountMeta::new_readonly(account_infos[3].key(), false),
63    ];
64    let instruction_data = create_cpi_accounts_and_instruction_data(
65        output_compressed_accounts,
66        output_compressed_account_indices,
67        output_compressed_account_hashes,
68        compressed_account_addresses,
69        invoking_program,
70        hashed_pubkeys,
71        sequence_numbers,
72        ctx.remaining_accounts,
73        &mut account_infos,
74        &mut accounts,
75    )?;
76
77    let bump = &[CPI_AUTHORITY_PDA_BUMP];
78    let seeds = &[&[CPI_AUTHORITY_PDA_SEED, bump][..]];
79    let instruction = anchor_lang::solana_program::instruction::Instruction {
80        program_id: account_compression::ID,
81        accounts,
82        data: instruction_data,
83    };
84    invoke_signed(&instruction, account_infos.as_slice(), seeds)?;
85    bench_sbf_end!("cpda_append_rest");
86
87    Ok(())
88}
89
90/// Creates CPI accounts, instruction data, and performs checks.
91/// - Merkle tree indices must be in order.
92/// - Hashes output accounts for insertion and event.
93/// - Collects sequence numbers for event.
94///
95/// Checks:
96/// 1. Checks whether a Merkle tree is program owned, if so checks write
97///    eligibility.
98/// 2. Checks ordering of Merkle tree indices.
99/// 3. Checks that addresses in output compressed accounts have been created or
100///    exist in input compressed accounts. An address may not be used in an
101///    output compressed accounts. This will close the account.
102#[allow(clippy::too_many_arguments)]
103pub fn create_cpi_accounts_and_instruction_data<'a>(
104    output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
105    output_compressed_account_indices: &mut [u32],
106    output_compressed_account_hashes: &mut [[u8; 32]],
107    compressed_account_addresses: &mut Vec<Option<[u8; 32]>>,
108    invoking_program: &Option<Pubkey>,
109    hashed_pubkeys: &mut Vec<(Pubkey, [u8; 32])>,
110    sequence_numbers: &mut Vec<MerkleTreeSequenceNumber>,
111    remaining_accounts: &'a [AccountInfo<'a>],
112    account_infos: &mut Vec<AccountInfo<'a>>,
113    accounts: &mut Vec<AccountMeta>,
114) -> Result<Vec<u8>> {
115    let mut current_index: i16 = -1;
116    let mut num_leaves_in_tree: u32 = 0;
117    let mut mt_next_index = 0;
118    let num_leaves = output_compressed_account_hashes.len();
119    let mut instruction_data = Vec::<u8>::with_capacity(12 + 33 * num_leaves);
120    let mut hashed_merkle_tree = [0u8; 32];
121    let mut index_merkle_tree_account = 0;
122    let number_of_merkle_trees =
123        output_compressed_accounts.last().unwrap().merkle_tree_index as usize + 1;
124    let mut merkle_tree_pubkeys = Vec::<Pubkey>::with_capacity(number_of_merkle_trees);
125
126    // Anchor instruction signature.
127    instruction_data.extend_from_slice(&[199, 144, 10, 82, 247, 142, 143, 7]);
128    // leaves vector length (for borsh compat)
129    instruction_data.extend_from_slice(&(num_leaves as u32).to_le_bytes());
130
131    for (j, account) in output_compressed_accounts.iter().enumerate() {
132        // if mt index == current index Merkle tree account info has already been added.
133        // if mt index != current index, Merkle tree account info is new, add it.
134        #[allow(clippy::comparison_chain)]
135        if account.merkle_tree_index as i16 == current_index {
136            // Do nothing, but it is the most common case.
137        } else if account.merkle_tree_index as i16 > current_index {
138            current_index = account.merkle_tree_index.into();
139            let seq;
140            // Check 1.
141            (mt_next_index, _, seq) = check_program_owner_state_merkle_tree(
142                &remaining_accounts[account.merkle_tree_index as usize],
143                invoking_program,
144            )?;
145            let account_info =
146                remaining_accounts[account.merkle_tree_index as usize].to_account_info();
147
148            sequence_numbers.push(MerkleTreeSequenceNumber {
149                pubkey: account_info.key(),
150                seq,
151            });
152            hashed_merkle_tree = match hashed_pubkeys.iter().find(|x| x.0 == account_info.key()) {
153                Some(hashed_merkle_tree) => hashed_merkle_tree.1,
154                None => {
155                    hash_to_bn254_field_size_be(&account_info.key().to_bytes())
156                        .unwrap()
157                        .0
158                }
159            };
160            // check Merkle tree uniqueness
161            if merkle_tree_pubkeys.contains(&account_info.key()) {
162                return err!(SystemProgramError::OutputMerkleTreeNotUnique);
163            } else {
164                merkle_tree_pubkeys.push(account_info.key());
165            }
166            accounts.push(AccountMeta {
167                pubkey: account_info.key(),
168                is_signer: false,
169                is_writable: true,
170            });
171            account_infos.push(account_info);
172
173            num_leaves_in_tree = 0;
174            index_merkle_tree_account += 1;
175        } else {
176            // Check 2.
177            // Output Merkle tree indices must be in order since we use the
178            // number of leaves in a Merkle tree to determine the correct leaf
179            // index. Since the leaf index is part of the hash this is security
180            // critical.
181            return err!(SystemProgramError::OutputMerkleTreeIndicesNotInOrder);
182        }
183
184        // Check 3.
185        if let Some(address) = account.compressed_account.address {
186            if let Some(position) = compressed_account_addresses
187                .iter()
188                .filter(|x| x.is_some())
189                .position(|&x| x.unwrap() == address)
190            {
191                compressed_account_addresses.remove(position);
192            } else {
193                msg!("Address {:?}, is no new address and does not exist in input compressed accounts.", address);
194                msg!(
195                    "Remaining compressed_account_addresses: {:?}",
196                    compressed_account_addresses
197                );
198                return Err(SystemProgramError::InvalidAddress.into());
199            }
200        }
201
202        output_compressed_account_indices[j] = mt_next_index + num_leaves_in_tree;
203        num_leaves_in_tree += 1;
204        if account.compressed_account.data.is_some() && invoking_program.is_none() {
205            msg!("Invoking program is not provided.");
206            msg!("Only program owned compressed accounts can have data.");
207            return err!(SystemProgramError::InvokingProgramNotProvided);
208        }
209        let hashed_owner = match hashed_pubkeys
210            .iter()
211            .find(|x| x.0 == account.compressed_account.owner)
212        {
213            Some(hashed_owner) => hashed_owner.1,
214            None => {
215                let hashed_owner =
216                    hash_to_bn254_field_size_be(&account.compressed_account.owner.to_bytes())
217                        .unwrap()
218                        .0;
219                hashed_pubkeys.push((account.compressed_account.owner, hashed_owner));
220                hashed_owner
221            }
222        };
223        // Compute output compressed account hash.
224        output_compressed_account_hashes[j] = account
225            .compressed_account
226            .hash_with_hashed_values::<Poseidon>(
227                &hashed_owner,
228                &hashed_merkle_tree,
229                &output_compressed_account_indices[j],
230            )?;
231        // - 1 since we want the index of the next account index.
232        instruction_data.extend_from_slice(&[index_merkle_tree_account - 1]);
233        instruction_data.extend_from_slice(&output_compressed_account_hashes[j]);
234    }
235    Ok(instruction_data)
236}
237
238#[test]
239fn test_instruction_data_borsh_compat() {
240    let mut vec = Vec::<u8>::new();
241    vec.extend_from_slice(&2u32.to_le_bytes());
242    vec.push(1);
243    vec.extend_from_slice(&[2u8; 32]);
244    vec.push(3);
245    vec.extend_from_slice(&[4u8; 32]);
246    let refe = vec![(1, [2u8; 32]), (3, [4u8; 32])];
247    let mut serialized = Vec::new();
248    Vec::<(u8, [u8; 32])>::serialize(&refe, &mut serialized).unwrap();
249    assert_eq!(serialized, vec);
250    let res = Vec::<(u8, [u8; 32])>::deserialize(&mut vec.as_slice()).unwrap();
251    assert_eq!(res, vec![(1, [2u8; 32]), (3, [4u8; 32])]);
252}