1use borsh::{BorshDeserialize, BorshSerialize};
9use solana_pubkey::Pubkey;
10use solana_sdk::instruction::{AccountMeta, Instruction};
11
12pub const REGISTRY_PROGRAM_ID: Pubkey =
18 solana_pubkey::pubkey!("Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX");
19
20pub const FORESTER_SEED: &[u8] = b"forester";
25pub const FORESTER_EPOCH_SEED: &[u8] = b"forester_epoch";
26pub const PROTOCOL_CONFIG_PDA_SEED: &[u8] = b"authority";
27
28pub const CLAIM_DISCRIMINATOR: [u8; 8] = [62, 198, 214, 193, 213, 159, 108, 210];
34
35pub const COMPRESS_AND_CLOSE_DISCRIMINATOR: [u8; 8] = [96, 94, 135, 18, 121, 42, 213, 117];
37
38pub const REGISTER_FORESTER_DISCRIMINATOR: [u8; 8] = [62, 47, 240, 103, 84, 200, 226, 73];
40
41pub const REGISTER_FORESTER_EPOCH_DISCRIMINATOR: [u8; 8] = [43, 120, 253, 194, 109, 192, 101, 188];
43
44pub const FINALIZE_REGISTRATION_DISCRIMINATOR: [u8; 8] = [230, 188, 172, 96, 204, 247, 98, 227];
46
47#[allow(dead_code)]
49pub const REPORT_WORK_DISCRIMINATOR: [u8; 8] = [170, 110, 232, 47, 145, 213, 138, 162];
50
51pub const PROTOCOL_CONFIG_PDA_DISCRIMINATOR: [u8; 8] = [96, 176, 239, 146, 1, 254, 99, 146];
57
58pub const FORESTER_PDA_DISCRIMINATOR: [u8; 8] = [51, 47, 187, 86, 82, 153, 117, 5];
60
61pub const FORESTER_EPOCH_PDA_DISCRIMINATOR: [u8; 8] = [29, 117, 211, 141, 99, 143, 250, 114];
63
64pub const EPOCH_PDA_DISCRIMINATOR: [u8; 8] = [66, 224, 46, 2, 167, 137, 120, 107];
66
67pub fn get_protocol_config_pda_address() -> (Pubkey, u8) {
73 Pubkey::find_program_address(&[PROTOCOL_CONFIG_PDA_SEED], ®ISTRY_PROGRAM_ID)
74}
75
76pub fn get_forester_pda(authority: &Pubkey) -> (Pubkey, u8) {
78 Pubkey::find_program_address(&[FORESTER_SEED, authority.as_ref()], ®ISTRY_PROGRAM_ID)
79}
80
81pub fn get_forester_epoch_pda(forester_pda: &Pubkey, epoch: u64) -> (Pubkey, u8) {
83 Pubkey::find_program_address(
84 &[
85 FORESTER_EPOCH_SEED,
86 forester_pda.as_ref(),
87 epoch.to_le_bytes().as_slice(),
88 ],
89 ®ISTRY_PROGRAM_ID,
90 )
91}
92
93pub fn get_forester_epoch_pda_from_authority(authority: &Pubkey, epoch: u64) -> (Pubkey, u8) {
95 let forester_pda = get_forester_pda(authority);
96 get_forester_epoch_pda(&forester_pda.0, epoch)
97}
98
99pub fn get_epoch_pda_address(epoch: u64) -> Pubkey {
101 Pubkey::find_program_address(&[&epoch.to_le_bytes()], ®ISTRY_PROGRAM_ID).0
102}
103
104#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
110pub struct ForesterConfig {
111 pub fee: u64,
113}
114
115#[derive(Debug, Default, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
117pub struct ForesterPda {
118 pub authority: Pubkey,
119 pub config: ForesterConfig,
120 pub active_weight: u64,
121 pub pending_weight: u64,
123 pub current_epoch: u64,
124 pub last_compressed_forester_epoch_pda_hash: [u8; 32],
126 pub last_registered_epoch: u64,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
131pub struct ProtocolConfig {
132 pub genesis_slot: u64,
134 pub min_weight: u64,
136 pub slot_length: u64,
138 pub registration_phase_length: u64,
140 pub active_phase_length: u64,
142 pub report_work_phase_length: u64,
144 pub network_fee: u64,
145 pub cpi_context_size: u64,
146 pub finalize_counter_limit: u64,
147 pub place_holder: Pubkey,
149 pub address_network_fee: u64,
150 pub place_holder_b: u64,
151 pub place_holder_c: u64,
152 pub place_holder_d: u64,
153 pub place_holder_e: u64,
154 pub place_holder_f: u64,
155}
156
157impl Default for ProtocolConfig {
158 fn default() -> Self {
159 Self {
160 genesis_slot: 0,
161 min_weight: 1,
162 slot_length: 10,
163 registration_phase_length: 100,
164 active_phase_length: 1000,
165 report_work_phase_length: 100,
166 network_fee: 5000,
167 cpi_context_size: 20 * 1024 + 8, finalize_counter_limit: 100,
169 place_holder: Pubkey::default(),
170 address_network_fee: 10000,
171 place_holder_b: 0,
172 place_holder_c: 0,
173 place_holder_d: 0,
174 place_holder_e: 0,
175 place_holder_f: 0,
176 }
177 }
178}
179
180#[derive(Debug, BorshDeserialize)]
183pub struct ProtocolConfigPda {
184 pub authority: Pubkey,
185 pub bump: u8,
186 pub config: ProtocolConfig,
187}
188
189#[derive(Debug, Copy, Clone, BorshSerialize, BorshDeserialize)]
191pub struct CompressAndCloseIndices {
192 pub source_index: u8,
193 pub mint_index: u8,
194 pub owner_index: u8,
195 pub rent_sponsor_index: u8,
196 pub delegate_index: u8,
197}
198
199pub fn build_claim_instruction(
214 authority: Pubkey,
215 registered_forester_pda: Pubkey,
216 rent_sponsor: Pubkey,
217 compression_authority: Pubkey,
218 compressible_config: Pubkey,
219 compressed_token_program: Pubkey,
220 token_accounts: &[Pubkey],
221) -> Instruction {
222 let mut accounts = vec![
223 AccountMeta::new(authority, true),
224 AccountMeta::new(registered_forester_pda, false),
225 AccountMeta::new(rent_sponsor, false),
226 AccountMeta::new_readonly(compression_authority, false),
227 AccountMeta::new_readonly(compressible_config, false),
228 AccountMeta::new_readonly(compressed_token_program, false),
229 ];
230
231 for token_account in token_accounts {
232 accounts.push(AccountMeta::new(*token_account, false));
233 }
234
235 Instruction {
236 program_id: REGISTRY_PROGRAM_ID,
237 accounts,
238 data: CLAIM_DISCRIMINATOR.to_vec(),
239 }
240}
241
242#[allow(clippy::too_many_arguments)]
251pub fn build_compress_and_close_instruction(
252 authority: Pubkey,
253 registered_forester_pda: Pubkey,
254 compression_authority: Pubkey,
255 compressible_config: Pubkey,
256 authority_index: u8,
257 destination_index: u8,
258 indices: Vec<CompressAndCloseIndices>,
259 remaining_accounts: Vec<AccountMeta>,
260) -> Instruction {
261 let mut accounts = vec![
262 AccountMeta::new(authority, true),
263 AccountMeta::new(registered_forester_pda, false),
264 AccountMeta::new(compression_authority, false),
265 AccountMeta::new_readonly(compressible_config, false),
266 ];
267 accounts.extend(remaining_accounts);
268
269 let mut data = COMPRESS_AND_CLOSE_DISCRIMINATOR.to_vec();
271 data.push(authority_index);
272 data.push(destination_index);
273 indices.serialize(&mut data).unwrap();
275
276 Instruction {
277 program_id: REGISTRY_PROGRAM_ID,
278 accounts,
279 data,
280 }
281}
282
283pub fn create_register_forester_instruction(
292 fee_payer: &Pubkey,
293 governance_authority: &Pubkey,
294 forester_authority: &Pubkey,
295 config: ForesterConfig,
296) -> Instruction {
297 let (forester_pda, bump) = get_forester_pda(forester_authority);
298 let (protocol_config_pda, _) = get_protocol_config_pda_address();
299
300 let accounts = vec![
301 AccountMeta::new(*fee_payer, true),
302 AccountMeta::new_readonly(*governance_authority, true),
303 AccountMeta::new_readonly(protocol_config_pda, false),
304 AccountMeta::new(forester_pda, false),
305 AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
306 ];
307
308 let mut data = REGISTER_FORESTER_DISCRIMINATOR.to_vec();
310 data.push(bump);
311 data.extend_from_slice(forester_authority.as_ref());
312 config.serialize(&mut data).unwrap();
313 data.push(1u8); data.extend_from_slice(&1u64.to_le_bytes()); Instruction {
318 program_id: REGISTRY_PROGRAM_ID,
319 accounts,
320 data,
321 }
322}
323
324pub fn create_register_forester_epoch_pda_instruction(
335 authority: &Pubkey,
336 derivation: &Pubkey,
337 epoch: u64,
338) -> Instruction {
339 let (forester_epoch_pda, _bump) = get_forester_epoch_pda_from_authority(derivation, epoch);
340 let (forester_pda, _) = get_forester_pda(derivation);
341 let epoch_pda = get_epoch_pda_address(epoch);
342 let protocol_config_pda = get_protocol_config_pda_address().0;
343
344 let accounts = vec![
345 AccountMeta::new(*authority, true), AccountMeta::new(forester_epoch_pda, false), AccountMeta::new_readonly(forester_pda, false), AccountMeta::new_readonly(*authority, true), AccountMeta::new(epoch_pda, false), AccountMeta::new_readonly(protocol_config_pda, false), AccountMeta::new_readonly(solana_sdk::system_program::id(), false), ];
353
354 let mut data = REGISTER_FORESTER_EPOCH_DISCRIMINATOR.to_vec();
356 data.extend_from_slice(&epoch.to_le_bytes());
357
358 Instruction {
359 program_id: REGISTRY_PROGRAM_ID,
360 accounts,
361 data,
362 }
363}
364
365pub fn create_finalize_registration_instruction(
372 authority: &Pubkey,
373 derivation: &Pubkey,
374 epoch: u64,
375) -> Instruction {
376 let (forester_epoch_pda, _bump) = get_forester_epoch_pda_from_authority(derivation, epoch);
377 let epoch_pda = get_epoch_pda_address(epoch);
378
379 let accounts = vec![
380 AccountMeta::new(forester_epoch_pda, false),
381 AccountMeta::new_readonly(*authority, true),
382 AccountMeta::new_readonly(epoch_pda, false),
383 ];
384
385 Instruction {
386 program_id: REGISTRY_PROGRAM_ID,
387 accounts,
388 data: FINALIZE_REGISTRATION_DISCRIMINATOR.to_vec(),
389 }
390}
391
392pub fn deserialize_protocol_config_pda(data: &[u8]) -> Result<ProtocolConfigPda, std::io::Error> {
399 if data.len() < 8 {
401 return Err(std::io::Error::new(
402 std::io::ErrorKind::InvalidData,
403 "Account data too short for discriminator",
404 ));
405 }
406 ProtocolConfigPda::deserialize(&mut &data[8..])
407}
408
409pub fn deserialize_forester_pda(data: &[u8]) -> Result<ForesterPda, std::io::Error> {
412 if data.len() < 8 {
414 return Err(std::io::Error::new(
415 std::io::ErrorKind::InvalidData,
416 "Account data too short for discriminator",
417 ));
418 }
419 ForesterPda::deserialize(&mut &data[8..])
420}
421
422#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
428pub struct ForesterEpochPda {
429 pub authority: Pubkey,
430 pub config: ForesterConfig,
431 pub epoch: u64,
432 pub weight: u64,
433 pub work_counter: u64,
434 pub has_reported_work: bool,
435 pub forester_index: u64,
436 pub epoch_active_phase_start_slot: u64,
437 pub total_epoch_weight: Option<u64>,
438 pub protocol_config: ProtocolConfig,
439 pub finalize_counter: u64,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
444pub struct EpochPda {
445 pub epoch: u64,
446 pub protocol_config: ProtocolConfig,
447 pub total_work: u64,
448 pub registered_weight: u64,
449}
450
451pub fn protocol_config_for_tests() -> ProtocolConfig {
458 ProtocolConfig {
459 genesis_slot: 0,
460 min_weight: 1,
461 slot_length: 10,
462 registration_phase_length: 0, active_phase_length: u64::MAX / 2, report_work_phase_length: 0,
465 network_fee: 5000,
466 cpi_context_size: 20 * 1024 + 8,
467 finalize_counter_limit: u64::MAX,
468 place_holder: Pubkey::default(),
469 address_network_fee: 10000,
470 place_holder_b: 0,
471 place_holder_c: 0,
472 place_holder_d: 0,
473 place_holder_e: 0,
474 place_holder_f: 0,
475 }
476}
477
478pub fn serialize_protocol_config_pda(
480 authority: Pubkey,
481 bump: u8,
482 config: ProtocolConfig,
483) -> Vec<u8> {
484 let mut data = PROTOCOL_CONFIG_PDA_DISCRIMINATOR.to_vec();
485 authority.serialize(&mut data).unwrap();
486 data.push(bump);
487 config.serialize(&mut data).unwrap();
488 data
489}
490
491pub fn serialize_forester_pda(forester: &ForesterPda) -> Vec<u8> {
493 let mut data = FORESTER_PDA_DISCRIMINATOR.to_vec();
494 forester.authority.serialize(&mut data).unwrap();
495 forester.config.serialize(&mut data).unwrap();
496 forester.active_weight.serialize(&mut data).unwrap();
497 forester.pending_weight.serialize(&mut data).unwrap();
498 forester.current_epoch.serialize(&mut data).unwrap();
499 forester
500 .last_compressed_forester_epoch_pda_hash
501 .serialize(&mut data)
502 .unwrap();
503 forester.last_registered_epoch.serialize(&mut data).unwrap();
504 data
505}
506
507pub fn serialize_forester_epoch_pda(epoch_pda: &ForesterEpochPda) -> Vec<u8> {
509 let mut data = FORESTER_EPOCH_PDA_DISCRIMINATOR.to_vec();
510 epoch_pda.serialize(&mut data).unwrap();
511 data
512}
513
514pub fn serialize_epoch_pda(epoch_pda: &EpochPda) -> Vec<u8> {
516 let mut data = EPOCH_PDA_DISCRIMINATOR.to_vec();
517 epoch_pda.serialize(&mut data).unwrap();
518 data
519}
520
521pub fn setup_test_protocol_accounts(
526 context: &mut litesvm::LiteSVM,
527 forester_authority: &Pubkey,
528) -> Result<(), String> {
529 let protocol_config = protocol_config_for_tests();
530
531 let (protocol_config_pda, protocol_bump) = get_protocol_config_pda_address();
533 let protocol_data = serialize_protocol_config_pda(
534 *forester_authority, protocol_bump,
536 protocol_config,
537 );
538 let protocol_account = solana_account::Account {
539 lamports: 1_000_000_000,
540 data: protocol_data,
541 owner: REGISTRY_PROGRAM_ID,
542 executable: false,
543 rent_epoch: 0,
544 };
545 context
546 .set_account(protocol_config_pda, protocol_account)
547 .map_err(|e| format!("Failed to set protocol config account: {}", e))?;
548
549 let (forester_pda, _forester_bump) = get_forester_pda(forester_authority);
551 let forester = ForesterPda {
552 authority: *forester_authority,
553 config: ForesterConfig::default(),
554 active_weight: 1,
555 pending_weight: 0,
556 current_epoch: 0,
557 last_compressed_forester_epoch_pda_hash: [0u8; 32],
558 last_registered_epoch: 0,
559 };
560 let forester_data = serialize_forester_pda(&forester);
561 let forester_account = solana_account::Account {
562 lamports: 1_000_000_000,
563 data: forester_data,
564 owner: REGISTRY_PROGRAM_ID,
565 executable: false,
566 rent_epoch: 0,
567 };
568 context
569 .set_account(forester_pda, forester_account)
570 .map_err(|e| format!("Failed to set forester account: {}", e))?;
571
572 let (forester_epoch_pda, _epoch_bump) =
574 get_forester_epoch_pda_from_authority(forester_authority, 0);
575 let forester_epoch = ForesterEpochPda {
576 authority: *forester_authority,
577 config: ForesterConfig::default(),
578 epoch: 0,
579 weight: 1,
580 work_counter: 0,
581 has_reported_work: false,
582 forester_index: 0,
583 epoch_active_phase_start_slot: 0,
584 total_epoch_weight: Some(1), protocol_config,
586 finalize_counter: 1, };
588 let forester_epoch_data = serialize_forester_epoch_pda(&forester_epoch);
589 let forester_epoch_account = solana_account::Account {
590 lamports: 1_000_000_000,
591 data: forester_epoch_data,
592 owner: REGISTRY_PROGRAM_ID,
593 executable: false,
594 rent_epoch: 0,
595 };
596 context
597 .set_account(forester_epoch_pda, forester_epoch_account)
598 .map_err(|e| format!("Failed to set forester epoch account: {}", e))?;
599
600 let epoch_pda_address = get_epoch_pda_address(0);
602 let epoch_pda = EpochPda {
603 epoch: 0,
604 protocol_config,
605 total_work: 0,
606 registered_weight: 1, };
608 let epoch_pda_data = serialize_epoch_pda(&epoch_pda);
609 let epoch_pda_account = solana_account::Account {
610 lamports: 1_000_000_000,
611 data: epoch_pda_data,
612 owner: REGISTRY_PROGRAM_ID,
613 executable: false,
614 rent_epoch: 0,
615 };
616 context
617 .set_account(epoch_pda_address, epoch_pda_account)
618 .map_err(|e| format!("Failed to set epoch pda account: {}", e))?;
619
620 Ok(())
621}