diff --git a/shine/doc/FUNDS_FLOW.md b/shine/doc/FUNDS_FLOW.md new file mode 100644 index 0000000..19f0d9b --- /dev/null +++ b/shine/doc/FUNDS_FLOW.md @@ -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`. diff --git a/shine/programs/shine_users/src/settings.rs b/shine/programs/shine_users/src/settings.rs index 847d1e9..7beed41 100644 --- a/shine/programs/shine_users/src/settings.rs +++ b/shine/programs/shine_users/src/settings.rs @@ -12,8 +12,10 @@ pub const USERS_ECONOMY_CONFIG_SPACE: usize = 8 + 96; /// `DAO_AUTHORITY` — адрес DAO-авторити, который имеет право обновлять economy-конфиг. pub const DAO_AUTHORITY: &str = deploy_config::DAO_AUTHORITY; -/// `REGISTRATION_FEE_RECEIVER` — кошелек, который получает комиссию за регистрацию пользователей. -pub const REGISTRATION_FEE_RECEIVER: &str = deploy_config::REGISTRATION_FEE_RECEIVER; +/// `SHINE_PAYMENTS_PROGRAM_ID` — адрес программы `shine_payments`, от которой вычисляется PDA inflow-вольта. +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-конфига. pub const START_REGISTRATION_FEE_LAMPORTS: u64 = 10_000_000; diff --git a/shine/programs/shine_users/src/users.rs b/shine/programs/shine_users/src/users.rs index 5159eb5..8bcc271 100644 --- a/shine/programs/shine_users/src/users.rs +++ b/shine/programs/shine_users/src/users.rs @@ -92,9 +92,9 @@ pub struct CreateUserPda<'info> { #[account(mut)] pub user_pda: AccountInfo<'info>, pub system_program: Program<'info, System>, - /// CHECK: адрес получателя комиссии проверяется вручную с константой settings. + /// CHECK: inflow-вольт shine_payments, адрес проверяется вручную как PDA. #[account(mut)] - pub fee_receiver: AccountInfo<'info>, + pub inflow_vault: AccountInfo<'info>, /// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции. pub instructions: AccountInfo<'info>, /// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную. @@ -110,9 +110,9 @@ pub struct UpdateUserPda<'info> { #[account(mut)] pub user_pda: AccountInfo<'info>, pub system_program: Program<'info, System>, - /// CHECK: адрес получателя комиссии проверяется вручную с константой settings. + /// CHECK: inflow-вольт shine_payments, адрес проверяется вручную как PDA. #[account(mut)] - pub fee_receiver: AccountInfo<'info>, + pub inflow_vault: AccountInfo<'info>, /// CHECK: sysvar инструкций, нужен для проверки встроенной Ed25519Program инструкции. pub instructions: AccountInfo<'info>, /// CHECK: PDA с экономическими настройками пользователей, адрес проверяется вручную. @@ -215,7 +215,7 @@ pub fn update_users_economy_config( pub fn create_user_pda(ctx: Context, args: CreateUserPdaArgs) -> Result<()> { validate_login(&args.login)?; validate_fields(&args.fields)?; - validate_fee_receiver(&ctx.accounts.fee_receiver)?; + validate_inflow_vault(&ctx.accounts.inflow_vault)?; require!( args.additional_limit % settings::LIMIT_STEP == 0, ErrCode::InvalidLimitIncrement @@ -296,7 +296,7 @@ pub fn create_user_pda(ctx: Context, args: CreateUserPdaArgs) -> .ok_or(error!(ErrCode::MathOverflow))?; transfer_lamports( &ctx.accounts.signer, - &ctx.accounts.fee_receiver, + &ctx.accounts.inflow_vault, &ctx.accounts.system_program.to_account_info(), total_fee, )?; @@ -307,7 +307,7 @@ pub fn create_user_pda(ctx: Context, args: CreateUserPdaArgs) -> pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> Result<()> { validate_login(&args.login)?; validate_fields(&args.fields)?; - validate_fee_receiver(&ctx.accounts.fee_receiver)?; + validate_inflow_vault(&ctx.accounts.inflow_vault)?; require!( args.additional_limit % settings::LIMIT_STEP == 0, ErrCode::InvalidLimitIncrement @@ -408,7 +408,7 @@ pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> if topup_fee > 0 { transfer_lamports( &ctx.accounts.signer, - &ctx.accounts.fee_receiver, + &ctx.accounts.inflow_vault, &ctx.accounts.system_program.to_account_info(), topup_fee, )?; @@ -721,10 +721,14 @@ fn validate_fields(fields: &UserMutableFields) -> Result<()> { Ok(()) } -fn validate_fee_receiver(fee_receiver: &AccountInfo) -> Result<()> { - let expected = Pubkey::from_str(settings::REGISTRATION_FEE_RECEIVER) +fn validate_inflow_vault(inflow_vault: &AccountInfo) -> Result<()> { + let payments_program_id = Pubkey::from_str(settings::SHINE_PAYMENTS_PROGRAM_ID) .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(()) } diff --git a/shine/tests/shine.ts b/shine/tests/shine.ts index d197f92..8aa8ee8 100644 --- a/shine/tests/shine.ts +++ b/shine/tests/shine.ts @@ -20,8 +20,9 @@ const KEY_STATUS_CREATED = 0; const LIMIT_STEP = 10_000n; const START_BONUS_LIMIT = 100_000n; -const FEE_RECEIVER = new PublicKey("9vXFoN9ngfN1gpqQ3HT5n3y9Wp2r7HnSQckirgwVwWwb"); -const USERS_ECONOMY_CONFIG_SEED = "shine_users_v1_economy_config"; +const USERS_ECONOMY_CONFIG_SEED = "shine_users_economy_config"; +const SHINE_PAYMENTS_PROGRAM_ID = new PublicKey("m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR"); +const SHINE_PAYMENTS_INFLOW_VAULT_SEED = "shine_payments_inflow_vault"; type MutableFields = { blockchainKey: PublicKey; @@ -148,6 +149,10 @@ describe("shine_users e2e", () => { [Buffer.from(USERS_ECONOMY_CONFIG_SEED, "utf8")], 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); if (!economyAi) { @@ -222,7 +227,7 @@ describe("shine_users e2e", () => { signer: provider.wallet.publicKey, userPda, systemProgram: SystemProgram.programId, - feeReceiver: FEE_RECEIVER, + inflowVault: inflowVaultPda, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, usersEconomyConfigPda, }) @@ -291,7 +296,7 @@ describe("shine_users e2e", () => { signer: provider.wallet.publicKey, userPda, systemProgram: SystemProgram.programId, - feeReceiver: FEE_RECEIVER, + inflowVault: inflowVaultPda, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, usersEconomyConfigPda, })