From d25c19cdaa36d5d54d08de15335346b4f194089138cba0b8b96778bd2bfb6365 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sat, 6 Jun 2026 17:12:26 +0400 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8=D0=B5=20state=20?= =?UTF-8?q?=D0=B2=20shine=5Fpayments=20=D0=B8=20=D0=BE=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20e2e=20=D1=82=D0=B5=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6_1659_shine_payments_e2e_перепись_и_q3.md | 185 ++++++++++++++++++ .../shine/programs/shine_payments/src/lib.rs | 31 ++- 2 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-06-06_1659_shine_payments_e2e_перепись_и_q3.md diff --git a/Dev_Docs/Pending_Features/2026-06-06_1659_shine_payments_e2e_перепись_и_q3.md b/Dev_Docs/Pending_Features/2026-06-06_1659_shine_payments_e2e_перепись_и_q3.md new file mode 100644 index 0000000..db8fa57 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-06_1659_shine_payments_e2e_перепись_и_q3.md @@ -0,0 +1,185 @@ +# Shine Payments: e2e после переписи без Anchor и добавления Q3 + +## Краткое описание + +Нужно вручную и через вспомогательные CLI-проверки подтвердить, что программа `shine_payments` после: + +- переписи на чистый `solana_program`; +- отказа от `programs/common`; +- добавления очереди `Q3`; +- обновления HTML UI; + +корректно работает на devnet с новым `program id`. + +Отличие от финального боевого сценария: + +- вместо DAO-механики используется обычный кошелёк `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`, которому даны права DAO на изменение коэффициента и выдачу лимитов менеджеру. + +## Что именно проверять + +### 1. Подготовка окружения + +Проверить и зафиксировать: + +- новый keypair программы `shine_payments`; +- новый `program id`; +- обновление `program id` в HTML UI и связанных настройках; +- наличие deploy authority, которой можно закрыть старый buffer/programdata, если это технически доступно; +- адреса тестовых кошельков: + - DAO/базовый кошелёк; + - менеджер; + - покупатель 1; + - покупатель 2; + - получатели выплат. + +### 2. Очистка/смена старой программы + +Проверить один из сценариев: + +- если возможно, закрыть старый `program buffer/programdata` текущими ключами; +- если закрытие невозможно или нецелесообразно, зафиксировать это и продолжить с новым `program id`. + +Отдельно проверить, что старые PDA предыдущей версии не используются новой программой. + +### 3. Деплой и init новой программы + +Проверить: + +- `cargo build-sbf` проходит; +- новая программа деплоится на devnet; +- `init` выполняется один раз на пустых PDA; +- после `init` читаются: + - `config`; + - `coef_limit`; + - `queues`; + - `inflow_vault`. + +Сразу после `init` запросить состояние очередей и зафиксировать, что: + +- `Q1`, `Q2`, `Q3` пустые; +- `tickets_total = 0`; +- `tickets_paid = 0`; +- все суммы равны `0`. + +### 4. Проверка покупки билета + +На минимальных суммах проверить: + +1. покупку через `buy_ticket_usd`; +2. покупку через `buy_ticket_sol`; +3. при необходимости ещё один вызов базового `buy_ticket`. + +После каждой покупки: + +- запросить состояние `Q1`; +- убедиться, что создался следующий ticket; +- проверить рост: + - `q1_tickets_total`; + - `q1_sum_total_usd_cents`; +- убедиться, что деньги покупки ушли в `dao_wallet`, а не в `inflow_vault`. + +### 5. Проверка DAO-управления + +Проверить: + +1. изменение коэффициента через `update_coef_limit`; +2. повторный запрос `coef_limit` и подтверждение нового значения; +3. выдачу менеджеру прав через `grant_manager_limits`: + - отдельно под `Q1`; + - отдельно под `Q2`; + - отдельно под `Q3`. + +После выдачи лимитов: + +- считать `manager_allowance_pda`; +- убедиться, что лимиты записаны отдельно по трём очередям. + +### 6. Проверка manager_add_ticket + +На минимальных суммах создать менеджерские тикеты: + +1. один ticket в `Q1`; +2. один ticket в `Q2`; +3. один ticket в `Q3`. + +После каждого добавления: + +- запросить состояние очередей; +- проверить рост счётчиков и сумм именно у нужной очереди; +- проверить уменьшение соответствующего manager allowance. + +### 7. Проверка приоритета очередей + +Подтвердить очередность `step_payout`: + +1. сначала выплачивается `Q1`; +2. затем `Q2`; +3. затем `Q3`. + +Для этого: + +- между шагами регулярно читать `queues`; +- фиксировать, какой именно ticket был следующим к выплате; +- убедиться, что при наличии pending в `Q1` программа не уходит в `Q2` или `Q3`. + +### 8. Проверка частичных выплат + +Перед выплатами пополнять `inflow_vault` только минимально достаточными суммами. + +Нужно проверить: + +1. частичную серию выплат, когда часть тикетов ещё остаётся pending; +2. дополнительную покупку билета в промежутке между выплатами; +3. повторную проверку приоритета после появления нового билета в `Q1`. + +После каждого `step_payout`: + +- запрашивать состояние очередей; +- проверять: + - рост `tickets_paid`; + - рост `sum_paid_usd_cents`; + - `is_paid = true` у погашенного ticket; + - правильный DAO multiplier: + - `Q1 -> 1x`; + - `Q2 -> 2x`; + - `Q3 -> 3x`. + +### 9. Проверка финального добора + +После частичных выплат: + +- купить ещё один билет; +- допополнить `inflow_vault`; +- выполнить оставшиеся `step_payout` до полного погашения всех трёх очередей. + +В конце: + +- все pending ticket должны отсутствовать; +- все суммы paid должны совпасть с total по каждой очереди; +- если вызвать `step_payout` на пустых очередях, доступный остаток `inflow_vault` должен уйти в `dao_wallet`. + +### 10. Финальный возврат лампортов + +После завершения теста вернуть все доступные остатки, которые можно вернуть текущими полномочиями, на базовый кошелёк: + +- `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` + +Отдельно зафиксировать: + +- что именно удалось вернуть; +- что именно нельзя вернуть без специальной инструкции закрытия или без deploy authority. + +## Ожидаемый результат + +- `buy_ticket_usd` и `buy_ticket_sol` создают ticket без ошибок чтения state; +- `Q3` работает наравне с `Q2`, но с третьим приоритетом; +- DAO может менять коэффициент и выдавать лимиты; +- менеджер может создавать билеты во все три очереди; +- `step_payout` соблюдает порядок `Q1 -> Q2 -> Q3`; +- DAO-множитель на выплатах равен `1x/2x/3x` для `Q1/Q2/Q3`; +- HTML UI и on-chain программа используют один и тот же актуальный `program id`; +- остатки средств после теста по максимуму возвращены на базовый DAO-кошелёк. + +## Статус + +- `pending` diff --git a/shine-solana/shine/programs/shine_payments/src/lib.rs b/shine-solana/shine/programs/shine_payments/src/lib.rs index db9dfd3..eb6fe83 100644 --- a/shine-solana/shine/programs/shine_payments/src/lib.rs +++ b/shine-solana/shine/programs/shine_payments/src/lib.rs @@ -250,11 +250,16 @@ impl<'a> Reader<'a> { } trait StateCodec: Sized { + fn encoded_len() -> usize; fn encode(&self) -> Vec; fn decode(data: &[u8]) -> Result; } impl StateCodec for ConfigState { + fn encoded_len() -> usize { + 1 + 32 + 32 + } + fn encode(&self) -> Vec { let mut out = Vec::with_capacity(65); out.push(self.version); @@ -274,6 +279,10 @@ impl StateCodec for ConfigState { } impl StateCodec for CoefLimitState { + fn encoded_len() -> usize { + 1 + 8 + 8 + 8 + } + fn encode(&self) -> Vec { let mut out = Vec::with_capacity(25); out.push(self.version); @@ -295,6 +304,10 @@ impl StateCodec for CoefLimitState { } impl StateCodec for QueuesState { + fn encoded_len() -> usize { + 1 + 12 * 8 + } + fn encode(&self) -> Vec { let mut out = Vec::with_capacity(97); out.push(self.version); @@ -339,6 +352,10 @@ impl StateCodec for QueuesState { } impl StateCodec for TicketState { + fn encoded_len() -> usize { + 1 + 1 + 8 + 1 + 32 + 8 + 8 + } + fn encode(&self) -> Vec { let mut out = Vec::with_capacity(59); out.push(self.version); @@ -370,6 +387,10 @@ impl StateCodec for TicketState { } impl StateCodec for ManagerAllowanceState { + fn encoded_len() -> usize { + 1 + 32 + 8 + 8 + 8 + } + fn encode(&self) -> Vec { let mut out = Vec::with_capacity(57); out.push(self.version); @@ -393,6 +414,10 @@ impl StateCodec for ManagerAllowanceState { } impl StateCodec for VaultState { + fn encoded_len() -> usize { + 1 + } + fn encode(&self) -> Vec { vec![self.version] } @@ -1162,9 +1187,9 @@ fn read_state(pda: &AccountInfo) -> Result { require!(!is_uninitialized_account(pda), PaymentsError::EmptyState); require_keys_eq!(*pda.owner, id(), PaymentsError::InvalidPdaAddress); let data = pda.try_borrow_data()?; - let used_len = data.iter().rposition(|b| *b != 0).map(|idx| idx + 1).unwrap_or(0); - require!(used_len > 0, PaymentsError::EmptyState); - T::decode(&data[..used_len]) + let encoded_len = T::encoded_len(); + require!(data.len() >= encoded_len, PaymentsError::InvalidAccountData); + T::decode(&data[..encoded_len]) } fn is_uninitialized_account(account: &AccountInfo) -> bool {