oil_api/state/
code.rs

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