1use crate::constants::*;
2use crate::errors::ErrorCodes;
3use crate::instructions::{
4 crank::crank,
5 initialize_account::{ShadowDriveStorageAccount, StorageAccount, StorageAccountV2},
6 initialize_config::StorageConfig,
7};
8use anchor_lang::prelude::*;
9use anchor_spl::associated_token::AssociatedToken;
10use anchor_spl::token::{Mint, Token, TokenAccount};
11use std::convert::TryInto;
12
13pub fn handler(mut ctx: impl MakeAccountImmutable, storage_used: u64) -> Result<()> {
15 let new_balance: Shades = ctx.crank(storage_used)?;
17
18 msg!("Charging user for storage; SPL transfers to emissions wallet");
19 {
20 ctx.charge_or_return_shades(new_balance)?;
21 }
22
23 msg!(
24 "Marking Storage Account as immutable: {}",
25 ctx.get_identifier()
26 );
27 {
28 ctx.mark_immutable();
29 }
30
31 Ok(())
32}
33
34#[derive(Accounts)]
35pub struct MakeAccountImmutableV1<'info> {
38 #[account(
41 mut,
42 seeds = [
43 "storage-config".as_bytes()
44 ],
45 bump,
46 )]
47 pub storage_config: Box<Account<'info, StorageConfig>>,
48
49 #[account(
52 mut,
53 seeds = [
54 "storage-account".as_bytes(),
55 &storage_account.owner_1.key().to_bytes(),
56 &storage_account.account_counter_seed.to_le_bytes()
57 ],
58 bump,
59 )]
60 pub storage_account: Box<Account<'info, StorageAccount>>,
61
62 #[account(
64 mut,
65 seeds = [
66 "stake-account".as_bytes(),
67 &storage_account.key().to_bytes(),
68 ],
69 bump,
70 )]
71 pub stake_account: Box<Account<'info, TokenAccount>>,
72
73 #[account(mut, address=shdw::emissions_wallet::ID)]
75 pub emissions_wallet: Box<Account<'info, TokenAccount>>,
76
77 #[account(mut, constraint=storage_account.is_owner(owner.key()))]
80 pub owner: Signer<'info>,
81
82 #[account(address=storage_config.uploader)]
84 pub uploader: Signer<'info>,
85
86 #[account(
88 init_if_needed,
89 payer = owner,
90 associated_token::mint = token_mint,
91 associated_token::authority = owner,
92 )]
93 pub owner_ata: Box<Account<'info, TokenAccount>>,
94
95 #[account(mut, address = shdw::ID)]
97 pub token_mint: Box<Account<'info, Mint>>,
98
99 pub system_program: Program<'info, System>,
101
102 pub token_program: Program<'info, Token>,
104
105 pub associated_token_program: Program<'info, AssociatedToken>,
107
108 pub rent: Sysvar<'info, Rent>,
110}
111
112#[derive(Accounts)]
113pub struct MakeAccountImmutableV2<'info> {
116 #[account(
119 mut,
120 seeds = [
121 "storage-config".as_bytes()
122 ],
123 bump,
124 )]
125 pub storage_config: Box<Account<'info, StorageConfig>>,
126
127 #[account(
130 mut,
131 seeds = [
132 "storage-account".as_bytes(),
133 &storage_account.owner_1.key().to_bytes(),
134 &storage_account.account_counter_seed.to_le_bytes()
135 ],
136 bump,
137 )]
138 pub storage_account: Box<Account<'info, StorageAccountV2>>,
139
140 #[account(
142 mut,
143 seeds = [
144 "stake-account".as_bytes(),
145 &storage_account.key().to_bytes(),
146 ],
147 bump,
148 )]
149 pub stake_account: Box<Account<'info, TokenAccount>>,
150
151 #[account(mut, address=shdw::emissions_wallet::ID)]
153 pub emissions_wallet: Box<Account<'info, TokenAccount>>,
154
155 #[account(mut, constraint=storage_account.is_owner(owner.key()))]
158 pub owner: Signer<'info>,
159
160 #[account(
162 init_if_needed,
163 payer = owner,
164 associated_token::mint = token_mint,
165 associated_token::authority = owner,
166 )]
167 pub owner_ata: Box<Account<'info, TokenAccount>>,
168
169 #[account(address=storage_config.uploader)]
171 pub uploader: Signer<'info>,
172
173 #[account(mut, address = shdw::ID)]
175 pub token_mint: Box<Account<'info, Mint>>,
176
177 pub system_program: Program<'info, System>,
179
180 pub token_program: Program<'info, Token>,
182
183 pub associated_token_program: Program<'info, AssociatedToken>,
185
186 pub rent: Sysvar<'info, Rent>,
188}
189
190type Shades = u64;
191
192pub trait MakeAccountImmutable {
193 fn crank(&mut self, storage_used: u64) -> Result<Shades>;
194 fn charge_or_return_shades(&mut self, new_balance: Shades) -> Result<()>;
195 fn get_identifier(&self) -> String;
196 fn mark_immutable(&mut self);
197}
198
199impl MakeAccountImmutable for Context<'_, '_, '_, '_, MakeAccountImmutableV1<'_>> {
200 fn crank(&mut self, storage_used: u64) -> Result<Shades> {
201 let pre_crank_balance = self.accounts.stake_account.amount;
202 let new_balance;
203 let account_info = self.accounts.storage_account.to_account_info();
204 {
205 if let Some((emission_fee, crank_fee)) = crank(
206 &self.accounts.storage_config,
207 &mut self.accounts.storage_account,
208 account_info,
209 &self.accounts.emissions_wallet,
210 &self.accounts.stake_account,
211 &self.accounts.token_program,
212 &self.accounts.owner_ata,
213 &self.accounts.token_mint,
214 *self.bumps.get("storage_config").unwrap(),
215 storage_used,
216 )? {
217 msg!("First, crank for any outstanding mutable fees (cranker = user)");
218 new_balance = pre_crank_balance
219 .checked_sub(emission_fee.checked_add(crank_fee).unwrap())
220 .unwrap();
221 } else {
222 new_balance = pre_crank_balance;
223 }
224 }
225
226 Ok(new_balance)
227 }
228 fn charge_or_return_shades(&mut self, new_balance: Shades) -> Result<()> {
229 let storage_config = &self.accounts.storage_config;
230 let storage_account = &self.accounts.storage_account;
231
232 let storage_config_seeds = [
234 "storage-config".as_bytes(),
235 &[*self.bumps.get("storage_config").unwrap()],
236 ];
237 let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
238
239 let immutable_storage_requested: u128 = storage_account.storage as u128;
241 let cost_of_storage: u64 = immutable_storage_requested
242 .checked_mul(storage_config.shades_per_gib as u128)
243 .unwrap()
244 .checked_div(BYTES_PER_GIB as u128)
245 .unwrap()
246 .try_into()
247 .unwrap();
248 msg!(
249 "User has {} bytes of storage, costing {} shades",
250 immutable_storage_requested,
251 cost_of_storage
252 );
253
254 if cost_of_storage <= new_balance {
255 msg!("User's stake account contains enough funds to cover immutable fee");
256
257 let return_amount = new_balance.checked_sub(cost_of_storage).unwrap();
259 msg!(
260 "Returning {} shades to user, {} to emissions wallet",
261 return_amount,
262 cost_of_storage,
263 );
264
265 require_eq!(
267 new_balance,
268 cost_of_storage.checked_add(return_amount).unwrap(),
269 ErrorCodes::InvalidTokenTransferAmounts
270 );
271
272 if return_amount > 0 {
274 anchor_spl::token::transfer(
275 CpiContext::new_with_signer(
276 self.accounts.token_program.to_account_info(),
277 anchor_spl::token::Transfer {
278 from: self.accounts.stake_account.to_account_info(),
279 to: self.accounts.owner_ata.to_account_info(),
280 authority: self.accounts.storage_config.to_account_info(),
281 },
282 signer_seeds,
283 ),
284 return_amount,
285 )
286 .map_err(|_| ErrorCodes::FailedToReturnUserFunds)?;
287 }
288
289 anchor_spl::token::transfer(
291 CpiContext::new_with_signer(
292 self.accounts.token_program.to_account_info(),
293 anchor_spl::token::Transfer {
294 from: self.accounts.stake_account.to_account_info(),
295 to: self.accounts.emissions_wallet.to_account_info(),
296 authority: self.accounts.storage_config.to_account_info(),
297 },
298 signer_seeds,
299 ),
300 cost_of_storage,
301 )
302 .map_err(|_| ErrorCodes::FailedToTransferToEmissionsWallet)?;
303 } else {
304 let additional_shades: u64 = cost_of_storage.checked_sub(new_balance).unwrap();
305 msg!(
306 "User's stake account does not contain enough funds, charging user additional {} shades",
307 additional_shades
308 );
309
310 require_eq!(
312 cost_of_storage,
313 additional_shades.checked_add(new_balance).unwrap(),
314 ErrorCodes::InvalidTokenTransferAmounts
315 );
316 anchor_spl::token::burn(
317 CpiContext::new_with_signer(
318 self.accounts.token_program.to_account_info(),
319 anchor_spl::token::Burn {
320 mint: self.accounts.token_mint.to_account_info(),
321 from: self.accounts.stake_account.to_account_info(),
322 authority: self.accounts.storage_config.to_account_info(),
323 },
324 signer_seeds,
325 ),
326 additional_shades,
327 )
328 .map_err(|_| ErrorCodes::FailedToTransferToEmissionsWalletFromUser)?;
329
330 anchor_spl::token::transfer(
332 CpiContext::new_with_signer(
333 self.accounts.token_program.to_account_info(),
334 anchor_spl::token::Transfer {
335 from: self.accounts.stake_account.to_account_info(),
336 to: self.accounts.emissions_wallet.to_account_info(),
337 authority: self.accounts.storage_config.to_account_info(),
338 },
339 signer_seeds,
340 ),
341 new_balance,
342 )
343 .map_err(|_| ErrorCodes::FailedToTransferToEmissionsWallet)?;
344 }
345
346 anchor_spl::token::close_account(CpiContext::new_with_signer(
348 self.accounts.token_program.to_account_info(),
349 anchor_spl::token::CloseAccount {
350 account: self.accounts.stake_account.to_account_info(),
351 destination: self.accounts.emissions_wallet.to_account_info(),
352 authority: self.accounts.storage_config.to_account_info(),
353 },
354 signer_seeds,
355 ))
356 .map_err(|_| ErrorCodes::FailedToCloseAccount)?;
357
358 Ok(())
359 }
360 fn get_identifier(&self) -> String {
361 self.accounts.storage_account.identifier.clone()
362 }
363 fn mark_immutable(&mut self) {
364 self.accounts.storage_account.immutable = true;
365 }
366}
367
368impl MakeAccountImmutable for Context<'_, '_, '_, '_, MakeAccountImmutableV2<'_>> {
369 fn crank(&mut self, storage_used: u64) -> Result<Shades> {
370 let pre_crank_balance = self.accounts.stake_account.amount;
371 let new_balance;
372 let account_info = self.accounts.storage_account.to_account_info();
373 {
374 if let Some((emission_fee, crank_fee)) = crank(
375 &self.accounts.storage_config,
376 &mut self.accounts.storage_account,
377 account_info,
378 &self.accounts.emissions_wallet,
379 &self.accounts.stake_account,
380 &self.accounts.token_program,
381 &self.accounts.owner_ata,
382 &self.accounts.token_mint,
383 *self.bumps.get("storage_config").unwrap(),
384 storage_used,
385 )? {
386 msg!("First, crank for any outstanding mutable fees (cranker = user)");
387 new_balance = pre_crank_balance
388 .checked_sub(emission_fee.checked_add(crank_fee).unwrap())
389 .unwrap();
390 } else {
391 new_balance = pre_crank_balance;
392 }
393 }
394
395 Ok(new_balance)
396 }
397 fn charge_or_return_shades(&mut self, new_balance: Shades) -> Result<()> {
398 let storage_config = &self.accounts.storage_config;
399 let storage_account = &self.accounts.storage_account;
400
401 let storage_config_seeds = [
403 "storage-config".as_bytes(),
404 &[*self.bumps.get("storage_config").unwrap()],
405 ];
406 let signer_seeds: &[&[&[u8]]] = &[&storage_config_seeds];
407
408 let immutable_storage_requested: u128 = storage_account.storage as u128;
410 let cost_of_storage: u64 = immutable_storage_requested
411 .checked_mul(storage_config.shades_per_gib as u128)
412 .unwrap()
413 .checked_div(BYTES_PER_GIB as u128)
414 .unwrap()
415 .try_into()
416 .unwrap();
417 msg!(
418 "User has {} bytes of storage, costing {} shades",
419 immutable_storage_requested,
420 cost_of_storage
421 );
422
423 if cost_of_storage <= new_balance {
424 msg!("User's stake account contains enough funds to cover immutable fee");
425
426 let return_amount = new_balance.checked_sub(cost_of_storage).unwrap();
428 msg!(
429 "Returning {} shades to user, {} to emissions wallet",
430 return_amount,
431 cost_of_storage,
432 );
433
434 require_eq!(
436 new_balance,
437 cost_of_storage.checked_add(return_amount).unwrap(),
438 ErrorCodes::InvalidTokenTransferAmounts
439 );
440
441 if return_amount > 0 {
443 anchor_spl::token::transfer(
444 CpiContext::new_with_signer(
445 self.accounts.token_program.to_account_info(),
446 anchor_spl::token::Transfer {
447 from: self.accounts.stake_account.to_account_info(),
448 to: self.accounts.owner_ata.to_account_info(),
449 authority: self.accounts.storage_config.to_account_info(),
450 },
451 signer_seeds,
452 ),
453 return_amount,
454 )
455 .map_err(|_| ErrorCodes::FailedToReturnUserFunds)?;
456 }
457
458 anchor_spl::token::transfer(
460 CpiContext::new_with_signer(
461 self.accounts.token_program.to_account_info(),
462 anchor_spl::token::Transfer {
463 from: self.accounts.stake_account.to_account_info(),
464 to: self.accounts.emissions_wallet.to_account_info(),
465 authority: self.accounts.storage_config.to_account_info(),
466 },
467 signer_seeds,
468 ),
469 cost_of_storage,
470 )
471 .map_err(|_| ErrorCodes::FailedToTransferToEmissionsWallet)?;
472 } else {
473 let additional_shades: u64 = cost_of_storage.checked_sub(new_balance).unwrap();
474 msg!(
475 "User's stake account does not contain enough funds, charging user additional {} shades",
476 additional_shades
477 );
478
479 require_eq!(
481 cost_of_storage,
482 additional_shades.checked_add(new_balance).unwrap(),
483 ErrorCodes::InvalidTokenTransferAmounts
484 );
485
486 anchor_spl::token::transfer(
488 CpiContext::new_with_signer(
489 self.accounts.token_program.to_account_info(),
490 anchor_spl::token::Transfer {
491 from: self.accounts.owner_ata.to_account_info(),
492 to: self.accounts.emissions_wallet.to_account_info(),
493 authority: self.accounts.owner.to_account_info(),
494 },
495 signer_seeds,
496 ),
497 additional_shades,
498 )
499 .map_err(|_| ErrorCodes::FailedToTransferToEmissionsWalletFromUser)?;
500
501 anchor_spl::token::transfer(
503 CpiContext::new_with_signer(
504 self.accounts.token_program.to_account_info(),
505 anchor_spl::token::Transfer {
506 from: self.accounts.stake_account.to_account_info(),
507 to: self.accounts.emissions_wallet.to_account_info(),
508 authority: self.accounts.storage_config.to_account_info(),
509 },
510 signer_seeds,
511 ),
512 new_balance,
513 )
514 .map_err(|_| ErrorCodes::FailedToTransferToEmissionsWallet)?;
515 }
516
517 anchor_spl::token::close_account(CpiContext::new_with_signer(
519 self.accounts.token_program.to_account_info(),
520 anchor_spl::token::CloseAccount {
521 account: self.accounts.stake_account.to_account_info(),
522 destination: self.accounts.emissions_wallet.to_account_info(),
523 authority: self.accounts.storage_config.to_account_info(),
524 },
525 signer_seeds,
526 ))
527 .map_err(|_| ErrorCodes::FailedToCloseAccount)?;
528
529 Ok(())
530 }
531 fn get_identifier(&self) -> String {
532 self.accounts.storage_account.identifier.clone()
533 }
534 fn mark_immutable(&mut self) {
535 self.accounts.storage_account.immutable = true;
536 }
537}