Skip to main content

shadow_drive_user_staking/instructions/
delete_account.rs

1use crate::constants::*;
2use crate::errors::ErrorCodes;
3use crate::instructions::{
4    crank::crank,
5    initialize_account::{ShadowDriveStorageAccount, StorageAccount, StorageAccountV2, UserInfo},
6    initialize_config::StorageConfig,
7};
8use anchor_lang::prelude::*;
9use anchor_spl::token::{Mint, Token, TokenAccount};
10use std::convert::TryFrom;
11
12/// This is the function that handles the `delete_account` ix
13pub fn handler(mut ctx: impl DeleteAccount, storage_used: u64) -> Result<()> {
14    // Cannot request to delete storage account if immutable
15    require!(
16        !ctx.check_immutable(),
17        ErrorCodes::StorageAccountMarkedImmutable
18    );
19
20    // Cannot delete a storage account which still has associated file accounts
21    // require_eq!(
22    //     ctx.accounts.storage_account.init_counter,
23    //     ctx.accounts.storage_account.del_counter,
24    //     ErrorCodes::NonzeroRemainingFileAccounts
25    // );
26
27    // Cannot delete a storage account not marked for deletion
28    require!(
29        ctx.check_delete_flag(),
30        ErrorCodes::AccountNotMarkedToBeDeleted
31    );
32
33    // Cannot delete a storage account in grace period
34    require!(
35        ctx.check_grace_period(),
36        ErrorCodes::AccountStillInGracePeriod
37    );
38
39    msg!("Deleting StorageAccount account: {}", ctx.get_identifier(),);
40    // account is marked with `close` in Context<_>
41
42    msg!("Returning stake to user");
43    ctx.return_stake(storage_used)?;
44
45    msg!("Updating global storage on StorageConfig account");
46    ctx.update_global_storage()?;
47
48    msg!("Update User Info");
49    ctx.increment_del_counter()?;
50
51    Ok(())
52}
53
54#[derive(Accounts)]
55/// This `DeleteAccount` context is used in the instruction that allows
56/// admins to delete user storage accounts.
57pub struct DeleteAccountV1<'info> {
58    /// This is the `StorageConfig` accounts that holds all of the admin, uploader keys.
59    #[account(
60        mut,
61        seeds = [
62            "storage-config".as_bytes()
63        ],
64        bump,
65    )]
66    pub storage_config: Box<Account<'info, StorageConfig>>,
67
68    /// This account is a PDA that holds a user's info (not specific to one storage account).
69    #[account(
70        mut,
71        seeds = [
72            "user-info".as_bytes(),
73            &storage_account.owner_1.key().to_bytes(),
74        ],
75        bump,
76    )]
77    pub user_info: Box<Account<'info, UserInfo>>,
78
79    /// Parent storage account.
80    #[account(
81        mut,
82        close = owner,
83        seeds = [
84            "storage-account".as_bytes(),
85            &storage_account.owner_1.key().to_bytes(),
86            &storage_account.account_counter_seed.to_le_bytes()
87        ],
88        bump,
89    )]
90    pub storage_account: Box<Account<'info, StorageAccount>>,
91
92    /// This token account serves as the account which holds user's stake for file storage.
93    #[account(
94        mut,
95        seeds = [
96            "stake-account".as_bytes(),
97            &storage_account.key().to_bytes(),
98        ],
99        bump,
100    )]
101    pub stake_account: Box<Account<'info, TokenAccount>>,
102
103    /// File owner, user
104    /// CHECK: There is a constraint that checks whether this account is an owner.
105    /// Also, our uploader keys are signing this transaction so presuamably we would only provide a good key.
106    /// We also may not need this account at all.
107    #[account(mut, constraint=storage_account.is_owner(owner.key()))]
108    pub owner: AccountInfo<'info>,
109
110    /// This is the user's token account, presumably with which they staked
111    #[account(
112        mut,
113        constraint = {
114            storage_account.is_owner(shdw_payer.owner)
115            && shdw_payer.mint == token_mint.key()
116        },
117    )]
118    pub shdw_payer: Account<'info, TokenAccount>,
119
120    /// Admin/uploader
121    #[account(constraint = uploader.key() == storage_config.uploader)]
122    pub uploader: Signer<'info>,
123
124    /// This token accountis the SHDW operator emissions wallet
125    #[account(mut, address=shdw::emissions_wallet::ID)]
126    pub emissions_wallet: Box<Account<'info, TokenAccount>>,
127
128    /// Token mint account
129    #[account(mut, address = shdw::ID)]
130    pub token_mint: Account<'info, Mint>,
131
132    /// System Program
133    pub system_program: Program<'info, System>,
134
135    /// Token Program
136    pub token_program: Program<'info, Token>,
137}
138
139#[derive(Accounts)]
140/// This `DeleteAccount` context is used in the instruction that allows
141/// admins to delete user storage accounts.
142pub struct DeleteAccountV2<'info> {
143    /// This is the `StorageConfig` accounts that holds all of the admin, uploader keys.
144    #[account(
145        mut,
146        seeds = [
147            "storage-config".as_bytes()
148        ],
149        bump,
150    )]
151    pub storage_config: Box<Account<'info, StorageConfig>>,
152
153    /// This account is a PDA that holds a user's info (not specific to one storage account).
154    #[account(
155        mut,
156        seeds = [
157            "user-info".as_bytes(),
158            &storage_account.owner_1.key().to_bytes(),
159        ],
160        bump,
161    )]
162    pub user_info: Box<Account<'info, UserInfo>>,
163
164    /// Parent storage account.
165    #[account(
166        mut,
167        close = owner,
168        seeds = [
169            "storage-account".as_bytes(),
170            &storage_account.owner_1.key().to_bytes(),
171            &storage_account.account_counter_seed.to_le_bytes()
172        ],
173        bump,
174    )]
175    pub storage_account: Box<Account<'info, StorageAccountV2>>,
176
177    /// This token account serves as the account which holds user's stake for file storage.
178    #[account(
179        mut,
180		close = owner,
181        seeds = [
182            "stake-account".as_bytes(),
183            &storage_account.key().to_bytes(),
184        ],
185        bump,
186    )]
187    pub stake_account: Box<Account<'info, TokenAccount>>,
188
189    /// File owner, user
190    /// CHECK: There is a constraint that checks whether this account is an owner.
191    /// Also, our uploader keys are signing this transaction so presuamably we would only provide a good key.
192    /// We also may not need this account at all.
193    #[account(mut, constraint=storage_account.is_owner(owner.key()))]
194    pub owner: AccountInfo<'info>,
195
196    /// This is the user's token account, presumably with which they staked
197    #[account(
198        mut,
199        constraint = {
200            storage_account.is_owner(shdw_payer.owner)
201            && shdw_payer.mint == token_mint.key()
202        },
203    )]
204    pub shdw_payer: Account<'info, TokenAccount>,
205
206    /// Admin/uploader
207    #[account(constraint = uploader.key() == storage_config.uploader)]
208    pub uploader: Signer<'info>,
209
210    /// This token accountis the SHDW operator emissions wallet
211    #[account(mut, address=shdw::emissions_wallet::ID)]
212    pub emissions_wallet: Box<Account<'info, TokenAccount>>,
213
214    /// Token mint account
215    #[account(mut, address = shdw::ID)]
216    pub token_mint: Account<'info, Mint>,
217
218    /// System Program
219    pub system_program: Program<'info, System>,
220
221    /// Token Program
222    pub token_program: Program<'info, Token>,
223}
224
225pub trait DeleteAccount {
226    fn check_immutable(&self) -> bool;
227    fn check_delete_flag(&self) -> bool;
228    fn check_grace_period(&self) -> bool;
229    fn get_identifier(&self) -> String;
230    fn return_stake(&mut self, storage_used: u64) -> Result<()>;
231    fn update_global_storage(&mut self) -> Result<()>;
232    fn increment_del_counter(&mut self) -> Result<()>;
233}
234
235impl DeleteAccount for Context<'_, '_, '_, '_, DeleteAccountV1<'_>> {
236    fn check_immutable(&self) -> bool {
237        self.accounts.storage_account.check_immutable()
238    }
239    fn check_delete_flag(&self) -> bool {
240        self.accounts.storage_account.check_delete_flag()
241    }
242    fn check_grace_period(&self) -> bool {
243        u32::try_from(Clock::get().unwrap().epoch).unwrap()
244            >= self
245                .accounts
246                .storage_account
247                .delete_request_epoch
248                .checked_add(DELETION_GRACE_PERIOD as u32)
249                .unwrap()
250    }
251    fn get_identifier(&self) -> String {
252        self.accounts.storage_account.get_identifier()
253    }
254    fn return_stake(&mut self, storage_used: u64) -> Result<()> {
255        // Pack seeds
256        let storage_config_seeds = [
257            "storage-config".as_bytes(),
258            &[*self.bumps.get("storage_config").unwrap()],
259        ];
260        let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
261
262        // Initalize return amount
263        let mut return_amount = self.accounts.stake_account.amount;
264
265        // If mutable account fees are on, charge any outstanding fees.
266        // Note: Cranker fee goes to emissions wallet.
267        let account_info = self.accounts.storage_account.to_account_info();
268        if let Some((emission_fee, crank_fee)) = crank(
269            &self.accounts.storage_config,
270            &mut self.accounts.storage_account,
271            account_info,
272            &self.accounts.emissions_wallet,
273            &self.accounts.stake_account,
274            &self.accounts.token_program,
275            &self.accounts.emissions_wallet,
276            &self.accounts.token_mint,
277            *self.bumps.get("storage_config").unwrap(),
278            storage_used,
279        )? {
280            // Subtract fee from return
281            let fee = emission_fee.checked_add(crank_fee).unwrap();
282            return_amount = return_amount.saturating_sub(fee);
283        }
284
285        // Return funds to user
286        anchor_spl::token::transfer(
287            CpiContext::new_with_signer(
288                self.accounts.token_program.to_account_info(),
289                anchor_spl::token::Transfer {
290                    from: self.accounts.stake_account.to_account_info(),
291                    to: self.accounts.shdw_payer.to_account_info(),
292                    authority: self.accounts.storage_config.to_account_info(),
293                },
294                signer_seeds,
295            ),
296            return_amount,
297        )?;
298
299        Ok(())
300    }
301    fn update_global_storage(&mut self) -> Result<()> {
302        let storage_config = &mut self.accounts.storage_config;
303
304        // Increase storage available
305        storage_config.storage_available = storage_config
306            .storage_available
307            .checked_add(self.accounts.storage_account.storage as u128)
308            .unwrap();
309
310        Ok(())
311    }
312    fn increment_del_counter(&mut self) -> Result<()> {
313        // Increment delete counter
314        self.accounts.user_info.del_counter =
315            self.accounts.user_info.del_counter.checked_add(1).unwrap();
316
317        Ok(())
318    }
319}
320
321impl DeleteAccount for Context<'_, '_, '_, '_, DeleteAccountV2<'_>> {
322    fn check_immutable(&self) -> bool {
323        self.accounts.storage_account.check_immutable()
324    }
325    fn check_delete_flag(&self) -> bool {
326        self.accounts.storage_account.check_delete_flag()
327    }
328    fn check_grace_period(&self) -> bool {
329        u32::try_from(Clock::get().unwrap().epoch).unwrap()
330            >= self
331                .accounts
332                .storage_account
333                .delete_request_epoch
334                .checked_add(DELETION_GRACE_PERIOD as u32)
335                .unwrap()
336    }
337    fn get_identifier(&self) -> String {
338        self.accounts.storage_account.get_identifier()
339    }
340    fn return_stake(&mut self, storage_used: u64) -> Result<()> {
341        // Pack seeds
342        let storage_config_seeds = [
343            "storage-config".as_bytes(),
344            &[*self.bumps.get("storage_config").unwrap()],
345        ];
346        let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
347
348        // Initalize return amount
349        let mut return_amount = self.accounts.stake_account.amount;
350
351        // If mutable account fees are on, charge any outstanding fees.
352        // Note: Cranker fee goes to emissions wallet.
353        let account_info = self.accounts.storage_account.to_account_info();
354        if let Some((emission_fee, crank_fee)) = crank(
355            &self.accounts.storage_config,
356            &mut self.accounts.storage_account,
357            account_info,
358            &self.accounts.emissions_wallet,
359            &self.accounts.stake_account,
360            &self.accounts.token_program,
361            &self.accounts.emissions_wallet,
362            &self.accounts.token_mint,
363            *self.bumps.get("storage_config").unwrap(),
364            storage_used,
365        )? {
366            // Subtract fee from return
367            let fee = emission_fee.checked_add(crank_fee).unwrap();
368            return_amount = return_amount.saturating_sub(fee);
369        }
370
371        // Return funds to user
372        anchor_spl::token::transfer(
373            CpiContext::new_with_signer(
374                self.accounts.token_program.to_account_info(),
375                anchor_spl::token::Transfer {
376                    from: self.accounts.stake_account.to_account_info(),
377                    to: self.accounts.shdw_payer.to_account_info(),
378                    authority: self.accounts.storage_config.to_account_info(),
379                },
380                signer_seeds,
381            ),
382            return_amount,
383        )?;
384
385        Ok(())
386    }
387    fn update_global_storage(&mut self) -> Result<()> {
388        let storage_config = &mut self.accounts.storage_config;
389
390        // Increase storage available
391        storage_config.storage_available = storage_config
392            .storage_available
393            .checked_add(self.accounts.storage_account.storage as u128)
394            .unwrap();
395
396        Ok(())
397    }
398    fn increment_del_counter(&mut self) -> Result<()> {
399        // Increment delete counter
400        self.accounts.user_info.del_counter =
401            self.accounts.user_info.del_counter.checked_add(1).unwrap();
402
403        Ok(())
404    }
405}