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