Направить комиссии shine_users только в inflow_vault shine_payments

This commit is contained in:
AidarKC 2026-05-16 18:56:52 +03:00
parent 32bc2c9dc3
commit b5bd253b8c
4 changed files with 75 additions and 17 deletions

47
shine/doc/FUNDS_FLOW.md Normal file
View File

@ -0,0 +1,47 @@
# Движение Средств (Shine)
Документ описывает, как перемещаются средства между счетами в текущей схеме.
## 1) Регистрация и увеличение лимита (`shine_users`)
### Регистрация пользователя (`create_user_pda`)
1. Плательщик: кошелек `signer` (кто отправил транзакцию).
2. Получатель комиссии: `inflow_vault` (PDA в программе `shine_payments`).
3. Сумма перевода:
- `registration_fee_lamports` из economy-конфига `shine_users`;
- плюс комиссия за `additional_limit` (по формуле через `limit_step` и `lamports_per_limit_step`).
### Увеличение лимита (`update_user_pda`)
1. Плательщик: кошелек `signer`.
2. Получатель комиссии: `inflow_vault` (тот же PDA `shine_payments`).
3. Сумма перевода:
- только комиссия за `additional_limit` (без регистрационной части).
## 2) Покупка билета (`shine_payments`)
### Покупка (`buy_ticket`, `buy_ticket_usd`, `buy_ticket_sol`)
1. Плательщик: кошелек покупателя (`signer`).
2. Получатель: `dao_wallet` (казна DAO из `ConfigState`).
3. В `inflow_vault` на этом шаге средства не зачисляются.
## 3) Шаг выплат (`shine_payments::step_payout`)
Источник выплат: `inflow_vault` (`ConfigState.inflow_vault`).
При шаге выплаты:
1. Из `inflow_vault` переводится `ticket` получателю тикета.
2. Из `inflow_vault` переводится DAO-часть в `dao_wallet`.
3. Из `inflow_vault` переводится `call_reward_lamports` вызывающему шаг.
Если очереди пусты:
1. Весь доступный остаток `inflow_vault` переводится в `dao_wallet`.
## 4) Какие адреса задаются настройками
1. `dao_wallet` — хранится в `ConfigState` (`shine_payments`), задается при `init`.
2. `inflow_vault` — PDA `shine_payments`, вычисляется по seed и program id.
3. Для `shine_users` получатель комиссии не настраивается отдельно:
- всегда используется PDA `inflow_vault` программы `shine_payments`.

View File

@ -12,8 +12,10 @@ pub const USERS_ECONOMY_CONFIG_SPACE: usize = 8 + 96;
/// `DAO_AUTHORITY` — адрес DAO-авторити, который имеет право обновлять economy-конфиг. /// `DAO_AUTHORITY` — адрес DAO-авторити, который имеет право обновлять economy-конфиг.
pub const DAO_AUTHORITY: &str = deploy_config::DAO_AUTHORITY; pub const DAO_AUTHORITY: &str = deploy_config::DAO_AUTHORITY;
/// `REGISTRATION_FEE_RECEIVER` — кошелек, который получает комиссию за регистрацию пользователей. /// `SHINE_PAYMENTS_PROGRAM_ID` — адрес программы `shine_payments`, от которой вычисляется PDA inflow-вольта.
pub const REGISTRATION_FEE_RECEIVER: &str = deploy_config::REGISTRATION_FEE_RECEIVER; pub const SHINE_PAYMENTS_PROGRAM_ID: &str = deploy_config::SHINE_PAYMENTS_PROGRAM_ID;
/// `SHINE_PAYMENTS_INFLOW_VAULT_SEED` — seed inflow-вольта в программе `shine_payments` (должен совпадать с payments settings).
pub const SHINE_PAYMENTS_INFLOW_VAULT_SEED: &[u8] = b"shine_payments_inflow_vault";
/// `START_REGISTRATION_FEE_LAMPORTS` — стартовая комиссия регистрации (0.01 SOL) для initial economy-конфига. /// `START_REGISTRATION_FEE_LAMPORTS` — стартовая комиссия регистрации (0.01 SOL) для initial economy-конфига.
pub const START_REGISTRATION_FEE_LAMPORTS: u64 = 10_000_000; pub const START_REGISTRATION_FEE_LAMPORTS: u64 = 10_000_000;

