Skip to main content

oil_api/state/
plot.rs

1use serde::{Deserialize, Serialize};
2use steel::*;
3
4use crate::state::plot_pda;
5
6use super::OilAccount;
7
8/// Plot account (one per wallet/authority)
9#[repr(C)]
10#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
11pub struct Plot {
12    /// The authority (owner) of this plot account.
13    pub authority: Pubkey,
14
15    /// Plot level (0-9: Basic to Master)
16    pub plot_level: u64,
17
18    /// Maximum fuel capacity (tank size)
19    pub fuel_capacity: u64,
20
21    /// Current fuel remaining in tank
22    pub current_fuel: u64,
23
24    /// Maximum number of rig slots (1-5)
25    pub max_rig_slots: u64,
26
27    /// Total mining power from all staked rigs
28    pub total_mining_power: u64,
29
30    /// Last block when rewards were claimed
31    pub last_claim_block: u64,
32
33    /// Last block when fuel was updated (for consumption calculation)
34    pub last_fuel_update_block: u64,
35
36    /// Timestamp of last upgrade (for 24h cooldown)
37    pub last_upgrade_timestamp: i64,
38
39    /// Sum of fuel_requirement of all rigs currently placed on this plot (placement constraint)
40    pub total_fuel_requirement: u64,
41
42    /// Number of rigs currently placed on this plot (≤ max_rig_slots)
43    pub num_rigs_staked: u64,
44
45    /// Refinery rewards factor at this plot's last claim (for claimable = power * (current - this))
46    pub last_refinery_rewards_factor: Numeric,
47
48    /// Sum of fuel_consumption_rate of all staked rigs (used for fuel consumption on claim). Stored in buffer_a.
49    /// buffer_a: total_fuel_consumption_rate
50    pub buffer_a: u64,
51    /// Buffer for future extensions
52    pub buffer_b: u64,
53    /// Buffer for future extensions
54    pub buffer_c: u64,
55}
56
57impl Plot {
58    pub fn pda(&self) -> (Pubkey, u8) {
59        plot_pda(self.authority)
60    }
61
62    pub fn initialize(&mut self, authority: Pubkey, clock: &Clock) {
63        self.authority = authority;
64        self.plot_level = 0;
65        self.fuel_capacity = 20;
66        self.current_fuel = 20; // Start with full tank
67        self.max_rig_slots = 1;
68        self.total_mining_power = 0;
69        self.last_claim_block = clock.slot;
70        self.last_fuel_update_block = clock.slot;
71        self.last_upgrade_timestamp = 0; // No upgrade yet, cooldown doesn't apply to first upgrade
72        self.total_fuel_requirement = 0;
73        self.num_rigs_staked = 0;
74        self.last_refinery_rewards_factor = Numeric::ZERO;
75        self.buffer_a = 0; // total_fuel_consumption_rate
76        self.buffer_b = 0;
77        self.buffer_c = 0;
78    }
79
80    /// Check if plot can be upgraded (24h cooldown)
81    pub fn can_upgrade(&self, clock: &Clock) -> bool {
82        if self.last_upgrade_timestamp == 0 {
83            return true; // First upgrade, no cooldown
84        }
85        let elapsed = clock.unix_timestamp - self.last_upgrade_timestamp;
86        elapsed >= 86400 // 24 hours in seconds
87    }
88
89    /// Get upgrade cost for the next level (in atomic units, 11 decimals)
90    /// Returns None if already at max level (9)
91    pub fn get_upgrade_cost(&self) -> Option<u64> {
92        if self.plot_level >= 9 {
93            return None; // Already at max level
94        }
95        let next_level = self.plot_level + 1;
96        Some(Self::upgrade_cost_for_level(next_level))
97    }
98    
99    /// Get upgrade cost for a specific level (in atomic units, 11 decimals)
100    pub fn upgrade_cost_for_level(level: u64) -> u64 {
101        const UPGRADE_COSTS: [u64; 9] = [
102            210,      // Level 1: 210 OIL
103            1_125,    // Level 2: 1,125 OIL
104            888,      // Level 3: 888 OIL
105            1_065,    // Level 4: 1,065 OIL
106            1_275,    // Level 5: 1,275 OIL
107            2_550,    // Level 6: 2,550 OIL
108            5_000,    // Level 7: 5,000 OIL
109            10_000,   // Level 8: 10,000 OIL
110            20_000,   // Level 9: 20,000 OIL
111        ];
112        if level == 0 || level > 9 {
113            return 0;
114        }
115        UPGRADE_COSTS[(level - 1) as usize] * crate::consts::ONE_OIL
116    }
117    
118    /// Get level stats (slots, fuel_capacity) for a specific level
119    pub fn level_stats(level: u64) -> (u64, u64) {
120        const LEVEL_STATS: [(u64, u64); 10] = [
121            (1, 20),      // Level 0
122            (1, 50),      // Level 1
123            (2, 120),     // Level 2
124            (2, 300),     // Level 3
125            (3, 750),     // Level 4
126            (3, 1_800),   // Level 5
127            (4, 4_500),   // Level 6
128            (4, 11_000),  // Level 7
129            (5, 27_000),  // Level 8
130            (5, 65_000),  // Level 9
131        ];
132        if level > 9 {
133            return LEVEL_STATS[9];
134        }
135        LEVEL_STATS[level as usize]
136    }
137    
138    /// Upgrade plot to next level
139    /// Returns the upgrade cost if successful, None if already at max level
140    pub fn upgrade(&mut self, clock: &Clock) -> Option<u64> {
141        if !self.can_upgrade(clock) {
142            return None; // Cooldown not met
143        }
144        if self.plot_level >= 9 {
145            return None; // Already at max level
146        }
147        let new_level = self.plot_level + 1;
148        let (slots, fuel_capacity) = Self::level_stats(new_level);
149        let cost = Self::upgrade_cost_for_level(new_level);
150        
151        self.plot_level = new_level;
152        self.max_rig_slots = slots;
153        self.fuel_capacity = fuel_capacity;
154        self.last_upgrade_timestamp = clock.unix_timestamp;
155        
156        Some(cost)
157    }
158
159    /// Apply fuel consumption since last_fuel_update_block using buffer_a (total_fuel_consumption_rate).
160    /// Uses fuel-supported blocks: rewards accrue only for blocks where fuel was available;
161    /// fuel can deplete mid-interval so we cap at fuel_supported_blocks and consume proportionally.
162    /// Returns effective mining power for the period (0 if no fuel; else scaled by fuel_supported_blocks/blocks_elapsed).
163    pub fn apply_fuel_consumption(&mut self, clock: &Clock) -> u64 {
164        let blocks_elapsed = clock.slot.saturating_sub(self.last_fuel_update_block);
165        if blocks_elapsed == 0 {
166            return if self.current_fuel > 0 {
167                self.total_mining_power
168            } else {
169                0
170            };
171        }
172        let total_rate = self.buffer_a; // total_fuel_consumption_rate (atomic units per block)
173        if total_rate == 0 {
174            // No consumption; full power for full period
175            return self.total_mining_power;
176        }
177        // consumption_per_block in fuel units = total_rate / ONE_OIL
178        // fuel_supported_blocks = min(blocks_elapsed, current_fuel / consumption_per_block)
179        //   = min(blocks_elapsed, current_fuel * ONE_OIL / total_rate)
180        let fuel_supported_blocks = {
181            let max_fuel_blocks = (self.current_fuel as u128)
182                .saturating_mul(crate::consts::ONE_OIL as u128)
183                .checked_div(total_rate as u128)
184                .unwrap_or(u64::MAX as u128) as u64;
185            blocks_elapsed.min(max_fuel_blocks)
186        };
187        // consumed = consumption_per_block * fuel_supported_blocks = (total_rate * fuel_supported_blocks) / ONE_OIL
188        let consumed = (total_rate as u128)
189            .saturating_mul(fuel_supported_blocks as u128)
190            .checked_div(crate::consts::ONE_OIL as u128)
191            .unwrap_or(0) as u64;
192        self.current_fuel = self.current_fuel.saturating_sub(consumed);
193        self.last_fuel_update_block = self.last_fuel_update_block.saturating_add(fuel_supported_blocks);
194        // Rewards accrue only for fuel_supported_blocks; effective power = total_mining_power * (fuel_supported_blocks / blocks_elapsed)
195        if fuel_supported_blocks == 0 {
196            return 0;
197        }
198        ((self.total_mining_power as u128) * (fuel_supported_blocks as u128) / (blocks_elapsed as u128)) as u64
199    }
200
201    /// Update fuel consumption based on staked rig consumption rates (alternative when rig list available).
202    /// Uses same fuel-supported-blocks logic as apply_fuel_consumption.
203    pub fn update_fuel_consumption(
204        &mut self,
205        staked_rig_consumption_rates: &[u64],
206        clock: &Clock,
207    ) -> u64 {
208        let blocks_elapsed = clock.slot.saturating_sub(self.last_fuel_update_block);
209        if blocks_elapsed == 0 {
210            return if self.current_fuel > 0 {
211                self.total_mining_power
212            } else {
213                0
214            };
215        }
216        let total_rate: u64 = staked_rig_consumption_rates.iter().copied().sum();
217        if total_rate == 0 {
218            return self.total_mining_power;
219        }
220        let fuel_supported_blocks = {
221            let max_fuel_blocks = (self.current_fuel as u128)
222                .saturating_mul(crate::consts::ONE_OIL as u128)
223                .checked_div(total_rate as u128)
224                .unwrap_or(u64::MAX as u128) as u64;
225            blocks_elapsed.min(max_fuel_blocks)
226        };
227        let mut consumed = 0u64;
228        for &consumption_rate in staked_rig_consumption_rates {
229            let rig_consumption = (consumption_rate as u128)
230                .saturating_mul(fuel_supported_blocks as u128)
231                .checked_div(crate::consts::ONE_OIL as u128)
232                .unwrap_or(0) as u64;
233            consumed = consumed.saturating_add(rig_consumption);
234        }
235        self.current_fuel = self.current_fuel.saturating_sub(consumed);
236        self.last_fuel_update_block = self.last_fuel_update_block.saturating_add(fuel_supported_blocks);
237        if fuel_supported_blocks == 0 {
238            return 0;
239        }
240        ((self.total_mining_power as u128) * (fuel_supported_blocks as u128) / (blocks_elapsed as u128)) as u64
241    }
242}
243
244account!(OilAccount, Plot);