Anchor
Anchor Vault

Anchor Vault

166 Graduates

Anchor Vault

Anchor Vault Challenge

Сховище

Сховище дозволяє користувачам безпечно зберігати свої активи. Сховище є фундаментальним будівельним блоком у DeFi, який у своїй основі дозволяє користувачам безпечно зберігати свої активи (у цьому випадку лампорти), які пізніше може вивести лише той самий користувач.

У цьому завданні ми створимо просте сховище для лампортів, яке демонструє, як працювати з базовими обліковими записами, адресами, похідними від програми (PDA), та міжпрограмним викликом (CPI). Якщо ви не знайомі з Anchor, вам варто почати з читання Вступу до Anchor, щоб ознайомитися з основними концепціями, які ми будемо використовувати в цій програмі.

Installation

Перш ніж почати, переконайтеся, що Rust та Anchor встановлені (див. офіційну документацію, якщо вам потрібне нагадування). Потім у терміналі виконайте:

anchor init blueshift_anchor_vault

Для цього завдання нам не потрібні додаткові пакети, тому ви можете відкрити новостворену папку і почати кодувати!

Template

Почнімо з базової структури програми. Ми реалізуємо все в lib.rs, оскільки це проста програма. Ось початковий шаблон з основними компонентами, які нам знадобляться:

rust
declare_id!("22222222222222222222222222222222222222222222");

#[program]
pub mod blueshift_anchor_vault {
    use super::*;

    pub fn deposit(ctx: Context<VaultAction>, amount: u64) -> Result<()> {
        // deposit logic
        Ok(())
    }

    pub fn withdraw(ctx: Context<VaultAction>) -> Result<()> {
        // withdraw logic
        Ok(())
    }
}

#[derive(Accounts)]
pub struct VaultAction<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"vault", signer.key().as_ref()],
        bump,
    )]
    pub vault: SystemAccount<'info>,
    pub system_program: Program<'info, System>,
}

#[error_code]
pub enum VaultError {
    // error enum
}

Примітка: не забудьте змінити ідентифікатор програми на 22222222222222222222222222222222222222222222, оскільки ми використовуємо його для тестування вашої програми.

Accounts

Оскільки обидві інструкції використовують однакові облікові записи, для спрощення та кращої читабельності ми можемо створити один контекст під назвою VaultAction і використовувати його як для deposit, так і для withdraw.

Структура облікового запису VaultAction повинна мати:

  • signer: це власник сховища і єдина особа, яка може вивести лампорти після створення сховища.

  • vault: PDA, отриманий з наступних сідів: [b"vault", signer.key().as_ref()], який зберігає лампорти для підписанта.

  • system_program: обліковий запис системної програми, який потрібно включити, оскільки ми будемо використовувати CPI інструкцію переказу з системної програми

Ось як ми визначаємо структуру облікового запису:

rust
#[derive(Accounts)]
pub struct VaultAction<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"vault", signer.key().as_ref()],
        bump,
    )]
    pub vault: SystemAccount<'info>,
    pub system_program: Program<'info, System>,
}

Розглянемо кожне обмеження облікового запису:

  1. signer: Обмеження mut потрібне, оскільки ми будемо змінювати його лампорти під час переказів.

  2. vault:

    • mut тому що ми будемо змінювати його лампорти.

    • seeds і bumps визначають, як отримати дійсний PDA з сідів.

  3. system_program: перевіряє, чи обліковий запис встановлено як виконуваний, і чи адреса відповідає System Program.

Errors

Для цієї невеликої програми нам не потрібно багато помилок, тому ми створимо лише 2 переліки:

  • VaultAlreadyExists: повідомляє нас, якщо в обліковому записі вже є лампорти, що означає, що сховище вже існує.

  • InvalidAmount: ми не можемо внести суму, меншу за мінімальну ренту для базового облікового запису, тому перевіряємо, що сума більша за це значення.

Це виглядатиме приблизно так:

rust
#[error_code]
pub enum VaultError {
    #[msg("Vault already exists")]
    VaultAlreadyExists,
    #[msg("Invalid amount")]
    InvalidAmount,
}

Deposit

Інструкція депозиту виконує такі кроки:

  1. Перевіряє, що сховище порожнє (має нуль лампортів), щоб запобігти подвійним депозитам

  2. Переконується, що сума депозиту перевищує мінімум, звільнений від ренти для SystemAccount

  3. Переказує лампорти від підписанта до сховища, використовуючи CPI до System Program

Спочатку реалізуємо ці перевірки:

rust
// Check if vault is empty
require_eq!(ctx.accounts.vault.lamports(), 0, VaultError::VaultAlreadyExists);

// Ensure amount exceeds rent-exempt minimum
require_gt!(amount, Rent::get()?.minimum_balance(0), VaultError::InvalidAmount);

Два макроси require діють як користувацькі захисні умови:

  • require_eq! підтверджує, що сховище порожнє (запобігає подвійним депозитам).

  • require_gt! перевіряє, що сума перевищує поріг, звільнений від ренти.

Після проходження перевірок, допоміжний засіб System Program від Anchor викликає CPI Transfer таким чином:

rust
use anchor_lang::system_program::{transfer, Transfer};

transfer(
    CpiContext::new(
        ctx.accounts.system_program.to_account_info(),
        Transfer {
            from: ctx.accounts.signer.to_account_info(),
            to: ctx.accounts.vault.to_account_info(),
        },
    ),
    amount,
)?;

Withdraw

Інструкція виведення коштів виконує такі кроки:

  1. Перевіряє, чи містить сховище лампорти (чи не порожнє)

  2. Використовує PDA сховища для підписання переказу від свого імені

  3. Переказує всі лампорти зі сховища назад підписанту

Спочатку перевіримо, чи є у сховищі лампорти для виведення:

rust
// Check if vault has any lamports
require_neq!(ctx.accounts.vault.lamports(), 0, VaultError::InvalidAmount);

Потім нам потрібно створити насіння для підписанта PDA та виконати переказ:

rust
// Create PDA signer seeds
let signer_key = ctx.accounts.signer.key();
let signer_seeds = &[b"vault", signer_key.as_ref(), &[ctx.bumps.vault]];

// Transfer all lamports from vault to signer
transfer(
    CpiContext::new_with_signer(
        ctx.accounts.system_program.to_account_info(),
        Transfer {
            from: ctx.accounts.vault.to_account_info(),
            to: ctx.accounts.signer.to_account_info(),
        },
        &[&signer_seeds[..]]
    ),
    ctx.accounts.vault.lamports()
)?;

Безпека цього виведення гарантується двома факторами:

  1. PDA сховища створюється з використанням відкритого ключа підписанта, що гарантує, що тільки початковий вкладник може зняти кошти

  2. Здатність PDA підписувати переказ перевіряється через насіння, яке ми надаємо в CpiContext::new_with_signer

Conclusion

Тепер ви можете перевірити свою програму за допомогою наших модульних тестів і отримати свої NFT!

Почніть з компіляції вашої програми, використовуючи таку команду в терміналі

anchor build

Це згенерувало файл .so безпосередньо у вашій папці target/deploy.

Тепер натисніть на кнопку take challenge і перетягніть туди файл!

Готові прийняти завдання?
Blueshift © 2025Commit: e573eab