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    /// Returns effective mining power (0 if fuel depleted). Call on claim before computing claimable.
161    pub fn apply_fuel_consumption(&mut self, clock: &Clock) -> u64 {
162        let blocks_elapsed = clock.slot.saturating_sub(self.last_fuel_update_block);
163        if blocks_elapsed == 0 {
164            return if self.current_fuel > 0 {
165                self.total_mining_power
166            } else {
167                0
168            };
169        }
170        let total_rate = self.buffer_a; // total_fuel_consumption_rate
171        let total_consumption = (total_rate as u128)
172            .saturating_mul(blocks_elapsed as u128)
173            .checked_div(crate::consts::ONE_OIL as u128)
174            .unwrap_or(0) as u64;
175        self.current_fuel = self.current_fuel.saturating_sub(total_consumption);
176        self.last_fuel_update_block = clock.slot;
177        if self.current_fuel > 0 {
178            self.total_mining_power
179        } else {
180            0
181        }
182    }
183
184    /// Update fuel consumption based on staked rig consumption rates (alternative when rig list available)
185    pub fn update_fuel_consumption(
186        &mut self,
187        staked_rig_consumption_rates: &[u64],
188        clock: &Clock,
189    ) -> u64 {
190        let blocks_elapsed = clock.slot.saturating_sub(self.last_fuel_update_block);
191        if blocks_elapsed == 0 {
192            return if self.current_fuel > 0 {
193                self.total_mining_power
194            } else {
195                0
196            };
197        }
198        let mut total_consumption = 0u64;
199        for &consumption_rate in staked_rig_consumption_rates {
200            let rig_consumption = (consumption_rate as u128)
201                .checked_mul(blocks_elapsed as u128)
202                .and_then(|x| x.checked_div(crate::consts::ONE_OIL as u128))
203                .unwrap_or(0) as u64;
204            total_consumption = total_consumption.saturating_add(rig_consumption);
205        }
206        self.current_fuel = self.current_fuel.saturating_sub(total_consumption);
207        self.last_fuel_update_block = clock.slot;
208        if self.current_fuel > 0 {
209            self.total_mining_power
210        } else {
211            0
212        }
213    }
214}
215
216account!(OilAccount, Plot);