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}