1use solana_program::{
2 msg,
3 account_info::AccountInfo,
4 program_error::ProgramError,
5 program_pack::Pack,
6 pubkey::Pubkey,
7 system_program,
8 sysvar
9};
10use spl_token::state::Mint;
11use mpl_core::{Asset, types::UpdateAuthority};
12
13use crate::{
14 consts::*,
15 state::{Bus, Config, Proof, ProofV2, Reprocessor, Tool, Treasury, WoodConfig, WoodTool},
16 utils::{AccountDeserialize, Discriminator},
17};
18
19pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> {
22 if !info.is_signer {
23 return Err(ProgramError::MissingRequiredSignature);
24 }
25
26 Ok(())
27}
28
29pub fn load_coal_bus<'a, 'info>(
37 info: &'a AccountInfo<'info>,
38 id: u64,
39 is_writable: bool,
40) -> Result<(), ProgramError> {
41 if info.owner.ne(&crate::id()) {
42 return Err(ProgramError::InvalidAccountOwner);
43 }
44
45 if info.key.ne(&COAL_BUS_ADDRESSES[id as usize]) {
46 return Err(ProgramError::InvalidSeeds);
47 }
48
49 if info.data_is_empty() {
50 return Err(ProgramError::UninitializedAccount);
51 }
52
53 let bus_data = info.data.borrow();
54 let bus = Bus::try_from_bytes(&bus_data)?;
55
56 if bus.id.ne(&id) {
57 return Err(ProgramError::InvalidAccountData);
58 }
59
60 if is_writable && !info.is_writable {
61 return Err(ProgramError::InvalidAccountData);
62 }
63
64 Ok(())
65}
66
67pub fn load_wood_bus<'a, 'info>(
75 info: &'a AccountInfo<'info>,
76 id: u64,
77 is_writable: bool,
78) -> Result<(), ProgramError> {
79 if info.owner.ne(&crate::id()) {
80 return Err(ProgramError::InvalidAccountOwner);
81 }
82
83 if info.key.ne(&WOOD_BUS_ADDRESSES[id as usize]) {
84 return Err(ProgramError::InvalidSeeds);
85 }
86
87 if info.data_is_empty() {
88 return Err(ProgramError::UninitializedAccount);
89 }
90
91 let bus_data = info.data.borrow();
92 let bus = Bus::try_from_bytes(&bus_data)?;
93
94 if bus.id.ne(&id) {
95 return Err(ProgramError::InvalidAccountData);
96 }
97
98 if is_writable && !info.is_writable {
99 return Err(ProgramError::InvalidAccountData);
100 }
101
102 Ok(())
103}
104
105pub fn load_any_coal_bus<'a, 'info>(
113 info: &'a AccountInfo<'info>,
114 is_writable: bool,
115) -> Result<(), ProgramError> {
116 if info.owner.ne(&crate::id()) {
117 return Err(ProgramError::InvalidAccountOwner);
118 }
119
120 if info.data_is_empty() {
121 return Err(ProgramError::UninitializedAccount);
122 }
123
124 if info.data.borrow()[0].ne(&(Bus::discriminator() as u8)) {
125 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
126 }
127
128 if !COAL_BUS_ADDRESSES.contains(info.key) {
129 return Err(ProgramError::InvalidSeeds);
130 }
131
132 if is_writable && !info.is_writable {
133 return Err(ProgramError::InvalidAccountData);
134 }
135
136 Ok(())
137}
138
139pub fn load_any_wood_bus<'a, 'info>(
147 info: &'a AccountInfo<'info>,
148 is_writable: bool,
149) -> Result<(), ProgramError> {
150 if info.owner.ne(&crate::id()) {
151 return Err(ProgramError::InvalidAccountOwner);
152 }
153
154 if info.data_is_empty() {
155 return Err(ProgramError::UninitializedAccount);
156 }
157
158 if info.data.borrow()[0].ne(&(Bus::discriminator() as u8)) {
159 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
160 }
161
162 if !WOOD_BUS_ADDRESSES.contains(info.key) {
163 return Err(ProgramError::InvalidSeeds);
164 }
165
166 if is_writable && !info.is_writable {
167 return Err(ProgramError::InvalidAccountData);
168 }
169
170 Ok(())
171}
172
173pub fn load_coal_config<'a, 'info>(
180 info: &'a AccountInfo<'info>,
181 is_writable: bool,
182) -> Result<(), ProgramError> {
183 if info.owner.ne(&crate::id()) {
184 return Err(ProgramError::InvalidAccountOwner);
185 }
186
187 if info.key.ne(&COAL_CONFIG_ADDRESS) {
188 return Err(ProgramError::InvalidSeeds);
189 }
190
191 if info.data_is_empty() {
192 return Err(ProgramError::UninitializedAccount);
193 }
194
195 if info.data.borrow()[0].ne(&(Config::discriminator() as u8)) {
196 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
197 }
198
199 if is_writable && !info.is_writable {
200 return Err(ProgramError::InvalidAccountData);
201 }
202
203 Ok(())
204}
205
206pub fn load_wood_config<'a, 'info>(
213 info: &'a AccountInfo<'info>,
214 is_writable: bool,
215) -> Result<(), ProgramError> {
216 if info.owner.ne(&crate::id()) {
217 return Err(ProgramError::InvalidAccountOwner);
218 }
219
220 if info.key.ne(&WOOD_CONFIG_ADDRESS) {
221 return Err(ProgramError::InvalidSeeds);
222 }
223
224 if info.data_is_empty() {
225 return Err(ProgramError::UninitializedAccount);
226 }
227
228 if info.data.borrow()[0].ne(&(WoodConfig::discriminator() as u8)) {
229 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
230 }
231
232 if is_writable && !info.is_writable {
233 return Err(ProgramError::InvalidAccountData);
234 }
235
236 Ok(())
237}
238
239pub fn load_coal_proof<'a, 'info>(
246 info: &'a AccountInfo<'info>,
247 authority: &Pubkey,
248 is_writable: bool,
249) -> Result<(), ProgramError> {
250 if info.owner.ne(&crate::id()) {
251 return Err(ProgramError::InvalidAccountOwner);
252 }
253
254 if info.data_is_empty() {
255 return Err(ProgramError::UninitializedAccount);
256 }
257
258 let proof_data = info.data.borrow();
259 let proof = Proof::try_from_bytes(&proof_data)?;
260
261 if proof.authority.ne(&authority) {
262 return Err(ProgramError::InvalidAccountData);
263 }
264
265 if is_writable && !info.is_writable {
266 return Err(ProgramError::InvalidAccountData);
267 }
268
269 Ok(())
270}
271
272pub fn load_reprocessor<'a, 'info>(
279 info: &'a AccountInfo<'info>,
280 authority: &Pubkey,
281 is_writable: bool,
282) -> Result<(), ProgramError> {
283 if info.owner.ne(&crate::id()) {
284 return Err(ProgramError::InvalidAccountOwner);
285 }
286
287 if info.data_is_empty() {
288 return Err(ProgramError::UninitializedAccount);
289 }
290
291 let data = info.data.borrow();
292 let reprocessor = Reprocessor::try_from_bytes(&data)?;
293
294 if reprocessor.authority.ne(&authority) {
295 return Err(ProgramError::InvalidAccountData);
296 }
297
298 if is_writable && !info.is_writable {
299 return Err(ProgramError::InvalidAccountData);
300 }
301
302 Ok(())
303}
304
305pub fn load_proof_v2<'a, 'info>(
312 info: &'a AccountInfo<'info>,
313 authority: &Pubkey,
314 resource: &Pubkey,
315 is_writable: bool,
316) -> Result<(), ProgramError> {
317 if info.owner.ne(&crate::id()) {
318 return Err(ProgramError::InvalidAccountOwner);
319 }
320
321 if info.data_is_empty() {
322 return Err(ProgramError::UninitializedAccount);
323 }
324
325 let proof_data = info.data.borrow();
326 let proof = ProofV2::try_from_bytes(&proof_data)?;
327
328 if proof.resource.ne(&resource) {
329 return Err(ProgramError::InvalidAccountData);
330 }
331
332 if proof.authority.ne(&authority) {
333 return Err(ProgramError::InvalidAccountData);
334 }
335
336 if is_writable && !info.is_writable {
337 return Err(ProgramError::InvalidAccountData);
338 }
339
340 Ok(())
341}
342
343pub fn load_coal_proof_with_miner<'a, 'info>(
350 info: &'a AccountInfo<'info>,
351 miner: &Pubkey,
352 is_writable: bool,
353) -> Result<(), ProgramError> {
354 if info.owner.ne(&crate::id()) {
355 return Err(ProgramError::InvalidAccountOwner);
356 }
357
358 if info.data_is_empty() {
359 return Err(ProgramError::UninitializedAccount);
360 }
361
362 let proof_data = info.data.borrow();
363 let proof = Proof::try_from_bytes(&proof_data)?;
364
365 if proof.miner.ne(&miner) {
366 return Err(ProgramError::InvalidAccountData);
367 }
368
369 if is_writable && !info.is_writable {
370 return Err(ProgramError::InvalidAccountData);
371 }
372
373 Ok(())
374}
375
376pub fn load_proof_v2_with_miner<'a, 'info>(
383 info: &'a AccountInfo<'info>,
384 miner: &Pubkey,
385 resource: &Pubkey,
386 is_writable: bool,
387) -> Result<(), ProgramError> {
388 if info.owner.ne(&crate::id()) {
389 return Err(ProgramError::InvalidAccountOwner);
390 }
391
392 if info.data_is_empty() {
393 return Err(ProgramError::UninitializedAccount);
394 }
395
396 let proof_data = info.data.borrow();
397 let proof = ProofV2::try_from_bytes(&proof_data)?;
398
399 if proof.resource.ne(&resource) {
400 return Err(ProgramError::InvalidAccountData);
401 }
402
403 if proof.miner.ne(&miner) {
404 return Err(ProgramError::InvalidAccountData);
405 }
406
407 if is_writable && !info.is_writable {
408 return Err(ProgramError::InvalidAccountData);
409 }
410
411 Ok(())
412}
413
414pub fn load_any_coal_proof<'a, 'info>(
420 info: &'a AccountInfo<'info>,
421 is_writable: bool,
422) -> Result<(), ProgramError> {
423 if info.owner.ne(&crate::id()) {
424 return Err(ProgramError::InvalidAccountOwner);
425 }
426
427 if info.data_is_empty() {
428 return Err(ProgramError::UninitializedAccount);
429 }
430
431 if info.data.borrow()[0].ne(&(Proof::discriminator() as u8)) {
432 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
433 }
434
435 if is_writable && !info.is_writable {
436 return Err(ProgramError::InvalidAccountData);
437 }
438
439 Ok(())
440}
441
442pub fn load_any_proof_v2<'a, 'info>(
448 info: &'a AccountInfo<'info>,
449 is_writable: bool,
450) -> Result<(), ProgramError> {
451 if info.owner.ne(&crate::id()) {
452 return Err(ProgramError::InvalidAccountOwner);
453 }
454
455 if info.data_is_empty() {
456 return Err(ProgramError::UninitializedAccount);
457 }
458
459 if info.data.borrow()[0].ne(&(ProofV2::discriminator() as u8)) {
460 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
461 }
462
463 if is_writable && !info.is_writable {
464 return Err(ProgramError::InvalidAccountData);
465 }
466
467 Ok(())
468}
469
470pub fn load_treasury<'a, 'info>(
477 info: &'a AccountInfo<'info>,
478 is_writable: bool,
479) -> Result<(), ProgramError> {
480 if info.owner.ne(&crate::id()) {
481 return Err(ProgramError::InvalidAccountOwner);
482 }
483
484 if info.key.ne(&TREASURY_ADDRESS) {
485 return Err(ProgramError::InvalidSeeds);
486 }
487
488 if info.data_is_empty() {
489 return Err(ProgramError::UninitializedAccount);
490 }
491
492 if info.data.borrow()[0].ne(&(Treasury::discriminator() as u8)) {
493 return Err(solana_program::program_error::ProgramError::InvalidAccountData);
494 }
495
496 if is_writable && !info.is_writable {
497 return Err(ProgramError::InvalidAccountData);
498 }
499
500 Ok(())
501}
502
503pub fn load_coal_treasury_tokens<'a, 'info>(
507 info: &'a AccountInfo<'info>,
508 is_writable: bool,
509) -> Result<(), ProgramError> {
510 if info.key.ne(&COAL_TREASURY_TOKENS_ADDRESS) {
511 return Err(ProgramError::InvalidSeeds);
512 }
513
514 load_token_account(info, Some(&TREASURY_ADDRESS), &COAL_MINT_ADDRESS, is_writable)
515}
516
517pub fn load_wood_treasury_tokens<'a, 'info>(
521 info: &'a AccountInfo<'info>,
522 is_writable: bool,
523) -> Result<(), ProgramError> {
524 if info.key.ne(&WOOD_TREASURY_TOKENS_ADDRESS) {
525 return Err(ProgramError::InvalidSeeds);
526 }
527
528 load_token_account(info, Some(&TREASURY_ADDRESS), &WOOD_MINT_ADDRESS, is_writable)
529}
530
531pub fn load_mint<'a, 'info>(
538 info: &'a AccountInfo<'info>,
539 address: Pubkey,
540 is_writable: bool,
541) -> Result<(), ProgramError> {
542 if info.owner.ne(&spl_token::id()) {
543 return Err(ProgramError::InvalidAccountOwner);
544 }
545
546 if info.key.ne(&address) {
547 return Err(ProgramError::InvalidSeeds);
548 }
549
550 if info.data_is_empty() {
551 return Err(ProgramError::UninitializedAccount);
552 }
553
554 Mint::unpack(&info.data.borrow())?;
555
556 if is_writable && !info.is_writable {
557 return Err(ProgramError::InvalidAccountData);
558 }
559
560 Ok(())
561}
562
563pub fn load_token_account<'a, 'info>(
571 info: &'a AccountInfo<'info>,
572 owner: Option<&Pubkey>,
573 mint: &Pubkey,
574 is_writable: bool,
575) -> Result<(), ProgramError> {
576 if info.owner.ne(&spl_token::id()) {
577 return Err(ProgramError::InvalidAccountOwner);
578 }
579
580 if info.data_is_empty() {
581 return Err(ProgramError::UninitializedAccount);
582 }
583
584 let account_data = info.data.borrow();
585 let account = spl_token::state::Account::unpack(&account_data)?;
586
587 if account.mint.ne(&mint) {
588 msg!("Invalid mint: {:?} == {:?}", account.mint, mint);
589 return Err(ProgramError::InvalidAccountData);
590 }
591
592 if let Some(owner) = owner {
593 if account.owner.ne(owner) {
594 msg!("Invalid owner: {:?} == {:?}", account.owner, owner);
595 return Err(ProgramError::InvalidAccountData);
596 }
597 }
598
599 if is_writable && !info.is_writable {
600 msg!("Invalid writable: {:?} == {:?}", info.is_writable, is_writable);
601 return Err(ProgramError::InvalidAccountData);
602 }
603
604 Ok(())
605}
606
607pub fn load_uninitialized_pda<'a, 'info>(
611 info: &'a AccountInfo<'info>,
612 seeds: &[&[u8]],
613 bump: u8,
614 program_id: &Pubkey,
615) -> Result<(), ProgramError> {
616 let pda = Pubkey::find_program_address(seeds, program_id);
617
618 if info.key.ne(&pda.0) {
619 return Err(ProgramError::InvalidSeeds);
620 }
621
622 if bump.ne(&pda.1) {
623 return Err(ProgramError::InvalidSeeds);
624 }
625
626 load_system_account(info, true)
627}
628
629pub fn load_system_account<'a, 'info>(
634 info: &'a AccountInfo<'info>,
635 is_writable: bool,
636) -> Result<(), ProgramError> {
637 if info.owner.ne(&system_program::id()) {
638 return Err(ProgramError::InvalidAccountOwner);
639 }
640
641 if !info.data_is_empty() {
642 return Err(ProgramError::AccountAlreadyInitialized);
643 }
644
645 if is_writable && !info.is_writable {
646 return Err(ProgramError::InvalidAccountData);
647 }
648
649 Ok(())
650}
651
652pub fn load_sysvar<'a, 'info>(
656 info: &'a AccountInfo<'info>,
657 key: Pubkey,
658) -> Result<(), ProgramError> {
659 if info.owner.ne(&sysvar::id()) {
660 return Err(ProgramError::InvalidAccountOwner);
661 }
662
663 load_account(info, key, false)
664}
665
666pub fn load_account<'a, 'info>(
670 info: &'a AccountInfo<'info>,
671 key: Pubkey,
672 is_writable: bool,
673) -> Result<(), ProgramError> {
674 if info.key.ne(&key) {
675 return Err(ProgramError::InvalidAccountData);
676 }
677
678 if is_writable && !info.is_writable {
679 return Err(ProgramError::InvalidAccountData);
680 }
681
682 Ok(())
683}
684
685pub fn load_program<'a, 'info>(
689 info: &'a AccountInfo<'info>,
690 key: Pubkey,
691) -> Result<(), ProgramError> {
692 if info.key.ne(&key) {
693 return Err(ProgramError::IncorrectProgramId);
694 }
695
696 if !info.executable {
697 return Err(ProgramError::InvalidAccountData);
698 }
699
700 Ok(())
701}
702
703pub fn load_any<'a, 'info>(
706 info: &'a AccountInfo<'info>,
707 is_writable: bool,
708) -> Result<(), ProgramError> {
709 if is_writable && !info.is_writable {
710 return Err(ProgramError::InvalidAccountData);
711 }
712
713 Ok(())
714}
715
716pub fn load_asset<'a, 'info>(
723 info: &'a AccountInfo<'info>,
724) -> Result<(f64, u64, String), ProgramError> {
725 if info.owner.ne(&mpl_core::ID) {
726 return Err(ProgramError::InvalidAccountOwner);
727 }
728
729 if info.data_is_empty() {
730 return Err(ProgramError::UninitializedAccount);
731 }
732
733 let asset = Asset::from_bytes(&info.data.borrow()).unwrap();
734
735 match asset.base.update_authority {
736 UpdateAuthority::Collection(address) => {
737 if address.ne(&FORGE_PICKAXE_COLLECTION) {
738 msg!("Invalid collection: {:?} == {:?}", address, FORGE_PICKAXE_COLLECTION);
739 return Err(ProgramError::InvalidAccountData);
740 }
741 }
742 _ => return Err(ProgramError::InvalidAccountData),
743 }
744
745 if asset.plugin_list.attributes.is_none() {
746 return Err(ProgramError::InvalidAccountData);
747 }
748
749 let attributes_plugin = asset.plugin_list.attributes.unwrap();
750 let durability_attr = attributes_plugin.attributes.attribute_list.iter().find(|attr| attr.key == "durability");
751 let multiplier_attr = attributes_plugin.attributes.attribute_list.iter().find(|attr| attr.key == "multiplier");
752 let resource_attr = attributes_plugin.attributes.attribute_list.iter().find(|attr| attr.key == "resource");
753 let durability = durability_attr.unwrap().value.parse::<f64>().unwrap();
754 let multiplier = multiplier_attr.unwrap().value.parse::<u64>().unwrap();
755 let resource = resource_attr.unwrap().value.clone();
756
757 Ok((durability, multiplier, resource))
758}
759
760pub fn load_tool<'a, 'info>(
761 info: &'a AccountInfo<'info>,
762 miner: &Pubkey,
763 is_writable: bool,
764) -> Result<(u64, u64), ProgramError> {
765 if info.owner.ne(&crate::id()) {
766 return Err(ProgramError::InvalidAccountOwner);
767 }
768
769 if info.data_is_empty() {
770 return Err(ProgramError::UninitializedAccount);
771 }
772
773 let tool_data = info.data.borrow();
774 let tool = Tool::try_from_bytes(&tool_data).unwrap();
775
776 if tool.miner.ne(&miner) {
777 return Err(ProgramError::InvalidAccountData);
778 }
779
780 if is_writable && !info.is_writable {
781 return Err(ProgramError::InvalidAccountData);
782 }
783
784 Ok((tool.durability, tool.multiplier))
785}
786
787pub fn is_tool<'a, 'info>(info: &'a AccountInfo<'info>,) -> bool {
788 info.data.borrow()[0].eq(&(Tool::discriminator() as u8))
789}
790
791pub fn load_wood_tool<'a, 'info>(
792 info: &'a AccountInfo<'info>,
793 miner: &Pubkey,
794 is_writable: bool,
795) -> Result<(u64, u64), ProgramError> {
796 if info.owner.ne(&crate::id()) {
797 return Err(ProgramError::InvalidAccountOwner);
798 }
799
800 if info.data_is_empty() {
801 return Err(ProgramError::UninitializedAccount);
802 }
803
804 let tool_data = info.data.borrow();
805 let tool = WoodTool::try_from_bytes(&tool_data).unwrap();
806
807 if tool.miner.ne(&miner) {
808 return Err(ProgramError::InvalidAccountData);
809 }
810
811 if is_writable && !info.is_writable {
812 return Err(ProgramError::InvalidAccountData);
813 }
814
815 Ok((tool.durability, tool.multiplier))
816}
817
818pub fn load_any_tool_with_asset<'a, 'info>(
819 info: &'a AccountInfo<'info>,
820 miner: &Pubkey,
821 asset: &Pubkey,
822 is_writable: bool,
823) -> Result<u64, ProgramError> {
824 if info.owner.ne(&crate::id()) {
825 return Err(ProgramError::InvalidAccountOwner);
826 }
827
828 if info.data_is_empty() {
829 return Err(ProgramError::UninitializedAccount);
830 }
831
832 if is_writable && !info.is_writable {
833 return Err(ProgramError::InvalidAccountData);
834 }
835
836 let tool_data = info.data.borrow();
837
838 match tool_data[0] {
839 d if d == Tool::discriminator() as u8 => {
840 let tool = Tool::try_from_bytes(&tool_data).unwrap();
841
842 if tool.miner.ne(&miner) {
843 return Err(ProgramError::InvalidAccountData);
844 }
845
846 if tool.asset.ne(&asset) {
847 return Err(ProgramError::InvalidAccountData);
848 }
849
850 return Ok(tool.durability);
851
852 },
853 d if d == WoodTool::discriminator() as u8 => {
854 let tool = WoodTool::try_from_bytes(&tool_data).unwrap();
855
856 if tool.miner.ne(&miner) {
857 return Err(ProgramError::InvalidAccountData);
858 }
859
860 if tool.asset.ne(&asset) {
861 return Err(ProgramError::InvalidAccountData);
862 }
863
864 return Ok(tool.durability);
865
866 },
867 _ => Err(ProgramError::InvalidAccountData),
868 }
869}
870
871pub fn amount_u64_to_f64(amount: u64) -> f64 {
872 (amount as f64) / 10f64.powf(TOKEN_DECIMALS as f64)
873}
874
875pub fn amount_f64_to_u64(amount: f64) -> u64 {
876 (amount * 10f64.powf(TOKEN_DECIMALS as f64)) as u64
877}