Skip to main content

ore_api/state/
round.rs

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    /// 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 number of unique miners that played in the round.
42    pub total_miners: u64,
43
44    /// The total amount of SOL put in the ORE vault.
45    pub total_vaulted: u64,
46
47    /// The total amount of SOL won by miners for the round.
48    pub total_winnings: u64,
49}
50
51#[repr(C)]
52#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
53pub struct RoundV4 {
54    /// The round number.
55    pub id: u64,
56
57    /// The amount of SOL deployed in each square.
58    pub sol: [u64; 25],
59
60    /// The amount of mass deployed in each square.
61    pub mass: [u64; 25],
62
63    /// The number of unique miners on each square.
64    pub miners: [u64; 25],
65
66    /// The entropy value.
67    pub entropy: [u8; 32],
68
69    /// The slot after which this account may be closed.
70    pub closes_at: u64,
71
72    /// The amount of ORE distributed as the motherlode reward.
73    pub motherlode: u64,
74
75    /// The account to which rent should be returned to when this account is closed.
76    pub rent_payer: Pubkey,
77
78    /// The amount of ORE to distribute to miners.
79    pub rewards: [u64; 25],
80
81    /// The total SOL collected by the protocol.
82    pub protocol_fee: u64,
83
84    /// The total number of unique miners that played in the round.
85    pub unique_miners: u64,
86
87    /// The winner of the solo reward.
88    pub winner: Pubkey,
89}
90
91impl RoundV1 {
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        // One out of four rounds get split rewards.
131        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        // One out of four rounds get split rewards.
185        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!(OreAccountV1, RoundV1);
200account!(OreAccountV4, RoundV4);
201
202pub enum Round {
203    RoundV1(RoundV1),
204    RoundV4(RoundV4),
205}
206
207impl Round {
208    pub fn id(&self) -> u64 {
209        match self {
210            Round::RoundV1(r) => r.id,
211            Round::RoundV4(r) => r.id,
212        }
213    }
214
215    pub fn deployed(&self) -> [u64; 25] {
216        match self {
217            Round::RoundV1(r) => r.deployed,
218            Round::RoundV4(r) => r.sol,
219        }
220    }
221
222    pub fn slot_hash(&self) -> [u8; 32] {
223        match self {
224            Round::RoundV1(r) => r.slot_hash,
225            Round::RoundV4(r) => r.entropy,
226        }
227    }
228
229    pub fn count(&self) -> [u64; 25] {
230        match self {
231            Round::RoundV1(r) => r.count,
232            Round::RoundV4(r) => r.miners,
233        }
234    }
235
236    pub fn expires_at(&self) -> u64 {
237        match self {
238            Round::RoundV1(r) => r.expires_at,
239            Round::RoundV4(r) => r.closes_at,
240        }
241    }
242
243    pub fn motherlode(&self) -> u64 {
244        match self {
245            Round::RoundV1(r) => r.motherlode,
246            Round::RoundV4(r) => r.motherlode,
247        }
248    }
249
250    pub fn rent_payer(&self) -> Pubkey {
251        match self {
252            Round::RoundV1(r) => r.rent_payer,
253            Round::RoundV4(r) => r.rent_payer,
254        }
255    }
256
257    pub fn top_miner(&self) -> Pubkey {
258        match self {
259            Round::RoundV1(r) => r.top_miner,
260            Round::RoundV4(r) => r.winner,
261        }
262    }
263
264    pub fn top_miner_reward(&self) -> u64 {
265        match self {
266            Round::RoundV1(r) => r.top_miner_reward,
267            Round::RoundV4(_) => 0,
268        }
269    }
270
271    pub fn total_deployed(&self) -> u64 {
272        match self {
273            Round::RoundV1(r) => r.total_deployed,
274            Round::RoundV4(r) => r.sol.iter().sum(),
275        }
276    }
277
278    pub fn total_miners(&self) -> u64 {
279        match self {
280            Round::RoundV1(r) => r.total_miners,
281            Round::RoundV4(r) => r.unique_miners,
282        }
283    }
284
285    pub fn total_vaulted(&self) -> u64 {
286        match self {
287            Round::RoundV1(r) => r.total_vaulted,
288            Round::RoundV4(_) => 0,
289        }
290    }
291
292    pub fn total_winnings(&self) -> u64 {
293        match self {
294            Round::RoundV1(r) => r.total_winnings,
295            Round::RoundV4(_) => 0,
296        }
297    }
298
299    pub fn pda(&self) -> (Pubkey, u8) {
300        match self {
301            Round::RoundV1(r) => r.pda(),
302            Round::RoundV4(r) => r.pda(),
303        }
304    }
305
306    pub fn rng(&self) -> Option<u64> {
307        match self {
308            Round::RoundV1(r) => r.rng(),
309            Round::RoundV4(r) => r.rng(),
310        }
311    }
312
313    pub fn winning_square(&self, rng: u64) -> usize {
314        match self {
315            Round::RoundV1(r) => r.winning_square(rng),
316            Round::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            Round::RoundV1(r) => r.top_miner_sample(rng, winning_square),
323            Round::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            Round::RoundV1(r) => r.calculate_total_winnings(winning_square),
330            Round::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            Round::RoundV1(r) => r.is_split_reward(rng),
337            Round::RoundV4(r) => r.is_split_reward(rng),
338        }
339    }
340
341    pub fn did_hit_motherlode(&self, rng: u64) -> bool {
342        match self {
343            Round::RoundV1(r) => r.did_hit_motherlode(rng),
344            Round::RoundV4(r) => r.did_hit_motherlode(rng),
345        }
346    }
347}