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 /// Buffer field (for future use) - previously is_pool_owned
52 pub buffer_c: u64,
53
54 /// Buffer field (for future use)
55 pub buffer_d: u64,
56
57 /// Buffer field (for future use)
58 pub buffer_e: u64,
59}
60
61impl Well {
62 pub fn pda(well_id: u64) -> (Pubkey, u8) {
63 well_pda(well_id)
64 }
65
66 /// Calculate current price for this epoch (Dutch auction - price decreases over time)
67 /// Price decays linearly from init_price down to AUCTION_FLOOR_PRICE over auction_duration_seconds.
68 pub fn current_price(&self, auction: &Auction, clock: &Clock) -> u64 {
69 use crate::consts::AUCTION_FLOOR_PRICE;
70
71 // If well has no owner (never been bid on), show starting price
72 use solana_program::pubkey::Pubkey;
73 if self.current_bidder == Pubkey::default() {
74 return self.init_price; // Return starting price for unowned wells
75 }
76
77 let elapsed = clock.unix_timestamp.saturating_sub(self.epoch_start_time as i64);
78 let duration = auction.auction_duration_seconds as i64;
79
80 if elapsed >= duration {
81 return AUCTION_FLOOR_PRICE; // Auction expired, price is at floor
82 }
83
84 // Linear decay: price = floor + (init_price - floor) * (remaining / duration)
85 // Price decays from init_price down to floor over duration
86 let remaining = duration - elapsed;
87 let price_range = self.init_price.saturating_sub(AUCTION_FLOOR_PRICE);
88 let decayed_amount = (price_range as u128 * remaining as u128 / duration as u128) as u64;
89 AUCTION_FLOOR_PRICE + decayed_amount
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