shadow_drive_user_staking/instructions/
refresh_stake.rs1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, Token, TokenAccount};
3
4use crate::constants::*;
5use crate::errors::ErrorCodes;
6use crate::instructions::{
7 initialize_account::{ShadowDriveStorageAccount, StorageAccount, StorageAccountV2},
8 initialize_config::StorageConfig,
9};
10
11pub fn handler(mut ctx: impl RefreshStake) -> Result<()> {
13 require!(
15 !ctx.check_immutable(),
16 ErrorCodes::StorageAccountMarkedImmutable
17 );
18
19 msg!("Charging user for storage");
20 ctx.refresh_stake()?;
21
22 ctx.unmark_delete();
24
25 Ok(())
26}
27
28#[derive(Accounts)]
29pub struct RefreshStakeV1<'info> {
32 #[account(
34 seeds = [
35 "storage-config".as_bytes()
36 ],
37 bump,
38 )]
39 pub storage_config: Box<Account<'info, StorageConfig>>,
40
41 #[account(
43 mut,
44 seeds = [
45 "storage-account".as_bytes(),
46 &storage_account.owner_1.key().to_bytes(),
47 &storage_account.account_counter_seed.to_le_bytes()
48 ],
49 bump,
50 )]
51 pub storage_account: Box<Account<'info, StorageAccount>>,
52
53 #[account(mut)]
56 pub owner: Signer<'info>,
57
58 #[account(mut, constraint = owner_ata.mint == shdw::ID)]
60 pub owner_ata: Box<Account<'info, TokenAccount>>,
61
62 #[account(
64 mut,
65 seeds = [
66 "stake-account".as_bytes(),
67 &storage_account.key().to_bytes()
68 ],
69 bump,
70 )]
71 pub stake_account: Box<Account<'info, TokenAccount>>,
72
73 #[account(address = shdw::ID)]
75 pub token_mint: Account<'info, Mint>,
76
77 pub system_program: Program<'info, System>,
79
80 pub token_program: Program<'info, Token>,
82}
83
84#[derive(Accounts)]
85pub struct RefreshStakeV2<'info> {
88 #[account(
90 seeds = [
91 "storage-config".as_bytes()
92 ],
93 bump,
94 )]
95 pub storage_config: Box<Account<'info, StorageConfig>>,
96
97 #[account(
99 mut,
100 seeds = [
101 "storage-account".as_bytes(),
102 &storage_account.owner_1.key().to_bytes(),
103 &storage_account.account_counter_seed.to_le_bytes()
104 ],
105 bump,
106 )]
107 pub storage_account: Box<Account<'info, StorageAccountV2>>,
108
109 #[account(mut)]
112 pub owner: Signer<'info>,
113
114 #[account(mut, constraint = owner_ata.mint == shdw::ID)]
116 pub owner_ata: Box<Account<'info, TokenAccount>>,
117
118 #[account(
120 mut,
121 seeds = [
122 "stake-account".as_bytes(),
123 &storage_account.key().to_bytes()
124 ],
125 bump,
126 )]
127 pub stake_account: Box<Account<'info, TokenAccount>>,
128
129 #[account(address = shdw::ID)]
131 pub token_mint: Account<'info, Mint>,
132
133 pub system_program: Program<'info, System>,
135
136 pub token_program: Program<'info, Token>,
138}
139
140fn safe_amount(storage: u64, rate_per_gib: u128) -> Result<u64> {
141 let result = (storage as u128)
142 .checked_mul(rate_per_gib as u128)
143 .unwrap()
144 .checked_div(BYTES_PER_GIB as u128)
145 .unwrap();
146 if (result as u64) as u128 == result {
147 Ok(result as u64)
148 } else {
149 err!(ErrorCodes::UnsignedIntegerCastFailed)
150 }
151}
152
153pub trait RefreshStake {
154 fn check_immutable(&self) -> bool;
155 fn refresh_stake(&mut self) -> Result<()>;
156 fn unmark_delete(&mut self);
157}
158
159impl RefreshStake for Context<'_, '_, '_, '_, RefreshStakeV1<'_>> {
160 fn check_immutable(&self) -> bool {
161 self.accounts.storage_account.check_immutable()
162 }
163 fn refresh_stake(&mut self) -> Result<()> {
164 let storage_config = &self.accounts.storage_config;
165 let storage_account = &self.accounts.storage_account;
166 let stake_account = &self.accounts.stake_account;
167
168 let total_cost: u64 = safe_amount(
170 storage_account.storage,
171 storage_config.shades_per_gib as u128,
172 )?
173 .max(1);
174
175 let refresh_amount: u64 = total_cost.saturating_sub(stake_account.amount);
177
178 if refresh_amount > 0 {
180 anchor_spl::token::transfer(
181 CpiContext::new(
182 self.accounts.token_program.to_account_info(),
183 anchor_spl::token::Transfer {
184 from: self.accounts.owner_ata.to_account_info(),
185 to: self.accounts.stake_account.to_account_info(),
186 authority: self.accounts.owner.to_account_info(),
187 },
188 ),
189 refresh_amount,
190 )?;
191 emit!(StakeRefreshed {
192 refreshed: true,
193 account: self.accounts.storage_account.key()
194 });
195 }
196
197 Ok(())
198 }
199
200 fn unmark_delete(&mut self) {
201 let storage_account = &mut self.accounts.storage_account;
202
203 if storage_account.to_be_deleted {
204 msg!("Account was marked for deletion. Unmarking");
205
206 storage_account.to_be_deleted = false;
208 storage_account.delete_request_epoch = 0;
209 }
210 }
211}
212
213impl RefreshStake for Context<'_, '_, '_, '_, RefreshStakeV2<'_>> {
214 fn check_immutable(&self) -> bool {
215 self.accounts.storage_account.check_immutable()
216 }
217 fn refresh_stake(&mut self) -> Result<()> {
218 let storage_config = &self.accounts.storage_config;
219 let storage_account = &self.accounts.storage_account;
220 let stake_account = &self.accounts.stake_account;
221
222 let total_cost: u64 = safe_amount(
224 storage_account.storage,
225 storage_config.shades_per_gib as u128,
226 )?
227 .max(1);
228
229 let refresh_amount: u64 = total_cost.saturating_sub(stake_account.amount);
231
232 if refresh_amount > 0 {
234 anchor_spl::token::transfer(
235 CpiContext::new(
236 self.accounts.token_program.to_account_info(),
237 anchor_spl::token::Transfer {
238 from: self.accounts.owner_ata.to_account_info(),
239 to: self.accounts.stake_account.to_account_info(),
240 authority: self.accounts.owner.to_account_info(),
241 },
242 ),
243 refresh_amount,
244 )?;
245 emit!(StakeRefreshed {
246 refreshed: true,
247 account: self.accounts.storage_account.key()
248 });
249 }
250 Ok(())
251 }
252
253 fn unmark_delete(&mut self) {
254 let storage_account = &mut self.accounts.storage_account;
255
256 if storage_account.to_be_deleted {
257 msg!("Account was marked for deletion. Unmarking");
258
259 storage_account.to_be_deleted = false;
261 storage_account.delete_request_epoch = 0;
262 }
263 }
264}
265
266#[event]
267pub struct StakeRefreshed {
268 pub refreshed: bool,
269 pub account: Pubkey,
270}