SHiNE-server/shine-solana/shine/programs/shine_users/src/lib.rs

1045 lines
53 KiB
Rust

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<ShineUsersError> 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<String>,
pub access_servers: Vec<String>,
pub sessions_mode: u8,
pub sessions: Vec<SessionRecord>,
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<String>,
pub access_servers: Vec<String>,
pub sessions_mode: u8,
pub sessions: Vec<SessionRecord>,
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<u8, ProgramError> {
let v = *self.data.get(self.cursor).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
self.cursor += 1;
Ok(v)
}
fn read_u32(&mut self) -> Result<u32, ProgramError> {
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<u64, ProgramError> {
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<Pubkey, ProgramError> {
Ok(Pubkey::new_from_array(self.read_fixed_32()?))
}
fn read_string_u8(&mut self) -> Result<String, ProgramError> {
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<CreateUserPdaArgs, ProgramError> {
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<UpdateUserPdaArgs, ProgramError> {
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<UserMutableFields, ProgramError> {
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<UserRecord, ProgramError> {
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<u8> {
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<UsersEconomyConfigState, ProgramError> {
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<UserRecord, ProgramError> {
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<BlockchainRecord, ProgramError> {
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<SessionRecord, ProgramError> {
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<Vec<u8>, 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<Vec<u8>, 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<u8>, 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<u8>, 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<u8>, 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<u8>, 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<u8>, 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<u8>, 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<u8>, 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<u8>, 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<u8>, 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<Vec<u8>, 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<ParsedEd25519Ref<'a>, 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<u16, ProgramError> {
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<u64, ProgramError> { (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<u8>, target_size: usize) -> Result<Vec<u8>, ProgramError> { require!(bytes.len() <= target_size, ShineUsersError::RecordTooLarge); bytes.resize(target_size, 0); Ok(bytes) }
fn read_pda_all(pda: &AccountInfo) -> Result<Vec<u8>, 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<u8, ProgramError> { 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<u32, ProgramError> { 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<u64, ProgramError> { 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<String, ProgramError> { 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)) }