shadow_drive_user_staking/instructions/
crank.rs

1use crate::constants::{shdw, BYTES_PER_GIB};
2use crate::errors::ErrorCodes;
3use crate::instructions::{
4    initialize_account::{ShadowDriveStorageAccount, StorageAccount, StorageAccountV2},
5    initialize_config::StorageConfig,
6};
7use anchor_lang::prelude::*;
8use anchor_spl::token::{Mint, Token, TokenAccount};
9use std::convert::TryInto;
10
11/// This is the function that handles the `crank` instruction
12pub fn handler(ctx: impl Crank, storage_used: u64) -> Result<()> {
13    ctx.crank(storage_used)
14}
15
16#[derive(Accounts)]
17/// This `Crank` context is used in the instruction that allows
18/// admins to delete user storage accounts.
19pub struct CrankV1<'info> {
20    /// This is the `StorageConfig` accounts that holds all of the admin, uploader keys.
21    #[account(
22        mut,
23        seeds = [
24            "storage-config".as_bytes()
25        ],
26        bump,
27    )]
28    pub storage_config: Box<Account<'info, StorageConfig>>,
29
30    /// Parent storage account.
31    #[account(
32        mut,
33        seeds = [
34            "storage-account".as_bytes(),
35            &storage_account.owner_1.key().to_bytes(),
36            &storage_account.account_counter_seed.to_le_bytes()
37        ],
38        bump,
39    )]
40    pub storage_account: Account<'info, StorageAccount>,
41
42    /// This token account serves as the account which holds user's stake for file storage.
43    #[account(
44        mut,
45        seeds = [
46            "stake-account".as_bytes(),
47            &storage_account.key().to_bytes(),
48        ],
49        bump,
50    )]
51    pub stake_account: Box<Account<'info, TokenAccount>>,
52
53    /// Cranker
54    #[account(mut, address=crate::constants::admin1::ID)]
55    pub cranker: Signer<'info>,
56
57    /// Cranker's ATA
58    #[account(
59        mut,
60        constraint = {
61            cranker_ata.owner == cranker.key()
62            && cranker_ata.mint == token_mint.key()
63        }
64    )]
65    pub cranker_ata: Box<Account<'info, TokenAccount>>,
66
67    /// This token accountis the SHDW operator emissions wallet
68    #[account(mut, address=shdw::emissions_wallet::ID)]
69    pub emissions_wallet: Box<Account<'info, TokenAccount>>,
70
71    /// Token mint account
72    #[account(mut, address = shdw::ID)]
73    pub token_mint: Account<'info, Mint>,
74
75    /// System Program
76    pub system_program: Program<'info, System>,
77
78    /// Token Program
79    pub token_program: Program<'info, Token>,
80}
81
82#[derive(Accounts)]
83/// This `Crank` context is used in the instruction that allows
84/// admins to delete user storage accounts.
85pub struct CrankV2<'info> {
86    /// This is the `StorageConfig` accounts that holds all of the admin, uploader keys.
87    #[account(
88        mut,
89        seeds = [
90            "storage-config".as_bytes()
91        ],
92        bump,
93    )]
94    pub storage_config: Box<Account<'info, StorageConfig>>,
95
96    /// Parent storage account.
97    #[account(
98        mut,
99        seeds = [
100            "storage-account".as_bytes(),
101            &storage_account.owner_1.key().to_bytes(),
102            &storage_account.account_counter_seed.to_le_bytes()
103        ],
104        bump,
105    )]
106    pub storage_account: Account<'info, StorageAccountV2>,
107
108    /// This token account serves as the account which holds user's stake for file storage.
109    #[account(
110        mut,
111        seeds = [
112            "stake-account".as_bytes(),
113            &storage_account.key().to_bytes(),
114        ],
115        bump,
116    )]
117    pub stake_account: Box<Account<'info, TokenAccount>>,
118
119    /// Cranker
120    #[account(mut, address=crate::constants::admin1::ID)]
121    pub cranker: Signer<'info>,
122
123    /// Cranker's ATA
124    #[account(
125        mut,
126        constraint = {
127            cranker_ata.owner == cranker.key()
128            && cranker_ata.mint == token_mint.key()
129        }
130    )]
131    pub cranker_ata: Box<Account<'info, TokenAccount>>,
132
133    /// This token accountis the SHDW operator emissions wallet
134    #[account(mut, address=shdw::emissions_wallet::ID)]
135    pub emissions_wallet: Box<Account<'info, TokenAccount>>,
136
137    /// Token mint account
138    #[account(mut, address = shdw::ID)]
139    pub token_mint: Account<'info, Mint>,
140
141    /// System Program
142    pub system_program: Program<'info, System>,
143
144    /// Token Program
145    pub token_program: Program<'info, Token>,
146}
147
148/// This public fn is a crank fn that can be called within this program
149/// to collect fees from some specified user's stake account.
150pub fn crank<
151    'info,
152    T: ShadowDriveStorageAccount + AccountSerialize + AccountDeserialize + Owner + Clone,
153>(
154    storage_config: &Account<'info, StorageConfig>,
155    storage_account: &mut Account<'info, T>,
156    storage_account_info: AccountInfo<'info>,
157    emissions_wallet: &Account<'info, TokenAccount>,
158    stake_account: &Account<'info, TokenAccount>,
159    token_program: &Program<'info, Token>,
160    cranker_ata: &Account<'info, TokenAccount>,
161    token_mint: &Account<'info, Mint>,
162    storage_config_bump: u8,
163    storage_used: u64,
164) -> Result<Option<(u64, u64)>> {
165    // Mutability checks. Added for checks when called from other instructions.
166    require!(
167        // Check for mutability on Shadow Drive
168        !storage_account.check_immutable(),
169        ErrorCodes::StorageAccountMarkedImmutable
170    );
171    require!(
172        // Check for solana account mutability
173        storage_account_info.is_writable,
174        ErrorCodes::SolanaStorageAccountNotMutable
175    );
176    // emissions_wallet, stake_account, cranker_ata checked by anchor_spl::token::transfer
177
178    let clock = Clock::get()?;
179    // If mutable account fees are on, charge any outstanding fees.
180    if let Some(start_epoch) = storage_config.mutable_fee_start_epoch {
181        // When to start charging
182        let fee_begin_epoch: u32 = start_epoch.max(storage_account.get_last_fee_epoch());
183
184        // Current epoch
185        let fee_end_epoch: u32 = clock.epoch.try_into().unwrap();
186
187        // Calculate fee: (time elapsed) * (shades per mb per epoch) * (bytes / bytes per mb)
188        let mut fee = (fee_end_epoch.checked_sub(fee_begin_epoch).unwrap() as u128)
189            .checked_mul(storage_config.shades_per_gib_per_epoch as u128)
190            .unwrap()
191            //instead of getting the storage reserved by an account pass in the storage used.
192            .checked_mul(storage_used as u128)
193            .unwrap()
194            .checked_div(BYTES_PER_GIB as u128)
195            .unwrap()
196            .try_into()
197            .unwrap();
198
199        // Fee is limited by stake account balance
200        if fee > stake_account.amount {
201            // Cap the fee at the stake_account balance
202            fee = stake_account.amount;
203
204            // Mark storage account for deletion,
205            // since stake account will be empty.
206            msg!("Insufficient stake to cover mutable fees, marking for deletion");
207            storage_account.mark_to_delete();
208        } else if fee == 0 {
209            // If fee_end_epoch == fee_begin_epoch or if rate = 0, stop here
210            return Ok(Some((0, 0)));
211        }
212        msg!(
213            "Collecting {} epochs of fees for {} bytes",
214            fee_end_epoch.checked_sub(fee_begin_epoch).unwrap(),
215            storage_used
216        );
217
218        // Update storage_account
219        storage_account.update_last_fee_epoch();
220
221        // Split fee into cranker_fee, emissions_fee
222        let cranker_fee: u64 = (fee as u128)
223            .checked_mul(storage_config.crank_bps as u128)
224            .unwrap()
225            .checked_div(10000)
226            .unwrap()
227            .try_into()
228            .unwrap();
229        let emissions_fee: u64 = fee;
230        msg!("Cranker fee: {} shades", cranker_fee);
231        msg!("Emissions fee: {} shades", emissions_fee);
232        msg!(
233            "Storage rate: {} shades per gb per epoch",
234            storage_config.shades_per_gib_per_epoch
235        );
236        // Transfer fees to emissions wallet, cranker
237        // Pack seeds
238        let storage_config_seeds = ["storage-config".as_bytes(), &[storage_config_bump]];
239        let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
240
241        anchor_spl::token::burn(
242            CpiContext::new_with_signer(
243                token_program.to_account_info(),
244                anchor_spl::token::Burn {
245                    mint: token_mint.to_account_info(),
246                    from: stake_account.to_account_info(),
247                    authority: storage_config.to_account_info(),
248                },
249                signer_seeds,
250            ),
251            emissions_fee,
252        )?;
253        // if cranker_fee > 0 {
254        //     anchor_spl::token::transfer(
255        //         CpiContext::new_with_signer(
256        //             token_program.to_account_info(),
257        //             anchor_spl::token::Transfer {
258        //                 from: stake_account.to_account_info(),
259        //                 to: cranker_ata.to_account_info(),
260        //                 authority: storage_config.to_account_info(),
261        //             },
262        //             signer_seeds,
263        //         ),
264        //         cranker_fee,
265        //     )?;
266        // }
267
268        Ok(Some((emissions_fee, cranker_fee)))
269    } else {
270        // Otherwise, crank should do nothing
271        Ok(None)
272    }
273}
274
275pub trait Crank {
276    fn crank(self, storage_used: u64) -> Result<()>;
277}
278
279impl Crank for Context<'_, '_, '_, '_, CrankV1<'_>> {
280    fn crank(self, storage_used: u64) -> Result<()> {
281        let account_info = self.accounts.storage_account.to_account_info();
282        match crank(
283            &self.accounts.storage_config,
284            &mut self.accounts.storage_account,
285            account_info,
286            &self.accounts.emissions_wallet,
287            &self.accounts.stake_account,
288            &self.accounts.token_program,
289            &self.accounts.cranker_ata,
290            &self.accounts.token_mint,
291            *self.bumps.get("storage_config").unwrap(),
292            storage_used,
293        )? {
294            Some(_) => {
295                msg!("Crank Turned");
296                Ok(())
297            }
298            None => {
299                msg!("Mutable fees are inactive");
300                Ok(())
301            }
302        }
303    }
304}
305
306impl Crank for Context<'_, '_, '_, '_, CrankV2<'_>> {
307    fn crank(self, storage_used: u64) -> Result<()> {
308        let account_info = self.accounts.storage_account.to_account_info();
309        match crank(
310            &self.accounts.storage_config,
311            &mut self.accounts.storage_account,
312            account_info,
313            &self.accounts.emissions_wallet,
314            &self.accounts.stake_account,
315            &self.accounts.token_program,
316            &self.accounts.cranker_ata,
317            &self.accounts.token_mint,
318            *self.bumps.get("storage_config").unwrap(),
319            storage_used,
320        )? {
321            Some(_) => {
322                msg!("Crank Turned");
323                Ok(())
324            }
325            None => {
326                msg!("Mutable fees are inactive");
327                Ok(())
328            }
329        }
330    }
331}