Skip to main content

solmail_program/instructions/credits/
deposit.rs

1use pinocchio::{
2    account_info::AccountInfo,
3    instruction::Signer,
4    program_error::ProgramError,
5    pubkey::{find_program_address, Pubkey},
6    seeds,
7    sysvars::{rent::Rent, Sysvar},
8    ProgramResult,
9};
10use pinocchio_system::instructions::{CreateAccount, Transfer};
11
12use crate::{
13    constants::{CREDITS_SEED, CREDITS_VAULT_SOL_SEED, MIN_ESCROW_LAMPORTS},
14    error::SolMailError,
15    state::credits::{UserCredits, USER_CREDITS_SIZE},
16};
17
18/// Deposit SOL credits
19///
20/// Accounts:
21/// 0. `[signer, writable]` User - depositor
22/// 1. `[writable]` User credits account (PDA)
23/// 2. `[writable]` Credits vault (PDA) - holds pooled SOL
24/// 3. `[]` System program
25///
26/// Data:
27/// - amount: u64 (8 bytes)
28pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
29    // Parse instruction data
30    if data.len() < 8 {
31        return Err(ProgramError::InvalidInstructionData);
32    }
33
34    let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
35
36    // Validate amount
37    if amount < MIN_ESCROW_LAMPORTS {
38        return Err(SolMailError::AmountBelowMinimum.into());
39    }
40
41    // Parse accounts
42    let [user, user_credits, credits_vault, _system_program] = accounts else {
43        return Err(ProgramError::NotEnoughAccountKeys);
44    };
45
46    // Verify user is signer
47    if !user.is_signer() {
48        return Err(SolMailError::MissingRequiredSignature.into());
49    }
50
51    // Derive user credits PDA
52    let user_key_bytes = user.key();
53    let (credits_pda, credits_bump) =
54        find_program_address(&[CREDITS_SEED, user_key_bytes.as_ref()], program_id);
55
56    if user_credits.key() != &credits_pda {
57        return Err(SolMailError::InvalidPda.into());
58    }
59
60    // Derive credits vault PDA
61    let (vault_pda, vault_bump) =
62        find_program_address(&[CREDITS_VAULT_SOL_SEED], program_id);
63
64    if credits_vault.key() != &vault_pda {
65        return Err(SolMailError::InvalidPda.into());
66    }
67
68    // Check if user credits account needs to be created
69    let is_new_account = user_credits.data_len() == 0;
70
71    if is_new_account {
72        // Calculate rent for credits account
73        let rent = Rent::get()?;
74        let rent_lamports = rent.minimum_balance(USER_CREDITS_SIZE);
75
76        // Build signer seeds for credits PDA
77        let credits_bump_ref = &[credits_bump];
78        let credits_seeds = seeds!(CREDITS_SEED, user_key_bytes.as_ref(), credits_bump_ref);
79        let credits_signer = Signer::from(&credits_seeds);
80
81        // Create user credits account
82        CreateAccount {
83            from: user,
84            to: user_credits,
85            lamports: rent_lamports,
86            space: USER_CREDITS_SIZE as u64,
87            owner: program_id,
88        }
89        .invoke_signed(&[credits_signer])?;
90
91        // Initialize user credits
92        let credits_data = unsafe { user_credits.borrow_mut_data_unchecked() };
93        let credits = UserCredits::from_bytes_mut(credits_data)?;
94        credits.owner.copy_from_slice(user.key().as_ref());
95        credits.sol_balance = 0;
96        credits.usdc_balance = 0;
97        credits.bump = credits_bump;
98    }
99
100    // Check if credits vault needs to be created
101    if credits_vault.lamports() == 0 {
102        // Create vault account with minimal rent
103        let rent = Rent::get()?;
104        let rent_lamports = rent.minimum_balance(0);
105
106        // Build signer seeds for vault PDA
107        let vault_bump_ref = &[vault_bump];
108        let vault_seeds = seeds!(CREDITS_VAULT_SOL_SEED, vault_bump_ref);
109        let vault_signer = Signer::from(&vault_seeds);
110
111        CreateAccount {
112            from: user,
113            to: credits_vault,
114            lamports: rent_lamports,
115            space: 0,
116            owner: program_id,
117        }
118        .invoke_signed(&[vault_signer])?;
119    }
120
121    // Transfer SOL from user to vault
122    Transfer {
123        from: user,
124        to: credits_vault,
125        lamports: amount,
126    }
127    .invoke()?;
128
129    // Update user credits balance
130    let credits_data = unsafe { user_credits.borrow_mut_data_unchecked() };
131    let credits = UserCredits::from_bytes_mut(credits_data)?;
132    credits.add_sol(amount)?;
133
134    Ok(())
135}