Skip to main content

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#[repr(C)]
10#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
11pub struct Well {
12    /// Well ID (0-3) - which well this is for
13    pub well_id: u64,
14    
15    /// Current epoch ID (increments each auction: 0, 1, 2, 3, etc.)
16    pub epoch_id: u64,
17    
18    /// Current bidder/owner (Pubkey::default() if unowned)
19    pub current_bidder: Pubkey,
20    
21    /// Initial price for current epoch (in lamports)
22    pub init_price: u64,
23    
24    /// Mining per second (MPS) - current mining rate (OIL per second, in atomic units)
25    pub mps: u64,
26    
27    /// Epoch start time (timestamp when current epoch started)
28    pub epoch_start_time: u64,
29    
30    /// Accumulated OIL mined by current owner (not yet claimed)
31    pub accumulated_oil: u64,
32    
33    /// Last time accumulated_oil was updated
34    pub last_update_time: u64,
35    
36    /// Number of halvings that have occurred (for rate calculation)
37    pub halving_count: u64,
38    
39    /// Total OIL ever mined from this well (lifetime)
40    pub lifetime_oil_mined: u64,
41    
42    /// Total OIL mined by current operator (doesn't reset when claimed, only when ownership changes)
43    pub operator_total_oil_mined: u64,
44    
45    /// Buffer field (for future use) - previously is_pool_owned
46    pub buffer_c: u64,
47    
48    /// Total contributed FOGO for current epoch (tracks native SOL balance in Well PDA's system account)
49    /// Incremented on each contribution, decremented when pool bids
50    /// Reset to 0 when epoch ends
51    pub total_contributed: u64,
52    
53    /// Pool bid cost - stores the bid_amount when pool bids
54    /// Used to calculate original_total when pool gets outbid
55    /// Reset to 0 when epoch ends
56    pub pool_bid_cost: u64,
57}
58
59impl Well {
60    pub fn pda(well_id: u64) -> (Pubkey, u8) {
61        well_pda(well_id)
62    }
63
64    pub fn current_price(&self, auction: &Auction, clock: &Clock) -> u64 {
65        use crate::consts::AUCTION_FLOOR_PRICE;
66        
67        // If well has no owner (never been bid on), show starting price
68        use solana_program::pubkey::Pubkey;
69        if self.current_bidder == Pubkey::default() {
70            return self.init_price; // Return starting price for unowned wells
71        }
72        
73        let elapsed = clock.unix_timestamp.saturating_sub(self.epoch_start_time as i64);
74        let duration = auction.auction_duration_seconds as i64;
75        
76        if elapsed >= duration {
77            return AUCTION_FLOOR_PRICE; // Auction expired, price is at floor
78        }
79        
80        // Linear decay: price = floor + (init_price - floor) * (remaining / duration)
81        let remaining = duration - elapsed;
82        let price_range = self.init_price.saturating_sub(AUCTION_FLOOR_PRICE);
83        let decayed_amount = (price_range as u128 * remaining as u128 / duration as u128) as u64;
84        AUCTION_FLOOR_PRICE + decayed_amount
85    }
86
87    pub fn update_accumulated_oil(&mut self, auction: &Auction, clock: &Clock) {
88        // Skip if no owner
89        use solana_program::pubkey::Pubkey;
90        if self.current_bidder == Pubkey::default() {
91            return;
92        }
93        
94        let last_update = self.last_update_time as i64;
95        let current_time = clock.unix_timestamp as u64;
96        let elapsed = clock.unix_timestamp.saturating_sub(last_update);
97        if elapsed <= 0 {
98            return;
99        }
100        
101        // Calculate OIL mined, accounting for halvings that occurred during the elapsed period
102        let oil_mined = if auction.halving_count == 0 {
103            // No halvings yet - check if first halving occurred during this period
104            let first_halving_time = auction.last_halving_time + Auction::FIRST_HALVING_PERIOD_SECONDS;
105            if current_time >= first_halving_time && (last_update as u64) < first_halving_time {
106                // First halving occurred during this period - calculate in segments
107                let time_before_halving = first_halving_time.saturating_sub(last_update as u64);
108                let time_after_halving = current_time.saturating_sub(first_halving_time);
109                let rate_before = self.mps;
110                let rate_after = self.mps / 2; // 50% reduction
111                rate_before.checked_mul(time_before_halving).unwrap_or(0)
112                    .checked_add(rate_after.checked_mul(time_after_halving).unwrap_or(0))
113                    .unwrap_or(u64::MAX)
114            } else {
115                // No halving during this period - use current rate
116                self.mps.checked_mul(elapsed as u64).unwrap_or(0)
117            }
118        } else {
119            // Halvings have occurred - check if more occurred during this period
120            let next_halving_time = auction.last_halving_time + auction.halving_period_seconds;
121            if current_time >= next_halving_time && (last_update as u64) < next_halving_time {
122                // One or more halvings occurred during this period
123                let mut total_oil = 0u64;
124                let mut segment_start = last_update as u64;
125                let mut current_rate = self.mps;
126                let mut last_halving = auction.last_halving_time;
127                
128                // Calculate segments for each halving that occurred
129                while segment_start < current_time {
130                    let next_halving = last_halving + auction.halving_period_seconds;
131                    if next_halving > current_time {
132                        // No more halvings - calculate remaining time
133                        let remaining_time = current_time.saturating_sub(segment_start);
134                        total_oil = total_oil.checked_add(
135                            current_rate.checked_mul(remaining_time).unwrap_or(0)
136                        ).unwrap_or(u64::MAX);
137                        break;
138                    } else {
139                        // Halving occurred - calculate segment before halving
140                        let segment_time = next_halving.saturating_sub(segment_start);
141                        total_oil = total_oil.checked_add(
142                            current_rate.checked_mul(segment_time).unwrap_or(0)
143                        ).unwrap_or(u64::MAX);
144                        // Apply 25% reduction for next segment
145                        current_rate = (current_rate * 3) / 4;
146                        segment_start = next_halving;
147                        last_halving = next_halving;
148                    }
149                }
150                total_oil
151            } else {
152                // No halving during this period - use current rate
153                self.mps.checked_mul(elapsed as u64).unwrap_or(0)
154            }
155        };
156        
157        self.accumulated_oil = self.accumulated_oil
158            .checked_add(oil_mined)
159            .unwrap_or(u64::MAX);
160        
161        self.lifetime_oil_mined = self.lifetime_oil_mined
162            .checked_add(oil_mined)
163            .unwrap_or(u64::MAX);
164        
165        // Track total mined by current operator (persists even after claiming)
166        self.operator_total_oil_mined = self.operator_total_oil_mined
167            .checked_add(oil_mined)
168            .unwrap_or(u64::MAX);
169        
170        self.last_update_time = current_time;
171    }
172
173    pub fn check_and_apply_halving(&mut self, auction: &mut Auction, clock: &Clock) {
174        // Check if we should apply halvings based on current time
175        let current_time = clock.unix_timestamp as u64;
176        let (halvings_to_apply, is_first_halving) = auction.should_apply_halving(current_time);
177        
178        if halvings_to_apply > 0 {
179            if is_first_halving {
180                // First halving: 50% reduction
181                self.mps = self.mps / 2; // 50% reduction
182                self.halving_count += 1;
183                auction.halving_count += 1;
184                auction.last_halving_time = current_time;
185            } else {
186                // Subsequent halvings: 25% reduction each (multiply by 0.75)
187                for _ in 0..halvings_to_apply {
188                    self.mps = (self.mps * 3) / 4; // 25% reduction (multiply by 0.75)
189                    self.halving_count += 1;
190                    auction.halving_count += 1;
191                }
192                // Update auction last_halving_time to current time
193                auction.last_halving_time = current_time;
194            }
195        }
196    }
197}
198
199account!(OilAccount, Well);
200