shadow_drive_user_staking/instructions/
claim_stake.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, Token, TokenAccount};
3
4use crate::constants::*;
5use crate::errors::ErrorCodes;
6use crate::instructions::{
7    decrease_storage::UnstakeInfo, initialize_account::{StorageAccount, StorageAccountV2},
8    initialize_config::StorageConfig,
9};
10
11/// This is the function that handles the `claim_stake` instruction
12pub fn handler(
13    ctx: impl ClaimStake
14) -> Result<()> {
15
16    // Must wait to unstake
17    let clock = Clock::get().unwrap();
18    let current_time = clock.unix_timestamp;
19    let current_epoch = clock.epoch;
20    let (unstake_time, unstake_epoch) = ctx.get_unstake_time();
21    
22    require!(
23        current_time.checked_sub(unstake_time).unwrap() >= UNSTAKE_TIME_PERIOD,
24        ErrorCodes::ClaimingStakeTooSoon
25    );
26    require!(
27        current_epoch.checked_sub(unstake_epoch).unwrap() >= UNSTAKE_EPOCH_PERIOD,
28        ErrorCodes::ClaimingStakeTooSoon
29    );
30
31    msg!("Transferring all outstanding unstake funds to user");
32    {
33        ctx.transfer_shades_to_user()?;
34    }
35
36    Ok(())
37}
38
39type Time = i64;
40type Epoch = u64;
41pub trait ClaimStake {
42    fn get_unstake_time(&self) -> (Time, Epoch);
43    fn transfer_shades_to_user(&self) -> Result<()>;
44}
45
46
47#[derive(Accounts)]
48/// This `ClaimStake` context is used in the instruction which allow users to
49/// claim funds after waiting an appropriate amount of time.
50pub struct ClaimStakeV2<'info> {
51    /// This is the `StorageConfig` accounts that holds all of the admin, uploader keys.
52    #[account(
53        mut,
54        seeds = [
55            "storage-config".as_bytes()
56        ],
57        bump,
58    )]
59    pub storage_config: Box<Account<'info, StorageConfig>>,
60
61    /// Parent storage account. Only used here for the key
62    #[account(
63        mut,
64        seeds = [
65            "storage-account".as_bytes(),
66            &storage_account.owner_1.key().to_bytes(),
67            &storage_account.account_counter_seed.to_le_bytes()
68        ],
69        bump,
70    )]
71    pub storage_account: Box<Account<'info, StorageAccountV2>>,
72
73    /// Account which stores time, epoch last unstaked. Close upon successful unstake.
74    #[account(
75        mut,
76        close = owner,
77        seeds = [
78            "unstake-info".as_bytes(),
79            &storage_account.key().to_bytes(),
80        ],
81        bump,
82    )]
83    pub unstake_info: Box<Account<'info, UnstakeInfo>>,
84
85    /// Account which stores SHDW when unstaking.  Close upon successful unstake.
86    #[account(
87        mut,
88        seeds = [
89            "unstake-account".as_bytes(),
90            &storage_account.key().to_bytes(),
91        ],
92        bump,
93    )]
94    pub unstake_account: Box<Account<'info, TokenAccount>>,
95
96    /// File owner, user, fee-payer
97    /// Requires mutability since owner/user is fee payer.
98    #[account(mut, address=unstake_info.unstaker)]
99    pub owner: Signer<'info>,
100
101    /// File owner, user, fee-payer
102    /// Requires mutability since owner/user is fee payer.
103    #[account(
104        mut,
105        constraint = {
106            owner_ata.owner == owner.key()
107            && owner_ata.mint == token_mint.key()
108        }
109    )]
110    pub owner_ata: Account<'info, TokenAccount>,
111
112    /// Token mint account
113    #[account(address = shdw::ID)]
114    pub token_mint: Account<'info, Mint>,
115
116    /// System Program
117    pub system_program: Program<'info, System>,
118
119    /// Token Programn
120    pub token_program: Program<'info, Token>,
121}
122
123
124#[derive(Accounts)]
125/// This `ClaimStake` context is used in the instruction which allow users to
126/// claim funds after waiting an appropriate amount of time.
127pub struct ClaimStakeV1<'info> {
128    /// This is the `StorageConfig` accounts that holds all of the admin, uploader keys.
129    #[account(
130        mut,
131        seeds = [
132            "storage-config".as_bytes()
133        ],
134        bump,
135    )]
136    pub storage_config: Box<Account<'info, StorageConfig>>,
137
138    /// Parent storage account. Only used here for the key
139    #[account(
140        mut,
141        seeds = [
142            "storage-account".as_bytes(),
143            &storage_account.owner_1.key().to_bytes(),
144            &storage_account.account_counter_seed.to_le_bytes()
145        ],
146        bump,
147    )]
148    pub storage_account: Box<Account<'info, StorageAccount>>,
149
150    /// Account which stores time, epoch last unstaked. Close upon successful unstake.
151    #[account(
152        mut,
153        close = owner,
154        seeds = [
155            "unstake-info".as_bytes(),
156            &storage_account.key().to_bytes(),
157        ],
158        bump,
159    )]
160    pub unstake_info: Box<Account<'info, UnstakeInfo>>,
161
162    /// Account which stores SHDW when unstaking.  Close upon successful unstake.
163    #[account(
164        mut,
165        seeds = [
166            "unstake-account".as_bytes(),
167            &storage_account.key().to_bytes(),
168        ],
169        bump,
170    )]
171    pub unstake_account: Box<Account<'info, TokenAccount>>,
172
173    /// File owner, user, fee-payer
174    /// Requires mutability since owner/user is fee payer.
175    #[account(mut, address=unstake_info.unstaker)]
176    pub owner: Signer<'info>,
177
178    /// File owner, user, fee-payer
179    /// Requires mutability since owner/user is fee payer.
180    #[account(
181        mut,
182        constraint = {
183            owner_ata.owner == owner.key()
184            && owner_ata.mint == token_mint.key()
185        }
186    )]
187    pub owner_ata: Box<Account<'info, TokenAccount>>,
188
189    /// Token mint account
190    #[account(address = shdw::ID)]
191    pub token_mint: Account<'info, Mint>,
192
193    /// System Program
194    pub system_program: Program<'info, System>,
195
196    /// Token Programn
197    pub token_program: Program<'info, Token>,
198}
199
200
201impl ClaimStake for Context<'_, '_, '_, '_, ClaimStakeV1<'_>> {
202    fn get_unstake_time(&self) -> (Time, Epoch) {
203        (self.accounts.unstake_info.time_last_unstaked, self.accounts.unstake_info.epoch_last_unstaked)
204    }
205    fn transfer_shades_to_user(&self) -> Result<()> {
206
207        // Pack seeds
208        let storage_config_seeds = [
209            "storage-config".as_bytes(),
210            &[*self.bumps.get("storage_config").unwrap()],
211        ];
212        let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
213
214        // Transfer shades to user
215        anchor_spl::token::transfer(
216            CpiContext::new_with_signer(
217                self.accounts.token_program.to_account_info(),
218                anchor_spl::token::Transfer {
219                    from: self.accounts.unstake_account.to_account_info(),
220                    to: self.accounts.owner_ata.to_account_info(),
221                    authority: self.accounts.storage_config.to_account_info(),
222                },
223                signer_seeds,
224            ),
225            self.accounts.unstake_account.amount,
226        )?;
227
228        // Close unstake account
229        anchor_spl::token::close_account(CpiContext::new_with_signer(
230            self.accounts.token_program.to_account_info(),
231            anchor_spl::token::CloseAccount {
232                account: self.accounts.unstake_account.to_account_info(),
233                destination: self.accounts.owner_ata.to_account_info(),
234                authority: self.accounts.storage_config.to_account_info(),
235            },
236            signer_seeds,
237        ))
238    }
239}
240
241impl ClaimStake for Context<'_, '_, '_, '_, ClaimStakeV2<'_>> {
242    fn get_unstake_time(&self) -> (Time, Epoch) {
243        (self.accounts.unstake_info.time_last_unstaked, self.accounts.unstake_info.epoch_last_unstaked)
244    }
245    fn transfer_shades_to_user(&self) -> Result<()> {
246
247        // Pack seeds
248        let storage_config_seeds = [
249            "storage-config".as_bytes(),
250            &[*self.bumps.get("storage_config").unwrap()],
251        ];
252        let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
253
254        // Transfer shades to user
255        anchor_spl::token::transfer(
256            CpiContext::new_with_signer(
257                self.accounts.token_program.to_account_info(),
258                anchor_spl::token::Transfer {
259                    from: self.accounts.unstake_account.to_account_info(),
260                    to: self.accounts.owner_ata.to_account_info(),
261                    authority: self.accounts.storage_config.to_account_info(),
262                },
263                signer_seeds,
264            ),
265            self.accounts.unstake_account.amount,
266        )?;
267
268        // Close unstake account
269        anchor_spl::token::close_account(CpiContext::new_with_signer(
270            self.accounts.token_program.to_account_info(),
271            anchor_spl::token::CloseAccount {
272                account: self.accounts.unstake_account.to_account_info(),
273                destination: self.accounts.owner_ata.to_account_info(),
274                authority: self.accounts.storage_config.to_account_info(),
275            },
276            signer_seeds,
277        ))
278    }
279
280}