use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint, entrypoint::ProgramResult, hash::hashv, instruction::Instruction, program::{get_return_data, invoke, invoke_signed}, program_error::ProgramError, program_memory::sol_memcmp, pubkey::Pubkey, rent::Rent, system_instruction, system_program, sysvar::{instructions::{load_current_index_checked, load_instruction_at_checked}, Sysvar}, }; use std::{convert::TryFrom, str::FromStr}; pub mod settings; solana_program::declare_id!("FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm"); entrypoint!(process_instruction); const MAGIC: &[u8; 5] = b"SHiNE"; const FORMAT_MAJOR: u8 = 1; const FORMAT_MINOR: u8 = 0; const MAX_SYNC_SERVERS: usize = 32; const MAX_SESSIONS: usize = 64; const MAX_SESSION_NAME_LEN: usize = 64; const MAX_AUTO_REALLOC_INCREASE: usize = 10_000; const ZERO_HASH: [u8; 32] = [0; 32]; const BLOCK_TYPE_ROOT_KEY: u8 = 1; const BLOCK_TYPE_DEVICE_KEY: u8 = 2; const BLOCK_TYPE_BLOCKCHAIN_REGISTRY: u8 = 3; const BLOCK_TYPE_SERVER_PROFILE: u8 = 30; const BLOCK_TYPE_ACCESS_SERVERS: u8 = 40; const BLOCK_TYPE_SESSIONS: u8 = 50; const BLOCK_TYPE_TRUSTED_STATE: u8 = 70; const BLOCK_VERSION_0: u8 = 0; const BLOCKCHAIN_TYPE_MAIN_USER: u8 = 1; const SESSIONS_MODE_MIXED: u8 = 1; const SESSIONS_MODE_PDA_ONLY: u8 = 10; const SESSION_TYPE_USER: u8 = 1; const SESSION_TYPE_SUBSERVER: u8 = 100; const LAST_BLOCK_STATE_PREFIX: &[u8] = b"SHiNE_LAST_BLOCK"; const IX_INIT_USERS_ECONOMY_CONFIG: u8 = 1; const IX_UPDATE_USERS_ECONOMY_CONFIG: u8 = 2; const IX_CREATE_USER_PDA: u8 = 3; const IX_UPDATE_USER_PDA: u8 = 4; const LOGIN_GUARD_IX_CLASSIFY_LOGIN: u8 = 1; #[repr(u32)] #[derive(Clone, Copy, Debug)] enum ShineUsersError { InvalidInstruction = 1, InvalidSigner = 2, InvalidPdaAddress = 3, UserAlreadyExists = 4, EmptyPdaData = 5, InvalidRecordData = 6, InvalidRecordMagic = 7, InvalidRecordFormat = 8, InvalidRecordLength = 9, InvalidLogin = 10, InvalidLimitIncrement = 11, InvalidFeeReceiver = 12, InvalidLoginGuardResponse = 13, PremiumLogin = 14, TrademarkLoginRequiresReview = 15, InvalidVersion = 16, InvalidPrevHash = 17, ImmutableFieldChanged = 18, BalanceDecrease = 19, InvalidSignature = 20, RecordTooLarge = 21, MathOverflow = 22, SystemAlreadyInitialized = 23, MissingRequiredSignature = 24, InvalidSystemProgram = 25, InvalidAccountOwner = 26, InvalidAccountData = 27, } impl From for ProgramError { fn from(value: ShineUsersError) -> Self { ProgramError::Custom(value as u32) } } macro_rules! require { ($cond:expr, $err:expr) => { if !($cond) { return Err(ProgramError::from($err)); } }; } macro_rules! require_keys_eq { ($left:expr, $right:expr, $err:expr) => { if $left != $right { return Err(ProgramError::from($err)); } }; } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SessionRecord { pub session_type: u8, pub session_version: u8, pub session_name: String, pub session_pub_key: Pubkey, } #[derive(Clone, Debug)] pub struct UserMutableFields { pub device_key: Pubkey, pub blockchain_public_key: Pubkey, pub blockchain_name: String, pub used_bytes: u64, pub last_block_number: u32, pub last_block_hash: [u8; 32], pub last_block_signature: [u8; 64], pub arweave_tx_id: String, pub is_server: bool, pub address_format_type: u8, pub address_format_version: u8, pub server_address: String, pub sync_servers: Vec, pub access_servers: Vec, pub sessions_mode: u8, pub sessions: Vec, pub trusted_count: u8, } #[derive(Clone, Debug)] pub struct CreateUserPdaArgs { pub login: String, pub root_key: Pubkey, pub created_at_ms: u64, pub additional_limit: u64, pub fields: UserMutableFields, pub signature: [u8; 64], } #[derive(Clone, Debug)] pub struct UpdateUserPdaArgs { pub login: String, pub root_key: Pubkey, pub created_at_ms: u64, pub updated_at_ms: u64, pub version: u32, pub prev_hash: [u8; 32], pub additional_limit: u64, pub fields: UserMutableFields, pub signature: [u8; 64], } #[derive(Clone, Debug)] pub struct UpdateUsersEconomyConfigArgs { pub registration_fee_lamports: u64, pub lamports_per_limit_step: u64, pub start_bonus_limit: u64, } #[derive(Clone, Debug)] pub struct UsersEconomyConfigState { pub version: u8, pub registration_fee_lamports: u64, pub lamports_per_limit_step: u64, pub start_bonus_limit: u64, } #[derive(Clone, Debug)] pub struct BlockchainRecord { pub blockchain_type: u8, pub blockchain_name: String, pub blockchain_public_key: Pubkey, pub paid_limit_bytes: u64, pub used_bytes: u64, pub last_block_number: u32, pub last_block_hash: [u8; 32], pub last_block_signature: [u8; 64], pub arweave_tx_id: String, } #[derive(Clone, Debug)] pub struct UserRecord { pub created_at_ms: u64, pub updated_at_ms: u64, pub record_number: u32, pub prev_record_hash: [u8; 32], pub login: String, pub root_key: Pubkey, pub device_key: Pubkey, pub blockchain: BlockchainRecord, pub is_server: bool, pub address_format_type: u8, pub address_format_version: u8, pub server_address: String, pub sync_servers: Vec, pub access_servers: Vec, pub sessions_mode: u8, pub sessions: Vec, pub trusted_count: u8, pub signature: [u8; 64], } struct Reader<'a> { data: &'a [u8], cursor: usize, } impl<'a> Reader<'a> { fn new(data: &'a [u8]) -> Self { Self { data, cursor: 0 } } fn read_u8(&mut self) -> Result { let v = *self.data.get(self.cursor).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; self.cursor += 1; Ok(v) } fn read_u32(&mut self) -> Result { let end = self.cursor.checked_add(4).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; self.cursor = end; Ok(u32::from_le_bytes([s[0], s[1], s[2], s[3]])) } fn read_u64(&mut self) -> Result { let end = self.cursor.checked_add(8).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; self.cursor = end; Ok(u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]])) } fn read_fixed_32(&mut self) -> Result<[u8; 32], ProgramError> { let end = self.cursor.checked_add(32).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; self.cursor = end; <[u8; 32]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidInstruction)) } fn read_fixed_64(&mut self) -> Result<[u8; 64], ProgramError> { let end = self.cursor.checked_add(64).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; self.cursor = end; <[u8; 64]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidInstruction)) } fn read_pubkey(&mut self) -> Result { Ok(Pubkey::new_from_array(self.read_fixed_32()?)) } fn read_string_u8(&mut self) -> Result { let len = self.read_u8()? as usize; let end = self.cursor.checked_add(len).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?; self.cursor = end; std::str::from_utf8(s).map(|v| v.to_string()).map_err(|_| ProgramError::from(ShineUsersError::InvalidInstruction)) } fn finish(self) -> Result<(), ProgramError> { require!(self.cursor == self.data.len(), ShineUsersError::InvalidInstruction); Ok(()) } } fn process_instruction(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { let mut r = Reader::new(instruction_data); let tag = r.read_u8()?; match tag { IX_INIT_USERS_ECONOMY_CONFIG => { r.finish()?; process_init_users_economy_config(program_id, accounts) } IX_UPDATE_USERS_ECONOMY_CONFIG => { let args = UpdateUsersEconomyConfigArgs { registration_fee_lamports: r.read_u64()?, lamports_per_limit_step: r.read_u64()?, start_bonus_limit: r.read_u64()?, }; r.finish()?; process_update_users_economy_config(program_id, accounts, args) } IX_CREATE_USER_PDA => { let args = parse_create_args(&mut r)?; r.finish()?; process_create_user_pda(program_id, accounts, args) } IX_UPDATE_USER_PDA => { let args = parse_update_args(&mut r)?; r.finish()?; process_update_user_pda(program_id, accounts, args) } _ => Err(ProgramError::from(ShineUsersError::InvalidInstruction)), } } fn parse_create_args(r: &mut Reader<'_>) -> Result { Ok(CreateUserPdaArgs { login: r.read_string_u8()?, root_key: r.read_pubkey()?, created_at_ms: r.read_u64()?, additional_limit: r.read_u64()?, fields: parse_fields(r)?, signature: r.read_fixed_64()?, }) } fn parse_update_args(r: &mut Reader<'_>) -> Result { Ok(UpdateUserPdaArgs { login: r.read_string_u8()?, root_key: r.read_pubkey()?, created_at_ms: r.read_u64()?, updated_at_ms: r.read_u64()?, version: r.read_u32()?, prev_hash: r.read_fixed_32()?, additional_limit: r.read_u64()?, fields: parse_fields(r)?, signature: r.read_fixed_64()?, }) } fn parse_fields(r: &mut Reader<'_>) -> Result { let device_key = r.read_pubkey()?; let blockchain_public_key = r.read_pubkey()?; let blockchain_name = r.read_string_u8()?; let used_bytes = r.read_u64()?; let last_block_number = r.read_u32()?; let last_block_hash = r.read_fixed_32()?; let last_block_signature = r.read_fixed_64()?; let arweave_tx_id = r.read_string_u8()?; let is_server = r.read_u8()? == 1; let (address_format_type, address_format_version, server_address, sync_servers) = if is_server { let aft = r.read_u8()?; let afv = r.read_u8()?; let addr = r.read_string_u8()?; let cnt = r.read_u8()? as usize; let mut list = Vec::with_capacity(cnt); for _ in 0..cnt { list.push(r.read_string_u8()?); } (aft, afv, addr, list) } else { (0, 0, String::new(), Vec::new()) }; let access_count = r.read_u8()? as usize; let mut access_servers = Vec::with_capacity(access_count); for _ in 0..access_count { access_servers.push(r.read_string_u8()?); } let sessions_mode = r.read_u8()?; let sessions_count = r.read_u8()? as usize; let mut sessions = Vec::with_capacity(sessions_count); for _ in 0..sessions_count { sessions.push(SessionRecord { session_type: r.read_u8()?, session_version: r.read_u8()?, session_name: r.read_string_u8()?, session_pub_key: r.read_pubkey()?, }); } let trusted_count = r.read_u8()?; Ok(UserMutableFields { device_key, blockchain_public_key, blockchain_name, used_bytes, last_block_number, last_block_hash, last_block_signature, arweave_tx_id, is_server, address_format_type, address_format_version, server_address, sync_servers, access_servers, sessions_mode, sessions, trusted_count, }) } fn process_init_users_economy_config(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let mut it = accounts.iter(); let signer = next_account_info(&mut it)?; let users_economy_config_pda = next_account_info(&mut it)?; let system_program_ai = next_account_info(&mut it)?; require!(signer.is_signer, ShineUsersError::InvalidSigner); require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram); let (expected_pda, bump) = find_users_economy_config_pda(program_id); require_keys_eq!(expected_pda, *users_economy_config_pda.key, ShineUsersError::InvalidPdaAddress); require!(users_economy_config_pda.owner == &system_program::id(), ShineUsersError::SystemAlreadyInitialized); require!(users_economy_config_pda.data_is_empty(), ShineUsersError::SystemAlreadyInitialized); let state = UsersEconomyConfigState { version: 1, registration_fee_lamports: settings::START_REGISTRATION_FEE_LAMPORTS, lamports_per_limit_step: settings::START_LAMPORTS_PER_LIMIT_STEP, start_bonus_limit: settings::START_BONUS_LIMIT, }; let data = serialize_users_economy_config(&state); create_pda_account( signer, users_economy_config_pda, system_program_ai, program_id, &[settings::USERS_ECONOMY_CONFIG_SEED, &[bump]], data.len(), )?; write_pda_exact(users_economy_config_pda, &data)?; Ok(()) } fn process_update_users_economy_config(program_id: &Pubkey, accounts: &[AccountInfo], args: UpdateUsersEconomyConfigArgs) -> ProgramResult { let mut it = accounts.iter(); let signer = next_account_info(&mut it)?; let users_economy_config_pda = next_account_info(&mut it)?; require!(signer.is_signer, ShineUsersError::InvalidSigner); let dao_authority = Pubkey::from_str(settings::DAO_AUTHORITY).map_err(|_| ProgramError::from(ShineUsersError::InvalidSigner))?; require_keys_eq!(dao_authority, *signer.key, ShineUsersError::InvalidSigner); let (expected_pda, _) = find_users_economy_config_pda(program_id); require_keys_eq!(expected_pda, *users_economy_config_pda.key, ShineUsersError::InvalidPdaAddress); require!(users_economy_config_pda.owner == program_id, ShineUsersError::InvalidPdaAddress); require!(args.lamports_per_limit_step > 0, ShineUsersError::InvalidRecordData); let mut state = read_users_economy_config(users_economy_config_pda)?; state.registration_fee_lamports = args.registration_fee_lamports; state.lamports_per_limit_step = args.lamports_per_limit_step; state.start_bonus_limit = args.start_bonus_limit; write_pda_exact(users_economy_config_pda, &serialize_users_economy_config(&state))?; Ok(()) } fn process_create_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args: CreateUserPdaArgs) -> ProgramResult { let mut it = accounts.iter(); let signer = next_account_info(&mut it)?; let user_pda = next_account_info(&mut it)?; let system_program_ai = next_account_info(&mut it)?; let inflow_vault = next_account_info(&mut it)?; let instructions_sysvar = next_account_info(&mut it)?; let users_economy_config_pda = next_account_info(&mut it)?; let login_guard_program = next_account_info(&mut it)?; require!(signer.is_signer, ShineUsersError::InvalidSigner); require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner); require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram); validate_login(&args.login)?; validate_fields(&args.fields)?; validate_inflow_vault(inflow_vault)?; require!(args.additional_limit % settings::LIMIT_STEP == 0, ShineUsersError::InvalidLimitIncrement); require_keys_eq!(*login_guard_program.key, Pubkey::from_str(settings::SHINE_LOGIN_GUARD_PROGRAM_ID).map_err(|_| ProgramError::from(ShineUsersError::InvalidLoginGuardResponse))?, ShineUsersError::InvalidLoginGuardResponse); classify_login_or_fail(login_guard_program, &args.login)?; validate_users_economy_config_pda(program_id, users_economy_config_pda)?; let economy = read_users_economy_config(users_economy_config_pda)?; let login_seed = login_seed_normalized(&args.login); let (expected_pda, bump) = find_user_pda(program_id, &login_seed); require_keys_eq!(expected_pda, *user_pda.key, ShineUsersError::InvalidPdaAddress); require!(user_pda.owner == &system_program::id(), ShineUsersError::UserAlreadyExists); require!(user_pda.data_is_empty(), ShineUsersError::UserAlreadyExists); let start_balance = economy.start_bonus_limit.checked_add(args.additional_limit).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?; let mut record = UserRecord { created_at_ms: args.created_at_ms, updated_at_ms: args.created_at_ms, record_number: 0, prev_record_hash: ZERO_HASH, login: args.login, root_key: args.root_key, device_key: args.fields.device_key, blockchain: BlockchainRecord { blockchain_type: BLOCKCHAIN_TYPE_MAIN_USER, blockchain_name: args.fields.blockchain_name, blockchain_public_key: args.fields.blockchain_public_key, paid_limit_bytes: start_balance, used_bytes: args.fields.used_bytes, last_block_number: args.fields.last_block_number, last_block_hash: args.fields.last_block_hash, last_block_signature: args.fields.last_block_signature, arweave_tx_id: args.fields.arweave_tx_id, }, is_server: args.fields.is_server, address_format_type: args.fields.address_format_type, address_format_version: args.fields.address_format_version, server_address: args.fields.server_address, sync_servers: args.fields.sync_servers, access_servers: args.fields.access_servers, sessions_mode: args.fields.sessions_mode, sessions: args.fields.sessions, trusted_count: args.fields.trusted_count, signature: [0; 64], }; validate_blockchain_limits(&record.blockchain, 0, 0, true)?; verify_last_block_state_signature(instructions_sysvar, &record)?; let unsigned = serialize_unsigned_record(&record)?; let unsigned_hash = hashv(&[&unsigned]); record.signature = verify_record_signature_hash(instructions_sysvar, &record.root_key, &args.signature, unsigned_hash.as_ref())?; let serialized = pad_to_fixed_size(serialize_full_record(&record)?, settings::USER_PDA_SPACE)?; create_pda_account( signer, user_pda, system_program_ai, program_id, &[settings::USER_PDA_SEED_PREFIX.as_bytes(), login_seed.as_bytes(), &[bump]], settings::USER_PDA_SPACE, )?; write_pda_exact(user_pda, &serialized)?; let total_fee = economy.registration_fee_lamports.checked_add(limit_fee_lamports(args.additional_limit, economy.lamports_per_limit_step)?).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?; transfer_lamports(signer, inflow_vault, system_program_ai, total_fee)?; Ok(()) } fn process_update_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args: UpdateUserPdaArgs) -> ProgramResult { let mut it = accounts.iter(); let signer = next_account_info(&mut it)?; let user_pda = next_account_info(&mut it)?; let system_program_ai = next_account_info(&mut it)?; let inflow_vault = next_account_info(&mut it)?; let instructions_sysvar = next_account_info(&mut it)?; let users_economy_config_pda = next_account_info(&mut it)?; require!(signer.is_signer, ShineUsersError::InvalidSigner); require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner); require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram); validate_login(&args.login)?; validate_fields(&args.fields)?; validate_inflow_vault(inflow_vault)?; require!(args.additional_limit % settings::LIMIT_STEP == 0, ShineUsersError::InvalidLimitIncrement); validate_users_economy_config_pda(program_id, users_economy_config_pda)?; let economy = read_users_economy_config(users_economy_config_pda)?; let normalized_login = login_seed_normalized(&args.login); require_keys_eq!(find_user_pda(program_id, &normalized_login).0, *user_pda.key, ShineUsersError::InvalidPdaAddress); require!(user_pda.owner == program_id, ShineUsersError::InvalidPdaAddress); let old_record = deserialize_record_from_pda(&read_pda_all(user_pda)?)?; require!(old_record.login == args.login, ShineUsersError::ImmutableFieldChanged); require!(old_record.created_at_ms == args.created_at_ms, ShineUsersError::ImmutableFieldChanged); require_keys_eq!(old_record.root_key, args.root_key, ShineUsersError::ImmutableFieldChanged); require!(args.version == old_record.record_number.saturating_add(1), ShineUsersError::InvalidVersion); require!(hash_unsigned_record(&old_record)? == args.prev_hash, ShineUsersError::InvalidPrevHash); require!(args.fields.blockchain_name == old_record.blockchain.blockchain_name, ShineUsersError::ImmutableFieldChanged); require_keys_eq!(args.fields.blockchain_public_key, old_record.blockchain.blockchain_public_key, ShineUsersError::ImmutableFieldChanged); let new_balance = old_record.blockchain.paid_limit_bytes.checked_add(args.additional_limit).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?; require!(new_balance >= old_record.blockchain.paid_limit_bytes, ShineUsersError::BalanceDecrease); let blockchain_state_unchanged = old_record.blockchain.used_bytes == args.fields.used_bytes && old_record.blockchain.last_block_number == args.fields.last_block_number && old_record.blockchain.last_block_hash == args.fields.last_block_hash && old_record.blockchain.last_block_signature == args.fields.last_block_signature && old_record.blockchain.arweave_tx_id == args.fields.arweave_tx_id; let mut new_record = build_update_record(&old_record, &args, new_balance)?; validate_blockchain_limits(&new_record.blockchain, old_record.blockchain.used_bytes, old_record.blockchain.last_block_number, false)?; if !blockchain_state_unchanged { verify_last_block_state_signature(instructions_sysvar, &new_record)?; } let unsigned = serialize_unsigned_record(&new_record)?; let unsigned_hash = hashv(&[&unsigned]); new_record.signature = verify_record_signature_hash(instructions_sysvar, &old_record.root_key, &args.signature, unsigned_hash.as_ref())?; let serialized = serialize_full_record(&new_record)?; ensure_pda_size_and_rent(user_pda, signer, system_program_ai, serialized.len())?; write_pda_prefix(user_pda, &serialized)?; let topup_fee = limit_fee_lamports(args.additional_limit, economy.lamports_per_limit_step)?; if topup_fee > 0 { transfer_lamports(signer, inflow_vault, system_program_ai, topup_fee)?; } Ok(()) } fn build_update_record(old_record: &UserRecord, args: &UpdateUserPdaArgs, new_balance: u64) -> Result { Ok(UserRecord { created_at_ms: old_record.created_at_ms, updated_at_ms: args.updated_at_ms, record_number: args.version, prev_record_hash: args.prev_hash, login: old_record.login.clone(), root_key: old_record.root_key, device_key: args.fields.device_key, blockchain: BlockchainRecord { blockchain_type: old_record.blockchain.blockchain_type, blockchain_name: args.fields.blockchain_name.clone(), blockchain_public_key: args.fields.blockchain_public_key, paid_limit_bytes: new_balance, used_bytes: args.fields.used_bytes, last_block_number: args.fields.last_block_number, last_block_hash: args.fields.last_block_hash, last_block_signature: args.fields.last_block_signature, arweave_tx_id: args.fields.arweave_tx_id.clone(), }, is_server: args.fields.is_server, address_format_type: args.fields.address_format_type, address_format_version: args.fields.address_format_version, server_address: args.fields.server_address.clone(), sync_servers: args.fields.sync_servers.clone(), access_servers: args.fields.access_servers.clone(), sessions_mode: args.fields.sessions_mode, sessions: args.fields.sessions.clone(), trusted_count: args.fields.trusted_count, signature: [0; 64], }) } fn classify_login_or_fail(login_guard_program: &AccountInfo, login: &str) -> ProgramResult { let login_guard_program_id = Pubkey::from_str(settings::SHINE_LOGIN_GUARD_PROGRAM_ID) .map_err(|_| ProgramError::from(ShineUsersError::InvalidLoginGuardResponse))?; require_keys_eq!(*login_guard_program.key, login_guard_program_id, ShineUsersError::InvalidLoginGuardResponse); let mut data = Vec::with_capacity(1 + 4 + login.len()); data.push(LOGIN_GUARD_IX_CLASSIFY_LOGIN); data.extend_from_slice(&(login.len() as u32).to_le_bytes()); data.extend_from_slice(login.as_bytes()); let ix = Instruction { program_id: login_guard_program_id, accounts: vec![], data }; invoke(&ix, &[login_guard_program.clone()])?; let (program_id, raw) = get_return_data().ok_or(ProgramError::from(ShineUsersError::InvalidLoginGuardResponse))?; require_keys_eq!(program_id, login_guard_program_id, ShineUsersError::InvalidLoginGuardResponse); require!(raw.len() == 4, ShineUsersError::InvalidLoginGuardResponse); let class = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]); match class { 0 => Ok(()), 1 => Err(ProgramError::from(ShineUsersError::PremiumLogin)), 2 => Err(ProgramError::from(ShineUsersError::TrademarkLoginRequiresReview)), _ => Err(ProgramError::from(ShineUsersError::InvalidLoginGuardResponse)), } } fn serialize_users_economy_config(state: &UsersEconomyConfigState) -> Vec { let mut out = Vec::with_capacity(1 + 8 + 8 + 8); out.push(state.version); out.extend_from_slice(&state.registration_fee_lamports.to_le_bytes()); out.extend_from_slice(&state.lamports_per_limit_step.to_le_bytes()); out.extend_from_slice(&state.start_bonus_limit.to_le_bytes()); out } fn validate_users_economy_config_pda(program_id: &Pubkey, pda: &AccountInfo) -> ProgramResult { let (expected_pda, _) = find_users_economy_config_pda(program_id); require_keys_eq!(expected_pda, *pda.key, ShineUsersError::InvalidPdaAddress); require!(pda.owner == program_id, ShineUsersError::InvalidPdaAddress); Ok(()) } fn read_users_economy_config(pda: &AccountInfo) -> Result { let raw = read_pda_all(pda)?; require!(!raw.is_empty(), ShineUsersError::EmptyPdaData); require!(raw.len() >= 25, ShineUsersError::InvalidAccountData); Ok(UsersEconomyConfigState { version: raw[0], registration_fee_lamports: u64::from_le_bytes(raw[1..9].try_into().unwrap()), lamports_per_limit_step: u64::from_le_bytes(raw[9..17].try_into().unwrap()), start_bonus_limit: u64::from_le_bytes(raw[17..25].try_into().unwrap()), }) } fn deserialize_record_from_pda(raw: &[u8]) -> Result { require!(raw.len() >= 9, ShineUsersError::InvalidRecordData); require!(sol_memcmp(&raw[0..5], MAGIC, 5) == 0, ShineUsersError::InvalidRecordMagic); require!(raw[5] == FORMAT_MAJOR && raw[6] == FORMAT_MINOR, ShineUsersError::InvalidRecordFormat); let record_len = u16::from_le_bytes([raw[7], raw[8]]) as usize; require!(record_len >= 9 + 64, ShineUsersError::InvalidRecordLength); require!(record_len <= raw.len(), ShineUsersError::InvalidRecordLength); let useful = &raw[..record_len]; let mut cursor = 9usize; let created_at_ms = read_u64_from(useful, &mut cursor)?; let updated_at_ms = read_u64_from(useful, &mut cursor)?; let record_number = read_u32_from(useful, &mut cursor)?; let prev_record_hash = read_fixed_32_from(useful, &mut cursor)?; let login = read_len_prefixed_string_from(useful, &mut cursor)?; let blocks_count = read_u8_from(useful, &mut cursor)? as usize; let mut root_key = None; let mut device_key = None; let mut blockchain = None; let mut is_server = false; let mut address_format_type = 0u8; let mut address_format_version = 0u8; let mut server_address = String::new(); let mut sync_servers = Vec::new(); let mut access_servers = Vec::new(); let mut sessions_mode = SESSIONS_MODE_MIXED; let mut sessions = Vec::new(); let mut trusted_count = 0u8; for _ in 0..blocks_count { let block_type = read_u8_from(useful, &mut cursor)?; let block_version = read_u8_from(useful, &mut cursor)?; require!(block_version == BLOCK_VERSION_0, ShineUsersError::InvalidRecordFormat); match block_type { BLOCK_TYPE_ROOT_KEY => { require!(root_key.is_none(), ShineUsersError::InvalidRecordData); root_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?)); } BLOCK_TYPE_DEVICE_KEY => { require!(device_key.is_none(), ShineUsersError::InvalidRecordData); device_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?)); } BLOCK_TYPE_BLOCKCHAIN_REGISTRY => { require!(blockchain.is_none(), ShineUsersError::InvalidRecordData); let count = read_u8_from(useful, &mut cursor)?; require!(count == 1, ShineUsersError::InvalidRecordData); blockchain = Some(read_blockchain_record(useful, &mut cursor)?); } BLOCK_TYPE_SERVER_PROFILE => { require!(!is_server, ShineUsersError::InvalidRecordData); is_server = read_u8_from(useful, &mut cursor)? == 1; require!(is_server, ShineUsersError::InvalidRecordData); address_format_type = read_u8_from(useful, &mut cursor)?; address_format_version = read_u8_from(useful, &mut cursor)?; server_address = read_len_prefixed_string_from(useful, &mut cursor)?; let sync_count = read_u8_from(useful, &mut cursor)? as usize; require!(sync_count <= MAX_SYNC_SERVERS, ShineUsersError::InvalidRecordData); for _ in 0..sync_count { sync_servers.push(read_len_prefixed_string_from(useful, &mut cursor)?); } } BLOCK_TYPE_ACCESS_SERVERS => { require!(access_servers.is_empty(), ShineUsersError::InvalidRecordData); let access_count = read_u8_from(useful, &mut cursor)? as usize; for _ in 0..access_count { access_servers.push(read_len_prefixed_string_from(useful, &mut cursor)?); } } BLOCK_TYPE_SESSIONS => { require!(sessions.is_empty(), ShineUsersError::InvalidRecordData); sessions_mode = read_u8_from(useful, &mut cursor)?; let sessions_count = read_u8_from(useful, &mut cursor)? as usize; require!(sessions_count <= MAX_SESSIONS, ShineUsersError::InvalidRecordData); for _ in 0..sessions_count { sessions.push(read_session_record(useful, &mut cursor)?); } } BLOCK_TYPE_TRUSTED_STATE => { trusted_count = read_u8_from(useful, &mut cursor)?; } _ => return Err(ProgramError::from(ShineUsersError::InvalidRecordFormat)), } } validate_sessions_fields(sessions_mode, &sessions)?; let signature = read_fixed_64_from(useful, &mut cursor)?; require!(cursor == useful.len(), ShineUsersError::InvalidRecordLength); Ok(UserRecord { created_at_ms, updated_at_ms, record_number, prev_record_hash, login, root_key: root_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?, device_key: device_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?, blockchain: blockchain.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?, is_server, address_format_type, address_format_version, server_address, sync_servers, access_servers, sessions_mode, sessions, trusted_count, signature, }) } fn read_blockchain_record(data: &[u8], cursor: &mut usize) -> Result { let blockchain_type = read_u8_from(data, cursor)?; require!(blockchain_type == BLOCKCHAIN_TYPE_MAIN_USER, ShineUsersError::InvalidRecordData); let blockchain_name = read_len_prefixed_string_from(data, cursor)?; let blockchain_public_key = Pubkey::new_from_array(read_fixed_32_from(data, cursor)?); let paid_limit_bytes = read_u64_from(data, cursor)?; let used_bytes = read_u64_from(data, cursor)?; let last_block_number = read_u32_from(data, cursor)?; let last_block_hash = read_fixed_32_from(data, cursor)?; let last_block_signature = read_fixed_64_from(data, cursor)?; let arweave_present = read_u8_from(data, cursor)?; let arweave_tx_id = match arweave_present { 0 => String::new(), 1 => read_len_prefixed_string_from(data, cursor)?, _ => return Err(ProgramError::from(ShineUsersError::InvalidRecordData)) }; Ok(BlockchainRecord { blockchain_type, blockchain_name, blockchain_public_key, paid_limit_bytes, used_bytes, last_block_number, last_block_hash, last_block_signature, arweave_tx_id, }) } fn read_session_record(data: &[u8], cursor: &mut usize) -> Result { Ok(SessionRecord { session_type: read_u8_from(data, cursor)?, session_version: read_u8_from(data, cursor)?, session_name: read_len_prefixed_string_from(data, cursor)?, session_pub_key: Pubkey::new_from_array(read_fixed_32_from(data, cursor)?), }) } fn serialize_unsigned_record(record: &UserRecord) -> Result, ProgramError> { let login_bytes = record.login.as_bytes(); require!(login_bytes.len() <= u8::MAX as usize, ShineUsersError::InvalidLogin); let mut out = Vec::new(); out.extend_from_slice(MAGIC); out.push(FORMAT_MAJOR); out.push(FORMAT_MINOR); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&record.created_at_ms.to_le_bytes()); out.extend_from_slice(&record.updated_at_ms.to_le_bytes()); out.extend_from_slice(&record.record_number.to_le_bytes()); out.extend_from_slice(&record.prev_record_hash); out.push(login_bytes.len() as u8); out.extend_from_slice(login_bytes); let blocks_count = if record.is_server { 7 } else { 6 }; out.push(blocks_count); write_root_key_block(&mut out, record); write_device_key_block(&mut out, record); write_blockchain_registry_block(&mut out, &record.blockchain)?; if record.is_server { write_server_profile_block(&mut out, record)?; } write_access_servers_block(&mut out, record)?; write_sessions_block(&mut out, record)?; write_trusted_state_block(&mut out, record); let record_len = out.len().checked_add(64).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?; require!(record_len <= u16::MAX as usize, ShineUsersError::RecordTooLarge); let len_bytes = (record_len as u16).to_le_bytes(); out[7] = len_bytes[0]; out[8] = len_bytes[1]; Ok(out) } fn serialize_full_record(record: &UserRecord) -> Result, ProgramError> { let mut out = serialize_unsigned_record(record)?; out.extend_from_slice(&record.signature); Ok(out) } fn hash_unsigned_record(record: &UserRecord) -> Result<[u8; 32], ProgramError> { let unsigned = serialize_unsigned_record(record)?; let digest = hashv(&[&unsigned]); let mut out = [0u8; 32]; out.copy_from_slice(digest.as_ref()); Ok(out) } fn write_root_key_block(out: &mut Vec, record: &UserRecord) { out.push(BLOCK_TYPE_ROOT_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.root_key.as_ref()); } fn write_device_key_block(out: &mut Vec, record: &UserRecord) { out.push(BLOCK_TYPE_DEVICE_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.device_key.as_ref()); } fn write_blockchain_registry_block(out: &mut Vec, blockchain: &BlockchainRecord) -> Result<(), ProgramError> { out.push(BLOCK_TYPE_BLOCKCHAIN_REGISTRY); out.push(BLOCK_VERSION_0); out.push(1); write_blockchain_record(out, blockchain) } fn write_blockchain_record(out: &mut Vec, blockchain: &BlockchainRecord) -> Result<(), ProgramError> { out.push(blockchain.blockchain_type); write_len_prefixed_string(out, &blockchain.blockchain_name)?; out.extend_from_slice(blockchain.blockchain_public_key.as_ref()); out.extend_from_slice(&blockchain.paid_limit_bytes.to_le_bytes()); out.extend_from_slice(&blockchain.used_bytes.to_le_bytes()); out.extend_from_slice(&blockchain.last_block_number.to_le_bytes()); out.extend_from_slice(&blockchain.last_block_hash); out.extend_from_slice(&blockchain.last_block_signature); if blockchain.arweave_tx_id.is_empty() { out.push(0); } else { out.push(1); write_len_prefixed_string(out, &blockchain.arweave_tx_id)?; } Ok(()) } fn write_server_profile_block(out: &mut Vec, record: &UserRecord) -> Result<(), ProgramError> { out.push(BLOCK_TYPE_SERVER_PROFILE); out.push(BLOCK_VERSION_0); out.push(1); out.push(record.address_format_type); out.push(record.address_format_version); write_len_prefixed_string(out, &record.server_address)?; require!(record.sync_servers.len() <= MAX_SYNC_SERVERS, ShineUsersError::InvalidRecordData); out.push(record.sync_servers.len() as u8); for login in &record.sync_servers { write_len_prefixed_string(out, login)?; } Ok(()) } fn write_access_servers_block(out: &mut Vec, record: &UserRecord) -> Result<(), ProgramError> { out.push(BLOCK_TYPE_ACCESS_SERVERS); out.push(BLOCK_VERSION_0); require!(record.access_servers.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); out.push(record.access_servers.len() as u8); for login in &record.access_servers { write_len_prefixed_string(out, login)?; } Ok(()) } fn write_sessions_block(out: &mut Vec, record: &UserRecord) -> Result<(), ProgramError> { out.push(BLOCK_TYPE_SESSIONS); out.push(BLOCK_VERSION_0); out.push(record.sessions_mode); require!(record.sessions.len() <= MAX_SESSIONS, ShineUsersError::InvalidRecordData); out.push(record.sessions.len() as u8); for session in &record.sessions { out.push(session.session_type); out.push(session.session_version); write_len_prefixed_string(out, &session.session_name)?; out.extend_from_slice(session.session_pub_key.as_ref()); } Ok(()) } fn write_trusted_state_block(out: &mut Vec, record: &UserRecord) { out.push(BLOCK_TYPE_TRUSTED_STATE); out.push(BLOCK_VERSION_0); out.push(record.trusted_count); } fn write_len_prefixed_string(out: &mut Vec, value: &str) -> Result<(), ProgramError> { let bytes = value.as_bytes(); require!(bytes.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); out.push(bytes.len() as u8); out.extend_from_slice(bytes); Ok(()) } fn verify_record_signature_hash(instructions_sysvar: &AccountInfo, root_key: &Pubkey, signature: &[u8; 64], message_hash: &[u8]) -> Result<[u8; 64], ProgramError> { verify_ed25519_signature_instruction(instructions_sysvar, -2, root_key, signature, message_hash)?; Ok(*signature) } fn verify_last_block_state_signature(instructions_sysvar: &AccountInfo, record: &UserRecord) -> ProgramResult { let message = serialize_last_block_state(record)?; let msg_hash = hashv(&[&message]); verify_ed25519_signature_instruction(instructions_sysvar, -1, &record.blockchain.blockchain_public_key, &record.blockchain.last_block_signature, msg_hash.as_ref()) } fn serialize_last_block_state(record: &UserRecord) -> Result, ProgramError> { let mut out = Vec::new(); out.extend_from_slice(LAST_BLOCK_STATE_PREFIX); write_len_prefixed_string(&mut out, &record.login)?; write_len_prefixed_string(&mut out, &record.blockchain.blockchain_name)?; out.extend_from_slice(&record.blockchain.last_block_number.to_le_bytes()); out.extend_from_slice(&record.blockchain.last_block_hash); out.extend_from_slice(&record.blockchain.used_bytes.to_le_bytes()); Ok(out) } struct ParsedEd25519Ref<'a> { pubkey: Pubkey, signature: [u8; 64], message: &'a [u8], } fn verify_ed25519_signature_instruction(instructions_sysvar: &AccountInfo, index_relative_to_current: i64, expected_pubkey: &Pubkey, expected_signature: &[u8; 64], expected_message: &[u8]) -> ProgramResult { require_keys_eq!(*instructions_sysvar.key, solana_program::sysvar::instructions::id(), ShineUsersError::InvalidSignature); let current_index = load_current_index_checked(instructions_sysvar).map_err(|_| ProgramError::from(ShineUsersError::InvalidSignature))? as i64; let target_index = current_index.checked_add(index_relative_to_current).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; require!(target_index >= 0, ShineUsersError::InvalidSignature); let ed_ix = load_instruction_at_checked(target_index as usize, instructions_sysvar).map_err(|_| ProgramError::from(ShineUsersError::InvalidSignature))?; require_keys_eq!(ed_ix.program_id, solana_program::ed25519_program::id(), ShineUsersError::InvalidSignature); let parsed = parse_ed25519_ix(ed_ix.data.as_slice())?; require!(parsed.pubkey == *expected_pubkey, ShineUsersError::InvalidSignature); require!(parsed.signature == *expected_signature, ShineUsersError::InvalidSignature); require!(parsed.message == expected_message, ShineUsersError::InvalidSignature); Ok(()) } fn parse_ed25519_ix<'a>(data: &'a [u8]) -> Result, ProgramError> { require!(data.len() >= 16, ShineUsersError::InvalidSignature); require!(data[0] == 1, ShineUsersError::InvalidSignature); let signature_offset = le_u16(data, 2)? as usize; let signature_ix_index = le_u16(data, 4)?; let pubkey_offset = le_u16(data, 6)? as usize; let pubkey_ix_index = le_u16(data, 8)?; let message_offset = le_u16(data, 10)? as usize; let message_size = le_u16(data, 12)? as usize; let message_ix_index = le_u16(data, 14)?; require!(signature_ix_index == u16::MAX, ShineUsersError::InvalidSignature); require!(pubkey_ix_index == u16::MAX, ShineUsersError::InvalidSignature); require!(message_ix_index == u16::MAX, ShineUsersError::InvalidSignature); let signature_end = signature_offset.checked_add(64).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let pubkey_end = pubkey_offset.checked_add(32).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let message_end = message_offset.checked_add(message_size).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let signature_slice = data.get(signature_offset..signature_end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let pubkey_slice = data.get(pubkey_offset..pubkey_end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let message = data.get(message_offset..message_end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let mut signature = [0u8; 64]; signature.copy_from_slice(signature_slice); let pubkey = Pubkey::new_from_array(<[u8; 32]>::try_from(pubkey_slice).map_err(|_| ProgramError::from(ShineUsersError::InvalidSignature))?); Ok(ParsedEd25519Ref { pubkey, signature, message }) } fn le_u16(data: &[u8], offset: usize) -> Result { let end = offset.checked_add(2).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; let s = data.get(offset..end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?; Ok(u16::from_le_bytes([s[0], s[1]])) } fn validate_login(login: &str) -> ProgramResult { require!(!login.is_empty(), ShineUsersError::InvalidLogin); require!(login.len() <= 20, ShineUsersError::InvalidLogin); for ch in login.chars() { if !(ch.is_ascii_alphabetic() || ch.is_ascii_digit() || ch == '_') { return Err(ProgramError::from(ShineUsersError::InvalidLogin)); } } Ok(()) } fn login_seed_normalized(login: &str) -> String { login.to_ascii_lowercase() } fn validate_fields(fields: &UserMutableFields) -> ProgramResult { require!(!fields.blockchain_name.is_empty(), ShineUsersError::InvalidRecordData); require!(fields.blockchain_name.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); require!(fields.arweave_tx_id.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); if fields.is_server { require!(!fields.server_address.is_empty(), ShineUsersError::InvalidRecordData); require!(fields.server_address.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); require!(fields.sync_servers.len() <= MAX_SYNC_SERVERS, ShineUsersError::InvalidRecordData); for login in &fields.sync_servers { require!(!login.is_empty(), ShineUsersError::InvalidRecordData); require!(login.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); } } else { require!(fields.server_address.is_empty(), ShineUsersError::InvalidRecordData); require!(fields.sync_servers.is_empty(), ShineUsersError::InvalidRecordData); } require!(fields.access_servers.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); for login in &fields.access_servers { require!(!login.is_empty(), ShineUsersError::InvalidRecordData); require!(login.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); } validate_sessions_fields(fields.sessions_mode, &fields.sessions) } fn validate_sessions_fields(mode: u8, sessions: &[SessionRecord]) -> ProgramResult { require!(mode == SESSIONS_MODE_MIXED || mode == SESSIONS_MODE_PDA_ONLY, ShineUsersError::InvalidRecordData); require!(sessions.len() <= MAX_SESSIONS, ShineUsersError::InvalidRecordData); for i in 0..sessions.len() { validate_session_record(&sessions[i])?; for j in (i + 1)..sessions.len() { require!(sessions[i].session_name != sessions[j].session_name, ShineUsersError::InvalidRecordData); require!(sessions[i].session_pub_key != sessions[j].session_pub_key, ShineUsersError::InvalidRecordData); } } Ok(()) } fn validate_session_record(session: &SessionRecord) -> ProgramResult { require!(session.session_type == SESSION_TYPE_USER || session.session_type == SESSION_TYPE_SUBSERVER, ShineUsersError::InvalidRecordData); require!(session.session_version == 1, ShineUsersError::InvalidRecordData); let bytes = session.session_name.as_bytes(); require!(!bytes.is_empty(), ShineUsersError::InvalidRecordData); require!(bytes.len() <= MAX_SESSION_NAME_LEN, ShineUsersError::InvalidRecordData); for &b in bytes { require!(b.is_ascii_alphanumeric() || b == b'_', ShineUsersError::InvalidRecordData); } Ok(()) } fn validate_blockchain_limits(blockchain: &BlockchainRecord, old_used_bytes: u64, old_last_block_number: u32, is_create: bool) -> ProgramResult { require!(blockchain.blockchain_type == BLOCKCHAIN_TYPE_MAIN_USER, ShineUsersError::InvalidRecordData); require!(blockchain.used_bytes <= blockchain.paid_limit_bytes, ShineUsersError::InvalidRecordData); if !is_create { require!(blockchain.used_bytes >= old_used_bytes && blockchain.last_block_number >= old_last_block_number, ShineUsersError::InvalidRecordData); } Ok(()) } fn validate_inflow_vault(inflow_vault: &AccountInfo) -> ProgramResult { let payments_program_id = Pubkey::from_str(settings::SHINE_PAYMENTS_PROGRAM_ID).map_err(|_| ProgramError::from(ShineUsersError::InvalidFeeReceiver))?; let (expected, _) = Pubkey::find_program_address(&[settings::SHINE_PAYMENTS_INFLOW_VAULT_SEED], &payments_program_id); require_keys_eq!(expected, *inflow_vault.key, ShineUsersError::InvalidFeeReceiver); Ok(()) } fn transfer_lamports<'a>(payer: &AccountInfo<'a>, recipient: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, lamports: u64) -> ProgramResult { if lamports == 0 { return Ok(()); } let ix = system_instruction::transfer(payer.key, recipient.key, lamports); invoke(&ix, &[payer.clone(), recipient.clone(), system_program_ai.clone()]) } fn create_pda_account<'a>(payer: &AccountInfo<'a>, pda: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, owner: &Pubkey, seeds: &[&[u8]], space: usize) -> ProgramResult { let rent = Rent::get()?; let lamports = rent.minimum_balance(space); let ix = system_instruction::create_account(payer.key, pda.key, lamports, space as u64, owner); invoke_signed(&ix, &[payer.clone(), pda.clone(), system_program_ai.clone()], &[seeds]) } fn ensure_pda_size_and_rent<'a>(pda: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, required_len: usize) -> ProgramResult { let current_len = pda.data_len(); if required_len <= current_len { return Ok(()); } let increase = required_len.checked_sub(current_len).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?; require!(increase <= MAX_AUTO_REALLOC_INCREASE, ShineUsersError::RecordTooLarge); let rent = Rent::get()?; let required_lamports = rent.minimum_balance(required_len); let current_lamports = pda.lamports(); let top_up = required_lamports.saturating_sub(current_lamports); if top_up > 0 { transfer_lamports(payer, pda, system_program_ai, top_up)?; } pda.realloc(required_len, false) } fn find_user_pda(program_id: &Pubkey, login: &str) -> (Pubkey, u8) { Pubkey::find_program_address(&[settings::USER_PDA_SEED_PREFIX.as_bytes(), login.as_bytes()], program_id) } fn find_users_economy_config_pda(program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[settings::USERS_ECONOMY_CONFIG_SEED], program_id) } fn limit_fee_lamports(limit_delta: u64, lamports_per_limit_step: u64) -> Result { (limit_delta / settings::LIMIT_STEP).checked_mul(lamports_per_limit_step).ok_or(ProgramError::from(ShineUsersError::MathOverflow)) } fn pad_to_fixed_size(mut bytes: Vec, target_size: usize) -> Result, ProgramError> { require!(bytes.len() <= target_size, ShineUsersError::RecordTooLarge); bytes.resize(target_size, 0); Ok(bytes) } fn read_pda_all(pda: &AccountInfo) -> Result, ProgramError> { Ok(pda.try_borrow_data().map_err(|_| ProgramError::from(ShineUsersError::InvalidAccountData))?.to_vec()) } fn write_pda_exact(pda: &AccountInfo, data: &[u8]) -> ProgramResult { let mut dst = pda.try_borrow_mut_data().map_err(|_| ProgramError::from(ShineUsersError::InvalidAccountData))?; require!(data.len() <= dst.len(), ShineUsersError::RecordTooLarge); dst[..data.len()].copy_from_slice(data); for b in &mut dst[data.len()..] { *b = 0; } Ok(()) } fn write_pda_prefix(pda: &AccountInfo, data: &[u8]) -> ProgramResult { let mut dst = pda.try_borrow_mut_data().map_err(|_| ProgramError::from(ShineUsersError::InvalidAccountData))?; require!(data.len() <= dst.len(), ShineUsersError::RecordTooLarge); dst[..data.len()].copy_from_slice(data); Ok(()) } fn read_u8_from(data: &[u8], cursor: &mut usize) -> Result { let v = *data.get(*cursor).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor += 1; Ok(v) } fn read_u32_from(data: &[u8], cursor: &mut usize) -> Result { let end = cursor.checked_add(4).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; Ok(u32::from_le_bytes([s[0], s[1], s[2], s[3]])) } fn read_u64_from(data: &[u8], cursor: &mut usize) -> Result { let end = cursor.checked_add(8).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; Ok(u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]])) } fn read_fixed_32_from(data: &[u8], cursor: &mut usize) -> Result<[u8; 32], ProgramError> { let end = cursor.checked_add(32).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; <[u8; 32]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidRecordData)) } fn read_fixed_64_from(data: &[u8], cursor: &mut usize) -> Result<[u8; 64], ProgramError> { let end = cursor.checked_add(64).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; <[u8; 64]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidRecordData)) } fn read_len_prefixed_string_from(data: &[u8], cursor: &mut usize) -> Result { let len = read_u8_from(data, cursor)? as usize; let end = cursor.checked_add(len).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; std::str::from_utf8(s).map(|v| v.to_string()).map_err(|_| ProgramError::from(ShineUsersError::InvalidRecordData)) }