1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, Token, TokenAccount};
3
4use crate::constants::*;
5use crate::errors::ErrorCodes;
6use crate::instructions::{
7 crank::crank,
8 initialize_account::{ShadowDriveStorageAccount, StorageAccount, StorageAccountV2},
9 initialize_config::StorageConfig,
10};
11
12pub fn handler(
14 mut ctx: impl DecreaseStorage,
15 remove_storage: u64,
16 storage_used: u64,
17) -> Result<()> {
18 require!(
20 !ctx.is_immutable(),
21 ErrorCodes::StorageAccountMarkedImmutable
22 );
23
24 msg!("Unstaking funds");
25 {
26 ctx.unstake(remove_storage, storage_used)?;
27 }
28
29 msg!(
30 "Decreasing storage on StorageAccount: {}",
31 ctx.get_identifier()
32 );
33 {
34 require_gte!(
40 ctx.get_current_storage(),
41 remove_storage,
42 ErrorCodes::RemovingTooMuchStorage
43 );
44 ctx.remove_storage(remove_storage)?;
45 }
46
47 msg!("Setting unstake info");
48 {
49 ctx.record_unstake_info()?;
50 }
51
52 Ok(())
53}
54
55#[derive(Accounts)]
56pub struct DecreaseStorageV1<'info> {
59 #[account(
61 mut,
62 seeds = [
63 "storage-config".as_bytes()
64 ],
65 bump,
66 )]
67 pub storage_config: Box<Account<'info, StorageConfig>>,
68
69 #[account(
71 mut,
72 seeds = [
73 "storage-account".as_bytes(),
74 &storage_account.owner_1.key().to_bytes(),
75 &storage_account.account_counter_seed.to_le_bytes()
76 ],
77 bump,
78 )]
79 pub storage_account: Box<Account<'info, StorageAccount>>,
80
81 #[account(
83 init_if_needed,
84 space = std::mem::size_of::<UnstakeInfo>() + 8,
85 payer = owner,
86 seeds = [
87 "unstake-info".as_bytes(),
88 &storage_account.key().to_bytes(),
89 ],
90 bump,
91 )]
92 pub unstake_info: Box<Account<'info, UnstakeInfo>>,
93
94 #[account(
96 init_if_needed,
97 payer = owner,
98 seeds = [
99 "unstake-account".as_bytes(),
100 &storage_account.key().to_bytes(),
101 ],
102 bump,
103 token::mint = token_mint,
104 token::authority = storage_config,
105 )]
106 pub unstake_account: Box<Account<'info, TokenAccount>>,
107
108 #[account(mut, constraint=storage_account.is_owner(owner.key()))]
111 pub owner: Signer<'info>,
112
113 #[account(
115 mut,
116 constraint = {
117 owner_ata.owner == owner.key()
118 && owner_ata.mint == token_mint.key()
119 }
120 )]
121 pub owner_ata: Box<Account<'info, TokenAccount>>,
122
123 #[account(
125 mut,
126 seeds = [
127 "stake-account".as_bytes(),
128 &storage_account.key().to_bytes(),
129 ],
130 bump,
131 )]
132 pub stake_account: Box<Account<'info, TokenAccount>>,
133
134 #[account(mut, address = shdw::ID)]
136 pub token_mint: Account<'info, Mint>,
137
138 #[account(constraint = uploader.key() == storage_config.uploader)]
140 pub uploader: Signer<'info>,
141
142 #[account(mut, address = shdw::emissions_wallet::ID)]
144 pub emissions_wallet: Box<Account<'info, TokenAccount>>,
145
146 pub system_program: Program<'info, System>,
148
149 pub token_program: Program<'info, Token>,
151
152 pub rent: Sysvar<'info, Rent>,
154}
155
156#[derive(Accounts)]
157pub struct DecreaseStorageV2<'info> {
160 #[account(
162 mut,
163 seeds = [
164 "storage-config".as_bytes()
165 ],
166 bump,
167 )]
168 pub storage_config: Box<Account<'info, StorageConfig>>,
169
170 #[account(
172 mut,
173 seeds = [
174 "storage-account".as_bytes(),
175 &storage_account.owner_1.key().to_bytes(),
176 &storage_account.account_counter_seed.to_le_bytes()
177 ],
178 bump,
179 )]
180 pub storage_account: Account<'info, StorageAccountV2>,
181
182 #[account(
184 init_if_needed,
185 space = std::mem::size_of::<UnstakeInfo>() + 8,
186 payer = owner,
187 seeds = [
188 "unstake-info".as_bytes(),
189 &storage_account.key().to_bytes(),
190 ],
191 bump,
192 )]
193 pub unstake_info: Box<Account<'info, UnstakeInfo>>,
194
195 #[account(
197 init_if_needed,
198 payer = owner,
199 seeds = [
200 "unstake-account".as_bytes(),
201 &storage_account.key().to_bytes(),
202 ],
203 bump,
204 token::mint = token_mint,
205 token::authority = storage_config,
206 )]
207 pub unstake_account: Box<Account<'info, TokenAccount>>,
208
209 #[account(mut, constraint=storage_account.is_owner(owner.key()))]
212 pub owner: Signer<'info>,
213
214 #[account(
216 mut,
217 constraint = {
218 owner_ata.owner == owner.key()
219 && owner_ata.mint == token_mint.key()
220 }
221 )]
222 pub owner_ata: Box<Account<'info, TokenAccount>>,
223
224 #[account(
226 mut,
227 seeds = [
228 "stake-account".as_bytes(),
229 &storage_account.key().to_bytes(),
230 ],
231 bump,
232 )]
233 pub stake_account: Box<Account<'info, TokenAccount>>,
234
235 #[account(mut, address = shdw::ID)]
237 pub token_mint: Account<'info, Mint>,
238
239 #[account(constraint = uploader.key() == storage_config.uploader)]
241 pub uploader: Signer<'info>,
242
243 #[account(mut, address = shdw::emissions_wallet::ID)]
245 pub emissions_wallet: Box<Account<'info, TokenAccount>>,
246
247 pub system_program: Program<'info, System>,
249
250 pub token_program: Program<'info, Token>,
252
253 pub rent: Sysvar<'info, Rent>,
255}
256
257#[account]
258pub struct UnstakeInfo {
259 pub time_last_unstaked: i64,
260 pub epoch_last_unstaked: u64,
261 pub unstaker: Pubkey,
262}
263
264fn safe_mul_div(a: u64, b: u64, c: u64) -> Result<u64> {
265 let result = (a as u128)
266 .checked_mul(b as u128)
267 .unwrap()
268 .checked_div(c as u128)
269 .unwrap();
270 if (result as u64) as u128 == result {
271 Ok(result as u64)
272 } else {
273 err!(ErrorCodes::UnsignedIntegerCastFailed)
274 }
275}
276
277pub trait DecreaseStorage {
278 fn is_immutable(&self) -> bool;
279 fn get_identifier(&self) -> String;
280 fn get_current_storage(&self) -> u64;
281 fn unstake(&mut self, remove_storage: u64, storage_used: u64) -> Result<()>;
282 fn remove_storage(&mut self, remove_storage: u64) -> Result<()>;
283 fn record_unstake_info(&mut self) -> Result<()>;
284}
285
286impl DecreaseStorage for Context<'_, '_, '_, '_, DecreaseStorageV1<'_>> {
287 fn is_immutable(&self) -> bool {
288 self.accounts.storage_account.immutable
289 }
290 fn get_identifier(&self) -> String {
291 self.accounts.storage_account.identifier.clone()
292 }
293 fn get_current_storage(&self) -> u64 {
294 self.accounts.storage_account.storage
295 }
296 fn unstake(&mut self, remove_storage: u64, storage_used: u64) -> Result<()> {
297 let mut unstake_amount = safe_mul_div(
299 remove_storage,
300 self.accounts.stake_account.amount,
301 self.accounts.storage_account.storage,
302 )
303 .unwrap()
304 .min(self.accounts.stake_account.amount);
305
306 let storage_config_seeds = [
308 "storage-config".as_bytes(),
309 &[*self.bumps.get("storage_config").unwrap()],
310 ];
311
312 let account_info = self.accounts.storage_account.to_account_info();
315 if let Some((emission_fee, crank_fee)) = crank(
316 &self.accounts.storage_config,
317 &mut self.accounts.storage_account,
318 account_info,
319 &self.accounts.emissions_wallet,
320 &self.accounts.stake_account,
321 &self.accounts.token_program,
322 &self.accounts.owner_ata,
323 &self.accounts.token_mint,
324 *self.bumps.get("storage_config").unwrap(),
325 storage_used,
326 )? {
327 let fee = emission_fee.checked_add(crank_fee).unwrap();
329 unstake_amount = unstake_amount.saturating_sub(fee);
330 }
331
332 let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
333 anchor_spl::token::transfer(
334 CpiContext::new_with_signer(
335 self.accounts.token_program.to_account_info(),
336 anchor_spl::token::Transfer {
337 from: self.accounts.stake_account.to_account_info(),
338 to: self.accounts.unstake_account.to_account_info(),
339 authority: self.accounts.storage_config.to_account_info(),
340 },
341 signer_seeds,
342 ),
343 unstake_amount,
344 )
345 }
346 fn remove_storage(&mut self, remove_storage: u64) -> Result<()> {
347 self.accounts.storage_account.storage = self
348 .accounts
349 .storage_account
350 .storage
351 .checked_sub(remove_storage)
352 .unwrap();
353 Ok(())
354 }
355 fn record_unstake_info(&mut self) -> Result<()> {
356 let clock = Clock::get().unwrap();
358
359 self.accounts.unstake_info.time_last_unstaked = clock.unix_timestamp;
361 self.accounts.unstake_info.epoch_last_unstaked = clock.epoch;
362 self.accounts.unstake_info.unstaker = self.accounts.owner.key();
363
364 Ok(())
365 }
366}
367
368impl DecreaseStorage for Context<'_, '_, '_, '_, DecreaseStorageV2<'_>> {
369 fn is_immutable(&self) -> bool {
370 self.accounts.storage_account.immutable
371 }
372 fn get_identifier(&self) -> String {
373 self.accounts.storage_account.identifier.clone()
374 }
375 fn get_current_storage(&self) -> u64 {
376 self.accounts.storage_account.storage
377 }
378 fn unstake(&mut self, remove_storage: u64, storage_used: u64) -> Result<()> {
379 let mut unstake_amount = safe_mul_div(
381 remove_storage,
382 self.accounts.stake_account.amount,
383 self.accounts.storage_account.storage,
384 )
385 .unwrap()
386 .min(self.accounts.stake_account.amount);
387
388 let storage_config_seeds = [
390 "storage-config".as_bytes(),
391 &[*self.bumps.get("storage_config").unwrap()],
392 ];
393
394 let account_info = self.accounts.storage_account.to_account_info();
397 if let Some((emission_fee, crank_fee)) = crank(
398 &self.accounts.storage_config,
399 &mut self.accounts.storage_account,
400 account_info,
401 &self.accounts.emissions_wallet,
402 &self.accounts.stake_account,
403 &self.accounts.token_program,
404 &self.accounts.owner_ata,
405 &self.accounts.token_mint,
406 *self.bumps.get("storage_config").unwrap(),
407 storage_used,
408 )? {
409 let fee = emission_fee.checked_add(crank_fee).unwrap();
411 unstake_amount = unstake_amount.saturating_sub(fee);
412 }
413
414 let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
415 anchor_spl::token::transfer(
416 CpiContext::new_with_signer(
417 self.accounts.token_program.to_account_info(),
418 anchor_spl::token::Transfer {
419 from: self.accounts.stake_account.to_account_info(),
420 to: self.accounts.unstake_account.to_account_info(),
421 authority: self.accounts.storage_config.to_account_info(),
422 },
423 signer_seeds,
424 ),
425 unstake_amount,
426 )
427 }
428 fn remove_storage(&mut self, remove_storage: u64) -> Result<()> {
429 self.accounts.storage_account.storage = self
430 .accounts
431 .storage_account
432 .storage
433 .checked_sub(remove_storage)
434 .unwrap();
435 Ok(())
436 }
437 fn record_unstake_info(&mut self) -> Result<()> {
438 let clock = Clock::get().unwrap();
440
441 self.accounts.unstake_info.time_last_unstaked = clock.unix_timestamp;
443 self.accounts.unstake_info.epoch_last_unstaked = clock.epoch;
444 self.accounts.unstake_info.unstaker = self.accounts.owner.key();
445
446 Ok(())
447 }
448}