1#![allow(clippy::too_many_arguments)]
2use account_compression::{
3 utils::constants::CPI_AUTHORITY_PDA_SEED, AddressMerkleTreeConfig, AddressQueueConfig,
4 NullifierQueueConfig, StateMerkleTreeConfig,
5};
6use anchor_lang::prelude::*;
7use light_merkle_tree_metadata::merkle_tree::MerkleTreeMetadata;
8
9pub mod account_compression_cpi;
10pub mod errors;
11pub use account_compression_cpi::{
12 batch_append::*, batch_nullify::*, batch_update_address_tree::*,
13 initialize_batched_address_tree::*, initialize_batched_state_tree::*,
14 initialize_tree_and_queue::*, migrate_state::*, nullify::*, register_program::*,
15 rollover_batched_address_tree::*, rollover_batched_state_tree::*, rollover_state_tree::*,
16 update_address_tree::*,
17};
18pub use protocol_config::{initialize::*, update::*};
19
20pub use crate::epoch::{finalize_registration::*, register_epoch::*, report_work::*};
21pub mod constants;
22pub mod epoch;
23pub mod protocol_config;
24pub mod selection;
25pub mod utils;
26use account_compression::MigrateLeafParams;
27use anchor_lang::solana_program::pubkey::Pubkey;
28use errors::RegistryError;
29use light_batched_merkle_tree::{
30 initialize_address_tree::InitAddressTreeAccountsInstructionData,
31 initialize_state_tree::InitStateTreeAccountsInstructionData,
32 merkle_tree::BatchedMerkleTreeAccount, queue::BatchedQueueAccount,
33};
34use protocol_config::state::ProtocolConfig;
35pub use selection::forester::*;
36#[cfg(not(target_os = "solana"))]
37pub mod sdk;
38
39#[cfg(not(feature = "no-entrypoint"))]
40solana_security_txt::security_txt! {
41 name: "light-registry",
42 project_url: "lightprotocol.com",
43 contacts: "email:security@lightprotocol.com",
44 policy: "https://github.com/Lightprotocol/light-protocol/blob/main/SECURITY.md",
45 source_code: "https://github.com/Lightprotocol/light-protocol"
46}
47
48declare_id!("Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX");
49
50#[program]
51pub mod light_registry {
52
53 use constants::DEFAULT_WORK_V1;
54
55 use super::*;
56
57 pub fn initialize_protocol_config(
60 ctx: Context<InitializeProtocolConfig>,
61 bump: u8,
62 protocol_config: ProtocolConfig,
63 ) -> Result<()> {
64 ctx.accounts.protocol_config_pda.authority = ctx.accounts.authority.key();
65 ctx.accounts.protocol_config_pda.bump = bump;
66 check_protocol_config(protocol_config)?;
67 ctx.accounts.protocol_config_pda.config = protocol_config;
68 Ok(())
69 }
70
71 pub fn update_protocol_config(
72 ctx: Context<UpdateProtocolConfig>,
73 protocol_config: Option<ProtocolConfig>,
74 ) -> Result<()> {
75 if let Some(new_authority) = ctx.accounts.new_authority.as_ref() {
76 ctx.accounts.protocol_config_pda.authority = new_authority.key();
77 }
78 if let Some(protocol_config) = protocol_config {
79 if protocol_config.genesis_slot != ctx.accounts.protocol_config_pda.config.genesis_slot
80 {
81 msg!("Genesis slot cannot be changed.");
82 return err!(RegistryError::InvalidConfigUpdate);
83 }
84 if protocol_config.active_phase_length
85 != ctx.accounts.protocol_config_pda.config.active_phase_length
86 {
87 msg!(
88 "Active phase length must not be changed, otherwise epochs will repeat {} {}.",
89 protocol_config.active_phase_length,
90 ctx.accounts.protocol_config_pda.config.active_phase_length
91 );
92 return err!(RegistryError::InvalidConfigUpdate);
93 }
94 check_protocol_config(protocol_config)?;
95 ctx.accounts.protocol_config_pda.config = protocol_config;
96 }
97 Ok(())
98 }
99
100 pub fn register_system_program(ctx: Context<RegisterProgram>, bump: u8) -> Result<()> {
101 let bump = &[bump];
102 let seeds = [CPI_AUTHORITY_PDA_SEED, bump];
103 let signer_seeds = &[&seeds[..]];
104
105 let accounts = account_compression::cpi::accounts::RegisterProgramToGroup {
106 authority: ctx.accounts.cpi_authority.to_account_info(),
107 program_to_be_registered: ctx.accounts.program_to_be_registered.to_account_info(),
108 system_program: ctx.accounts.system_program.to_account_info(),
109 registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(),
110 group_authority_pda: ctx.accounts.group_pda.to_account_info(),
111 };
112
113 let cpi_ctx = CpiContext::new_with_signer(
114 ctx.accounts.account_compression_program.to_account_info(),
115 accounts,
116 signer_seeds,
117 );
118
119 account_compression::cpi::register_program_to_group(cpi_ctx)
120 }
121
122 pub fn deregister_system_program(ctx: Context<DeregisterProgram>, bump: u8) -> Result<()> {
123 let bump = &[bump];
124 let seeds = [CPI_AUTHORITY_PDA_SEED, bump];
125 let signer_seeds = &[&seeds[..]];
126
127 let accounts = account_compression::cpi::accounts::DeregisterProgram {
128 authority: ctx.accounts.cpi_authority.to_account_info(),
129 registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(),
130 group_authority_pda: ctx.accounts.group_pda.to_account_info(),
131 close_recipient: ctx.accounts.authority.to_account_info(),
132 };
133
134 let cpi_ctx = CpiContext::new_with_signer(
135 ctx.accounts.account_compression_program.to_account_info(),
136 accounts,
137 signer_seeds,
138 );
139
140 account_compression::cpi::deregister_program(cpi_ctx)
141 }
142
143 pub fn register_forester(
144 ctx: Context<RegisterForester>,
145 _bump: u8,
146 authority: Pubkey,
147 config: ForesterConfig,
148 weight: Option<u64>,
149 ) -> Result<()> {
150 ctx.accounts.forester_pda.authority = authority;
151 ctx.accounts.forester_pda.config = config;
152
153 if let Some(weight) = weight {
154 ctx.accounts.forester_pda.active_weight = weight;
155 }
156 Ok(())
157 }
158
159 pub fn update_forester_pda(
160 ctx: Context<UpdateForesterPda>,
161 config: Option<ForesterConfig>,
162 ) -> Result<()> {
163 if let Some(authority) = ctx.accounts.new_authority.as_ref() {
164 ctx.accounts.forester_pda.authority = authority.key();
165 }
166 if let Some(config) = config {
167 ctx.accounts.forester_pda.config = config;
168 }
169 Ok(())
170 }
171
172 pub fn update_forester_pda_weight(
173 ctx: Context<UpdateForesterPdaWeight>,
174 new_weight: u64,
175 ) -> Result<()> {
176 ctx.accounts.forester_pda.active_weight = new_weight;
177 Ok(())
178 }
179
180 pub fn register_forester_epoch<'info>(
185 ctx: Context<'_, '_, '_, 'info, RegisterForesterEpoch<'info>>,
186 epoch: u64,
187 ) -> Result<()> {
188 if ctx.accounts.epoch_pda.registered_weight == 0 {
190 (*ctx.accounts.epoch_pda).clone_from(&EpochPda {
191 epoch,
192 protocol_config: ctx.accounts.protocol_config.config,
193 total_work: 0,
194 registered_weight: 0,
195 });
196 }
197 let current_solana_slot = anchor_lang::solana_program::clock::Clock::get()?.slot;
198 let current_epoch = ctx
200 .accounts
201 .epoch_pda
202 .protocol_config
203 .get_latest_register_epoch(current_solana_slot)?;
204
205 if current_epoch != epoch {
206 return err!(RegistryError::InvalidEpoch);
207 }
208 process_register_for_epoch(
210 &ctx.accounts.authority.key(),
211 &mut ctx.accounts.forester_pda,
212 &mut ctx.accounts.forester_epoch_pda,
213 &mut ctx.accounts.epoch_pda,
214 current_solana_slot,
215 )?;
216 Ok(())
217 }
218
219 pub fn finalize_registration<'info>(
223 ctx: Context<'_, '_, '_, 'info, FinalizeRegistration<'info>>,
224 ) -> Result<()> {
225 let current_solana_slot = anchor_lang::solana_program::clock::Clock::get()?.slot;
226 let current_active_epoch = ctx
227 .accounts
228 .epoch_pda
229 .protocol_config
230 .get_current_active_epoch(current_solana_slot)?;
231 if current_active_epoch != ctx.accounts.epoch_pda.epoch {
232 return err!(RegistryError::InvalidEpoch);
233 }
234 ctx.accounts.forester_epoch_pda.total_epoch_weight =
235 Some(ctx.accounts.epoch_pda.registered_weight);
236 ctx.accounts.forester_epoch_pda.finalize_counter += 1;
237 if ctx.accounts.forester_epoch_pda.finalize_counter
243 > ctx
244 .accounts
245 .forester_epoch_pda
246 .protocol_config
247 .finalize_counter_limit
248 {
249 return err!(RegistryError::FinalizeCounterExceeded);
250 }
251
252 Ok(())
253 }
254
255 pub fn report_work<'info>(ctx: Context<'_, '_, '_, 'info, ReportWork<'info>>) -> Result<()> {
256 let current_solana_slot = anchor_lang::solana_program::clock::Clock::get()?.slot;
257 ctx.accounts
258 .epoch_pda
259 .protocol_config
260 .is_report_work_phase(current_solana_slot, ctx.accounts.epoch_pda.epoch)?;
261 if ctx.accounts.epoch_pda.epoch != ctx.accounts.forester_epoch_pda.epoch {
262 return err!(RegistryError::InvalidEpoch);
263 }
264 if ctx.accounts.forester_epoch_pda.has_reported_work {
265 return err!(RegistryError::ForesterAlreadyReportedWork);
266 }
267 ctx.accounts.epoch_pda.total_work += ctx.accounts.forester_epoch_pda.work_counter;
268 ctx.accounts.forester_epoch_pda.has_reported_work = true;
269 Ok(())
270 }
271
272 pub fn initialize_address_merkle_tree(
273 ctx: Context<InitializeMerkleTreeAndQueue>,
274 bump: u8,
275 program_owner: Option<Pubkey>,
276 forester: Option<Pubkey>,
277 merkle_tree_config: AddressMerkleTreeConfig,
278 queue_config: AddressQueueConfig,
279 ) -> Result<()> {
280 if let Some(network_fee) = merkle_tree_config.network_fee {
283 if network_fee != ctx.accounts.protocol_config_pda.config.network_fee {
284 return err!(RegistryError::InvalidNetworkFee);
285 }
286 if forester.is_some() {
287 msg!("Forester pubkey must not be defined for trees serviced by light foresters.");
288 return err!(RegistryError::ForesterDefined);
289 }
290 } else if forester.is_none() {
291 msg!("Forester pubkey required for trees without a network fee.");
292 msg!("Trees without a network fee will not be serviced by light foresters.");
293 return err!(RegistryError::ForesterUndefined);
294 }
295 if queue_config.network_fee.is_some() {
297 return err!(RegistryError::InvalidNetworkFee);
298 }
299 process_initialize_address_merkle_tree(
300 ctx,
301 bump,
302 0,
303 program_owner,
304 forester,
305 merkle_tree_config,
306 queue_config,
307 )
308 }
309
310 pub fn initialize_state_merkle_tree(
311 ctx: Context<InitializeMerkleTreeAndQueue>,
312 bump: u8,
313 program_owner: Option<Pubkey>,
314 forester: Option<Pubkey>,
315 merkle_tree_config: StateMerkleTreeConfig,
316 queue_config: NullifierQueueConfig,
317 ) -> Result<()> {
318 if let Some(network_fee) = merkle_tree_config.network_fee {
321 if network_fee != ctx.accounts.protocol_config_pda.config.network_fee {
322 return err!(RegistryError::InvalidNetworkFee);
323 }
324 } else if forester.is_none() {
325 msg!("Forester pubkey required for trees without a network fee.");
326 msg!("Trees without a network fee will not be serviced by light foresters.");
327 return err!(RegistryError::ForesterUndefined);
328 }
329
330 if queue_config.network_fee.is_some() {
332 return err!(RegistryError::InvalidNetworkFee);
333 }
334 check_cpi_context(
335 ctx.accounts
336 .cpi_context_account
337 .as_ref()
338 .unwrap()
339 .to_account_info(),
340 &ctx.accounts.protocol_config_pda.config,
341 )?;
342 process_initialize_state_merkle_tree(
343 &ctx,
344 bump,
345 0,
346 program_owner,
347 forester,
348 merkle_tree_config,
349 queue_config,
350 )?;
351
352 process_initialize_cpi_context(
353 bump,
354 ctx.accounts.authority.to_account_info(),
355 ctx.accounts
356 .cpi_context_account
357 .as_ref()
358 .unwrap()
359 .to_account_info(),
360 ctx.accounts.merkle_tree.to_account_info(),
361 ctx.accounts
362 .light_system_program
363 .as_ref()
364 .unwrap()
365 .to_account_info(),
366 )
367 }
368
369 pub fn nullify<'info>(
370 ctx: Context<'_, '_, '_, 'info, NullifyLeaves<'info>>,
371 bump: u8,
372 change_log_indices: Vec<u64>,
373 leaves_queue_indices: Vec<u16>,
374 indices: Vec<u64>,
375 proofs: Vec<Vec<[u8; 32]>>,
376 ) -> Result<()> {
377 let metadata = ctx.accounts.merkle_tree.load()?.metadata;
378 check_forester(
379 &metadata,
380 ctx.accounts.authority.key(),
381 ctx.accounts.nullifier_queue.key(),
382 &mut ctx.accounts.registered_forester_pda,
383 DEFAULT_WORK_V1,
384 )?;
385
386 process_nullify(
387 &ctx,
388 bump,
389 change_log_indices,
390 leaves_queue_indices,
391 indices,
392 proofs,
393 )
394 }
395
396 #[allow(clippy::too_many_arguments)]
397 pub fn update_address_merkle_tree(
398 ctx: Context<UpdateAddressMerkleTree>,
399 bump: u8,
400 changelog_index: u16,
401 indexed_changelog_index: u16,
402 value: u16,
403 low_address_index: u64,
404 low_address_value: [u8; 32],
405 low_address_next_index: u64,
406 low_address_next_value: [u8; 32],
407 low_address_proof: [[u8; 32]; 16],
408 ) -> Result<()> {
409 let metadata = ctx.accounts.merkle_tree.load()?.metadata;
410
411 check_forester(
412 &metadata,
413 ctx.accounts.authority.key(),
414 ctx.accounts.queue.key(),
415 &mut ctx.accounts.registered_forester_pda,
416 DEFAULT_WORK_V1,
417 )?;
418 process_update_address_merkle_tree(
419 &ctx,
420 bump,
421 changelog_index,
422 indexed_changelog_index,
423 value,
424 low_address_index,
425 low_address_value,
426 low_address_next_index,
427 low_address_next_value,
428 low_address_proof,
429 )
430 }
431
432 pub fn rollover_address_merkle_tree_and_queue<'info>(
433 ctx: Context<'_, '_, '_, 'info, RolloverAddressMerkleTreeAndQueue<'info>>,
434 bump: u8,
435 ) -> Result<()> {
436 let metadata = ctx.accounts.old_merkle_tree.load()?.metadata;
437 check_forester(
438 &metadata,
439 ctx.accounts.authority.key(),
440 ctx.accounts.old_queue.key(),
441 &mut ctx.accounts.registered_forester_pda,
442 DEFAULT_WORK_V1,
443 )?;
444
445 process_rollover_address_merkle_tree_and_queue(&ctx, bump)
446 }
447
448 pub fn rollover_state_merkle_tree_and_queue<'info>(
449 ctx: Context<'_, '_, '_, 'info, RolloverStateMerkleTreeAndQueue<'info>>,
450 bump: u8,
451 ) -> Result<()> {
452 let metadata = ctx.accounts.old_merkle_tree.load()?.metadata;
453 check_forester(
454 &metadata,
455 ctx.accounts.authority.key(),
456 ctx.accounts.old_queue.key(),
457 &mut ctx.accounts.registered_forester_pda,
458 DEFAULT_WORK_V1,
459 )?;
460
461 check_cpi_context(
462 ctx.accounts.cpi_context_account.to_account_info(),
463 &ctx.accounts.protocol_config_pda.config,
464 )?;
465 process_rollover_state_merkle_tree_and_queue(&ctx, bump)?;
466 process_initialize_cpi_context(
467 bump,
468 ctx.accounts.authority.to_account_info(),
469 ctx.accounts.cpi_context_account.to_account_info(),
470 ctx.accounts.new_merkle_tree.to_account_info(),
471 ctx.accounts.light_system_program.to_account_info(),
472 )
473 }
474
475 pub fn initialize_batched_state_merkle_tree<'info>(
476 ctx: Context<'_, '_, '_, 'info, InitializeBatchedStateMerkleTreeAndQueue<'info>>,
477 bump: u8,
478 params: Vec<u8>,
479 ) -> Result<()> {
480 let params = InitStateTreeAccountsInstructionData::try_from_slice(¶ms)?;
481 if let Some(network_fee) = params.network_fee {
482 if network_fee != ctx.accounts.protocol_config_pda.config.network_fee {
483 return err!(RegistryError::InvalidNetworkFee);
484 }
485 if params.forester.is_some() {
486 msg!("Forester pubkey must not be defined for trees serviced by light foresters.");
487 return err!(RegistryError::ForesterDefined);
488 }
489 } else if params.forester.is_none() {
490 msg!("Forester pubkey required for trees without a network fee.");
491 msg!("Trees without a network fee will not be serviced by light foresters.");
492 return err!(RegistryError::ForesterUndefined);
493 }
494 check_cpi_context(
495 ctx.accounts.cpi_context_account.to_account_info(),
496 &ctx.accounts.protocol_config_pda.config,
497 )?;
498
499 process_initialize_batched_state_merkle_tree(&ctx, bump, params.try_to_vec().unwrap())?;
500
501 process_initialize_cpi_context(
502 bump,
503 ctx.accounts.authority.to_account_info(),
504 ctx.accounts.cpi_context_account.to_account_info(),
505 ctx.accounts.merkle_tree.to_account_info(),
506 ctx.accounts.light_system_program.to_account_info(),
507 )
508 }
509
510 pub fn batch_nullify<'info>(
511 ctx: Context<'_, '_, '_, 'info, BatchNullify<'info>>,
512 bump: u8,
513 data: Vec<u8>,
514 ) -> Result<()> {
515 let merkle_tree =
516 BatchedMerkleTreeAccount::state_from_account_info(&ctx.accounts.merkle_tree)
517 .map_err(ProgramError::from)?;
518 check_forester(
519 &merkle_tree.metadata,
520 ctx.accounts.authority.key(),
521 ctx.accounts.merkle_tree.key(),
522 &mut ctx.accounts.registered_forester_pda,
523 merkle_tree.queue_batches.batch_size,
525 )?;
526
527 process_batch_nullify(&ctx, bump, data)
528 }
529
530 pub fn batch_append<'info>(
531 ctx: Context<'_, '_, '_, 'info, BatchAppend<'info>>,
532 bump: u8,
533 data: Vec<u8>,
534 ) -> Result<()> {
535 let queue_account =
536 BatchedQueueAccount::output_from_account_info(&ctx.accounts.output_queue)
537 .map_err(ProgramError::from)?;
538 let merkle_tree =
539 BatchedMerkleTreeAccount::state_from_account_info(&ctx.accounts.merkle_tree)
540 .map_err(ProgramError::from)?;
541 check_forester(
545 &merkle_tree.metadata,
546 ctx.accounts.authority.key(),
547 ctx.accounts.merkle_tree.key(),
548 &mut ctx.accounts.registered_forester_pda,
549 queue_account.batch_metadata.batch_size,
551 )?;
552 process_batch_append(&ctx, bump, data)
553 }
554
555 pub fn initialize_batched_address_merkle_tree(
556 ctx: Context<InitializeBatchedAddressTree>,
557 bump: u8,
558 params: Vec<u8>,
559 ) -> Result<()> {
560 let params = InitAddressTreeAccountsInstructionData::try_from_slice(¶ms)?;
561 if let Some(network_fee) = params.network_fee {
562 if network_fee != ctx.accounts.protocol_config_pda.config.network_fee {
563 return err!(RegistryError::InvalidNetworkFee);
564 }
565 if params.forester.is_some() {
566 msg!("Forester pubkey must not be defined for trees serviced by light foresters.");
567 return err!(RegistryError::ForesterDefined);
568 }
569 } else if params.forester.is_none() {
570 msg!("Forester pubkey required for trees without a network fee.");
571 msg!("Trees without a network fee will not be serviced by light foresters.");
572 return err!(RegistryError::ForesterUndefined);
573 }
574 process_initialize_batched_address_merkle_tree(&ctx, bump, params.try_to_vec()?)
575 }
576
577 pub fn batch_update_address_tree<'info>(
578 ctx: Context<'_, '_, '_, 'info, BatchUpdateAddressTree<'info>>,
579 bump: u8,
580 data: Vec<u8>,
581 ) -> Result<()> {
582 let account =
583 BatchedMerkleTreeAccount::address_from_account_info(&ctx.accounts.merkle_tree)
584 .map_err(ProgramError::from)?;
585 check_forester(
586 &account.metadata,
587 ctx.accounts.authority.key(),
588 ctx.accounts.merkle_tree.key(),
589 &mut ctx.accounts.registered_forester_pda,
590 account.queue_batches.batch_size,
591 )?;
592
593 process_batch_update_address_tree(&ctx, bump, data)
594 }
595
596 pub fn rollover_batched_address_merkle_tree<'info>(
597 ctx: Context<'_, '_, '_, 'info, RolloverBatchedAddressMerkleTree<'info>>,
598 bump: u8,
599 ) -> Result<()> {
600 let account = BatchedMerkleTreeAccount::address_from_account_info(
601 &ctx.accounts.old_address_merkle_tree,
602 )
603 .map_err(ProgramError::from)?;
604 check_forester(
605 &account.metadata,
606 ctx.accounts.authority.key(),
607 ctx.accounts.old_address_merkle_tree.key(),
608 &mut ctx.accounts.registered_forester_pda,
609 DEFAULT_WORK_V1,
610 )?;
611 process_rollover_batched_address_merkle_tree(&ctx, bump)
612 }
613
614 pub fn rollover_batched_state_merkle_tree<'info>(
615 ctx: Context<'_, '_, '_, 'info, RolloverBatchedStateMerkleTree<'info>>,
616 bump: u8,
617 ) -> Result<()> {
618 let account =
619 BatchedMerkleTreeAccount::state_from_account_info(&ctx.accounts.old_state_merkle_tree)
620 .map_err(ProgramError::from)?;
621 check_forester(
622 &account.metadata,
623 ctx.accounts.authority.key(),
624 ctx.accounts.old_state_merkle_tree.key(),
625 &mut ctx.accounts.registered_forester_pda,
626 DEFAULT_WORK_V1,
627 )?;
628 check_cpi_context(
629 ctx.accounts.cpi_context_account.to_account_info(),
630 &ctx.accounts.protocol_config_pda.config,
631 )?;
632
633 process_rollover_batched_state_merkle_tree(&ctx, bump)?;
634
635 process_initialize_cpi_context(
636 bump,
637 ctx.accounts.authority.to_account_info(),
638 ctx.accounts.cpi_context_account.to_account_info(),
639 ctx.accounts.new_state_merkle_tree.to_account_info(),
640 ctx.accounts.light_system_program.to_account_info(),
641 )
642 }
643
644 pub fn migrate_state<'info>(
645 ctx: Context<'_, '_, '_, 'info, MigrateState<'info>>,
646 bump: u8,
647 inputs: MigrateLeafParams,
648 ) -> Result<()> {
649 check_forester(
650 &ctx.accounts.merkle_tree.load()?.metadata,
651 ctx.accounts.authority.key(),
652 ctx.accounts.merkle_tree.key(),
653 &mut Some(ctx.accounts.registered_forester_pda.clone()),
654 DEFAULT_WORK_V1,
655 )?;
656 process_migrate_state(&ctx, bump, inputs)
657 }
658}
659
660pub fn check_forester(
664 metadata: &MerkleTreeMetadata,
665 authority: Pubkey,
666 queue: Pubkey,
667 registered_forester_pda: &mut Option<Account<'_, ForesterEpochPda>>,
668 num_work_items: u64,
669) -> Result<()> {
670 if let Some(forester_pda) = registered_forester_pda.as_mut() {
671 ForesterEpochPda::check_forester_in_program(
676 forester_pda,
677 &authority,
678 &queue,
679 num_work_items,
680 )?;
681 if metadata.rollover_metadata.network_fee == 0 {
682 return err!(RegistryError::InvalidNetworkFee);
683 }
684 Ok(())
685 } else if metadata.access_metadata.forester == authority {
686 Ok(())
687 } else {
688 err!(RegistryError::InvalidSigner)
689 }
690}