diff --git a/shine/Anchor.toml b/shine/Anchor.toml index 7d19493..a440efb 100644 --- a/shine/Anchor.toml +++ b/shine/Anchor.toml @@ -6,12 +6,12 @@ resolution = true skip-lint = false [programs.devnet] -shine_payments = "4QDCcaURt7phJGcvDS4VQQtrqDmUSMXvkWnMnMKCiUyE" -shine_users = "8Z3HQizFRhyVu5cNBwWNBXZHTpu89VMkn7Wuk1oCtkeJ" +shine_payments = "m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR" +shine_users = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm" [programs.localnet] -shine_payments = "4QDCcaURt7phJGcvDS4VQQtrqDmUSMXvkWnMnMKCiUyE" -shine_users = "5dFcWDNp42Xn9Vv4oDMJzM4obBJ8hvDuAtPX54fT5L3t" +shine_payments = "m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR" +shine_users = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm" [registry] url = "https://api.apr.dev" diff --git a/shine/doc/SHINE_PAYMENTS_V2.md b/shine/doc/SHINE_PAYMENTS_V2.md index 9eb7773..5b5f825 100644 --- a/shine/doc/SHINE_PAYMENTS_V2.md +++ b/shine/doc/SHINE_PAYMENTS_V2.md @@ -15,13 +15,13 @@ 1. `config_pda` (`shine_payments_v2_config`) - `dao_wallet` - - `manager_wallet` (права на смену coef/limit) + - `manager_wallet` (сервисный параметр, для будущих сценариев) - `inflow_vault` - - `call_reward_lamports` 2. `coef_limit_pda` (`shine_payments_v2_coef_limit`) - `coef_ppm` (fixed-point, scale = 1_000_000) - - `limit_lamports` (лимит долга очереди 1 для обычной покупки) + - `limit_lamports` (лимит суммарной исторической суммы очереди 1 для обычной покупки) + - `call_reward_lamports` (награда за шаг выплат, максимум 0.01 SOL) 3. `queues_pda` (`shine_payments_v2_queues`) - очередь 1: `tickets_total`, `tickets_paid`, `sum_total`, `sum_paid` @@ -52,8 +52,9 @@ - вызывается один раз (кто угодно); - создает `config_pda`, `coef_limit_pda`, `queues_pda`, `inflow_vault_pda`. -2. `update_coef_limit` (только `manager_wallet` из config) - - меняет коэффициент и лимит покупки в очередь 1. +2. `update_coef_limit` (только `dao_wallet` из config) + - меняет коэффициент, лимит покупки в очередь 1 и награду шага выплат; + - ограничение награды: не более `0.01 SOL`. 3. `grant_manager_limits` (только `dao_wallet` из config) - DAO выдает/добавляет лимиты менеджеру: @@ -65,7 +66,8 @@ 4. `buy_ticket` - обычная покупка билета в очередь 1; - сумма покупки идет в DAO; - - тикет получает выплату `input * coef_ppm / 1_000_000`. + - тикет получает выплату `input * coef_ppm / 1_000_000`; + - проверка лимита выполняется по `q1_sum_total` (исторически накопленная сумма, без вычета уже выплаченного). 5. `manager_add_ticket` - менеджер добавляет тикет в очередь 1 или 2; @@ -79,7 +81,7 @@ - шаг выплаты: - `X` получателю тикета, - `X` в DAO, - - `reward` вызывающему. + - `reward` вызывающему (из `coef_limit_pda`). - если обе очереди пусты/выплачены: - переводит весь доступный остаток inflow-вольта в DAO (без reward). diff --git a/shine/programs/shine_payments/src/lib.rs b/shine/programs/shine_payments/src/lib.rs index fe08be5..14de416 100644 --- a/shine/programs/shine_payments/src/lib.rs +++ b/shine/programs/shine_payments/src/lib.rs @@ -5,7 +5,7 @@ use std::str::FromStr; pub mod settings; -declare_id!("4QDCcaURt7phJGcvDS4VQQtrqDmUSMXvkWnMnMKCiUyE"); +declare_id!("m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR"); #[program] pub mod shine_payments { @@ -42,7 +42,6 @@ pub mod shine_payments { dao_wallet, manager_wallet, inflow_vault: ctx.accounts.inflow_vault_pda.key(), - call_reward_lamports: settings::START_CALL_REWARD_LAMPORTS, }; create_and_store_state( ctx.program_id, @@ -58,6 +57,7 @@ pub mod shine_payments { version: 1, coef_ppm: settings::START_COEF_PPM, limit_lamports: settings::START_LIMIT_LAMPORTS, + call_reward_lamports: settings::START_CALL_REWARD_LAMPORTS, }; create_and_store_state( ctx.program_id, @@ -104,19 +104,27 @@ pub mod shine_payments { Ok(()) } - pub fn update_coef_limit(ctx: Context, args: UpdateCoefLimitArgs) -> Result<()> { + pub fn update_coef_limit( + ctx: Context, + args: UpdateCoefLimitArgs, + ) -> Result<()> { let config = read_state::(&ctx.accounts.config_pda)?; require_keys_eq!( - config.manager_wallet, + config.dao_wallet, ctx.accounts.signer.key(), - PaymentsError::UnauthorizedManager + PaymentsError::UnauthorizedDao ); require!(args.coef_ppm > 0, PaymentsError::InvalidCoefficient); require!(args.limit_lamports > 0, PaymentsError::InvalidLimit); + require!( + args.call_reward_lamports <= settings::MAX_CALL_REWARD_LAMPORTS, + PaymentsError::InvalidCallReward + ); let mut coef_limit = read_state::(&ctx.accounts.coef_limit_pda)?; coef_limit.coef_ppm = args.coef_ppm; coef_limit.limit_lamports = args.limit_lamports; + coef_limit.call_reward_lamports = args.call_reward_lamports; write_state(&ctx.accounts.coef_limit_pda, &coef_limit)?; Ok(()) } @@ -198,12 +206,9 @@ pub mod shine_payments { PaymentsError::InvalidDaoWallet ); - let current_debt = queues - .q1_sum_total - .checked_sub(queues.q1_sum_paid) - .ok_or(error!(ErrCode::MathOverflow))?; + let queue1_sum_total_before = queues.q1_sum_total; require!( - current_debt < coef_limit.limit_lamports, + queue1_sum_total_before < coef_limit.limit_lamports, PaymentsError::QueueTemporarilyPaused ); @@ -250,7 +255,7 @@ pub mod shine_payments { is_paid: false, recipient_wallet: args.recipient_wallet, payout_lamports, - debt_before_lamports: current_debt, + debt_before_lamports: queue1_sum_total_before, }; create_state_with_seeds( ctx.program_id, @@ -280,7 +285,10 @@ pub mod shine_payments { args: ManagerAddTicketArgs, ) -> Result<()> { require!(args.payout_lamports > 0, PaymentsError::InvalidPayoutAmount); - require!(args.queue_id == 1 || args.queue_id == 2, PaymentsError::InvalidTicketQueue); + require!( + args.queue_id == 1 || args.queue_id == 2, + PaymentsError::InvalidTicketQueue + ); let (expected_manager_pda, _) = find_manager_allowance_pda(ctx.program_id, ctx.accounts.signer.key); @@ -289,7 +297,8 @@ pub mod shine_payments { ctx.accounts.manager_allowance_pda.key(), ErrCode::InvalidPdaAddress ); - let mut allowance = read_state::(&ctx.accounts.manager_allowance_pda)?; + let mut allowance = + read_state::(&ctx.accounts.manager_allowance_pda)?; require_keys_eq!( allowance.manager_wallet, ctx.accounts.signer.key(), @@ -297,16 +306,10 @@ pub mod shine_payments { ); let mut queues = read_state::(&ctx.accounts.queues_pda)?; - let current_debt = if args.queue_id == 1 { - queues - .q1_sum_total - .checked_sub(queues.q1_sum_paid) - .ok_or(error!(ErrCode::MathOverflow))? + let debt_before_total = if args.queue_id == 1 { + queues.q1_sum_total } else { - queues - .q2_sum_total - .checked_sub(queues.q2_sum_paid) - .ok_or(error!(ErrCode::MathOverflow))? + queues.q2_sum_total }; if args.queue_id == 1 { @@ -351,7 +354,7 @@ pub mod shine_payments { is_paid: false, recipient_wallet: args.recipient_wallet, payout_lamports: args.payout_lamports, - debt_before_lamports: current_debt, + debt_before_lamports: debt_before_total, }; let seed_prefix = if args.queue_id == 1 { settings::Q1_TICKET_SEED @@ -396,6 +399,7 @@ pub mod shine_payments { pub fn step_payout(ctx: Context) -> Result<()> { let config = read_state::(&ctx.accounts.config_pda)?; + let coef_limit = read_state::(&ctx.accounts.coef_limit_pda)?; let mut queues = read_state::(&ctx.accounts.queues_pda)?; let _vault_state = read_state::(&ctx.accounts.inflow_vault_pda)?; @@ -451,7 +455,10 @@ pub mod shine_payments { ticket.queue_id == target_queue, PaymentsError::InvalidTicketQueue ); - require!(ticket.index == next_index, PaymentsError::InvalidTicketIndex); + require!( + ticket.index == next_index, + PaymentsError::InvalidTicketIndex + ); require!(!ticket.is_paid, PaymentsError::TicketAlreadyPaid); require_keys_eq!( ctx.accounts.ticket_recipient_wallet.key(), @@ -462,7 +469,7 @@ pub mod shine_payments { let needed = ticket .payout_lamports .checked_add(ticket.payout_lamports) - .and_then(|v| v.checked_add(config.call_reward_lamports)) + .and_then(|v| v.checked_add(coef_limit.call_reward_lamports)) .ok_or(error!(ErrCode::MathOverflow))?; require!( available_vault_lamports(&ctx.accounts.inflow_vault_pda)? >= needed, @@ -482,7 +489,7 @@ pub mod shine_payments { transfer_from_vault( &ctx.accounts.inflow_vault_pda, &ctx.accounts.signer, - config.call_reward_lamports, + coef_limit.call_reward_lamports, )?; ticket.is_paid = true; @@ -535,7 +542,7 @@ pub struct Init<'info> { #[derive(Accounts)] pub struct UpdateCoefLimit<'info> { - /// CHECK: подписант-менеджер, проверяется атрибутом `signer` и сверкой адреса в коде. + /// CHECK: подписант-DAO, проверяется атрибутом `signer` и сверкой адреса в коде. #[account(mut, signer)] pub signer: AccountInfo<'info>, /// CHECK: PDA конфига, читается и валидируется вручную. @@ -611,6 +618,9 @@ pub struct StepPayout<'info> { /// CHECK: PDA очередей, читается и валидируется вручную. #[account(mut)] pub queues_pda: AccountInfo<'info>, + /// CHECK: PDA коэффициента/лимита/награды, читается и валидируется вручную. + #[account(mut)] + pub coef_limit_pda: AccountInfo<'info>, /// CHECK: PDA inflow-вольта, адрес сверяется с конфигом вручную. #[account(mut)] pub inflow_vault_pda: AccountInfo<'info>, @@ -629,6 +639,7 @@ pub struct StepPayout<'info> { pub struct UpdateCoefLimitArgs { pub coef_ppm: u64, pub limit_lamports: u64, + pub call_reward_lamports: u64, } #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] @@ -657,7 +668,6 @@ pub struct ConfigState { pub dao_wallet: Pubkey, pub manager_wallet: Pubkey, pub inflow_vault: Pubkey, - pub call_reward_lamports: u64, } #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] @@ -665,6 +675,7 @@ pub struct CoefLimitState { pub version: u8, pub coef_ppm: u64, pub limit_lamports: u64, + pub call_reward_lamports: u64, } #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] @@ -722,6 +733,8 @@ pub enum PaymentsError { InvalidCoefficient, #[msg("Некорректный лимит")] InvalidLimit, + #[msg("Некорректная награда за шаг выплаты")] + InvalidCallReward, #[msg("Некорректная сумма")] InvalidAmount, #[msg("Очередь временно приостановлена: достигнут лимит")] @@ -749,7 +762,11 @@ fn ensure_expected_pdas(program_id: &Pubkey, accounts: &Init) -> Result<()> { let (coef, _) = find_single_pda(program_id, settings::COEF_LIMIT_SEED); let (queues, _) = find_single_pda(program_id, settings::QUEUES_SEED); let (inflow, _) = find_single_pda(program_id, settings::INFLOW_VAULT_SEED); - require_keys_eq!(config, accounts.config_pda.key(), ErrCode::InvalidPdaAddress); + require_keys_eq!( + config, + accounts.config_pda.key(), + ErrCode::InvalidPdaAddress + ); require_keys_eq!( coef, accounts.coef_limit_pda.key(), @@ -819,14 +836,7 @@ fn create_state_with_seeds<'info, T: AnchorSerialize>( space: usize, state: &T, ) -> Result<()> { - create_pda( - pda, - payer, - system_program, - program_id, - seeds, - space as u64, - )?; + create_pda(pda, payer, system_program, program_id, seeds, space as u64)?; write_state(pda, state) } @@ -856,7 +866,10 @@ fn transfer_from_vault(vault: &AccountInfo, recipient: &AccountInfo, amount: u64 } let mut vault_lamports = vault.try_borrow_mut_lamports()?; let mut recipient_lamports = recipient.try_borrow_mut_lamports()?; - require!(**vault_lamports >= amount, PaymentsError::NotEnoughInflowForStep); + require!( + **vault_lamports >= amount, + PaymentsError::NotEnoughInflowForStep + ); **vault_lamports = vault_lamports .checked_sub(amount) .ok_or(error!(ErrCode::MathOverflow))?; diff --git a/shine/programs/shine_payments/src/settings.rs b/shine/programs/shine_payments/src/settings.rs index 6a03c88..20eaa50 100644 --- a/shine/programs/shine_payments/src/settings.rs +++ b/shine/programs/shine_payments/src/settings.rs @@ -4,7 +4,7 @@ pub const QUEUES_SEED: &[u8] = b"shine_payments_v2_queues"; pub const INFLOW_VAULT_SEED: &[u8] = b"shine_payments_v2_inflow_vault"; pub const Q1_TICKET_SEED: &[u8] = b"shine_payments_v2_q1_ticket"; pub const Q2_TICKET_SEED: &[u8] = b"shine_payments_v2_q2_ticket"; -pub const MANAGER_ALLOWANCE_SEED: &[u8] = b"shine_payments_v2_manager_allowance"; +pub const MANAGER_ALLOWANCE_SEED: &[u8] = b"shine_p_v2_manager_allow"; pub const CONFIG_SPACE: usize = 8 + 160; pub const COEF_LIMIT_SPACE: usize = 8 + 96; @@ -17,6 +17,7 @@ pub const COEF_SCALE_PPM: u64 = 1_000_000; pub const START_COEF_PPM: u64 = 5_000_000; // 5.0 pub const START_LIMIT_LAMPORTS: u64 = 100 * 1_000_000_000; // 100 SOL pub const START_CALL_REWARD_LAMPORTS: u64 = 8_000_000; // 0.008 SOL +pub const MAX_CALL_REWARD_LAMPORTS: u64 = 10_000_000; // 0.01 SOL -pub const DAO_WALLET: &str = "6bFc5Gz5qF172GQhK5HpDbWs8F6qcSxdHn5XqAstf1fY"; +pub const DAO_WALLET: &str = "9vXFoN9ngfN1gpqQ3HT5n3y9Wp2r7HnSQckirgwVwWwb"; pub const MANAGER_WALLET: &str = "4yzHKs2zFXpyqqCETe8KpAs4xhEo4QhJ2ybyTgRZphZv"; diff --git a/shine/programs/shine_payments/web/admin_tools.html b/shine/programs/shine_payments/web/admin_tools.html index 20a1ac6..a15e1a7 100644 --- a/shine/programs/shine_payments/web/admin_tools.html +++ b/shine/programs/shine_payments/web/admin_tools.html @@ -5,66 +5,92 @@ Тех. инструменты — Shine Payments Devnet -

Техническая страница (Devnet)

-
Программа:
+
+ +

Техническая страница (Devnet)

+
Программа:
-
-
- - - +
+
+ + + +
+
+
-
-
-
-
-

Коэффициент и лимит

-
Право изменения: загрузка...
-
- - - +
+

Коэффициент, лимит и награда шага выплат

+
Право изменения: загрузка...
+
+ + + + +
+
Лимит покупки Q1 = max(limit_lamports - q1_sum_total, 0)
+
Шаг выплаты = payout + payout + reward
+
-
-
-
-

Адреса и агрегаты

-
Загрузка...
-
+
+

Адреса и агрегаты

+
Загрузка...
+
-
-

Очередь 1 (все билеты)

-
-
-
+
+

Очередь 1 (все билеты)

+
+
+
-
-

Очередь 2 (все билеты)

-
-
+
+

Очередь 2 (все билеты)

+
+
+