ore_api/state/
round.rs

1use steel::*;
2
3use crate::state::round_pda;
4
5use super::OreAccount;
6
7#[repr(C)]
8#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
9pub struct Round {
10    /// The round number.
11    pub id: u64,
12
13    /// The amount of SOL deployed in each square.
14    pub deployed: [u64; 25],
15
16    /// The hash of the end slot, provided by solana, used for random number generation.
17    pub slot_hash: [u8; 32],
18
19    /// The count of miners on each square.
20    pub count: [u64; 25],
21
22    /// The slot at which claims for this round account end.
23    pub expires_at: u64,
24
25    /// The amount of ORE in the motherlode.
26    pub motherlode: u64,
27
28    /// The account to which rent should be returned when this account is closed.
29    pub rent_payer: Pubkey,
30
31    /// The top miner of the round.
32    pub top_miner: Pubkey,
33
34    /// The amount of ORE to distribute to the top miner.
35    pub top_miner_reward: u64,
36
37    /// The total amount of SOL deployed in the round.
38    pub total_deployed: u64,
39
40    /// The total amount of SOL put in the ORE vault.
41    pub total_vaulted: u64,
42
43    /// The total amount of SOL won by miners for the round.
44    pub total_winnings: u64,
45}
46
47impl Round {
48    pub fn pda(&self) -> (Pubkey, u8) {
49        round_pda(self.id)
50    }
51
52    pub fn rng(&self) -> Option<u64> {
53        if self.slot_hash == [0; 32] || self.slot_hash == [u8::MAX; 32] {
54            return None;
55        }
56        let r1 = u64::from_le_bytes(self.slot_hash[0..8].try_into().unwrap());
57        let r2 = u64::from_le_bytes(self.slot_hash[8..16].try_into().unwrap());
58        let r3 = u64::from_le_bytes(self.slot_hash[16..24].try_into().unwrap());
59        let r4 = u64::from_le_bytes(self.slot_hash[24..32].try_into().unwrap());
60        let r = r1 ^ r2 ^ r3 ^ r4;
61        Some(r)
62    }
63
64    pub fn winning_square(&self, rng: u64) -> usize {
65        (rng % 25) as usize
66    }
67
68    pub fn top_miner_sample(&self, rng: u64, winning_square: usize) -> u64 {
69        if self.deployed[winning_square] == 0 {
70            return 0;
71        }
72        rng.reverse_bits() % self.deployed[winning_square]
73    }
74
75    pub fn calculate_total_winnings(&self, winning_square: usize) -> u64 {
76        let mut total_winnings = 0;
77        for (i, &deployed) in self.deployed.iter().enumerate() {
78            if i != winning_square {
79                total_winnings += deployed;
80            }
81        }
82        total_winnings
83    }
84
85    pub fn is_split_reward(&self, rng: u64) -> bool {
86        // One out of four rounds get split rewards.
87        let rng = rng.reverse_bits().to_le_bytes();
88        let r1 = u16::from_le_bytes(rng[0..2].try_into().unwrap());
89        let r2 = u16::from_le_bytes(rng[2..4].try_into().unwrap());
90        let r3 = u16::from_le_bytes(rng[4..6].try_into().unwrap());
91        let r4 = u16::from_le_bytes(rng[6..8].try_into().unwrap());
92        let r = r1 ^ r2 ^ r3 ^ r4;
93        r % 2 == 0
94    }
95
96    pub fn did_hit_motherlode(&self, rng: u64) -> bool {
97        rng.reverse_bits() % 625 == 0
98    }
99}
100
101account!(OreAccount, Round);
102
103#[cfg(test)]
104mod tests {
105    use solana_program::rent::Rent;
106
107    use super::*;
108
109    #[test]
110    fn test_rent() {
111        let size_of_round = 8 + std::mem::size_of::<Round>();
112        let required_rent = Rent::default().minimum_balance(size_of_round);
113        println!("required_rent: {}", required_rent);
114        assert!(false);
115    }
116}