View File

@ -92,9 +92,9 @@ pub struct CreateUserPda<'info> {
#[account(mut)] #[account(mut)]
pub user_pda: AccountInfo<'info>, pub user_pda: AccountInfo<'info>,
pub system_program: Program<'info, System>, pub system_program: Program<'info, System>,
/// CHECK: адрес получателя комиссии проверяется вручную с константой settings. /// CHECK: inflow-вольт shine_payments, адрес проверяется вручную как PDA.
#[account(mut)] #[account(mut)]
pub fee_receiver: AccountInfo<'info>, pub inflow_vault: AccountInfo<'info>,
/// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции. /// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции.
pub instructions: AccountInfo<'info>, pub instructions: AccountInfo<'info>,
/// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную. /// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную.
@ -110,9 +110,9 @@ pub struct UpdateUserPda<'info> {
#[account(mut)] #[account(mut)]
pub user_pda: AccountInfo<'info>, pub user_pda: AccountInfo<'info>,
pub system_program: Program<'info, System>, pub system_program: Program<'info, System>,
/// CHECK: адрес получателя комиссии проверяется вручную с константой settings. /// CHECK: inflow-вольт shine_payments, адрес проверяется вручную как PDA.
#[account(mut)] #[account(mut)]
pub fee_receiver: AccountInfo<'info>, pub inflow_vault: AccountInfo<'info>,
/// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции. /// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции.
pub instructions: AccountInfo<'info>, pub instructions: AccountInfo<'info>,
/// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную. /// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную.
@ -215,7 +215,7 @@ pub fn update_users_economy_config(
pub fn create_user_pda(ctx: Context<CreateUserPda>, args: CreateUserPdaArgs) -> Result<()> { pub fn create_user_pda(ctx: Context<CreateUserPda>, args: CreateUserPdaArgs) -> Result<()> {
validate_login(&args.login)?; validate_login(&args.login)?;
validate_fields(&args.fields)?; validate_fields(&args.fields)?;
validate_fee_receiver(&ctx.accounts.fee_receiver)?; validate_inflow_vault(&ctx.accounts.inflow_vault)?;
require!( require!(
args.additional_limit % settings::LIMIT_STEP == 0, args.additional_limit % settings::LIMIT_STEP == 0,
ErrCode::InvalidLimitIncrement ErrCode::InvalidLimitIncrement
@ -296,7 +296,7 @@ pub fn create_user_pda(ctx: Context<CreateUserPda>, args: CreateUserPdaArgs) ->
.ok_or(error!(ErrCode::MathOverflow))?; .ok_or(error!(ErrCode::MathOverflow))?;
transfer_lamports( transfer_lamports(
&ctx.accounts.signer, &ctx.accounts.signer,
&ctx.accounts.fee_receiver, &ctx.accounts.inflow_vault,
&ctx.accounts.system_program.to_account_info(), &ctx.accounts.system_program.to_account_info(),
total_fee, total_fee,
)?; )?;
@ -307,7 +307,7 @@ pub fn create_user_pda(ctx: Context<CreateUserPda>, args: CreateUserPdaArgs) ->
pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) -> Result<()> { pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) -> Result<()> {
validate_login(&args.login)?; validate_login(&args.login)?;
validate_fields(&args.fields)?; validate_fields(&args.fields)?;
validate_fee_receiver(&ctx.accounts.fee_receiver)?; validate_inflow_vault(&ctx.accounts.inflow_vault)?;
require!( require!(
args.additional_limit % settings::LIMIT_STEP == 0, args.additional_limit % settings::LIMIT_STEP == 0,
ErrCode::InvalidLimitIncrement ErrCode::InvalidLimitIncrement
@ -408,7 +408,7 @@ pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) ->
if topup_fee > 0 { if topup_fee > 0 {
transfer_lamports( transfer_lamports(
&ctx.accounts.signer, &ctx.accounts.signer,
&ctx.accounts.fee_receiver, &ctx.accounts.inflow_vault,
&ctx.accounts.system_program.to_account_info(), &ctx.accounts.system_program.to_account_info(),
topup_fee, topup_fee,
)?; )?;
@ -721,10 +721,14 @@ fn validate_fields(fields: &UserMutableFields) -> Result<()> {
Ok(()) Ok(())
} }
fn validate_fee_receiver(fee_receiver: &AccountInfo) -> Result<()> { fn validate_inflow_vault(inflow_vault: &AccountInfo) -> Result<()> {
let expected = Pubkey::from_str(settings::REGISTRATION_FEE_RECEIVER) let payments_program_id = Pubkey::from_str(settings::SHINE_PAYMENTS_PROGRAM_ID)
.map_err(|_| error!(ErrCode::InvalidFeeReceiver))?; .map_err(|_| error!(ErrCode::InvalidFeeReceiver))?;
require_keys_eq!(expected, *fee_receiver.key, ErrCode::InvalidFeeReceiver); let (expected, _) = Pubkey::find_program_address(
&[settings::SHINE_PAYMENTS_INFLOW_VAULT_SEED],
&payments_program_id,
);
require_keys_eq!(expected, *inflow_vault.key, ErrCode::InvalidFeeReceiver);
Ok(()) Ok(())
} }

View File

@ -20,8 +20,9 @@ const KEY_STATUS_CREATED = 0;
const LIMIT_STEP = 10_000n; const LIMIT_STEP = 10_000n;
const START_BONUS_LIMIT = 100_000n; const START_BONUS_LIMIT = 100_000n;
const FEE_RECEIVER = new PublicKey("9vXFoN9ngfN1gpqQ3HT5n3y9Wp2r7HnSQckirgwVwWwb"); const USERS_ECONOMY_CONFIG_SEED = "shine_users_economy_config";
const USERS_ECONOMY_CONFIG_SEED = "shine_users_v1_economy_config"; const SHINE_PAYMENTS_PROGRAM_ID = new PublicKey("m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR");
const SHINE_PAYMENTS_INFLOW_VAULT_SEED = "shine_payments_inflow_vault";
type MutableFields = { type MutableFields = {
blockchainKey: PublicKey; blockchainKey: PublicKey;
@ -148,6 +149,10 @@ describe("shine_users e2e", () => {
[Buffer.from(USERS_ECONOMY_CONFIG_SEED, "utf8")], [Buffer.from(USERS_ECONOMY_CONFIG_SEED, "utf8")],
program.programId program.programId
); );
const [inflowVaultPda] = PublicKey.findProgramAddressSync(
[Buffer.from(SHINE_PAYMENTS_INFLOW_VAULT_SEED, "utf8")],
SHINE_PAYMENTS_PROGRAM_ID
);
const economyAi = await provider.connection.getAccountInfo(usersEconomyConfigPda); const economyAi = await provider.connection.getAccountInfo(usersEconomyConfigPda);
if (!economyAi) { if (!economyAi) {
@ -222,7 +227,7 @@ describe("shine_users e2e", () => {
signer: provider.wallet.publicKey, signer: provider.wallet.publicKey,
userPda, userPda,
systemProgram: SystemProgram.programId, systemProgram: SystemProgram.programId,
feeReceiver: FEE_RECEIVER, inflowVault: inflowVaultPda,
instructions: SYSVAR_INSTRUCTIONS_PUBKEY, instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
usersEconomyConfigPda, usersEconomyConfigPda,
}) })
@ -291,7 +296,7 @@ describe("shine_users e2e", () => {
signer: provider.wallet.publicKey, signer: provider.wallet.publicKey,
userPda, userPda,
systemProgram: SystemProgram.programId, systemProgram: SystemProgram.programId,
feeReceiver: FEE_RECEIVER, inflowVault: inflowVaultPda,
instructions: SYSVAR_INSTRUCTIONS_PUBKEY, instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
usersEconomyConfigPda, usersEconomyConfigPda,
}) })