oil_api/state/
whitelist.rs

1use serde::{Deserialize, Serialize};
2use solana_program::program_error::ProgramError;
3use solana_program::log::sol_log;
4use steel::*;
5
6use super::OilAccount;
7
8/// Whitelist tracks access codes for pre-mine phase.
9/// Each code can be used by multiple wallets - codes are shared.
10/// PDA includes only code_hash (not tied to specific wallets).
11#[repr(C)]
12#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable, Serialize, Deserialize)]
13pub struct Whitelist {
14    /// The code hash (first 32 bytes of keccak256 hash of the code string)
15    pub code_hash: [u8; 32],
16    
17    /// Number of times this code has been used (optional tracking)
18    pub usage_count: u64,
19}
20
21impl Whitelist {
22    /// Derives the PDA for a Whitelist account.
23    /// PDA seeds: [b"whitelist", code_hash]
24    /// This allows the same code to be used by multiple wallets.
25    pub fn pda(code_hash: [u8; 32]) -> (Pubkey, u8) {
26        use crate::ID;
27        Pubkey::find_program_address(&[b"whitelist", &code_hash], &ID)
28    }
29
30    /// Validates and processes a premine access code for a new transaction.
31    /// 
32    /// This function:
33    /// - Requires access code if pre-mine is active
34    /// - Validates the code account exists and matches the provided hash
35    /// - Increments the code's usage count
36    /// - Ignores access code if pre-mine is not active (not an error)
37    /// 
38    /// Returns Ok(()) if validation passes or code is ignored, Err if validation fails.
39    pub fn validate_premine_code<'a>(
40        is_premine: bool,
41        has_access_code: bool,
42        access_code_hash: [u8; 32],
43        whitelist_info_opt: Option<&AccountInfo<'a>>,
44    ) -> Result<(), ProgramError> {
45        if is_premine {
46            if !has_access_code {
47                return Err(ProgramError::InvalidArgument); // Access code required during pre-mine
48            }
49            
50            // Validate access code: check if Whitelist PDA exists
51            let whitelist_info = whitelist_info_opt.ok_or(ProgramError::NotEnoughAccountKeys)?;
52            
53            // Derive PDA using code_hash only (not tied to wallet)
54            let (whitelist_pda, _) = Self::pda(access_code_hash);
55            whitelist_info.has_address(&whitelist_pda)?;
56            
57            // Check if account exists (has data)
58            if whitelist_info.data_is_empty() {
59                return Err(ProgramError::InvalidArgument); // Access code not found
60            }
61            
62            // Validate the code hash matches
63            let whitelist = whitelist_info.as_account::<Whitelist>(&crate::ID)?;
64            if whitelist.code_hash != access_code_hash {
65                return Err(ProgramError::InvalidArgument); // Access code hash mismatch
66            }
67            
68            // Increment usage count (optional tracking)
69            let whitelist_mut = whitelist_info.as_account_mut::<Whitelist>(&crate::ID)?;
70            whitelist_mut.usage_count = whitelist_mut.usage_count.saturating_add(1);
71            
72            sol_log(&format!(
73                "Pre-mine: access code validated, usage_count={}",
74                whitelist_mut.usage_count
75            ));
76        } else if has_access_code {
77            // Access code provided but not in pre-mine - ignore it (not an error)
78            sol_log("Access code provided but pre-mine is not active - ignoring");
79        }
80        
81        Ok(())
82    }
83}
84
85account!(OilAccount, Whitelist);