ore_api/state/
round.rs

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