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    /// Buffer field (for future extensions)
54    pub buffer_a: u64,
55
56    /// Buffer field (for future extensions)
57    pub buffer_b: u64,
58}
59
60impl Refinery {
61    pub fn pda() -> (Pubkey, u8) {
62        refinery_pda()
63    }
64
65    pub fn initialize(&mut self, admin: Pubkey, clock: &Clock, initial_emission: u64) {
66        self.total_network_mining_power = 0;
67        self.emission_per_block = initial_emission; // In atomic units (e.g., 7,100,000,000 for 0.071 OIL/block)
68        self.last_emission_update_block = clock.slot;
69        self.rig_supplies = [u64::MAX; 15]; // u64::MAX = unlimited
70        self.rig_current = [0; 15];
71        // rig_configs should be set by caller with initial values
72        self.admin = admin;
73        self.refinery_rewards_factor = Numeric::ZERO;
74        self.last_rewards_factor_block = clock.slot;
75        self.buffer_a = 0;
76        self.buffer_b = 0;
77    }
78
79    /// Update emission rate based on days elapsed (growth/decreasing phase)
80    pub fn update_emission_rate(&mut self, clock: &Clock, blocks_per_day: u64) {
81        let initialization_block = self.last_emission_update_block;
82        let blocks_elapsed = clock.slot.saturating_sub(initialization_block);
83        let days_elapsed = blocks_elapsed / blocks_per_day;
84
85        // Growth phase (Day 1-7): Linear interpolation from 0.071 → 0.179
86        if days_elapsed <= 7 {
87            let start_emission = 7_100_000_000u64; // 0.071 OIL/block in atomic units
88            let peak_emission = 17_900_000_000u64; // 0.179 OIL/block in atomic units
89            
90            if days_elapsed == 0 {
91                self.emission_per_block = start_emission;
92            } else if days_elapsed >= 7 {
93                self.emission_per_block = peak_emission;
94            } else {
95                // Linear interpolation: start + (peak - start) × (days - 1) / 6
96                let days_progress = (days_elapsed - 1) as u128;
97                let emission_range = (peak_emission - start_emission) as u128;
98                let interpolated = start_emission as u128
99                    + (emission_range * days_progress) / 6;
100                self.emission_per_block = interpolated as u64;
101            }
102            self.last_emission_update_block = clock.slot;
103            return;
104        }
105
106        // Peak phase (Day 8-14): Maintain peak
107        if days_elapsed <= 14 {
108            self.emission_per_block = 17_900_000_000u64; // 0.179 OIL/block
109            self.last_emission_update_block = clock.slot;
110            return;
111        }
112
113        // Decreasing phase (Day 15+): Decrease by 2% per day
114        // Check if 24 hours (in blocks) have passed since last update
115        let blocks_since_update = clock.slot.saturating_sub(self.last_emission_update_block);
116        let blocks_per_day = blocks_per_day;
117        
118        if blocks_since_update >= blocks_per_day {
119            // Apply 2% reduction
120            let min_emission = 3_600_000_000u64; // 0.036 OIL/block minimum
121            let new_emission = (self.emission_per_block as u128 * 98) / 100; // Multiply by 0.98
122            self.emission_per_block = new_emission.max(min_emission as u128) as u64;
123            self.last_emission_update_block = clock.slot;
124        }
125    }
126
127    /// Check if rig can be minted (supply limit check)
128    pub fn can_mint_rig(&self, rig_type: u64) -> bool {
129        let idx = rig_type as usize;
130        if idx >= 15 {
131            return false;
132        }
133        
134        let max_supply = self.rig_supplies[idx];
135        if max_supply == u64::MAX {
136            true // Unlimited supply
137        } else {
138            self.rig_current[idx] < max_supply
139        }
140    }
141
142    /// Increment rig supply after minting
143    pub fn increment_rig_supply(&mut self, rig_type: u64) {
144        let idx = rig_type as usize;
145        if idx < 15 {
146            self.rig_current[idx] = self.rig_current[idx].saturating_add(1);
147        }
148    }
149
150    /// Update mining power when rig is placed/removed
151    pub fn update_mining_power(&mut self, mining_power_change: i64) {
152        if mining_power_change > 0 {
153            self.total_network_mining_power = self.total_network_mining_power
154                .saturating_add(mining_power_change as u64);
155        } else {
156            self.total_network_mining_power = self.total_network_mining_power
157                .saturating_sub((-mining_power_change) as u64);
158        }
159    }
160
161    /// Advance refinery_rewards_factor up to current block (call before claim or when reading claimable).
162    /// Claimable for a plot = plot.total_mining_power * (refinery.rewards_factor - plot.last_refinery_rewards_factor).
163    pub fn advance_rewards_factor(&mut self, clock: &Clock) {
164        let blocks = clock.slot.saturating_sub(self.last_rewards_factor_block);
165        if self.total_network_mining_power > 0 && blocks > 0 {
166            let total_emission = self.emission_per_block.saturating_mul(blocks);
167            self.refinery_rewards_factor +=
168                Numeric::from_fraction(total_emission, self.total_network_mining_power);
169        }
170        self.last_rewards_factor_block = clock.slot;
171    }
172}
173
174account!(OilAccount, Refinery);