يستخدم هذا الدليل السريع مشروع البداية الخاص بـ
إطار عمل Anchor الذي يُنشئه anchor init.
ستقوم بإنشاء المشروع محليًا، وتشغيل اختباراته، وبناء البرنامج، والتجوُّل عبر كود
البرنامج الذي يُولِّده Anchor.
المتطلبات الأساسية
قبل أن تبدأ، ثبِّت أدوات تطوير سولانا. يشمل التثبيت Rust وواجهة سطر أوامر سولانا وواجهة سطر أوامر Anchor.
استخدم الإصدار 1.1.2 أو أعلى من واجهة سطر أوامر Anchor لهذا القالب. تحقق من الإصدار المثبت لديك:
$anchor --version
إنشاء المشروع
شغِّل الأوامر التالية في طرفيتك:
$anchor init my-program$cd my-program
يتضمن مشروع البداية برنامج سولانا واحدًا ضمن programs/my-program. يحتوي
البرنامج على تعليمتين: إحداهما لتهيئة حساب عداد والأخرى لزيادة قيمة العداد.
تُوضح بعض أجزاء القالب أنماطًا شائعة في برامج سولانا: اشتقاق عناوين حسابات PDA، وإجراء Cross Program Invocation (CPI) لنقل SOL، واستخدام فحوصات أخطاء مخصصة لإيقاف التعليمة عند فشل أحد الشروط.
بناء البرنامج
شغِّل anchor build لتصريف برنامج البداية:
$anchor build
يُكتب البرنامج المُصرَّف إلى target/deploy/my_program.so. عند نشر البرنامج،
يُخزَّن محتوى ملف .so هذا في حساب على السلسلة.
تشغيل الاختبار
شغّل الاختبار الافتراضي:
$anchor test
يستخدم Anchor.toml الخاص بهذا القالب أمر اختبار Rust:
skip_local_validator = true[scripts]test = "cargo test"
يقوم الاختبار بتحميل البرنامج المُجمَّع في LiteSVM،
وإنشاء دافع، وإرسال تعليمتَي initialize وincrement، ثم التحقق من
حالة حساب العداد.
يقوم تشغيل anchor test أيضاً بتجميع البرنامج، لذا لا تحتاج إلى تشغيل
anchor build أولاً عند الاختبار محلياً.
نشر البرنامج
تُعدّ الاختبارات المحلية أسرع حلقة للتغذية الراجعة. عندما تكون مستعداً للنشر على شبكة، مثل devnet، قم أولاً بالبناء ثم انشر على الكلاستر.
يتطلب نشر برنامج سولانا SOL لأن البرنامج مخزّن في program account، ويجب على الحساب دفع تكلفة المساحة التي يستخدمها. على devnet، اطلب SOL مجاني من Solana Faucet أو باستخدام Solana CLI:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
الملفات المصدرية
يحتوي دليل src على برنامج سولانا. تشرح وثائق
هيكل البرنامج
الخاصة بـ Anchor الماكرو الأساسية المستخدمة هنا، بما فيها declare_id!،
و#[program]، و#[derive(Accounts)]، و#[account]. يستعرض هذا
القسم ملفات القالب.
lib.rs
lib.rs هو نقطة دخول البرنامج. يربط الملفات المصدرية، ويحدد عنوان البرنامج،
ويعرّف تعليمات البرنامج التي يمكن للمستخدمين استدعاؤها.
pub mod constants;pub mod error;pub mod instructions;pub mod state;use anchor_lang::prelude::*;pub use constants::*;pub use instructions::*;pub use state::*;declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");#[program]pub mod my_program {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {crate::instructions::initialize::handle_initialize(ctx)}pub fn increment(ctx: Context<Increment>) -> Result<()> {crate::instructions::increment::handle_increment(ctx)}}
[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");
تظهر نفس عنوان البرنامج في الإعدادات والكود. Anchor.toml يخبر Anchor بالعنوان
الذي سيتم نشره أو استدعاؤه على الشبكة. declare_id! يُعرِّف عنوان البرنامج
داخل البرنامج نفسه لأغراض التحقق الأمني.
constants.rs
يحتفظ constants.rs بالقيم المشتركة في مكان واحد. في هذا القالب، يشتق
COUNTER_SEED عنوان PDA الخاص بالعداد، ويُنقل HELLO_WORLD_LAMPORTS
أثناء التهيئة، ويُفحص MAX_COUNT قبل عملية الزيادة.
use anchor_lang::prelude::*;#[constant]pub const COUNTER_SEED: &[u8] = b"counter";#[constant]pub const HELLO_WORLD_LAMPORTS: u64 = 1;#[constant]pub const MAX_COUNT: u64 = 10;
#[derive(Accounts)]pub struct Initialize<'info> {// ...#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {// ...anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;Ok(())}
يستخدم initialize.rs ثابتَين:
COUNTER_SEEDيستخرج عنوان PDA الخاص بالعداد.HELLO_WORLD_LAMPORTSيحدد المبلغ المحوَّل من الدافع إلى حساب العداد.
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;Ok(())}
يستخدم increment.rs الثابتَ MAX_COUNT بوصفه الحدَّ
الأقصى للعداد. إذا كان العدد الحالي قد بلغ الحد الأقصى بالفعل، فإن
require! يُعيد CounterOverflow ولا يتغير بيانات الحساب.
state.rs
يُعرِّف state.rs أنواع بيانات مخصصة للحسابات التي يُنشئها البرنامج ويمتلكها.
يُعرِّف البرنامج تعليمات لإنشاء تلك البيانات وتهيئتها وتحديثها، غير أن بيانات
العداد لا تُخزَّن داخل البرنامج نفسه، بل تُخزَّن في حساب منفصل له عنوانه الخاص.
use anchor_lang::prelude::*;#[account]#[derive(InitSpace)]pub struct Counter {pub count: u64,pub authority: Pubkey,}
#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();Ok(())}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...ctx.accounts.counter.count += 1;Ok(())}
state.rs يُعرِّف بيانات حساب Counter. تستخدم ملفات التعليمات هذا النوع
عند إنشاء الحساب وتحديثه:
Counter::INIT_SPACEيُحدِّد حجم الحساب وفقاً للحقول المُعرَّفة فيstate.rs.countوauthorityهي قيم الحقول المكتوبة عند تهيئة الحساب.count += 1يُحدِّث قيمة العداد المخزَّنة بعد اجتياز التحقق.
error.rs
error.rs يُعرِّف الأخطاء المخصَّصة للبرنامج. في هذا القالب، توضِّح الأخطاء كيف
تتوقف معالجات التعليمات عندما لا يكون للمُستدعي صلاحية تحديث العداد، أو عندما
يكون العداد قد بلغ MAX_COUNT بالفعل.
use anchor_lang::prelude::*;#[error_code]pub enum ErrorCode {#[msg("Only the counter authority can update this counter")]Unauthorized,#[msg("Counter has reached the maximum value")]CounterOverflow,}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
error.rs يُسمِّي الأخطاء التي يمكن أن تُرجعها التعليمة:
ErrorCode::Unauthorizedيُرجَع عندما لا يكون الموقِّع هو الجهة المخوَّلة المخزَّنة في حساب العداد.ErrorCode::CounterOverflowيُرجَع عندما يكون العداد قد بلغMAX_COUNTبالفعل.
instructions.rs
instructions.rs يربط ملفات التعليمات بـ crate الخاص بالبرنامج حتى يتمكن
lib.rs من الوصول إلى كود تعليمات initialize وincrement. يُعرِّف كل
ملف تعليمات الحسابات المطلوبة لتلك التعليمة ومنطق المعالج الذي يعمل بعد أن يتحقق
Anchor من صحة تلك الحسابات.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs يُعرِّف الحسابات المطلوبة لإنشاء حساب العداد، ثم يكتب القيم
الأولية للحساب. تستخدم بنية #[derive(Accounts)]
قيود الحساب
الخاصة بـ Anchor لتحديد الحسابات المطلوبة وكيفية إنشاء حساب العداد الجديد.
use anchor_lang::prelude::*;use crate::{constants::*, state::Counter};#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Account Context
تُعرِّف بنية Initialize الحسابات التي يجب تضمينها عندما يستدعي مستخدم
تعليمة initialize. يتحقق Anchor من هذه الحسابات قبل تشغيل المعالج.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Payer Account
يدفع حساب payer تكلفة إنشاء حساب العداد. يعني النوع Signer<'info> أن
الدافع يجب أن يوقّع المعاملة، و #[account(mut)] يعني أن حساب الدافع قابل
للتعديل لأنه سيتم خصم lamports منه.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Counter Account
يخزّن حساب counter بيانات Counter من state.rs. init يخبر
Anchor بإنشاء هذا الحساب قبل تشغيل المعالج، و payer = payer يخبر Anchor
بالحساب الذي يدفع تكلفة الإنشاء.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
حجم الحساب
يخبر القيد space Anchor بمقدار بيانات الحساب المراد تخصيصها. يخزّن Anchor
أولاً محدِّداً بحجم 8 بايت، ثم البايتات المطلوبة لحقول Counter. يتيح
المحدِّد لـ Anchor التعرف على هذا الحساب باعتباره حساب Counter قبل إلغاء
تسلسل بيانات الحساب.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
عنوان العداد
يحدد القيدان seeds و bump عنوان PDA المتوقع لحساب العداد. يتحقق
Anchor من أن حساب counter المقدَّم يتطابق مع ذلك العنوان. يستخدم القالب
PDA حتى يتمكن المستخدمون من اشتقاق عنوان العداد من معرّف البرنامج وseed، مما
يجعل عنوان العداد حتمياً.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
System Program
حساب system_program مطلوب لأن إنشاء حساب جديد يستخدم System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
دالة المعالج
تعمل الدالة handle_initialize بعد أن يتحقق Anchor من صحة الحسابات في
Initialize. تمنح قيمة ctx المعالجَ إمكانية الوصول إلى تلك الحسابات
المتحقق منها.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
البيانات الأولية
يكتب المعالج القيم الأولى في حساب العداد الجديد. يبدأ العداد عند 0، ويصبح
الدافع هو الجهة المخوّلة بزيادة العداد لاحقاً.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
حسابات التحويل
يُضمَّن CPI الخاص بالتحويل هذا فقط لتوضيح كيفية تمرير CPI للحسابات إلى برنامج
آخر. تسرد بنية Transfer الحسابات التي يستخدمها System Program في عملية
التحويل.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
سياق CPI
CpiContext::new يجمع البرنامج المُستدعى مع الحسابات التي يتم تمريرها إلى
ذلك البرنامج. هذا هو الشكل الأساسي لـ CPI: اختر البرنامج المراد استدعاؤه، اجمع
الحسابات التي يتوقعها ذلك البرنامج، ثم مرّر كليهما إلى الاستدعاء. هنا، البرنامج
المُستدعى هو System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Invoke Transfer
anchor_lang::system_program::transfer يستدعي تعليمة التحويل الخاصة بـ
System Program. في هذا القالب، يُعدّ التحويل مثالاً بسيطاً على استدعاء برنامج
آخر من برنامجك. إذا فشل CPI الخاص بالتحويل، فإن تعليمة initialize تفشل
أيضاً.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Log Message
msg! يكتب رسالةً في سجلات البرنامج. Ok(()) يشير إلى أن التعليمة
أُنجزت بنجاح.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
increment.rs
increment.rs يُعرّف الحسابات المطلوبة لتحديث حساب عداد موجود. يتحقق المعالج من
أن الموقّع هو الجهة المخوّلة المخزّنة، ويتحقق من أن العدد لم يبلغ الحد
MAX_COUNT المحدد، ثم يزيد العدد.
use anchor_lang::prelude::*;use crate::{constants::*, error::ErrorCode, state::Counter};#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Account Context
يُعرّف هيكل Increment الحسابات التي يجب تضمينها عندما يستدعي المستخدم
تعليمة increment. يتحقق Anchor من هذه الحسابات قبل تشغيل المعالج.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Counter Account
يخزّن حساب counter بيانات Counter. يتيح قيد mut للمعالج تحديث
العدد المخزّن، ويتحقق قيدا seeds وbump من عنوان PDA الخاص بالعداد.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Authority Signer
يجب أن يوقّع حساب authority على المعاملة. يتحقق المعالج لاحقاً من أن هذا
الموقّع يطابق الجهة المخوّلة المخزّنة في حساب العداد.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
دالة المعالج
تعمل دالة handle_increment بعد أن يتحقق Anchor من الحسابات في
Increment. تمنح قيمة ctx المعالجَ إمكانية الوصول إلى تلك الحسابات
التي تم التحقق منها.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
التحقق من الصلاحية
يتحقق الفحص الأول من أن الموقِّع مخوَّل بتحديث هذا العداد. إذا كان عنوان
الموقِّع لا يتطابق مع counter.authority، تتوقف التعليمة مع
ErrorCode::Unauthorized. يوضح هذا التفويضَ على مستوى التطبيق: يمتلك
البرنامج بيانات العداد، لكنه ينفذ قاعدة تحدد أي موقِّع يمكنه تغيير تلك البيانات.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
التحقق من الحد الأقصى للعداد
يمنع الفحص الثاني العداد من تجاوز MAX_COUNT. إذا كان العداد قد بلغ الحد
بالفعل، تتوقف التعليمة مع ErrorCode::CounterOverflow. هذا الحد قاعدة
اصطناعية في القالب تتيح لك رؤية كيف تُوقف الأخطاء المخصصة تنفيذ التعليمة قبل
تغيير بيانات الحساب.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
تحديث العداد
فقط بعد اجتياز كلا الفحصين يقوم المعالج بتحديث بيانات الحساب. يضيف هذا السطر واحداً إلى قيمة العداد المخزَّنة.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
رسالة السجل
تكتب msg! العداد المحدَّث إلى سجلات البرنامج. تشير Ok(()) إلى أن
التعليمة أُعيدت بنجاح.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
ملف الاختبار
programs/my-program/tests/test_initialize.rs هو اختبار تكامل بلغة Rust. لا
يُشغّل validator محلياً. بدلاً من ذلك، يُحمّل ملف .so المُجمَّع في LiteSVM،
ويبني المعاملات التي تستدعي البرنامج، ويقرأ حساب العداد بعد كل معاملة. يبني
الاختبار التعليمات لمعاملة سولانا عن طريق تحديد معرّف البرنامج المراد استدعاؤه،
وتوفير instruction data، وتمرير الحسابات المطلوبة.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);
سياق حساب Initialize يحدد الحسابات المطلوبة من قِبَل تعليمة
initialize. يمرر الاختبار تلك الحسابات نفسها إلى المساعد
my_program::accounts::Initialize المُولَّد:
payerيُمرَّر بوصفهpayer: payer.pubkey().counterيُمرَّر بوصفهcounter.system_programيُمرَّر بوصفهsystem_program::ID.
my_program::instruction::Initialize {}.data()
يُنشئ instruction data. هنا يتم ترميز وسيطات التعليمة، غير أن تعليمة
initialize هذه لا تتطلب أي وسيطات.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);
سياق حساب Increment يحدد الحسابات المطلوبة من قِبَل تعليمة
increment. يمرر الاختبار تلك الحسابات نفسها إلى المساعد
my_program::accounts::Increment المُولَّد:
counterيُمرَّر بوصفهcounter.authorityيُمرَّر بوصفهauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
يُنشئ instruction data. هنا يتم ترميز وسيطات التعليمة، غير أن تعليمة
increment هذه لا تتطلب أي وسيطات.
use {anchor_lang::{prelude::Pubkey,solana_program::{instruction::Instruction, system_program},AccountDeserialize, InstructionData, ToAccountMetas,},litesvm::LiteSVM,solana_keypair::Keypair,solana_message::{Message, VersionedMessage},solana_signer::Signer,solana_transaction::versioned::VersionedTransaction,};#[test]fn test_initialize() {let program_id = my_program::id();let payer = Keypair::new();let counter = Pubkey::find_program_address(&[my_program::constants::COUNTER_SEED],&program_id,).0;let mut svm = LiteSVM::new();let bytes = include_bytes!(concat!(env!("CARGO_TARGET_TMPDIR"),"/../deploy/my_program.so"));svm.add_program(program_id, bytes).unwrap();svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 0);assert_eq!(counter_state.authority, payer.pubkey());let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 1);assert_eq!(counter_state.authority, payer.pubkey());}
إعداد المشروع
تخبر ملفات المشروع الجذرية كلاً من Anchor وCargo بكيفية بناء البرنامج واختباره ونشره. للاطلاع على مرجع كامل، راجع توثيق Anchor الخاص بـ إعداد Anchor.toml وAnchor CLI.
skip_local_validator = true[toolchain][features]resolution = trueskip-lint = false[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"[provider]cluster = "localnet"wallet = "~/.config/solana/id.json"[scripts]test = "cargo test"[hooks]
Is this page helpful?