1use serde::{Deserialize, Serialize};
2use steel::*;
3
4use crate::state::round_pda;
5
6use super::OilAccount;
7
8#[repr(C)]
9#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize, Default)]
10pub struct Round {
11 pub id: u64,
13
14 pub deployed: [u64; 25],
16
17 pub slot_hash: [u8; 32],
19
20 pub count: [u64; 25],
22
23 pub expires_at: u64,
25
26 pub rent_payer: Pubkey,
28
29 pub top_miner: Pubkey,
31
32 pub top_miner_reward: u64,
34
35 pub total_deployed: u64,
37
38 pub total_miners: u64,
40
41 pub total_vaulted: u64,
43
44 pub total_winnings: u64,
46
47 pub gusher_sol: u64,
49
50 pub deployed_pooled: [u64; 25],
55
56 pub total_pooled: u64,
58
59 pub pool_rewards_sol: u64,
62
63 pub pool_rewards_oil: u64,
66
67 pub pool_members: u64,
69
70 pub pool_cumulative: [u64; 25],
74}
75
76impl Round {
77 pub fn pda(&self) -> (Pubkey, u8) {
78 round_pda(self.id)
79 }
80
81 pub fn rng(&self) -> Option<u64> {
82 if self.slot_hash == [0; 32] || self.slot_hash == [u8::MAX; 32] {
83 return None;
84 }
85 let r1 = u64::from_le_bytes(self.slot_hash[0..8].try_into().unwrap());
86 let r2 = u64::from_le_bytes(self.slot_hash[8..16].try_into().unwrap());
87 let r3 = u64::from_le_bytes(self.slot_hash[16..24].try_into().unwrap());
88 let r4 = u64::from_le_bytes(self.slot_hash[24..32].try_into().unwrap());
89 let r = r1 ^ r2 ^ r3 ^ r4;
90 Some(r)
91 }
92
93 pub fn winning_square(&self, rng: u64) -> usize {
94 (rng % 25) as usize
95 }
96
97 pub fn top_miner_sample(&self, rng: u64, winning_square: usize) -> u64 {
98 if self.deployed[winning_square] == 0 {
99 return 0;
100 }
101 rng.reverse_bits() % self.deployed[winning_square]
102 }
103
104 pub fn calculate_total_winnings(&self, winning_square: usize) -> u64 {
105 let mut total_winnings = 0;
106 for (i, &deployed) in self.deployed.iter().enumerate() {
107 if i != winning_square {
108 total_winnings += deployed;
109 }
110 }
111 total_winnings
112 }
113
114 pub fn is_split_reward(&self, rng: u64) -> bool {
115 let rng = rng.reverse_bits().to_le_bytes();
117 let r1 = u16::from_le_bytes(rng[0..2].try_into().unwrap());
118 let r2 = u16::from_le_bytes(rng[2..4].try_into().unwrap());
119 let r3 = u16::from_le_bytes(rng[4..6].try_into().unwrap());
120 let r4 = u16::from_le_bytes(rng[6..8].try_into().unwrap());
121 let r = r1 ^ r2 ^ r3 ^ r4;
122 r % 2 == 0
123 }
124
125 pub fn did_hit_gusher_sol_only(&self, rng: u64) -> bool {
126 rng.reverse_bits() % 325 == 0
127 }
128}
129
130account!(OilAccount, Round);
131
132#[cfg(test)]
133mod tests {
134 use solana_program::rent::Rent;
135
136 use super::*;
137
138 #[test]
139 fn test_rent() {
140 let size_of_round = 8 + std::mem::size_of::<Round>();
141 let required_rent = Rent::default().minimum_balance(size_of_round);
142 println!("Round account size: {} bytes", size_of_round);
143 println!("Required rent: {} lamports ({} SOL)", required_rent, required_rent as f64 / 1_000_000_000.0);
144
145 assert_eq!(size_of_round, 1008, "Round account size should be 1008 bytes (v4 with pool_cumulative, no padding)");
148
149 assert!(required_rent > 0, "Required rent should be greater than 0");
151 assert!(required_rent < 10_000_000_000, "Required rent should be less than 10 SOL");
152 }
153}