miracle-api 0.6.0

Miracle is a pay2e protocol for sovereign individuals living in Mirascape Horizon.
Documentation
use steel::*;

use super::MiracleAccount;

/// Proof accounts track a member's basic claim information.
/// Every member is allowed one proof account which is required by the program to claim rewards.
/// A member is usually mapped to a public wallet address.
///
/// Note: Double-spending prevention is handled by a bitmap-based epoch tracking system
/// combined with the Dual Merkle Tree system for unlimited claim history.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Proof {
    /// Bitmap tracking claimed epochs (512 bits = 8 u64s, covers ~1.4 years)
    /// Each bit represents whether an epoch has been claimed
    /// Circulation: epoch % 512 determines the bit position
    pub claimed_epochs: [u64; 8],

    /// The signer authorized to use this proof, usually member pubkey
    pub authority: Pubkey,

    /// Timestamp of the last claim (Unix timestamp)
    pub last_claim_at: i64,

    /// Epoch of the last claim (project-specific epoch number)
    pub last_claim_epoch: u64,

    /// Sum of all successful claims (lifetime total)
    pub total_claimed_rewards: u64,

    /// Number of successful claims
    pub claim_count: u32,

    /// Padding to ensure 8-byte alignment
    pub _padding: [u8; 4],
}

account!(MiracleAccount, Proof);

impl Proof {
    /// Check if a specific epoch has been claimed using bitmap tracking
    ///
    /// ## Parameters
    /// - `epoch`: The epoch number to check
    ///
    /// ## Returns
    /// - `true` if the epoch has been claimed, `false` otherwise
    ///
    /// ## Circulation Logic
    /// Uses modulo 512 to circulate the bitmap: epoch % 512 determines the bit position
    /// This provides a rolling 512-day window (~1.4 years) for claim tracking
    pub fn is_epoch_claimed(&self, epoch: u64) -> bool {
        let bitmap_index = (epoch % 512) as usize;
        let u64_index = bitmap_index / 64;
        let bit_position = bitmap_index % 64;

        (self.claimed_epochs[u64_index] & (1u64 << bit_position)) != 0
    }

    /// Mark a specific epoch as claimed in the bitmap
    ///
    /// ## Parameters
    /// - `epoch`: The epoch number to mark as claimed
    ///
    /// ## Circulation Logic
    /// Uses modulo 512 to circulate the bitmap: epoch % 512 determines the bit position
    /// This provides a rolling 512-day window (~1.4 years) for claim tracking
    pub fn mark_epoch_claimed(&mut self, epoch: u64) {
        let bitmap_index = (epoch % 512) as usize;
        let u64_index = bitmap_index / 64;
        let bit_position = bitmap_index % 64;

        self.claimed_epochs[u64_index] |= 1u64 << bit_position;
    }

    /// Clear a specific epoch claim in the bitmap (for testing/reset purposes)
    ///
    /// ## Parameters
    /// - `epoch`: The epoch number to clear
    ///
    /// ## Circulation Logic
    /// Uses modulo 512 to circulate the bitmap: epoch % 512 determines the bit position
    /// This provides a rolling 512-day window (~1.4 years) for claim tracking
    pub fn clear_epoch_claimed(&mut self, epoch: u64) {
        let bitmap_index = (epoch % 512) as usize;
        let u64_index = bitmap_index / 64;
        let bit_position = bitmap_index % 64;

        self.claimed_epochs[u64_index] &= !(1u64 << bit_position);
    }

    /// Get the number of claimed epochs in the bitmap
    ///
    /// ## Returns
    /// - The total number of claimed epochs tracked in the bitmap
    pub fn claimed_epoch_count(&self) -> u32 {
        self.claimed_epochs
            .iter()
            .map(|&u64_val| u64_val.count_ones())
            .sum()
    }

    /// Check if the bitmap is empty (no epochs claimed)
    ///
    /// ## Returns
    /// - `true` if no epochs are claimed, `false` otherwise
    pub fn is_empty(&self) -> bool {
        self.claimed_epochs.iter().all(|&u64_val| u64_val == 0)
    }
}