oil_api/state/
well.rs

1use serde::{Deserialize, Serialize};
2use steel::*;
3
4use crate::state::well_pda;
5
6use super::{OilAccount, Auction};
7
8/// Well account (one per well)
9/// PDA: [WELL, well_id]
10/// Tracks current auction state for a well
11#[repr(C)]
12#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
13pub struct Well {
14    /// Well ID (0-3) - which well this is for
15    pub well_id: u64,
16    
17    /// Current epoch ID (increments each auction: 0, 1, 2, 3, etc.)
18    /// Starts at 0, increments when bid happens
19    pub epoch_id: u64,
20    
21    /// Current bidder/owner (Pubkey::default() if unowned)
22    pub current_bidder: Pubkey,
23    
24    /// Initial price for current epoch (in lamports)
25    /// Doubles from current price when bid happens
26    pub init_price: u64,
27    
28    /// Mining per second (MPS) - current mining rate (OIL per second, in atomic units)
29    /// This is the base rate adjusted for halvings
30    pub mps: u64,
31    
32    /// Epoch start time (timestamp when current epoch started)
33    pub epoch_start_time: u64,
34    
35    /// Accumulated OIL mined by current owner (not yet claimed)
36    pub accumulated_oil: u64,
37    
38    /// Last time accumulated_oil was updated
39    pub last_update_time: u64,
40    
41    /// Number of halvings that have occurred (for rate calculation)
42    pub halving_count: u64,
43    
44    /// Total OIL ever mined from this well (lifetime)
45    pub lifetime_oil_mined: u64,
46    
47    /// Total OIL mined by current operator (doesn't reset when claimed, only when ownership changes)
48    /// Repurposed from buffer_a
49    pub operator_total_oil_mined: u64,
50    
51    /// Last epoch ID that was synced (similar to checkpoint_id in Driller)
52    /// Used to enforce sync_auction_state before set_bid (like checkpoint before deploy)
53    /// Repurposed from buffer_b
54    pub last_synced_epoch_id: u64,
55    
56    /// Buffer field (for future use) - previously is_pool_owned
57    pub buffer_c: u64,
58    
59    /// Buffer field (for future use)
60    pub buffer_d: u64,
61    
62    /// Buffer field (for future use)
63    pub buffer_e: u64,
64}
65
66impl Well {
67    pub fn pda(well_id: u64) -> (Pubkey, u8) {
68        well_pda(well_id)
69    }
70
71    /// Calculate current price for this epoch (Dutch auction - price decreases over time)
72    pub fn current_price(&self, auction: &Auction, clock: &Clock) -> u64 {
73        // If well has no owner (never been bid on), show starting price
74        use solana_program::pubkey::Pubkey;
75        if self.current_bidder == Pubkey::default() {
76            return self.init_price; // Return starting price for unowned wells
77        }
78        
79        let elapsed = clock.unix_timestamp.saturating_sub(self.epoch_start_time as i64);
80        let duration = auction.auction_duration_seconds as i64;
81        
82        if elapsed >= duration {
83            return 0; // Auction expired, free to claim
84        }
85        
86        // Linear decay: price = init_price * (1 - elapsed / duration)
87        // Applies to both solo-owned and pool-owned wells
88        let remaining = duration - elapsed;
89        (self.init_price as u128 * remaining as u128 / duration as u128) as u64
90    }
91
92    /// Update accumulated OIL for this epoch state
93    pub fn update_accumulated_oil(&mut self, clock: &Clock) {
94        // Skip if no owner
95        use solana_program::pubkey::Pubkey;
96        if self.current_bidder == Pubkey::default() {
97            return;
98        }
99        
100        let last_update = self.last_update_time as i64;
101        let elapsed = clock.unix_timestamp.saturating_sub(last_update);
102        if elapsed <= 0 {
103            return;
104        }
105        
106        // Calculate OIL mined: rate * time
107        let oil_mined = self.mps
108            .checked_mul(elapsed as u64)
109            .unwrap_or(0);
110        
111        self.accumulated_oil = self.accumulated_oil
112            .checked_add(oil_mined)
113            .unwrap_or(u64::MAX);
114        
115        self.lifetime_oil_mined = self.lifetime_oil_mined
116            .checked_add(oil_mined)
117            .unwrap_or(u64::MAX);
118        
119        // Track total mined by current operator (persists even after claiming)
120        self.operator_total_oil_mined = self.operator_total_oil_mined
121            .checked_add(oil_mined)
122            .unwrap_or(u64::MAX);
123        
124        self.last_update_time = clock.unix_timestamp as u64;
125    }
126
127    /// Check and apply halving if needed, updating mining rate
128    /// Time-based halving: halvings occur every 28 days (halving_period_seconds)
129    /// Uses 50% reduction (multiply by 0.5) per halving, matching Macaron's model
130    pub fn check_and_apply_halving(&mut self, auction: &mut Auction, clock: &Clock) {
131        // Check if we should apply halvings based on current time
132        let current_time = clock.unix_timestamp as u64;
133        let halvings_to_apply = auction.should_apply_halving(current_time);
134        
135        if halvings_to_apply > 0 {
136            // Apply halvings: 50% reduction (multiply by 0.5) per halving
137            // Formula: new_rate = old_rate * 0.5 = old_rate / 2
138            for _ in 0..halvings_to_apply {
139                self.mps = self.mps / 2; // 50% reduction (multiply by 0.5)
140                self.halving_count += 1;
141            }
142            
143            // Update auction last_halving_time to current time
144            // This ensures we don't apply the same halving multiple times
145            auction.last_halving_time = current_time;
146        }
147    }
148}
149
150account!(OilAccount, Well);
151