
Сховище
Сховище дозволяє користувачам безпечно зберігати свої активи. Сховище є фундаментальним будівельним блоком у DeFi, який у своїй основі дозволяє користувачам безпечно зберігати свої активи (у цьому випадку лампорти), які пізніше може вивести лише той самий користувач.
У цьому завданні ми створимо просте сховище для лампортів, яке демонструє, як працювати з базовими обліковими записами, адресами, похідними від програми (PDA), та міжпрограмним викликом (CPI). Якщо ви не знайомі з Anchor, вам варто почати з читання Вступу до Anchor, щоб ознайомитися з основними концепціями, які ми будемо використовувати в цій програмі.
Installation
Перш ніж почати, переконайтеся, що Rust та Anchor встановлені (див. офіційну документацію, якщо вам потрібне нагадування). Потім у терміналі виконайте:
anchor init blueshift_anchor_vaultДля цього завдання нам не потрібні додаткові пакети, тому ви можете відкрити новостворену папку і почати кодувати!
Template
Почнімо з базової структури програми. Ми реалізуємо все в lib.rs, оскільки це проста програма. Ось початковий шаблон з основними компонентами, які нам знадобляться:
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 інструкцію переказу з системної програми
Ось як ми визначаємо структуру облікового запису:
#[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>,
}Розглянемо кожне обмеження облікового запису:
signer: Обмеженняmutпотрібне, оскільки ми будемо змінювати його лампорти під час переказів.vault:mutтому що ми будемо змінювати його лампорти.seedsіbumpsвизначають, як отримати дійсний PDA з сідів.
system_program: перевіряє, чи обліковий запис встановлено як виконуваний, і чи адреса відповідає System Program.
Errors
Для цієї невеликої програми нам не потрібно багато помилок, тому ми створимо лише 2 переліки:
VaultAlreadyExists: повідомляє нас, якщо в обліковому записі вже є лампорти, що означає, що сховище вже існує.InvalidAmount: ми не можемо внести суму, меншу за мінімальну ренту для базового облікового запису, тому перевіряємо, що сума більша за це значення.
Це виглядатиме приблизно так:
#[error_code]
pub enum VaultError {
#[msg("Vault already exists")]
VaultAlreadyExists,
#[msg("Invalid amount")]
InvalidAmount,
}Deposit
Інструкція депозиту виконує такі кроки:
Перевіряє, що сховище порожнє (має нуль лампортів), щоб запобігти подвійним депозитам
Переконується, що сума депозиту перевищує мінімум, звільнений від ренти для
SystemAccountПереказує лампорти від підписанта до сховища, використовуючи CPI до System Program
Спочатку реалізуємо ці перевірки:
// 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 таким чином:
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
Інструкція виведення коштів виконує такі кроки:
Перевіряє, чи містить сховище лампорти (чи не порожнє)
Використовує PDA сховища для підписання переказу від свого імені
Переказує всі лампорти зі сховища назад підписанту
Спочатку перевіримо, чи є у сховищі лампорти для виведення:
// Check if vault has any lamports
require_neq!(ctx.accounts.vault.lamports(), 0, VaultError::InvalidAmount);Потім нам потрібно створити насіння для підписанта PDA та виконати переказ:
// 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()
)?;Безпека цього виведення гарантується двома факторами:
PDA сховища створюється з використанням відкритого ключа підписанта, що гарантує, що тільки початковий вкладник може зняти кошти
Здатність PDA підписувати переказ перевіряється через насіння, яке ми надаємо в
CpiContext::new_with_signer
Conclusion
Тепер ви можете перевірити свою програму за допомогою наших модульних тестів і отримати свої NFT!
Почніть з компіляції вашої програми, використовуючи таку команду в терміналі
anchor buildЦе згенерувало файл .so безпосередньо у вашій папці target/deploy.
Тепер натисніть на кнопку take challenge і перетягніть туди файл!