miracle_api/state/proof.rs
1use steel::*;
2
3use super::MiracleAccount;
4
5/// Proof accounts track a member's basic claim information.
6/// Every member is allowed one proof account which is required by the program to claim rewards.
7/// A member is usually mapped to a public wallet address.
8///
9/// Note: Double-spending prevention is handled by a bitmap-based epoch tracking system
10/// combined with the Dual Merkle Tree system for unlimited claim history.
11#[repr(C)]
12#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
13pub struct Proof {
14 /// Bitmap tracking claimed epochs (512 bits = 8 u64s, covers ~1.4 years)
15 /// Each bit represents whether an epoch has been claimed
16 /// Circulation: epoch % 512 determines the bit position
17 pub claimed_epochs: [u64; 8],
18
19 /// The signer authorized to use this proof, usually member pubkey
20 pub authority: Pubkey,
21
22 /// Timestamp of the last claim (Unix timestamp)
23 pub last_claim_at: i64,
24
25 /// Epoch of the last claim (project-specific epoch number)
26 pub last_claim_epoch: u64,
27
28 /// Sum of all successful claims (lifetime total)
29 pub total_claimed_rewards: u64,
30
31 /// Number of successful claims
32 pub claim_count: u32,
33
34 /// Padding to ensure 8-byte alignment
35 pub _padding: [u8; 4],
36}
37
38account!(MiracleAccount, Proof);
39
40impl Proof {
41 /// Check if a specific epoch has been claimed using bitmap tracking
42 ///
43 /// ## Parameters
44 /// - `epoch`: The epoch number to check
45 ///
46 /// ## Returns
47 /// - `true` if the epoch has been claimed, `false` otherwise
48 ///
49 /// ## Circulation Logic
50 /// Uses modulo 512 to circulate the bitmap: epoch % 512 determines the bit position
51 /// This provides a rolling 512-day window (~1.4 years) for claim tracking
52 pub fn is_epoch_claimed(&self, epoch: u64) -> bool {
53 let bitmap_index = (epoch % 512) as usize;
54 let u64_index = bitmap_index / 64;
55 let bit_position = bitmap_index % 64;
56
57 (self.claimed_epochs[u64_index] & (1u64 << bit_position)) != 0
58 }
59
60 /// Mark a specific epoch as claimed in the bitmap
61 ///
62 /// ## Parameters
63 /// - `epoch`: The epoch number to mark as claimed
64 ///
65 /// ## Circulation Logic
66 /// Uses modulo 512 to circulate the bitmap: epoch % 512 determines the bit position
67 /// This provides a rolling 512-day window (~1.4 years) for claim tracking
68 pub fn mark_epoch_claimed(&mut self, epoch: u64) {
69 let bitmap_index = (epoch % 512) as usize;
70 let u64_index = bitmap_index / 64;
71 let bit_position = bitmap_index % 64;
72
73 self.claimed_epochs[u64_index] |= 1u64 << bit_position;
74 }
75
76 /// Clear a specific epoch claim in the bitmap (for testing/reset purposes)
77 ///
78 /// ## Parameters
79 /// - `epoch`: The epoch number to clear
80 ///
81 /// ## Circulation Logic
82 /// Uses modulo 512 to circulate the bitmap: epoch % 512 determines the bit position
83 /// This provides a rolling 512-day window (~1.4 years) for claim tracking
84 pub fn clear_epoch_claimed(&mut self, epoch: u64) {
85 let bitmap_index = (epoch % 512) as usize;
86 let u64_index = bitmap_index / 64;
87 let bit_position = bitmap_index % 64;
88
89 self.claimed_epochs[u64_index] &= !(1u64 << bit_position);
90 }
91
92 /// Get the number of claimed epochs in the bitmap
93 ///
94 /// ## Returns
95 /// - The total number of claimed epochs tracked in the bitmap
96 pub fn claimed_epoch_count(&self) -> u32 {
97 self.claimed_epochs
98 .iter()
99 .map(|&u64_val| u64_val.count_ones())
100 .sum()
101 }
102
103 /// Check if the bitmap is empty (no epochs claimed)
104 ///
105 /// ## Returns
106 /// - `true` if no epochs are claimed, `false` otherwise
107 pub fn is_empty(&self) -> bool {
108 self.claimed_epochs.iter().all(|&u64_val| u64_val == 0)
109 }
110}