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);