1use serde::{Deserialize, Serialize};
2use steel::*;
3
4use crate::state::{stake_pda, Pool};
5
6use super::OilAccount;
7
8#[repr(C)]
9#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
10pub struct Stake {
11 pub authority: Pubkey,
13
14 pub balance: u64,
16
17 pub lock_duration_days: u64,
19
20 pub lock_ends_at: u64,
22
23 pub buffer_c: u64,
25
26 pub buffer_d: u64,
28
29 pub buffer_e: u64,
31
32 pub last_claim_at: i64,
34
35 pub last_deposit_at: i64,
37
38 pub last_withdraw_at: i64,
40
41 pub rewards_factor: Numeric,
43
44 pub rewards: u64,
46
47 pub lifetime_rewards: u64,
49
50 pub buffer_f: u64,
52}
53
54impl Stake {
55 pub fn pda(&self) -> (Pubkey, u8) {
56 stake_pda(self.authority)
57 }
58
59 pub fn calculate_multiplier(lock_duration_days: u64) -> f64 {
60 if lock_duration_days == 0 {
61 return 1.0;
62 }
63
64 let lookup: [(u64, f64); 6] = [
65 (7, 1.18),
66 (30, 1.78),
67 (90, 3.35),
68 (180, 5.69),
69 (365, 10.5),
70 (730, 20.0),
71 ];
72
73 let days = lock_duration_days.min(730);
75
76 for &(d, m) in &lookup {
78 if days == d {
79 return m;
80 }
81 }
82
83 for i in 0..lookup.len() - 1 {
85 let (d1, m1) = lookup[i];
86 let (d2, m2) = lookup[i + 1];
87
88 if days >= d1 && days <= d2 {
89 let ratio = (days - d1) as f64 / (d2 - d1) as f64;
90 return m1 + (m2 - m1) * ratio;
91 }
92 }
93
94 if days > 730 {
96 return 20.0;
97 }
98
99 if days < 7 {
101 return 1.0 + (days as f64 / 7.0) * 0.18;
102 }
103
104 1.0 }
106
107 pub fn score(&self) -> u64 {
108 let multiplier = Self::calculate_multiplier(self.lock_duration_days);
109 ((self.balance as u128 * (multiplier * 1_000_000.0) as u128) / 1_000_000) as u64
111 }
112
113 pub fn is_locked(&self, clock: &Clock) -> bool {
114 self.lock_ends_at > 0 && (clock.unix_timestamp as u64) < self.lock_ends_at
115 }
116
117 pub fn remaining_lock_seconds(&self, clock: &Clock) -> u64 {
118 if self.lock_ends_at == 0 || (clock.unix_timestamp as u64) >= self.lock_ends_at {
119 return 0;
120 }
121 self.lock_ends_at - (clock.unix_timestamp as u64)
122 }
123
124 pub fn calculate_penalty_percent(lock_duration_days: u64) -> u64 {
125 match lock_duration_days {
126 0 => 0, 1..=7 => 5, 8..=30 => 10, 31..=180 => 25, 181..=365 => 40, 366..=730 => 60, _ => 60, }
134 }
135
136 pub fn claim(&mut self, amount: u64, clock: &Clock, pool: &Pool) -> u64 {
137 self.update_rewards(pool);
138 let amount = self.rewards.min(amount);
139 self.rewards -= amount;
140 self.last_claim_at = clock.unix_timestamp;
141 amount
142 }
143
144 pub fn deposit(
145 &mut self,
146 amount: u64,
147 clock: &Clock,
148 pool: &mut Pool,
149 sender: &TokenAccount,
150 ) -> u64 {
151 self.update_rewards(pool);
152
153 let old_score = self.score();
155
156 let amount = sender.amount().min(amount);
157 self.balance += amount;
158 self.last_deposit_at = clock.unix_timestamp;
159
160 let new_score = self.score();
162
163 pool.total_staked += amount;
165 pool.total_staked_score = pool.total_staked_score.saturating_add(new_score).saturating_sub(old_score);
166
167 amount
168 }
169
170 pub fn withdraw(&mut self, amount: u64, clock: &Clock, pool: &mut Pool) -> u64 {
171 self.update_rewards(pool);
172
173 let old_score = self.score();
175
176 let amount = self.balance.min(amount);
177 self.balance -= amount;
178 self.last_withdraw_at = clock.unix_timestamp;
179
180 if self.balance == 0 {
183 self.lock_duration_days = 0;
184 self.lock_ends_at = 0;
185 }
186
187 let new_score = self.score();
189
190 pool.total_staked -= amount;
192 pool.total_staked_score = pool.total_staked_score.saturating_add(new_score).saturating_sub(old_score);
193
194 amount
195 }
196
197 pub fn update_rewards(&mut self, pool: &Pool) {
198 if pool.stake_rewards_factor > self.rewards_factor {
200 let accumulated_rewards = pool.stake_rewards_factor - self.rewards_factor;
201 if accumulated_rewards < Numeric::ZERO {
202 panic!("Accumulated rewards is negative");
203 }
204 let score = self.score();
206 let personal_rewards = accumulated_rewards * Numeric::from_u64(score);
207 self.rewards += personal_rewards.to_u64();
208 self.lifetime_rewards += personal_rewards.to_u64();
209 }
210
211 self.rewards_factor = pool.stake_rewards_factor;
213 }
214}
215
216account!(OilAccount, Stake);