From b83e8c3979d4079164a3756b2c2f450e8cc3cc7bb378296f1d82031752a10ced Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 9 Jun 2026 22:48:59 +0400 Subject: [PATCH] =?UTF-8?q?solana:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BD=D0=B5=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=20common?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shine-solana/shine/Cargo.lock | 262 +------------ shine-solana/shine/Cargo.toml | 1 - shine-solana/shine/programs/common/Cargo.toml | 10 - .../programs/common/src/deploy_config.rs | 43 --- shine-solana/shine/programs/common/src/lib.rs | 2 - .../shine/programs/common/src/utils.rs | 359 ------------------ 6 files changed, 4 insertions(+), 673 deletions(-) delete mode 100644 shine-solana/shine/programs/common/Cargo.toml delete mode 100644 shine-solana/shine/programs/common/src/deploy_config.rs delete mode 100644 shine-solana/shine/programs/common/src/lib.rs delete mode 100644 shine-solana/shine/programs/common/src/utils.rs diff --git a/shine-solana/shine/Cargo.lock b/shine-solana/shine/Cargo.lock index 25725e3..1397baf 100644 --- a/shine-solana/shine/Cargo.lock +++ b/shine-solana/shine/Cargo.lock @@ -14,188 +14,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "anchor-attribute-access-control" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f70fd141a4d18adf11253026b32504f885447048c7494faf5fa83b01af9c0cf" -dependencies = [ - "anchor-syn", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-account" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715a261c57c7679581e06f07a74fa2af874ac30f86bd8ea07cca4a7e5388a064" -dependencies = [ - "anchor-syn", - "bs58", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-constant" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730d6df8ae120321c5c25e0779e61789e4b70dc8297102248902022f286102e4" -dependencies = [ - "anchor-syn", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-error" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e6e449cc3a37b2880b74dcafb8e5a17b954c0e58e376432d7adc646fb333ef" -dependencies = [ - "anchor-syn", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-event" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7710e4c54adf485affcd9be9adec5ef8846d9c71d7f31e16ba86ff9fc1dd49f" -dependencies = [ - "anchor-syn", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-program" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ecfd49b2aeadeb32f35262230db402abed76ce87e27562b34f61318b2ec83c" -dependencies = [ - "anchor-lang-idl", - "anchor-syn", - "anyhow", - "bs58", - "heck", - "proc-macro2", - "quote", - "serde_json", - "syn 1.0.109", -] - -[[package]] -name = "anchor-derive-accounts" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be89d160793a88495af462a7010b3978e48e30a630c91de47ce2c1d3cb7a6149" -dependencies = [ - "anchor-syn", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-derive-serde" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc6ee78acb7bfe0c2dd2abc677aaa4789c0281a0c0ef01dbf6fe85e0fd9e6e4" -dependencies = [ - "anchor-syn", - "borsh-derive-internal", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-derive-space" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134a01c0703f6fd355a0e472c033f6f3e41fac1ef6e370b20c50f4c8d022cea7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-lang" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6bab117055905e930f762c196e08f861f8dfe7241b92cee46677a3b15561a0a" -dependencies = [ - "anchor-attribute-access-control", - "anchor-attribute-account", - "anchor-attribute-constant", - "anchor-attribute-error", - "anchor-attribute-event", - "anchor-attribute-program", - "anchor-derive-accounts", - "anchor-derive-serde", - "anchor-derive-space", - "base64 0.21.7", - "bincode", - "borsh 0.10.4", - "bytemuck", - "solana-program", - "thiserror 1.0.69", -] - -[[package]] -name = "anchor-lang-idl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e8599d21995f68e296265aa5ab0c3cef582fd58afec014d01bd0bce18a4418" -dependencies = [ - "anchor-lang-idl-spec", - "anyhow", - "heck", - "serde", - "serde_json", - "sha2 0.10.9", -] - -[[package]] -name = "anchor-lang-idl-spec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdf143115440fe621bdac3a29a1f7472e09f6cd82b2aa569429a0c13f103838" -dependencies = [ - "anyhow", - "serde", -] - -[[package]] -name = "anchor-syn" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc7a6d90cc643df0ed2744862cdf180587d1e5d28936538c18fc8908489ed67" -dependencies = [ - "anyhow", - "bs58", - "heck", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 1.0.109", - "thiserror 1.0.69", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - [[package]] name = "arrayref" version = "0.3.9" @@ -220,12 +38,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -410,13 +222,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "common" -version = "0.1.0" -dependencies = [ - "anchor-lang", -] - [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -607,15 +412,6 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "indexmap" version = "2.9.0" @@ -626,12 +422,6 @@ dependencies = [ "hashbrown 0.15.2", ] -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - [[package]] name = "js-sys" version = "0.3.77" @@ -954,12 +744,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1001,18 +785,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.9.9" @@ -1263,7 +1035,7 @@ dependencies = [ "solana-pubkey", "solana-sdk-ids", "solana-system-interface", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -1546,7 +1318,7 @@ dependencies = [ "solana-sysvar", "solana-sysvar-id", "solana-vote-interface", - "thiserror 2.0.12", + "thiserror", "wasm-bindgen", ] @@ -1677,7 +1449,7 @@ checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" dependencies = [ "libsecp256k1", "solana-define-syscall", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -1902,33 +1674,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "thiserror-impl", ] [[package]] @@ -1995,12 +1747,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "version_check" version = "0.9.5" diff --git a/shine-solana/shine/Cargo.toml b/shine-solana/shine/Cargo.toml index f69b9ad..3b11a08 100644 --- a/shine-solana/shine/Cargo.toml +++ b/shine-solana/shine/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "programs/common", "programs/shine_login_guard", "programs/shine_users", "programs/shine_payments", diff --git a/shine-solana/shine/programs/common/Cargo.toml b/shine-solana/shine/programs/common/Cargo.toml deleted file mode 100644 index 44c4d21..0000000 --- a/shine-solana/shine/programs/common/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "common" -version = "0.1.0" -edition = "2021" - -[dependencies] -anchor-lang = "0.31.1" - - -[features] diff --git a/shine-solana/shine/programs/common/src/deploy_config.rs b/shine-solana/shine/programs/common/src/deploy_config.rs deleted file mode 100644 index 95a7540..0000000 --- a/shine-solana/shine/programs/common/src/deploy_config.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Единый деплой-конфиг проекта SHINE. -//! Здесь хранятся адреса и параметры, которые зависят от окружения деплоя. - -// ========================= -// Program IDs -// ========================= - -/// `SHINE_PAYMENTS_PROGRAM_ID` — адрес программы `shine_payments` для текущего окружения. -pub const SHINE_PAYMENTS_PROGRAM_ID: &str = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW"; - -/// `SHINE_USERS_PROGRAM_ID` — адрес программы `shine_users` для текущего окружения. -pub const SHINE_USERS_PROGRAM_ID: &str = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm"; - -/// `SHINE_LOGIN_GUARD_PROGRAM_ID` — адрес программы проверки платных логинов. -pub const SHINE_LOGIN_GUARD_PROGRAM_ID: &str = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo"; - -// ========================= -// DAO / роли управления -// ========================= - -/// `DAO_AUTHORITY` — кошелек DAO/управления, который имеет право менять защищенные настройки. -pub const DAO_AUTHORITY: &str = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P"; - -/// `DAO_TREASURY_WALLET` — кошелек казны DAO для поступления DAO-части выплат в `shine_payments`. -pub const DAO_TREASURY_WALLET: &str = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P"; - -// ========================= -// Комиссии / получатели -// ========================= - -/// `REGISTRATION_FEE_RECEIVER` — кошелек получателя комиссии за регистрацию в `shine_users`. -pub const REGISTRATION_FEE_RECEIVER: &str = "9vXFoN9ngfN1gpqQ3HT5n3y9Wp2r7HnSQckirgwVwWwb"; - -// ========================= -// Оракул (Pyth SOL/USD) -// ========================= - -/// `PYTH_SOL_USD_FEED_ID` — feed id Pyth для пары SOL/USD (используется для проверки feed внутри аккаунта). -pub const PYTH_SOL_USD_FEED_ID: &str = - "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"; - -/// `PYTH_SOL_USD_ACCOUNT` — адрес Solana-аккаунта обновлений цены Pyth для SOL/USD. -pub const PYTH_SOL_USD_ACCOUNT: &str = "7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE"; diff --git a/shine-solana/shine/programs/common/src/lib.rs b/shine-solana/shine/programs/common/src/lib.rs deleted file mode 100644 index 180d185..0000000 --- a/shine-solana/shine/programs/common/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod utils; -pub mod deploy_config; diff --git a/shine-solana/shine/programs/common/src/utils.rs b/shine-solana/shine/programs/common/src/utils.rs deleted file mode 100644 index 93f465d..0000000 --- a/shine-solana/shine/programs/common/src/utils.rs +++ /dev/null @@ -1,359 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_lang::solana_program::{program::invoke_signed, system_instruction}; - -/// сдесь коды всех ошибок - -#[error_code] -pub enum ErrCode { - /// Система уже инициализирована и не может быть инициализирована повторно! - #[msg("Система уже инициализирована и не может быть инициализирована повторно!")] - SystemAlreadyInitialized = 1000, - - #[msg("PDA не содержит данных или не инициализирован")] - EmptyPdaData = 1002, - - #[msg("Пользователь уже зарегистрирован")] - UserAlreadyExists = 1003, - - #[msg("Некорректный логин")] - InvalidLogin = 1004, - - #[msg("Не совпадает PDA адрес")] - InvalidPdaAddress = 1006, - - #[msg("Формат данных не поддерживается")] - UnsupportedFormat = 1011, - - #[msg("Ошибка при десериализации")] - DeserializationError = 1012, - - /// PDA уже существует, создание невозможно - #[msg("PDA-аккаунт уже существует и не может быть создан повторно.")] - PdaAlreadyExists = 1009, - - #[msg("Подписавший не совпадает с ожидаемым пользователем (это потому что пока временно можно регистрировать пользователя с другово аккаунта")] - InvalidSigner = 1005, - - /// Не получилось создат ьпользователя, система уже перегружена, попробуйте поззже!" - #[msg("Не получилось создать пользователя, система уже перегружена, попробуйте поззже!")] - NoSuitableIdPda = 1010, - - #[msg("Невалидная цифровая подпись записи")] - InvalidSignature = 1013, - - #[msg("Невалидный формат записи")] - InvalidRecordFormat = 1014, - - #[msg("Невалидная длина записи")] - InvalidRecordLength = 1015, - - #[msg("Невалидные данные записи")] - InvalidRecordData = 1016, - - #[msg("Невалидный хэш предыдущей версии")] - InvalidPrevHash = 1017, - - #[msg("Попытка изменить неизменяемое поле")] - ImmutableFieldChanged = 1018, - - #[msg("Попытка уменьшить лимит/баланс")] - BalanceDecrease = 1019, - - #[msg("Невалидная версия записи")] - InvalidVersion = 1020, - - #[msg("Размер записи превышает допустимый")] - RecordTooLarge = 1021, - - #[msg("Переполнение при вычислении")] - MathOverflow = 1022, - - #[msg("Неверный адрес получателя комиссии")] - InvalidFeeReceiver = 1023, - - #[msg("Пополнение лимита должно быть кратно шагу")] - InvalidLimitIncrement = 1024, - - #[msg("Невалидная magic-сигнатура записи")] - InvalidRecordMagic = 1025, - - #[msg("Логин относится к платным и требует отдельной покупки через DAO")] - PremiumLogin = 1026, - - #[msg("Некорректный ответ программы проверки логина")] - InvalidLoginGuardResponse = 1027, - - #[msg("Логин использует брендовый термин и требует дополнительной верификации")] - TrademarkLoginRequiresReview = 1028, -} - -///---------------------------------------------------------------------------------------------------------- -/// Базовые функции для работы с PDA -///---------------------------------------------------------------------------------------------------------- - -/// Создаёт PDA аккаунт (если его ещё нет), и записывает в него массив байт. -/// -/// Аргументы: -/// - `pda_account`: аккаунт, куда записываем -/// - `signer`: кто платит за создание (обычно пользователь) -/// - `program_id`: адрес текущей программы -/// - `seeds`: слайс сидов, по которым создавался PDA -/// - `data`: байты для записи -/// - `space`: желаемый размер аккаунта -pub fn create_and_write_pda<'info>( - pda_account: &AccountInfo<'info>, - signer: &AccountInfo<'info>, - system_program: &AccountInfo<'info>, - program_id: &Pubkey, - seeds: &[&[u8]], - data: Vec, - space: u64, -) -> Result<()> { - // ─────────────────────────────────────────────── - // 1. Проверяем, создан ли аккаунт (если нет — owner = default) - if pda_account.owner == &Pubkey::default() { - msg!("Создаём PDA с размером {} байт", space); - - let space = space; //+ 128; // Добавляется запас под метаданные - // Вычисляем необходимую арендную плату - let lamports = Rent::get()?.minimum_balance(space as usize); - - // Формируем инструкцию - let create_instr = system_instruction::create_account( - signer.key, - pda_account.key, - lamports, - space, - program_id, - ); - - // Выполняем инструкцию с подписью от PDA - invoke_signed( - &create_instr, - &[signer.clone(), pda_account.clone(), system_program.clone()], - &[&seeds], - )?; - } - - // ─────────────────────────────────────────────── - // 2. Пишем данные в аккаунт - let mut account_data = pda_account.try_borrow_mut_data()?; - - let copy_len = std::cmp::min(account_data.len(), data.len()); - account_data[..copy_len].copy_from_slice(&data[..copy_len]); - - // Если хочешь дополнить оставшееся нулями — раскомментируй: - // for i in copy_len..account_data.len() { - // account_data[i] = 0; - // } - - msg!("Успешно записано {} байт в PDA", copy_len); - Ok(()) -} - -/// Создаёт PDA аккаунт (если его ещё нет). -/// -/// ⚠️ Если аккаунт уже существует, выбрасывается ошибка. -/// Используется внутри инструкций смарт-контракта. -/// -/// Аргументы: -/// - `pda_account`: аккаунт, который хотим создать (PDA) -/// - `signer`: кто оплачивает создание аккаунта (обычно пользователь) -/// - `system_program`: системная программа (`111...111`) -/// - `program_id`: адрес текущей программы (используется для подписи PDA) -/// - `seeds`: массив сидов, по которым вычислялся PDA -/// - `space`: желаемый размер аккаунта в байтах (только данных, без метаданных) -pub fn create_pda<'info>( - pda_account: &AccountInfo<'info>, - signer: &AccountInfo<'info>, - system_program: &AccountInfo<'info>, - program_id: &Pubkey, - seeds: &[&[u8]], - space: u64, -) -> Result<()> { - // ─────────────────────────────────────────────── - // 1. Проверяем, существует ли аккаунт - if pda_account.owner != &Pubkey::default() { - // Если владелец не равен Pubkey::default, значит аккаунт уже создан - // Возвращаем ошибку с пояснением - return Err(error!(ErrCode::PdaAlreadyExists)); - } - - // ─────────────────────────────────────────────── - // 2. Логируем, что будем создавать PDA - msg!("Создаём PDA-аккаунт на {} байт", space); - - // Добавляем запас под метаданные Solana (примерно 128 байт) - let full_space = space; - - // Получаем минимальный баланс для аренды (чтобы аккаунт не удалили) - let lamports = Rent::get()?.minimum_balance(full_space as usize); - - // ─────────────────────────────────────────────── - // 3. Создаём инструкцию system_program для создания аккаунта - let create_instr = system_instruction::create_account( - signer.key, // от имени кого - pda_account.key, // для какого PDA - lamports, // сколько лампортов перевести - full_space, // сколько байт выделить - program_id, // кто будет владельцем PDA - ); - - // ─────────────────────────────────────────────── - // 4. Выполняем инструкцию с подписью PDA (через сиды) - invoke_signed( - &create_instr, - &[signer.clone(), pda_account.clone(), system_program.clone()], - &[&seeds], // PDA сиды → для подписи - )?; - - Ok(()) -} - -/// Записывает массив байт в PDA аккаунт (в начало data-секции). -/// -/// ⚠️ Убедись, что PDA был передан как `#[account(mut)]` -/// ⚠️ Эта функция ничего не создаёт, только пишет. -/// -/// Аргументы: -/// - `pda_account`: аккаунт, в который пишем (должен быть mut) -/// - `data`: бинарный массив, который нужно записать -pub fn write_to_pda<'info>(pda_account: &AccountInfo<'info>, data: &[u8]) -> Result<()> { - // ─────────────────────────────────────────────── - // 1. Получаем доступ к данным PDA (на запись) - let mut account_data = pda_account.try_borrow_mut_data()?; - - // ─────────────────────────────────────────────── - // 2. Вычисляем сколько байт реально можно записать - // (на случай, если data длиннее, чем выделено место) - let copy_len = std::cmp::min(account_data.len(), data.len()); - - // ─────────────────────────────────────────────── - // 3. Копируем данные в аккаунт (с самого начала) - account_data[..copy_len].copy_from_slice(&data[..copy_len]); - - // Логируем, сколько байт записано - msg!("Успешно записано {} байт в PDA", copy_len); - - Ok(()) -} - -/// ------------------------------------------------------------------------ -/// safe_read_pda ‒ «безопасное чтение PDA» -/// ------------------------------------------------------------------------ -/// -/// * Принимает: ссылку на `AccountInfo<'info>` PDA-аккаунта. -/// * Возвращает: `Vec` с данными аккаунта. -/// Если аккаунта нет или его данные пусты — возвращается `Vec::new()` -/// длиной 0 байт. -/// -/// Как работает ─────────────────────────────────────────────────────────── -/// 1. Проверяем, что аккаунт **инициализирован**: у не-инициализированного -/// owner = Pubkey::default(). Если owner нулевой — сразу отдаём пустой вектор. -/// 2. Если длина буфера == 0 (Anchor helper `data_is_empty()`), тоже отдаём пустой. -/// 3. Пытаемся безопасно (`try_borrow_data`) получить ссылку на данные. -/// - Успех → копируем их в Vec и возвращаем. -/// - Ошибка (например, конфликт borrow) → логируем и возвращаем пустой Vec. -/// -/// пример использования -/// let raw_bytes = safe_read_pda(&ctx.accounts.readonly_pda); -/// require!(!raw_bytes.is_empty(), ErrCode::EmptyPdaData); -/// msg!("Размер считанных данных: {}", raw_bytes.len()); -/// ------------------------------------------------------------------------ -pub fn safe_read_pda<'info>(pda_account: &AccountInfo<'info>) -> Vec { - // ───────────────────────────────────────────────────────────────────── - // 1) Аккаунт Н*Е* СУЩЕСТВУЕТ или не инициализирован: - // owner == Pubkey::default() (в Solana нулевой owner у пустого счёта) - // ───────────────────────────────────────────────────────────────────── - if pda_account.owner == &Pubkey::default() { - msg!("safe_read_pda: аккаунт не инициализирован ‒ возвращаем пустой массив"); - return Vec::new(); // [] - } - - // ───────────────────────────────────────────────────────────────────── - // 2) У аккаунта нет данных (длина 0) — тоже считаем «пустым» - // ───────────────────────────────────────────────────────────────────── - if pda_account.data_is_empty() { - msg!("safe_read_pda: у аккаунта data_len == 0 ‒ возвращаем пустой массив"); - return Vec::new(); - } - - // ───────────────────────────────────────────────────────────────────── - // 3) Пытаемся безопасно забрать буфер данных; ошибки перехватываем - // ───────────────────────────────────────────────────────────────────── - match pda_account.try_borrow_data() { - Ok(data_ref) => { - // to_vec() копирует bytes → Vec, чтобы дальше работать без borrow-лифа - data_ref.to_vec() - } - Err(e) => { - // Ошибка при borrow (например, уже есть активное мутабельное заимствование) - msg!( - "safe_read_pda: ошибка borrow_data ({:?}) ‒ возвращаем пустой массив", - e - ); - Vec::new() - } - } -} - -/// ------------------------------------------------------------------------ -/// delete_pda_with_assign — закрыть PDA, вернуть ренту и освободить адрес -/// ------------------------------------------------------------------------ -/// -/// Параметры: -/// - `pda_account` : PDA-аккаунт (mut), который закрываем (owned вашей программой) -/// - `recipient` : счёт, на который возвращаем лампорты (обычно пользователь) -/// - `system_program`: системная программа (111...111) -/// - `program_id` : Pubkey вашей программы (проверка владельца) -/// - `seeds` : сиды PDA (в том же порядке, как при создании), чтобы PDA «подписал» assign -/// -/// Делает: -/// 1) Проверяет, что PDA принадлежит вашей программе. -/// 2) Обнуляет данные и сжимает их до 0 байт (realloc(0)). -/// 3) Переводит все лампорты PDA на `recipient`. -/// 4) Делает `assign` владельца на System Program (через `invoke_signed`). -/// -/// Результат: -/// — В конце транзакции аккаунт с lamports=0 и data_len=0 будет удалён рантаймом, -/// владелец = System Program (чисто/ожидаемо). -/// — В следующей транзакции можно снова создать PDA с тем же сидом. -/// ------------------------------------------------------------------------ - -pub fn delete_pda_return_rent<'info>( - pda_account: &AccountInfo<'info>, - recipient: &AccountInfo<'info>, - program_id: &Pubkey, -) -> Result<()> { - // 0) проверки - require!( - pda_account.owner != &Pubkey::default(), - ErrCode::EmptyPdaData - ); - require!(pda_account.owner == program_id, ErrCode::InvalidPdaAddress); - - // 1) Переложить все лампорты с PDA на получателя (мы владелец, это разрешено) - let amount = **pda_account.lamports.borrow(); - if amount > 0 { - **recipient.lamports.borrow_mut() = recipient - .lamports() - .checked_add(amount) - .ok_or(ProgramError::InsufficientFunds)?; - **pda_account.lamports.borrow_mut() = 0; - } - - // 2) Нулим данные (если были) - if !pda_account.data_is_empty() { - let mut data = pda_account.try_borrow_mut_data()?; - for b in data.iter_mut() { - *b = 0; - } - } - - // 3) Сжать до 0 байт - pda_account.realloc(0, false)?; - - // Никаких assign/transfer больше не делаем — это надёжнее. - msg!("PDA закрыт: рента отправлена на {}", recipient.key); - Ok(()) -}