Skip to main content

solmail_program/instructions/escrow/
refund.rs

1use pinocchio::{
2    account_info::AccountInfo,
3    program_error::ProgramError,
4    pubkey::{find_program_address, Pubkey},
5    sysvars::{clock::Clock, Sysvar},
6    ProgramResult,
7};
8
9use crate::{
10    constants::{ESCROW_SEED, ESCROW_VAULT_SEED},
11    error::SolMailError,
12    state::escrow::EscrowOrder,
13};
14
15/// Refund escrow after timeout
16///
17/// Accounts:
18/// 0. `[signer, writable]` Payer - original escrow creator (receives refund)
19/// 1. `[writable]` Escrow state account (PDA)
20/// 2. `[writable]` Escrow vault account (PDA)
21/// 3. `[]` System program
22///
23/// Data:
24/// - mail_id_low: u64 (8 bytes)
25/// - mail_id_high: u64 (8 bytes)
26pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
27    // Parse instruction data
28    if data.len() < 16 {
29        return Err(ProgramError::InvalidInstructionData);
30    }
31
32    let mail_id_low = u64::from_le_bytes(data[0..8].try_into().unwrap());
33    let mail_id_high = u64::from_le_bytes(data[8..16].try_into().unwrap());
34
35    // Parse accounts
36    let [payer, escrow_state, escrow_vault, _system_program] = accounts else {
37        return Err(ProgramError::NotEnoughAccountKeys);
38    };
39
40    // Verify payer is signer
41    if !payer.is_signer() {
42        return Err(SolMailError::MissingRequiredSignature.into());
43    }
44
45    // Build mail_id bytes for PDA verification
46    let mut mail_id_bytes = [0u8; 16];
47    mail_id_bytes[..8].copy_from_slice(&mail_id_low.to_le_bytes());
48    mail_id_bytes[8..].copy_from_slice(&mail_id_high.to_le_bytes());
49
50    // Verify escrow state PDA
51    let (escrow_pda, _) =
52        find_program_address(&[ESCROW_SEED, &mail_id_bytes], program_id);
53
54    if escrow_state.key() != &escrow_pda {
55        return Err(SolMailError::InvalidPda.into());
56    }
57
58    // Read escrow state
59    let escrow_data = unsafe { escrow_state.borrow_data_unchecked() };
60    let escrow = EscrowOrder::from_bytes(escrow_data)?;
61
62    // Verify payer matches original escrow creator
63    if payer.key().as_ref() != &escrow.payer {
64        return Err(SolMailError::InvalidPayer.into());
65    }
66
67    // Check not already delivered
68    if escrow.delivered {
69        return Err(SolMailError::AlreadyDelivered.into());
70    }
71
72    // Check timeout has passed
73    let clock = Clock::get()?;
74    if !escrow.is_timed_out(clock.unix_timestamp) {
75        return Err(SolMailError::TimeoutNotReached.into());
76    }
77
78    // Verify vault PDA
79    let (vault_pda, _) =
80        find_program_address(&[ESCROW_VAULT_SEED, &mail_id_bytes], program_id);
81
82    if escrow_vault.key() != &vault_pda {
83        return Err(SolMailError::InvalidPda.into());
84    }
85
86    // Zero escrow state data before closing (prevent revival attacks)
87    let state_data = unsafe { escrow_state.borrow_mut_data_unchecked() };
88    state_data.iter_mut().for_each(|b| *b = 0);
89
90    // Transfer all SOL from vault back to payer
91    let vault_lamports = escrow_vault.lamports();
92    unsafe {
93        *escrow_vault.borrow_mut_lamports_unchecked() = 0;
94        *payer.borrow_mut_lamports_unchecked() += vault_lamports;
95    }
96
97    // Close escrow state account - return rent to payer
98    let escrow_lamports = escrow_state.lamports();
99    unsafe {
100        *escrow_state.borrow_mut_lamports_unchecked() = 0;
101        *payer.borrow_mut_lamports_unchecked() += escrow_lamports;
102    }
103
104    Ok(())
105}