Skip to main content

solmail_program/instructions/escrow/
confirm.rs

1use pinocchio::{
2    account_info::AccountInfo,
3    program_error::ProgramError,
4    pubkey::{find_program_address, Pubkey},
5    ProgramResult,
6};
7
8use crate::{
9    constants::{ESCROW_SEED, ESCROW_VAULT_SEED, ORACLE_PUBKEY, TREASURY_SEED},
10    error::SolMailError,
11    state::escrow::EscrowOrder,
12};
13
14/// Confirm delivery and release funds to treasury
15///
16/// Accounts:
17/// 0. `[signer]` Oracle - authorized backend service
18/// 1. `[writable]` Escrow state account (PDA)
19/// 2. `[writable]` Escrow vault account (PDA)
20/// 3. `[writable]` Treasury account (PDA)
21/// 4. `[]` 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 [oracle, escrow_state, escrow_vault, treasury, _system_program] = accounts else {
37        return Err(ProgramError::NotEnoughAccountKeys);
38    };
39
40    // Verify oracle is signer
41    if !oracle.is_signer() {
42        return Err(SolMailError::MissingRequiredSignature.into());
43    }
44
45    // Verify oracle is authorized
46    if oracle.key().as_ref() != &ORACLE_PUBKEY {
47        return Err(SolMailError::UnauthorizedOracle.into());
48    }
49
50    // Build mail_id bytes for PDA verification
51    let mut mail_id_bytes = [0u8; 16];
52    mail_id_bytes[..8].copy_from_slice(&mail_id_low.to_le_bytes());
53    mail_id_bytes[8..].copy_from_slice(&mail_id_high.to_le_bytes());
54
55    // Verify escrow state PDA
56    let (escrow_pda, _) =
57        find_program_address(&[ESCROW_SEED, &mail_id_bytes], program_id);
58
59    if escrow_state.key() != &escrow_pda {
60        return Err(SolMailError::InvalidPda.into());
61    }
62
63    // Read and validate escrow state
64    let escrow_data = unsafe { escrow_state.borrow_mut_data_unchecked() };
65    let escrow = EscrowOrder::from_bytes_mut(escrow_data)?;
66
67    // Check not already delivered
68    if escrow.delivered {
69        return Err(SolMailError::AlreadyDelivered.into());
70    }
71
72    // Verify vault PDA
73    let (vault_pda, _vault_bump) =
74        find_program_address(&[ESCROW_VAULT_SEED, &mail_id_bytes], program_id);
75
76    if escrow_vault.key() != &vault_pda {
77        return Err(SolMailError::InvalidPda.into());
78    }
79
80    // Verify treasury PDA
81    let (treasury_pda, _) = find_program_address(&[TREASURY_SEED], program_id);
82
83    if treasury.key() != &treasury_pda {
84        return Err(SolMailError::InvalidPda.into());
85    }
86
87    // Mark as delivered
88    escrow.delivered = true;
89
90    // Zero escrow state data before closing (prevent revival attacks)
91    let state_data = unsafe { escrow_state.borrow_mut_data_unchecked() };
92    state_data.iter_mut().for_each(|b| *b = 0);
93
94    // Transfer SOL from vault to treasury
95    let vault_lamports = escrow_vault.lamports();
96
97    // Use raw lamport transfer (subtract from vault, add to treasury)
98    unsafe {
99        *escrow_vault.borrow_mut_lamports_unchecked() = 0;
100        *treasury.borrow_mut_lamports_unchecked() += vault_lamports;
101    }
102
103    // Close escrow state account - return rent to treasury
104    let escrow_lamports = escrow_state.lamports();
105    unsafe {
106        *escrow_state.borrow_mut_lamports_unchecked() = 0;
107        *treasury.borrow_mut_lamports_unchecked() += escrow_lamports;
108    }
109
110    Ok(())
111}