light_system_program/invoke_cpi/
verify_signer.rs1use account_compression::{
2 utils::{check_discrimininator::check_discriminator, constants::CPI_AUTHORITY_PDA_SEED},
3 AddressMerkleTreeAccount, StateMerkleTreeAccount,
4};
5use anchor_lang::prelude::*;
6use light_concurrent_merkle_tree::zero_copy::ConcurrentMerkleTreeZeroCopy;
7use light_hasher::Poseidon;
8use light_heap::{bench_sbf_end, bench_sbf_start};
9use light_macros::heap_neutral;
10use std::mem;
11
12use crate::{
13 errors::SystemProgramError, sdk::compressed_account::PackedCompressedAccountWithMerkleContext,
14 OutputCompressedAccountWithPackedContext,
15};
16
17pub fn cpi_signer_checks(
24 invoking_programid: &Pubkey,
25 authority: &Pubkey,
26 input_compressed_accounts_with_merkle_context: &[PackedCompressedAccountWithMerkleContext],
27 output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
28) -> Result<()> {
29 bench_sbf_start!("cpda_cpi_signer_checks");
30 cpi_signer_check(invoking_programid, authority)?;
31 bench_sbf_end!("cpda_cpi_signer_checks");
32 bench_sbf_start!("cpd_input_checks");
33 input_compressed_accounts_signer_check(
34 input_compressed_accounts_with_merkle_context,
35 invoking_programid,
36 )?;
37 bench_sbf_end!("cpd_input_checks");
38 bench_sbf_start!("cpda_cpi_write_checks");
39 output_compressed_accounts_write_access_check(output_compressed_accounts, invoking_programid)?;
40 bench_sbf_end!("cpda_cpi_write_checks");
41 Ok(())
42}
43
44#[heap_neutral]
47pub fn cpi_signer_check(invoking_program: &Pubkey, authority: &Pubkey) -> Result<()> {
48 let seeds = [CPI_AUTHORITY_PDA_SEED];
49 let derived_signer = Pubkey::try_find_program_address(&seeds, invoking_program)
50 .ok_or(ProgramError::InvalidSeeds)?
51 .0;
52 if derived_signer != *authority {
53 msg!(
54 "Cpi signer check failed. Derived cpi signer {} != authority {}",
55 derived_signer,
56 authority
57 );
58 return err!(SystemProgramError::CpiSignerCheckFailed);
59 }
60 Ok(())
61}
62
63pub fn input_compressed_accounts_signer_check(
65 input_compressed_accounts_with_merkle_context: &[PackedCompressedAccountWithMerkleContext],
66 invoking_program_id: &Pubkey,
67) -> Result<()> {
68 input_compressed_accounts_with_merkle_context
69 .iter()
70 .try_for_each(
71 |compressed_account_with_context: &PackedCompressedAccountWithMerkleContext| {
72 let invoking_program_id = invoking_program_id.key();
73 if invoking_program_id == compressed_account_with_context.compressed_account.owner {
74 Ok(())
75 } else {
76 msg!(
77 "Input signer check failed. Program cannot invalidate an account it doesn't own. Owner {} != invoking_program_id {}",
78 compressed_account_with_context.compressed_account.owner,
79 invoking_program_id
80 );
81 err!(SystemProgramError::SignerCheckFailed)
82 }
83 },
84 )
85}
86
87#[inline(never)]
93pub fn output_compressed_accounts_write_access_check(
94 output_compressed_accounts: &[OutputCompressedAccountWithPackedContext],
95 invoking_program_id: &Pubkey,
96) -> Result<()> {
97 for compressed_account in output_compressed_accounts.iter() {
98 if compressed_account.compressed_account.data.is_some()
99 && compressed_account.compressed_account.owner != invoking_program_id.key()
100 {
101 msg!(
102 "Signer/Program cannot write into an account it doesn't own. Write access check failed compressed account owner {} != invoking_program_id {}",
103 compressed_account.compressed_account.owner,
104 invoking_program_id.key()
105 );
106 msg!("compressed_account: {:?}", compressed_account);
107 return err!(SystemProgramError::WriteAccessCheckFailed);
108 }
109 if compressed_account.compressed_account.data.is_none()
110 && compressed_account.compressed_account.owner == invoking_program_id.key()
111 {
112 msg!("For program owned compressed accounts the data field needs to be defined.");
113 msg!("compressed_account: {:?}", compressed_account);
114 return err!(SystemProgramError::DataFieldUndefined);
115 }
116 }
117 Ok(())
118}
119
120pub fn check_program_owner_state_merkle_tree<'a, 'b: 'a>(
121 merkle_tree_acc_info: &'b AccountInfo<'a>,
122 invoking_program: &Option<Pubkey>,
123) -> Result<(u32, Option<u64>, u64)> {
124 let (seq, next_index) = {
125 let merkle_tree = merkle_tree_acc_info.try_borrow_data()?;
126 check_discriminator::<StateMerkleTreeAccount>(&merkle_tree).map_err(ProgramError::from)?;
127 let merkle_tree = ConcurrentMerkleTreeZeroCopy::<Poseidon, 26>::from_bytes_zero_copy(
128 &merkle_tree[8 + mem::size_of::<StateMerkleTreeAccount>()..],
129 )
130 .map_err(ProgramError::from)?;
131
132 let seq = merkle_tree.sequence_number() as u64 + 1;
133 let next_index: u32 = merkle_tree.next_index().try_into().unwrap();
134
135 (seq, next_index)
136 };
137
138 let merkle_tree =
139 AccountLoader::<StateMerkleTreeAccount>::try_from(merkle_tree_acc_info).unwrap();
140 let merkle_tree_unpacked = merkle_tree.load()?;
141
142 let network_fee = if merkle_tree_unpacked.metadata.rollover_metadata.network_fee != 0 {
143 Some(merkle_tree_unpacked.metadata.rollover_metadata.network_fee)
144 } else {
145 None
146 };
147 if merkle_tree_unpacked.metadata.access_metadata.program_owner != Pubkey::default() {
148 if let Some(invoking_program) = invoking_program {
149 if *invoking_program == merkle_tree_unpacked.metadata.access_metadata.program_owner {
150 return Ok((next_index, network_fee, seq));
151 }
152 }
153 msg!(
154 "invoking_program.key() {:?} == merkle_tree_unpacked.program_owner {:?}",
155 invoking_program,
156 merkle_tree_unpacked.metadata.access_metadata.program_owner
157 );
158 return Err(SystemProgramError::InvalidMerkleTreeOwner.into());
159 }
160 Ok((next_index, network_fee, seq))
161}
162
163pub fn check_program_owner_address_merkle_tree<'a, 'b: 'a>(
164 merkle_tree_acc_info: &'b AccountInfo<'a>,
165 invoking_program: &Option<Pubkey>,
166) -> Result<Option<u64>> {
167 let merkle_tree =
168 AccountLoader::<AddressMerkleTreeAccount>::try_from(merkle_tree_acc_info).unwrap();
169 let merkle_tree_unpacked = merkle_tree.load()?;
170 let network_fee = if merkle_tree_unpacked.metadata.rollover_metadata.network_fee != 0 {
171 Some(merkle_tree_unpacked.metadata.rollover_metadata.network_fee)
172 } else {
173 None
174 };
175 if merkle_tree_unpacked.metadata.access_metadata.program_owner != Pubkey::default() {
176 if let Some(invoking_program) = invoking_program {
177 if *invoking_program == merkle_tree_unpacked.metadata.access_metadata.program_owner {
178 msg!(
179 "invoking_program.key() {:?} == merkle_tree_unpacked.program_owner {:?}",
180 invoking_program,
181 merkle_tree_unpacked.metadata.access_metadata.program_owner
182 );
183 return Ok(network_fee);
184 }
185 }
186 msg!(
187 "invoking_program.key() {:?} == merkle_tree_unpacked.program_owner {:?}",
188 invoking_program,
189 merkle_tree_unpacked.metadata.access_metadata.program_owner
190 );
191 err!(SystemProgramError::InvalidMerkleTreeOwner)
192 } else {
193 Ok(network_fee)
194 }
195}
196
197#[cfg(test)]
198mod test {
199 use super::*;
200 use crate::sdk::compressed_account::{CompressedAccount, CompressedAccountData};
201
202 #[test]
203 fn test_cpi_signer_check() {
204 for _ in 0..1000 {
205 let seeds = [CPI_AUTHORITY_PDA_SEED];
206 let invoking_program = Pubkey::new_unique();
207 let (derived_signer, _) = Pubkey::find_program_address(&seeds[..], &invoking_program);
208 assert_eq!(cpi_signer_check(&invoking_program, &derived_signer), Ok(()));
209
210 let authority = Pubkey::new_unique();
211 let invoking_program = Pubkey::new_unique();
212 assert!(
213 cpi_signer_check(&invoking_program, &authority)
214 == Err(ProgramError::InvalidSeeds.into())
215 || cpi_signer_check(&invoking_program, &authority)
216 == Err(SystemProgramError::CpiSignerCheckFailed.into())
217 );
218 }
219 }
220
221 #[test]
222 fn test_input_compressed_accounts_signer_check() {
223 let authority = Pubkey::new_unique();
224 let mut compressed_account_with_context = PackedCompressedAccountWithMerkleContext {
225 compressed_account: CompressedAccount {
226 owner: authority,
227 ..CompressedAccount::default()
228 },
229 ..PackedCompressedAccountWithMerkleContext::default()
230 };
231
232 assert_eq!(
233 input_compressed_accounts_signer_check(
234 &[compressed_account_with_context.clone()],
235 &authority
236 ),
237 Ok(())
238 );
239
240 compressed_account_with_context.compressed_account.owner = Pubkey::new_unique();
241 assert_eq!(
242 input_compressed_accounts_signer_check(&[compressed_account_with_context], &authority),
243 Err(SystemProgramError::SignerCheckFailed.into())
244 );
245 }
246
247 #[test]
248 fn test_output_compressed_accounts_write_access_check() {
249 let authority = Pubkey::new_unique();
250 let compressed_account = CompressedAccount {
251 owner: authority,
252 data: Some(CompressedAccountData::default()),
253 ..CompressedAccount::default()
254 };
255 let output_compressed_account = OutputCompressedAccountWithPackedContext {
256 compressed_account,
257 ..OutputCompressedAccountWithPackedContext::default()
258 };
259
260 assert_eq!(
261 output_compressed_accounts_write_access_check(&[output_compressed_account], &authority),
262 Ok(())
263 );
264
265 let compressed_account = CompressedAccount {
267 owner: Pubkey::new_unique(),
268 ..CompressedAccount::default()
269 };
270 let mut output_compressed_account = OutputCompressedAccountWithPackedContext {
271 compressed_account,
272 ..OutputCompressedAccountWithPackedContext::default()
273 };
274
275 assert_eq!(
276 output_compressed_accounts_write_access_check(
277 &[output_compressed_account.clone()],
278 &authority
279 ),
280 Ok(())
281 );
282
283 output_compressed_account.compressed_account.data = Some(CompressedAccountData::default());
285
286 assert_eq!(
287 output_compressed_accounts_write_access_check(&[output_compressed_account], &authority),
288 Err(SystemProgramError::WriteAccessCheckFailed.into())
289 );
290 }
291}