shadow_drive_user_staking/instructions/
crank.rs1use 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
11pub fn handler(ctx: impl Crank, storage_used: u64) -> Result<()> {
13 ctx.crank(storage_used)
14}
15
16#[derive(Accounts)]
17pub struct CrankV1<'info> {
20 #[account(
22 mut,
23 seeds = [
24 "storage-config".as_bytes()
25 ],
26 bump,
27 )]
28 pub storage_config: Box<Account<'info, StorageConfig>>,
29
30 #[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 #[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 #[account(mut, address=crate::constants::admin1::ID)]
55 pub cranker: Signer<'info>,
56
57 #[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 #[account(mut, address=shdw::emissions_wallet::ID)]
69 pub emissions_wallet: Box<Account<'info, TokenAccount>>,
70
71 #[account(mut, address = shdw::ID)]
73 pub token_mint: Account<'info, Mint>,
74
75 pub system_program: Program<'info, System>,
77
78 pub token_program: Program<'info, Token>,
80}
81
82#[derive(Accounts)]
83pub struct CrankV2<'info> {
86 #[account(
88 mut,
89 seeds = [
90 "storage-config".as_bytes()
91 ],
92 bump,
93 )]
94 pub storage_config: Box<Account<'info, StorageConfig>>,
95
96 #[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 #[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 #[account(mut, address=crate::constants::admin1::ID)]
121 pub cranker: Signer<'info>,
122
123 #[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 #[account(mut, address=shdw::emissions_wallet::ID)]
135 pub emissions_wallet: Box<Account<'info, TokenAccount>>,
136
137 #[account(mut, address = shdw::ID)]
139 pub token_mint: Account<'info, Mint>,
140
141 pub system_program: Program<'info, System>,
143
144 pub token_program: Program<'info, Token>,
146}
147
148pub 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 require!(
167 !storage_account.check_immutable(),
169 ErrorCodes::StorageAccountMarkedImmutable
170 );
171 require!(
172 storage_account_info.is_writable,
174 ErrorCodes::SolanaStorageAccountNotMutable
175 );
176 let clock = Clock::get()?;
179 if let Some(start_epoch) = storage_config.mutable_fee_start_epoch {
181 let fee_begin_epoch: u32 = start_epoch.max(storage_account.get_last_fee_epoch());
183
184 let fee_end_epoch: u32 = clock.epoch.try_into().unwrap();
186
187 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 .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 if fee > stake_account.amount {
201 fee = stake_account.amount;
203
204 msg!("Insufficient stake to cover mutable fees, marking for deletion");
207 storage_account.mark_to_delete();
208 } else if fee == 0 {
209 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 storage_account.update_last_fee_epoch();
220
221 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 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 Ok(Some((emissions_fee, cranker_fee)))
269 } else {
270 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}