Skip to main content

oil_api/state/
refinery.rs

1use serde::{Deserialize, Serialize};
2use steel::*;
3
4use crate::state::refinery_pda;
5
6use super::OilAccount;
7
8/// Rig configuration (stats for each rig type)
9#[repr(C)]
10#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
11pub struct RigConfig {
12    /// Mining power for this rig type
13    pub mining_power: u64,
14
15    /// Fuel requirement for placement
16    pub fuel_requirement: u64,
17
18    /// Fuel consumption rate per block (in atomic units with 11 decimals)
19    pub fuel_consumption_rate: u64,
20}
21
22/// Refinery global state (singleton account)
23#[repr(C)]
24#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
25pub struct Refinery {
26    /// Sum of all players' Mining Power
27    pub total_network_mining_power: u64,
28
29    /// Current emission rate per block (in atomic units with 11 decimals)
30    pub emission_per_block: u64,
31
32    /// Block when emission rate was last updated (tracks initialization time for day calculation)
33    pub last_emission_update_block: u64,
34
35    /// Max supply per rig type (u64::MAX = unlimited, otherwise the max supply)
36    pub rig_supplies: [u64; 15],
37
38    /// Current supply per rig type (number minted)
39    pub rig_current: [u64; 15],
40
41    /// Stats configuration for each rig type (0-14)
42    pub rig_configs: [RigConfig; 15],
43
44    /// Admin authority that can update rig_configs
45    pub admin: Pubkey,
46
47    /// Cumulative reward per unit of mining power (updated when advancing emission; used for claim math)
48    pub refinery_rewards_factor: Numeric,
49
50    /// Block through which refinery_rewards_factor has been applied (for lazy updates)
51    pub last_rewards_factor_block: u64,
52
53    /// Init timestamp (unix seconds) for emission schedule; set once in initialize.
54    pub init_timestamp: i64,
55    /// Last emission rate update timestamp (unix seconds); used for 24h decay and day calculation.
56    pub last_emission_update_timestamp: i64,
57
58    /// Reserved for future use (do not use for new features; add new named fields or more buffers).
59    pub reserved_a: u64,
60    /// Reserved for future use.
61    pub reserved_b: u64,
62    /// Buffer for future extensions.
63    pub buffer_a: u64,
64    /// Buffer for future extensions.
65    pub buffer_b: u64,
66    /// Buffer for future extensions.
67    pub buffer_c: u64,
68    /// Buffer for future extensions.
69    pub buffer_d: u64,
70}
71
72impl Refinery {
73    pub fn pda() -> (Pubkey, u8) {
74        refinery_pda()
75    }
76
77    pub fn initialize(&mut self, admin: Pubkey, clock: &Clock, initial_emission: u64) {
78        self.total_network_mining_power = 0;
79        self.emission_per_block = initial_emission; // In atomic units (e.g., 7,100,000,000 for 0.071 OIL/block)
80        self.last_emission_update_block = clock.slot;
81        self.rig_supplies = [u64::MAX; 15]; // u64::MAX = unlimited
82        self.rig_current = [0; 15];
83        // rig_configs should be set by caller with initial values
84        self.admin = admin;
85        self.refinery_rewards_factor = Numeric::ZERO;
86        self.last_rewards_factor_block = clock.slot;
87        self.init_timestamp = clock.unix_timestamp;
88        self.last_emission_update_timestamp = clock.unix_timestamp;
89        self.reserved_a = 0;
90        self.reserved_b = 0;
91        self.buffer_a = 0;
92        self.buffer_b = 0;
93        self.buffer_c = 0;
94        self.buffer_d = 0;
95    }
96
97    /// Update emission rate based on days elapsed (timestamp-based; robust to block rate drift).
98    /// Uses Clock::unix_timestamp and init_timestamp / last_emission_update_timestamp.
99    pub fn update_emission_rate(&mut self, clock: &Clock, _blocks_per_day: u64) {
100        const SECONDS_PER_DAY: i64 = 86400;
101        let now = clock.unix_timestamp;
102        let days_elapsed = (now - self.init_timestamp).max(0) / SECONDS_PER_DAY;
103
104        // Growth phase (Day 1-7): Linear interpolation from 0.071 → 0.179
105        if days_elapsed <= 7 {
106            let start_emission = 7_100_000_000u64; // 0.071 OIL/block in atomic units
107            let peak_emission = 17_900_000_000u64; // 0.179 OIL/block in atomic units
108            
109            if days_elapsed == 0 {
110                self.emission_per_block = start_emission;
111            } else if days_elapsed >= 7 {
112                self.emission_per_block = peak_emission;
113            } else {
114                let days_progress = (days_elapsed - 1) as u128;
115                let emission_range = (peak_emission - start_emission) as u128;
116                let interpolated = start_emission as u128
117                    + (emission_range * days_progress) / 6;
118                self.emission_per_block = interpolated as u64;
119            }
120            self.last_emission_update_block = clock.slot;
121            self.last_emission_update_timestamp = now;
122            return;
123        }
124
125        // Peak phase (Day 8-14): Maintain peak
126        if days_elapsed <= 14 {
127            self.emission_per_block = 17_900_000_000u64; // 0.179 OIL/block
128            self.last_emission_update_block = clock.slot;
129            self.last_emission_update_timestamp = now;
130            return;
131        }
132
133        // Decreasing phase (Day 15+): Decrease by 2% per 24 hours (timestamp-based)
134        let seconds_since_update = (now - self.last_emission_update_timestamp).max(0);
135        if seconds_since_update >= SECONDS_PER_DAY {
136            let min_emission = 3_600_000_000u64; // 0.036 OIL/block minimum
137            let new_emission = (self.emission_per_block as u128 * 98) / 100;
138            self.emission_per_block = new_emission.max(min_emission as u128) as u64;
139            self.last_emission_update_block = clock.slot;
140            self.last_emission_update_timestamp = now;
141        }
142    }
143
144    /// Check if rig can be minted (supply limit check)
145    pub fn can_mint_rig(&self, rig_type: u64) -> bool {
146        let idx = rig_type as usize;
147        if idx >= 15 {
148            return false;
149        }
150        
151        let max_supply = self.rig_supplies[idx];
152        if max_supply == u64::MAX {
153            true // Unlimited supply
154        } else {
155            self.rig_current[idx] < max_supply
156        }
157    }
158
159    /// Increment rig supply after minting
160    pub fn increment_rig_supply(&mut self, rig_type: u64) {
161        let idx = rig_type as usize;
162        if idx < 15 {
163            self.rig_current[idx] = self.rig_current[idx].saturating_add(1);
164        }
165    }
166
167    /// Update mining power when rig is placed/removed
168    pub fn update_mining_power(&mut self, mining_power_change: i64) {
169        if mining_power_change > 0 {
170            self.total_network_mining_power = self.total_network_mining_power
171                .saturating_add(mining_power_change as u64);
172        } else {
173            self.total_network_mining_power = self.total_network_mining_power
174                .saturating_sub((-mining_power_change) as u64);
175        }
176    }
177
178    /// Advance refinery_rewards_factor up to current block (call before claim or when reading claimable).
179    /// Claimable for a plot = plot.total_mining_power * (refinery.rewards_factor - plot.last_refinery_rewards_factor).
180    pub fn advance_rewards_factor(&mut self, clock: &Clock) {
181        let blocks = clock.slot.saturating_sub(self.last_rewards_factor_block);
182        if self.total_network_mining_power > 0 && blocks > 0 {
183            let total_emission = self.emission_per_block.saturating_mul(blocks);
184            self.refinery_rewards_factor +=
185                Numeric::from_fraction(total_emission, self.total_network_mining_power);
186        }
187        self.last_rewards_factor_block = clock.slot;
188    }
189}
190
191account!(OilAccount, Refinery);