Направить комиссии shine_users только в inflow_vault shine_payments
This commit is contained in:
parent
32bc2c9dc3
commit
b5bd253b8c
47
shine/doc/FUNDS_FLOW.md
Normal file
47
shine/doc/FUNDS_FLOW.md
Normal 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`.
|
||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user