1use serde::{Deserialize, Serialize};
2use steel::*;
3
4use crate::state::{round_pda, OreAccountV4};
5
6use super::OreAccountV1;
7
8#[repr(C)]
9#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
10pub struct RoundV1 {
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 motherlode: u64,
28
29 pub rent_payer: Pubkey,
31
32 pub top_miner: Pubkey,
34
35 pub top_miner_reward: u64,
37
38 pub total_deployed: u64,
40
41 pub total_miners: u64,
43
44 pub total_vaulted: u64,
46
47 pub total_winnings: u64,
49}
50
51#[repr(C)]
52#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
53pub struct RoundV4 {
54 pub id: u64,
56
57 pub deployed: [u64; 25],
60
61 pub mass: [u64; 25],
63
64 pub count: [u64; 25],
67
68 pub slot_hash: [u8; 32],
71
72 pub expires_at: u64,
75
76 pub motherlode: u64,
78
79 pub rent_payer: Pubkey,
81
82 pub rewards: [u64; 25],
84
85 pub total_vaulted: u64,
88
89 pub total_winnings: u64,
92
93 pub total_miners: u64,
96
97 pub top_miner: Pubkey,
100}
101
102impl RoundV1 {
103 pub fn pda(&self) -> (Pubkey, u8) {
104 round_pda(self.id)
105 }
106
107 pub fn rng(&self) -> Option<u64> {
108 if self.slot_hash == [0; 32] || self.slot_hash == [u8::MAX; 32] {
109 return None;
110 }
111 let r1 = u64::from_le_bytes(self.slot_hash[0..8].try_into().unwrap());
112 let r2 = u64::from_le_bytes(self.slot_hash[8..16].try_into().unwrap());
113 let r3 = u64::from_le_bytes(self.slot_hash[16..24].try_into().unwrap());
114 let r4 = u64::from_le_bytes(self.slot_hash[24..32].try_into().unwrap());
115 let r = r1 ^ r2 ^ r3 ^ r4;
116 Some(r)
117 }
118
119 pub fn winning_square(&self, rng: u64) -> usize {
120 (rng % 25) as usize
121 }
122
123 pub fn top_miner_sample(&self, rng: u64, winning_square: usize) -> u64 {
124 if self.deployed[winning_square] == 0 {
125 return 0;
126 }
127 rng.reverse_bits() % self.deployed[winning_square]
128 }
129
130 pub fn calculate_total_winnings(&self, winning_square: usize) -> u64 {
131 let mut total_winnings = 0;
132 for (i, &deployed) in self.deployed.iter().enumerate() {
133 if i != winning_square {
134 total_winnings += deployed;
135 }
136 }
137 total_winnings
138 }
139
140 pub fn is_split_reward(&self, rng: u64) -> bool {
141 let rng = rng.reverse_bits().to_le_bytes();
143 let r1 = u16::from_le_bytes(rng[0..2].try_into().unwrap());
144 let r2 = u16::from_le_bytes(rng[2..4].try_into().unwrap());
145 let r3 = u16::from_le_bytes(rng[4..6].try_into().unwrap());
146 let r4 = u16::from_le_bytes(rng[6..8].try_into().unwrap());
147 let r = r1 ^ r2 ^ r3 ^ r4;
148 r % 2 == 0
149 }
150
151 pub fn did_hit_motherlode(&self, rng: u64) -> bool {
152 rng.reverse_bits() % 625 == 0
153 }
154}
155
156impl RoundV4 {
157 pub fn pda(&self) -> (Pubkey, u8) {
158 round_pda(self.id)
159 }
160
161 pub fn rng(&self) -> Option<u64> {
162 if self.slot_hash == [0; 32] || self.slot_hash == [u8::MAX; 32] {
163 return None;
164 }
165 let r1 = u64::from_le_bytes(self.slot_hash[0..8].try_into().unwrap());
166 let r2 = u64::from_le_bytes(self.slot_hash[8..16].try_into().unwrap());
167 let r3 = u64::from_le_bytes(self.slot_hash[16..24].try_into().unwrap());
168 let r4 = u64::from_le_bytes(self.slot_hash[24..32].try_into().unwrap());
169 let r = r1 ^ r2 ^ r3 ^ r4;
170 Some(r)
171 }
172
173 pub fn winning_square(&self, rng: u64) -> usize {
174 (rng % 25) as usize
175 }
176
177 pub fn top_miner_sample(&self, rng: u64, winning_square: usize) -> u64 {
178 if self.deployed[winning_square] == 0 {
179 return 0;
180 }
181 rng.reverse_bits() % self.deployed[winning_square]
182 }
183
184 pub fn calculate_total_winnings(&self, winning_square: usize) -> u64 {
185 let mut total_winnings = 0;
186 for (i, &deployed) in self.deployed.iter().enumerate() {
187 if i != winning_square {
188 total_winnings += deployed;
189 }
190 }
191 total_winnings
192 }
193
194 pub fn is_split_reward(&self, rng: u64) -> bool {
195 let rng = rng.reverse_bits().to_le_bytes();
197 let r1 = u16::from_le_bytes(rng[0..2].try_into().unwrap());
198 let r2 = u16::from_le_bytes(rng[2..4].try_into().unwrap());
199 let r3 = u16::from_le_bytes(rng[4..6].try_into().unwrap());
200 let r4 = u16::from_le_bytes(rng[6..8].try_into().unwrap());
201 let r = r1 ^ r2 ^ r3 ^ r4;
202 r % 2 == 0
203 }
204
205 pub fn did_hit_motherlode(&self, rng: u64) -> bool {
206 rng.reverse_bits() % 625 == 0
207 }
208
209 pub fn total_deployed(&self) -> u64 {
210 self.deployed.iter().sum()
211 }
212
213 pub fn top_miner_reward(&self) -> u64 {
214 self.rewards.iter().sum()
215 }
216}
217
218account!(OreAccountV1, RoundV1);
219account!(OreAccountV4, RoundV4);
220
221#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
222pub enum Round {
223 V1(RoundV1),
224 V4(RoundV4),
225}
226
227impl Round {
228 pub fn id(&self) -> u64 {
229 match self {
230 Round::V1(r) => r.id,
231 Round::V4(r) => r.id,
232 }
233 }
234
235 pub fn deployed(&self) -> [u64; 25] {
236 match self {
237 Round::V1(r) => r.deployed,
238 Round::V4(r) => r.deployed,
239 }
240 }
241
242 pub fn slot_hash(&self) -> [u8; 32] {
243 match self {
244 Round::V1(r) => r.slot_hash,
245 Round::V4(r) => r.slot_hash,
246 }
247 }
248
249 pub fn count(&self) -> [u64; 25] {
250 match self {
251 Round::V1(r) => r.count,
252 Round::V4(r) => r.count,
253 }
254 }
255
256 pub fn expires_at(&self) -> u64 {
257 match self {
258 Round::V1(r) => r.expires_at,
259 Round::V4(r) => r.expires_at,
260 }
261 }
262
263 pub fn motherlode(&self) -> u64 {
264 match self {
265 Round::V1(r) => r.motherlode,
266 Round::V4(r) => r.motherlode,
267 }
268 }
269
270 pub fn rent_payer(&self) -> Pubkey {
271 match self {
272 Round::V1(r) => r.rent_payer,
273 Round::V4(r) => r.rent_payer,
274 }
275 }
276
277 pub fn top_miner(&self) -> Pubkey {
278 match self {
279 Round::V1(r) => r.top_miner,
280 Round::V4(r) => r.top_miner,
281 }
282 }
283
284 pub fn top_miner_reward(&self) -> u64 {
285 match self {
286 Round::V1(r) => r.top_miner_reward,
287 Round::V4(r) => r.rewards.iter().sum(),
288 }
289 }
290
291 pub fn total_deployed(&self) -> u64 {
292 match self {
293 Round::V1(r) => r.total_deployed,
294 Round::V4(r) => r.deployed.iter().sum(),
295 }
296 }
297
298 pub fn total_miners(&self) -> u64 {
299 match self {
300 Round::V1(r) => r.total_miners,
301 Round::V4(r) => r.total_miners,
302 }
303 }
304
305 pub fn total_vaulted(&self) -> u64 {
306 match self {
307 Round::V1(r) => r.total_vaulted,
308 Round::V4(r) => r.total_vaulted,
309 }
310 }
311
312 pub fn total_winnings(&self) -> u64 {
313 match self {
314 Round::V1(r) => r.total_winnings,
315 Round::V4(r) => r.total_winnings,
316 }
317 }
318
319 pub fn pda(&self) -> (Pubkey, u8) {
320 match self {
321 Round::V1(r) => r.pda(),
322 Round::V4(r) => r.pda(),
323 }
324 }
325
326 pub fn rng(&self) -> Option<u64> {
327 match self {
328 Round::V1(r) => r.rng(),
329 Round::V4(r) => r.rng(),
330 }
331 }
332
333 pub fn winning_square(&self, rng: u64) -> usize {
334 match self {
335 Round::V1(r) => r.winning_square(rng),
336 Round::V4(r) => r.winning_square(rng),
337 }
338 }
339
340 pub fn top_miner_sample(&self, rng: u64, winning_square: usize) -> u64 {
341 match self {
342 Round::V1(r) => r.top_miner_sample(rng, winning_square),
343 Round::V4(r) => r.top_miner_sample(rng, winning_square),
344 }
345 }
346
347 pub fn calculate_total_winnings(&self, winning_square: usize) -> u64 {
348 match self {
349 Round::V1(r) => r.calculate_total_winnings(winning_square),
350 Round::V4(r) => r.calculate_total_winnings(winning_square),
351 }
352 }
353
354 pub fn is_split_reward(&self, rng: u64) -> bool {
355 match self {
356 Round::V1(r) => r.is_split_reward(rng),
357 Round::V4(r) => r.is_split_reward(rng),
358 }
359 }
360
361 pub fn did_hit_motherlode(&self, rng: u64) -> bool {
362 match self {
363 Round::V1(r) => r.did_hit_motherlode(rng),
364 Round::V4(r) => r.did_hit_motherlode(rng),
365 }
366 }
367}