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    /// TODO: Rename to sol.
59    pub deployed: [u64; 25],
60
61    /// The amount of mass deployed in each square.
62    pub mass: [u64; 25],
63
64    /// The number of unique miners on each square.
65    /// TODO rename to miners.
66    pub count: [u64; 25],
67
68    /// The entropy value.
69    /// TODO: Rename to entropy.
70    pub slot_hash: [u8; 32],
71
72    /// The slot after which this account may be closed.
73    /// TODO: Rename to closes_at.
74    pub expires_at: u64,
75
76    /// The amount of ORE distributed as the motherlode reward.
77    pub motherlode: u64,
78
79    /// The account to which rent should be returned to when this account is closed.
80    pub rent_payer: Pubkey,
81
82    /// The amount of ORE to distribute to miners.
83    pub rewards: [u64; 25],
84
85    /// The total SOL collected by the protocol.
86    /// TODO: Rename to protocol_fee.
87    pub total_vaulted: u64,
88
89    /// The total SOL returned to miners.
90    /// TODO: Rename to total_returned.
91    pub total_winnings: u64,
92
93    /// The total number of unique miners that played in the round.
94    /// TODO rename to unique_miners.
95    pub total_miners: u64,
96
97    /// The winner of the solo reward.
98    /// TODO: Rename to winner.
99    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        // One out of four rounds get split rewards.
142        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        // One out of four rounds get split rewards.
196        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}