Anchor 101
What is Anchor
Anchor — це провідний фреймворк для розробки смарт-контрактів на Solana, що пропонує повний робочий процес для написання, тестування, розгортання та взаємодії з програмами в мережі.
Ключові переваги
- Зменшення шаблонного коду: Anchor абстрагує повторювану роботу з управління обліковими записами, серіалізації інструкцій та обробки помилок, щоб ви могли зосередитися лише на основній бізнес-логіці.
- Вбудована безпека: Ретельні перевірки, такі як верифікація власності облікових записів та валідація даних, працюють з коробки, запобігаючи багатьом поширеним вразливостям ще до їх появи.
Макроси Anchor
declare_id!()
: Оголошує, за якою адресою в мережі розміщується програма.#[program]
: Позначає модуль, що містить кожну точку входу інструкції та функцію бізнес-логіки.#[derive(Accounts)]
: Перераховує облікові записи, які потребує інструкція, та автоматично забезпечує їхні обмеження.#[error_code]
: Визначає користувацькі, зрозумілі типи помилок, які роблять налагодження чіткішим і швидшим.
Разом ці процедурні макроси абстрагують низькорівневе управління байтами, дозволяючи вам створювати безпечні, готові до виробництва програми Solana з набагато меншими зусиллями.
Структура програми
Почнімо з базової версії програми, щоб детально пояснити, що насправді робить кожен макрос:
declare_id!("22222222222222222222222222222222222222222222");
#[program]
pub mod blueshift_anchor_vault {
use super::*;
pub fn deposit(ctx: Context<VaultAction>, amount: u64) -> Result<()> {
// ...
Ok(())
}
pub fn withdraw(ctx: Context<VaultAction>) -> Result<()> {
// ...
Ok(())
}
}
#[derive(Accounts)]
pub struct VaultAction<'info> {
// ...
}
#[error_code]
pub enum VaultError {
// ...
}
Це трансформується у сфокусовані модулі замість того, щоб усе запихати в lib.rs
для більш структурованих програм. Дерево папок програми виглядатиме приблизно так:
src
├── instructions
│ ├── instruction1.rs
│ ├── mod.rs
│ ├── instruction2.rs
│ └── instruction3.rs
├── errors.rs
├── lib.rs
└── state.rs
declare_id!()
Макрос declare_id!()
призначає вашій програмі її адресу в мережі; унікальний відкритий ключ, отриманий з пари ключів у папці target
проєкту. Ця пара ключів підписує та розгортає скомпільований бінарний файл .so
, що містить усю логіку та дані програми.
Примітка: Ми використовуємо заповнювач 222222...
у прикладах Blueshift через наш внутрішній набір тестів. У виробничому середовищі Anchor згенерує для вас новий ідентифікатор програми, коли ви запустите стандартні команди збірки та розгортання.
#[program]
і #[derive(Accounts)]
Кожна інструкція має власну структуру Context, яка перераховує всі облікові записи і, за потреби, будь-які дані, які знадобляться інструкції.
У цьому прикладі deposit
і withdraw
використовують одні й ті самі облікові записи; з цієї причини ми створимо єдину структуру облікового запису під назвою VaultAction
, щоб зробити все ефективнішим і простішим.
Детальніше про макрос #[derive(Accounts)]
#[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>,
}
Як ми бачимо з фрагмента коду, макрос #[derive(Accounts)]
виконує три критично важливі функції:
- Оголошує всі облікові записи, які потрібні конкретній інструкції.
- Автоматично застосовує перевірки обмежень, блокуючи багато помилок і потенційних вразливостей під час виконання.
- Генерує допоміжні методи, які дозволяють безпечно отримувати доступ до облікових записів і змінювати їх.
Він досягає цього за допомогою комбінації типів облікових записів та вбудованих атрибутів.
Типи облікових записів у нашому прикладі
Signer<'info>
: Перевіряє, чи обліковий запис підписав транзакцію; важливо для безпеки та для CPI, які вимагають підпису.SystemAccount<'info>
: Підтверджує, що обліковий запис належить System Program.Program<'info, System>
: Гарантує, що обліковий запис є виконуваним і відповідає ідентифікатору System Program, що дозволяє виконувати CPI, такі як створення облікового запису або переказ лампортів.
Вбудовані атрибути, з якими ви зустрінетесь
mut
: Позначає обліковий запис як змінюваний; обов'язково, коли його баланс лампортів або дані можуть змінюватися.seeds & bump
: Перевіряє, чи обліковий запис є адресою, похідною від програми (PDA), згенерованою з наданих початкових значень плюс байт корекції.
Примітка PDA важливі, тому що:
- Коли їх використовує програма, якій вони належать, PDA можуть підписувати CPI від імені програми.
- Вони надають детерміновані, перевірювані адреси для збереження стану програми.
#[error_code]
Макрос #[error_code]
дозволяє визначати чіткі, користувацькі помилки всередині програми.
#[error_code]
pub enum VaultError {
#[msg("Vault already exists")]
VaultAlreadyExists,
#[msg("Invalid amount")]
InvalidAmount,
}
Кожен варіант перерахування може містити атрибут #[msg(...)]
, який записує описовий рядок щоразу, коли виникає помилка; це набагато корисніше, ніж просто числовий код під час налагодження.