shine-solana/shine/programs/shine_users/src/users.rs

875 lines
29 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::settings;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::{
ed25519_program,
hash::hashv,
instruction::Instruction,
program::invoke,
system_instruction,
sysvar::instructions::{load_current_index_checked, load_instruction_at_checked},
};
use common::utils::{create_pda, safe_read_pda, write_to_pda, ErrCode};
use std::str::FromStr;
const MAGIC: &[u8; 5] = b"SHiNE";
const FORMAT_MAJOR: u8 = 1;
const FORMAT_MINOR: u8 = 0;
const KEY_STATUS_CREATED: u8 = 0;
const RESERVED_BYTES: [u8; 5] = [0, 0, 0, 0, 0];
const ZERO_HASH: [u8; 32] = [0; 32];
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct UserMutableFields {
pub blockchain_key: Pubkey,
pub device_key: Pubkey,
pub chain_number: u16,
pub is_server: bool,
pub server_key: Pubkey,
pub server_address: String,
pub connection_servers: Vec<String>,
pub trusted_count: u8,
}
#[derive(AnchorSerialize, AnchorDeserialize, 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: Vec<u8>,
}
#[derive(AnchorSerialize, AnchorDeserialize, 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: Vec<u8>,
pub additional_limit: u64,
pub fields: UserMutableFields,
pub signature: Vec<u8>,
}
pub struct UserRecord {
pub created_at_ms: u64,
pub updated_at_ms: u64,
pub version: u32,
pub prev_hash: [u8; 32],
pub login: String,
pub root_key_status: u8,
pub root_key: Pubkey,
pub blockchain_key_status: u8,
pub blockchain_key: Pubkey,
pub device_key_status: u8,
pub device_key: Pubkey,
pub chain_number: u16,
pub balance: u64,
pub is_server: bool,
pub server_key: Pubkey,
pub server_address: String,
pub connection_servers: Vec<String>,
pub trusted_count: u8,
pub signature: [u8; 64],
}
#[derive(AnchorSerialize, AnchorDeserialize, 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(Accounts)]
pub struct CreateUserPda<'info> {
/// CHECK: подписант транзакции, валидируется Anchor как signer и mut.
#[account(mut, signer)]
pub signer: AccountInfo<'info>,
/// CHECK: PDA пользователя, адрес проверяется вручную через seed в обработчике.
#[account(mut)]
pub user_pda: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: адрес получателя комиссии проверяется вручную с константой settings.
#[account(mut)]
pub fee_receiver: AccountInfo<'info>,
/// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции.
pub instructions: AccountInfo<'info>,
/// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную.
pub users_economy_config_pda: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct UpdateUserPda<'info> {
/// CHECK: подписант транзакции, валидируется Anchor как signer и mut.
#[account(mut, signer)]
pub signer: AccountInfo<'info>,
/// CHECK: PDA пользователя, адрес проверяется вручную через seed в обработчике.
#[account(mut)]
pub user_pda: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: адрес получателя комиссии проверяется вручную с константой settings.
#[account(mut)]
pub fee_receiver: AccountInfo<'info>,
/// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции.
pub instructions: AccountInfo<'info>,
/// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную.
pub users_economy_config_pda: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct InitUsersEconomyConfig<'info> {
/// CHECK: подписант и плательщик, валидируется Anchor как signer и mut.
#[account(mut, signer)]
pub signer: AccountInfo<'info>,
/// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную.
#[account(mut)]
pub users_economy_config_pda: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct UpdateUsersEconomyConfig<'info> {
/// CHECK: подписант (должен быть DAO authority из settings).
#[account(mut, signer)]
pub signer: AccountInfo<'info>,
/// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную.
#[account(mut)]
pub users_economy_config_pda: AccountInfo<'info>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct UpdateUsersEconomyConfigArgs {
pub registration_fee_lamports: u64,
pub lamports_per_limit_step: u64,
pub start_bonus_limit: u64,
}
pub fn init_users_economy_config(ctx: Context<InitUsersEconomyConfig>) -> Result<()> {
let (expected_pda, bump) = find_users_economy_config_pda(ctx.program_id);
require_keys_eq!(
expected_pda,
ctx.accounts.users_economy_config_pda.key(),
ErrCode::InvalidPdaAddress
);
require!(
ctx.accounts.users_economy_config_pda.owner == &Pubkey::default(),
ErrCode::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 bytes = state
.try_to_vec()
.map_err(|_| error!(ErrCode::DeserializationError))?;
let seeds: &[&[u8]] = &[settings::USERS_ECONOMY_CONFIG_SEED, &[bump]];
create_pda(
&ctx.accounts.users_economy_config_pda,
&ctx.accounts.signer,
&ctx.accounts.system_program.to_account_info(),
ctx.program_id,
seeds,
settings::USERS_ECONOMY_CONFIG_SPACE as u64,
)?;
write_to_pda(&ctx.accounts.users_economy_config_pda, &bytes)?;
Ok(())
}
pub fn update_users_economy_config(
ctx: Context<UpdateUsersEconomyConfig>,
args: UpdateUsersEconomyConfigArgs,
) -> Result<()> {
let dao_authority =
Pubkey::from_str(settings::DAO_AUTHORITY).map_err(|_| error!(ErrCode::InvalidSigner))?;
require_keys_eq!(dao_authority, ctx.accounts.signer.key(), ErrCode::InvalidSigner);
let (expected_pda, _) = find_users_economy_config_pda(ctx.program_id);
require_keys_eq!(
expected_pda,
ctx.accounts.users_economy_config_pda.key(),
ErrCode::InvalidPdaAddress
);
require!(
ctx.accounts.users_economy_config_pda.owner == ctx.program_id,
ErrCode::InvalidPdaAddress
);
require!(args.lamports_per_limit_step > 0, ErrCode::InvalidRecordData);
let mut state = read_users_economy_config(&ctx.accounts.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;
let bytes = state
.try_to_vec()
.map_err(|_| error!(ErrCode::DeserializationError))?;
write_to_pda(&ctx.accounts.users_economy_config_pda, &bytes)?;
Ok(())
}
pub fn create_user_pda(ctx: Context<CreateUserPda>, args: CreateUserPdaArgs) -> Result<()> {
validate_login(&args.login)?;
validate_fields(&args.fields)?;
validate_fee_receiver(&ctx.accounts.fee_receiver)?;
require!(
args.additional_limit % settings::LIMIT_STEP == 0,
ErrCode::InvalidLimitIncrement
);
let economy = read_users_economy_config(&ctx.accounts.users_economy_config_pda)?;
let (expected_pda, bump) = find_user_pda(ctx.program_id, &args.login);
require_keys_eq!(
expected_pda,
ctx.accounts.user_pda.key(),
ErrCode::InvalidPdaAddress
);
require!(
ctx.accounts.user_pda.owner == &Pubkey::default(),
ErrCode::UserAlreadyExists
);
let start_balance = economy
.start_bonus_limit
.checked_add(args.additional_limit)
.ok_or(error!(ErrCode::MathOverflow))?;
let mut record = UserRecord {
created_at_ms: args.created_at_ms,
updated_at_ms: args.created_at_ms,
version: 0,
prev_hash: ZERO_HASH,
login: args.login.clone(),
root_key_status: KEY_STATUS_CREATED,
root_key: args.root_key,
blockchain_key_status: KEY_STATUS_CREATED,
blockchain_key: args.fields.blockchain_key,
device_key_status: KEY_STATUS_CREATED,
device_key: args.fields.device_key,
chain_number: args.fields.chain_number,
balance: start_balance,
is_server: args.fields.is_server,
server_key: args.fields.server_key,
server_address: args.fields.server_address.clone(),
connection_servers: args.fields.connection_servers.clone(),
trusted_count: args.fields.trusted_count,
signature: [0; 64],
};
let unsigned = serialize_unsigned_record(&record)?;
record.signature = verify_record_signature(
&ctx.accounts.instructions,
&record.root_key,
&args.signature,
&unsigned,
)?;
let serialized = serialize_full_record(&record)?;
require!(
serialized.len() <= settings::USER_PDA_SPACE,
ErrCode::RecordTooLarge
);
let padded = pad_to_fixed_size(serialized, settings::USER_PDA_SPACE)?;
let pda_seeds: &[&[u8]] = &[
settings::USER_PDA_SEED_PREFIX.as_bytes(),
args.login.as_bytes(),
&[bump],
];
create_pda(
&ctx.accounts.user_pda,
&ctx.accounts.signer,
&ctx.accounts.system_program.to_account_info(),
ctx.program_id,
pda_seeds,
settings::USER_PDA_SPACE as u64,
)?;
write_to_pda(&ctx.accounts.user_pda, &padded)?;
let total_fee = economy
.registration_fee_lamports
.checked_add(limit_fee_lamports(args.additional_limit, economy.lamports_per_limit_step)?)
.ok_or(error!(ErrCode::MathOverflow))?;
transfer_lamports(
&ctx.accounts.signer,
&ctx.accounts.fee_receiver,
&ctx.accounts.system_program.to_account_info(),
total_fee,
)?;
Ok(())
}
pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) -> Result<()> {
validate_login(&args.login)?;
validate_fields(&args.fields)?;
validate_fee_receiver(&ctx.accounts.fee_receiver)?;
require!(
args.additional_limit % settings::LIMIT_STEP == 0,
ErrCode::InvalidLimitIncrement
);
let economy = read_users_economy_config(&ctx.accounts.users_economy_config_pda)?;
let (expected_pda, _) = find_user_pda(ctx.program_id, &args.login);
require_keys_eq!(
expected_pda,
ctx.accounts.user_pda.key(),
ErrCode::InvalidPdaAddress
);
require!(
ctx.accounts.user_pda.owner == ctx.program_id,
ErrCode::InvalidPdaAddress
);
let raw = safe_read_pda(&ctx.accounts.user_pda);
require!(!raw.is_empty(), ErrCode::EmptyPdaData);
let old_record = deserialize_record_from_pda(&raw)?;
require!(
old_record.login == args.login,
ErrCode::ImmutableFieldChanged
);
require!(
old_record.created_at_ms == args.created_at_ms,
ErrCode::ImmutableFieldChanged
);
require_keys_eq!(
old_record.root_key,
args.root_key,
ErrCode::ImmutableFieldChanged
);
require!(
old_record.root_key_status == KEY_STATUS_CREATED
&& old_record.blockchain_key_status == KEY_STATUS_CREATED
&& old_record.device_key_status == KEY_STATUS_CREATED,
ErrCode::InvalidRecordData
);
require!(
args.version == old_record.version.saturating_add(1),
ErrCode::InvalidVersion
);
let expected_prev_hash = hash_unsigned_record(&old_record)?;
let provided_prev_hash = vec_to_hash32(&args.prev_hash)?;
require!(
expected_prev_hash == provided_prev_hash,
ErrCode::InvalidPrevHash
);
let new_balance = old_record
.balance
.checked_add(args.additional_limit)
.ok_or(error!(ErrCode::MathOverflow))?;
require!(new_balance >= old_record.balance, ErrCode::BalanceDecrease);
let mut new_record = UserRecord {
created_at_ms: old_record.created_at_ms,
updated_at_ms: args.updated_at_ms,
version: args.version,
prev_hash: provided_prev_hash,
login: old_record.login.clone(),
root_key_status: old_record.root_key_status,
root_key: old_record.root_key,
blockchain_key_status: old_record.blockchain_key_status,
blockchain_key: args.fields.blockchain_key,
device_key_status: old_record.device_key_status,
device_key: args.fields.device_key,
chain_number: args.fields.chain_number,
balance: new_balance,
is_server: args.fields.is_server,
server_key: args.fields.server_key,
server_address: args.fields.server_address.clone(),
connection_servers: args.fields.connection_servers.clone(),
trusted_count: args.fields.trusted_count,
signature: [0; 64],
};
let unsigned = serialize_unsigned_record(&new_record)?;
new_record.signature = verify_record_signature(
&ctx.accounts.instructions,
&new_record.root_key,
&args.signature,
&unsigned,
)?;
let serialized = serialize_full_record(&new_record)?;
require!(
serialized.len() <= settings::USER_PDA_SPACE,
ErrCode::RecordTooLarge
);
let padded = pad_to_fixed_size(serialized, settings::USER_PDA_SPACE)?;
write_to_pda(&ctx.accounts.user_pda, &padded)?;
let topup_fee = limit_fee_lamports(args.additional_limit, economy.lamports_per_limit_step)?;
if topup_fee > 0 {
transfer_lamports(
&ctx.accounts.signer,
&ctx.accounts.fee_receiver,
&ctx.accounts.system_program.to_account_info(),
topup_fee,
)?;
}
Ok(())
}
fn serialize_unsigned_record(record: &UserRecord) -> Result<Vec<u8>> {
let login_bytes = record.login.as_bytes();
require!(login_bytes.len() <= u8::MAX as usize, ErrCode::InvalidLogin);
let server_address_bytes = record.server_address.as_bytes();
require!(
server_address_bytes.len() <= u8::MAX as usize,
ErrCode::InvalidRecordData
);
require!(
record.connection_servers.len() <= u8::MAX as usize,
ErrCode::InvalidRecordData
);
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.version.to_le_bytes());
out.extend_from_slice(&record.prev_hash);
out.push(login_bytes.len() as u8);
out.extend_from_slice(login_bytes);
out.push(record.root_key_status);
out.extend_from_slice(record.root_key.as_ref());
out.push(record.blockchain_key_status);
out.extend_from_slice(record.blockchain_key.as_ref());
out.push(record.device_key_status);
out.extend_from_slice(record.device_key.as_ref());
out.extend_from_slice(&record.chain_number.to_le_bytes());
out.extend_from_slice(&record.balance.to_le_bytes());
out.push(if record.is_server { 1 } else { 0 });
if record.is_server {
out.extend_from_slice(record.server_key.as_ref());
out.push(server_address_bytes.len() as u8);
out.extend_from_slice(server_address_bytes);
}
out.push(record.connection_servers.len() as u8);
for login in &record.connection_servers {
let bytes = login.as_bytes();
require!(bytes.len() <= u8::MAX as usize, ErrCode::InvalidRecordData);
out.push(bytes.len() as u8);
out.extend_from_slice(bytes);
}
out.push(record.trusted_count);
out.extend_from_slice(&RESERVED_BYTES);
let record_len = out
.len()
.checked_add(64)
.ok_or(error!(ErrCode::MathOverflow))?;
require!(record_len <= u16::MAX as usize, ErrCode::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>> {
let mut out = serialize_unsigned_record(record)?;
out.extend_from_slice(&record.signature);
Ok(out)
}
fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord> {
require!(raw.len() >= 9, ErrCode::InvalidRecordData);
require!(&raw[0..5] == MAGIC, ErrCode::InvalidRecordMagic);
require!(
raw[5] == FORMAT_MAJOR && raw[6] == FORMAT_MINOR,
ErrCode::InvalidRecordFormat
);
let record_len = u16::from_le_bytes([raw[7], raw[8]]) as usize;
require!(record_len >= 9 + 64, ErrCode::InvalidRecordLength);
require!(record_len <= raw.len(), ErrCode::InvalidRecordLength);
let useful = &raw[..record_len];
let mut cursor = 9usize;
let created_at_ms = read_u64(useful, &mut cursor)?;
let updated_at_ms = read_u64(useful, &mut cursor)?;
let version = read_u32(useful, &mut cursor)?;
let prev_hash = read_fixed_32(useful, &mut cursor)?;
let login = read_len_prefixed_string(useful, &mut cursor)?;
let root_key_status = read_u8(useful, &mut cursor)?;
let root_key = Pubkey::new_from_array(read_fixed_32(useful, &mut cursor)?);
let blockchain_key_status = read_u8(useful, &mut cursor)?;
let blockchain_key = Pubkey::new_from_array(read_fixed_32(useful, &mut cursor)?);
let device_key_status = read_u8(useful, &mut cursor)?;
let device_key = Pubkey::new_from_array(read_fixed_32(useful, &mut cursor)?);
let chain_number = read_u16(useful, &mut cursor)?;
let balance = read_u64(useful, &mut cursor)?;
let is_server = read_u8(useful, &mut cursor)? == 1;
let (server_key, server_address) = if is_server {
(
Pubkey::new_from_array(read_fixed_32(useful, &mut cursor)?),
read_len_prefixed_string(useful, &mut cursor)?,
)
} else {
(Pubkey::default(), String::new())
};
let connections_count = read_u8(useful, &mut cursor)? as usize;
let mut connection_servers = Vec::with_capacity(connections_count);
for _ in 0..connections_count {
connection_servers.push(read_len_prefixed_string(useful, &mut cursor)?);
}
let trusted_count = read_u8(useful, &mut cursor)?;
require!(
useful.get(cursor..cursor + 5) == Some(&RESERVED_BYTES),
ErrCode::InvalidRecordData
);
cursor += 5;
let signature = read_fixed_64(useful, &mut cursor)?;
require!(cursor == useful.len(), ErrCode::InvalidRecordLength);
Ok(UserRecord {
created_at_ms,
updated_at_ms,
version,
prev_hash,
login,
root_key_status,
root_key,
blockchain_key_status,
blockchain_key,
device_key_status,
device_key,
chain_number,
balance,
is_server,
server_key,
server_address,
connection_servers,
trusted_count,
signature,
})
}
fn hash_unsigned_record(record: &UserRecord) -> Result<[u8; 32]> {
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 verify_record_signature(
instructions_sysvar: &AccountInfo,
root_key: &Pubkey,
signature: &[u8],
unsigned: &[u8],
) -> Result<[u8; 64]> {
require_keys_eq!(
*instructions_sysvar.key,
anchor_lang::solana_program::sysvar::instructions::id(),
ErrCode::InvalidSignature
);
let provided_sig = vec_to_signature(signature)?;
let msg_hash = hashv(&[unsigned]);
let current_ix_index = load_current_index_checked(instructions_sysvar)
.map_err(|_| error!(ErrCode::InvalidSignature))?;
require!(current_ix_index > 0, ErrCode::InvalidSignature);
let ed_ix = load_instruction_at_checked((current_ix_index - 1) as usize, instructions_sysvar)
.map_err(|_| error!(ErrCode::InvalidSignature))?;
let parsed = parse_ed25519_ix(&ed_ix)?;
require_keys_eq!(parsed.pubkey, *root_key, ErrCode::InvalidSignature);
require!(
parsed.message == msg_hash.as_ref(),
ErrCode::InvalidSignature
);
require!(parsed.signature == provided_sig, ErrCode::InvalidSignature);
Ok(parsed.signature)
}
struct ParsedEd25519 {
pub pubkey: Pubkey,
pub signature: [u8; 64],
pub message: Vec<u8>,
}
fn parse_ed25519_ix(ix: &Instruction) -> Result<ParsedEd25519> {
require_keys_eq!(
ix.program_id,
ed25519_program::id(),
ErrCode::InvalidSignature
);
let data = &ix.data;
require!(data.len() >= 16, ErrCode::InvalidSignature);
require!(data[0] == 1, ErrCode::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, ErrCode::InvalidSignature);
require!(pubkey_ix_index == u16::MAX, ErrCode::InvalidSignature);
require!(message_ix_index == u16::MAX, ErrCode::InvalidSignature);
let signature_end = signature_offset
.checked_add(64)
.ok_or(error!(ErrCode::InvalidSignature))?;
let pubkey_end = pubkey_offset
.checked_add(32)
.ok_or(error!(ErrCode::InvalidSignature))?;
let message_end = message_offset
.checked_add(message_size)
.ok_or(error!(ErrCode::InvalidSignature))?;
let signature_slice = data
.get(signature_offset..signature_end)
.ok_or(error!(ErrCode::InvalidSignature))?;
let pubkey_slice = data
.get(pubkey_offset..pubkey_end)
.ok_or(error!(ErrCode::InvalidSignature))?;
let message = data
.get(message_offset..message_end)
.ok_or(error!(ErrCode::InvalidSignature))?
.to_vec();
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(|_| error!(ErrCode::InvalidSignature))?,
);
Ok(ParsedEd25519 {
pubkey,
signature,
message,
})
}
fn le_u16(data: &[u8], offset: usize) -> Result<u16> {
let end = offset
.checked_add(2)
.ok_or(error!(ErrCode::InvalidSignature))?;
let s = data
.get(offset..end)
.ok_or(error!(ErrCode::InvalidSignature))?;
Ok(u16::from_le_bytes([s[0], s[1]]))
}
fn validate_login(login: &str) -> Result<()> {
require!(!login.is_empty(), ErrCode::InvalidLogin);
require!(login.len() <= 25, ErrCode::InvalidLogin);
for ch in login.chars() {
if !(ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_') {
return Err(error!(ErrCode::InvalidLogin));
}
}
Ok(())
}
fn validate_fields(fields: &UserMutableFields) -> Result<()> {
if fields.is_server {
require!(
!fields.server_address.is_empty(),
ErrCode::InvalidRecordData
);
require!(
fields.server_address.as_bytes().len() <= u8::MAX as usize,
ErrCode::InvalidRecordData
);
} else {
require!(fields.server_address.is_empty(), ErrCode::InvalidRecordData);
}
require!(
fields.connection_servers.len() <= u8::MAX as usize,
ErrCode::InvalidRecordData
);
for login in &fields.connection_servers {
require!(!login.is_empty(), ErrCode::InvalidRecordData);
require!(
login.as_bytes().len() <= u8::MAX as usize,
ErrCode::InvalidRecordData
);
}
Ok(())
}
fn validate_fee_receiver(fee_receiver: &AccountInfo) -> Result<()> {
let expected = Pubkey::from_str(settings::REGISTRATION_FEE_RECEIVER)
.map_err(|_| error!(ErrCode::InvalidFeeReceiver))?;
require_keys_eq!(expected, *fee_receiver.key, ErrCode::InvalidFeeReceiver);
Ok(())
}
fn transfer_lamports<'info>(
payer: &AccountInfo<'info>,
recipient: &AccountInfo<'info>,
system_program: &AccountInfo<'info>,
lamports: u64,
) -> Result<()> {
if lamports == 0 {
return Ok(());
}
let ix = system_instruction::transfer(payer.key, recipient.key, lamports);
invoke(
&ix,
&[payer.clone(), recipient.clone(), system_program.clone()],
)?;
Ok(())
}
fn limit_fee_lamports(limit_delta: u64, lamports_per_limit_step: u64) -> Result<u64> {
let units = limit_delta / settings::LIMIT_STEP;
units
.checked_mul(lamports_per_limit_step)
.ok_or(error!(ErrCode::MathOverflow))
}
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 read_users_economy_config(pda: &AccountInfo) -> Result<UsersEconomyConfigState> {
let raw = safe_read_pda(pda);
require!(!raw.is_empty(), ErrCode::EmptyPdaData);
let mut slice: &[u8] = &raw;
UsersEconomyConfigState::deserialize(&mut slice)
.map_err(|_| error!(ErrCode::DeserializationError))
}
fn pad_to_fixed_size(mut bytes: Vec<u8>, target_size: usize) -> Result<Vec<u8>> {
require!(bytes.len() <= target_size, ErrCode::RecordTooLarge);
bytes.resize(target_size, 0);
Ok(bytes)
}
fn vec_to_signature(input: &[u8]) -> Result<[u8; 64]> {
require!(input.len() == 64, ErrCode::InvalidSignature);
let mut out = [0u8; 64];
out.copy_from_slice(input);
Ok(out)
}
fn vec_to_hash32(input: &[u8]) -> Result<[u8; 32]> {
require!(input.len() == 32, ErrCode::InvalidPrevHash);
let mut out = [0u8; 32];
out.copy_from_slice(input);
Ok(out)
}
fn read_u8(data: &[u8], cursor: &mut usize) -> Result<u8> {
let v = *data
.get(*cursor)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor += 1;
Ok(v)
}
fn read_u16(data: &[u8], cursor: &mut usize) -> Result<u16> {
let end = cursor
.checked_add(2)
.ok_or(error!(ErrCode::InvalidRecordData))?;
let slice = data
.get(*cursor..end)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor = end;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
fn read_u32(data: &[u8], cursor: &mut usize) -> Result<u32> {
let end = cursor
.checked_add(4)
.ok_or(error!(ErrCode::InvalidRecordData))?;
let slice = data
.get(*cursor..end)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor = end;
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
fn read_u64(data: &[u8], cursor: &mut usize) -> Result<u64> {
let end = cursor
.checked_add(8)
.ok_or(error!(ErrCode::InvalidRecordData))?;
let slice = data
.get(*cursor..end)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor = end;
Ok(u64::from_le_bytes([
slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
]))
}
fn read_fixed_32(data: &[u8], cursor: &mut usize) -> Result<[u8; 32]> {
let end = cursor
.checked_add(32)
.ok_or(error!(ErrCode::InvalidRecordData))?;
let slice = data
.get(*cursor..end)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor = end;
let mut out = [0u8; 32];
out.copy_from_slice(slice);
Ok(out)
}
fn read_fixed_64(data: &[u8], cursor: &mut usize) -> Result<[u8; 64]> {
let end = cursor
.checked_add(64)
.ok_or(error!(ErrCode::InvalidRecordData))?;
let slice = data
.get(*cursor..end)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor = end;
let mut out = [0u8; 64];
out.copy_from_slice(slice);
Ok(out)
}
fn read_len_prefixed_string(data: &[u8], cursor: &mut usize) -> Result<String> {
let len = read_u8(data, cursor)? as usize;
let end = cursor
.checked_add(len)
.ok_or(error!(ErrCode::InvalidRecordData))?;
let slice = data
.get(*cursor..end)
.ok_or(error!(ErrCode::InvalidRecordData))?;
*cursor = end;
let value = std::str::from_utf8(slice).map_err(|_| error!(ErrCode::InvalidRecordData))?;
Ok(value.to_string())
}