عنوان مشتق من البرنامج

في هذا القسم، ستتعلم كيفية بناء برنامج أساسي للإنشاء والقراءة والتحديث والحذف (CRUD).

يوضح هذا الدليل برنامجًا بسيطًا حيث يمكن للمستخدمين إنشاء وتحديث وحذف رسالة. توجد كل رسالة في حساب بعنوان محدد مشتق من البرنامج نفسه (عنوان مشتق من البرنامج أو PDA).

يرشدك هذا الدليل خلال بناء واختبار برنامج سولانا باستخدام إطار عمل Anchor مع توضيح العناوين المشتقة من البرنامج (PDAs). لمزيد من التفاصيل، راجع صفحة العناوين المشتقة من البرنامج.

للمرجع، يمكنك عرض الكود النهائي بعد إكمال قسمي PDA واستدعاء البرامج المتقاطعة (CPI).

الكود الأولي

ابدأ بفتح رابط Solana Playground مع الكود الأولي. ثم انقر على زر "Import" لإضافة البرنامج إلى مشاريع Solana Playground الخاصة بك.

استيراداستيراد

في ملف lib.rs، ستجد برنامجًا به تعليمات create، update، وdelete لإضافتها في الخطوات التالية.

lib.rs
use anchor_lang::prelude::*;
declare_id!("8KPzbM2Cwn4Yjak7QYAEH9wyoQh86NcBicaLuzPaejdw");
#[program]
pub mod pda {
use super::*;
pub fn create(_ctx: Context<Create>) -> Result<()> {
Ok(())
}
pub fn update(_ctx: Context<Update>) -> Result<()> {
Ok(())
}
pub fn delete(_ctx: Context<Delete>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct Create {}
#[derive(Accounts)]
pub struct Update {}
#[derive(Accounts)]
pub struct Delete {}
#[account]
pub struct MessageAccount {}

قبل البدء، قم بتشغيل build في طرفية Playground للتحقق من أن البرنامج الأولي يتم بناؤه بنجاح.

Terminal
$
build

تحديد نوع حساب الرسالة

أولاً، حدد بنية حساب الرسالة الذي ينشئه البرنامج. تحدد هذه البنية البيانات المراد تخزينها في الحساب الذي ينشئه البرنامج.

في lib.rs، قم بتحديث بنية MessageAccount بما يلي:

lib.rs
#[account]
pub struct MessageAccount {
pub user: Pubkey,
pub message: String,
pub bump: u8,
}

قم ببناء البرنامج مرة أخرى عن طريق تشغيل build في الطرفية.

Terminal
$
build

يحدد هذا الكود البيانات التي سيتم تخزينها في حساب الرسالة. بعد ذلك، ستقوم بإضافة تعليمات البرنامج.

إضافة تعليمة الإنشاء

الآن، أضف تعليمة create التي تقوم بإنشاء وتهيئة MessageAccount.

ابدأ بتحديد الحسابات المطلوبة للتعليمة عن طريق تحديث بنية Create بما يلي:

lib.rs
#[derive(Accounts)]
#[instruction(message: String)]
pub struct Create<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
seeds = [b"message", user.key().as_ref()],
bump,
payer = user,
space = 8 + 32 + 4 + message.len() + 1
)]
pub message_account: Account<'info, MessageAccount>,
pub system_program: Program<'info, System>,
}

بعد ذلك، أضف منطق العمل لتعليمة create عن طريق تحديث الدالة create بما يلي:

lib.rs
pub fn create(ctx: Context<Create>, message: String) -> Result<()> {
msg!("Create Message: {}", message);
let account_data = &mut ctx.accounts.message_account;
account_data.user = ctx.accounts.user.key();
account_data.message = message;
account_data.bump = ctx.bumps.message_account;
Ok(())
}

أعد بناء البرنامج.

Terminal
$
build

إضافة تعليمة التحديث

بعد ذلك، أضف تعليمة update لتغيير MessageAccount برسالة جديدة.

مثل الخطوة السابقة، حدد أولاً الحسابات المطلوبة بواسطة تعليمة update.

قم بتحديث بنية Update بما يلي:

lib.rs
#[derive(Accounts)]
#[instruction(message: String)]
pub struct Update<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
mut,
seeds = [b"message", user.key().as_ref()],
bump = message_account.bump,
realloc = 8 + 32 + 4 + message.len() + 1,
realloc::payer = user,
realloc::zero = true,
)]
pub message_account: Account<'info, MessageAccount>,
pub system_program: Program<'info, System>,
}

بعد ذلك، أضف المنطق لتعليمة update.

lib.rs
pub fn update(ctx: Context<Update>, message: String) -> Result<()> {
msg!("Update Message: {}", message);
let account_data = &mut ctx.accounts.message_account;
account_data.message = message;
Ok(())
}

أعد بناء البرنامج

Terminal
$
build

إضافة تعليمة الحذف

بعد ذلك، أضف تعليمة delete لإغلاق MessageAccount.

قم بتحديث بنية Delete بما يلي:

lib.rs
#[derive(Accounts)]
pub struct Delete<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
mut,
seeds = [b"message", user.key().as_ref()],
bump = message_account.bump,
close = user,
)]
pub message_account: Account<'info, MessageAccount>,
}

بعد ذلك، أضف المنطق لتعليمة delete.

lib.rs
pub fn delete(_ctx: Context<Delete>) -> Result<()> {
msg!("Delete Message");
Ok(())
}

أعد بناء البرنامج.

Terminal
$
build

نشر البرنامج

لقد أكملت الآن برنامج CRUD الأساسي. قم بنشر البرنامج عن طريق تشغيل deploy في طرفية Playground.

في هذا المثال، ستقوم بنشر البرنامج على devnet، وهي مجموعة سولانا مخصصة لاختبار التطوير.

تتصل محفظة Playground بـ devnet افتراضيًا. تأكد من أن محفظة Playground الخاصة بك تحتوي على SOL من devnet لدفع تكاليف نشر البرنامج. احصل على SOL من devnet من صنبور سولانا.

Terminal
$
deploy

إعداد ملف الاختبار

يتضمن الكود الأولي أيضًا ملف اختبار في anchor.test.ts.

anchor.test.ts
import { PublicKey } from "@solana/web3.js";
describe("pda", () => {
it("Create Message Account", async () => {});
it("Update Message Account", async () => {});
it("Delete Message Account", async () => {});
});

أضف الكود أدناه داخل describe()، ولكن قبل أقسام it().

anchor.test.ts
const program = pg.program;
const wallet = pg.wallet;
const [messagePda, messageBump] = PublicKey.findProgramAddressSync(
[Buffer.from("message"), wallet.publicKey.toBuffer()],
program.programId
);

قم بتشغيل ملف الاختبار عن طريق تنفيذ test في طرفية بلايجراوند للتحقق من أنه يعمل كما هو متوقع. تضيف الخطوات التالية الاختبارات الفعلية.

Terminal
$
test

استدعاء تعليمة الإنشاء

قم بتحديث الاختبار الأول بما يلي:

anchor.test.ts
it("Create Message Account", async () => {
const message = "Hello, World!";
const transactionSignature = await program.methods
.create(message)
.accounts({
messageAccount: messagePda
})
.rpc({ commitment: "confirmed" });
const messageAccount = await program.account.messageAccount.fetch(
messagePda,
"confirmed"
);
console.log(JSON.stringify(messageAccount, null, 2));
console.log(
"Transaction Signature:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
});

استدعاء تعليمة التحديث

قم بتحديث الاختبار الثاني بما يلي:

anchor.test.ts
it("Update Message Account", async () => {
const message = "Hello, Solana!";
const transactionSignature = await program.methods
.update(message)
.accounts({
messageAccount: messagePda
})
.rpc({ commitment: "confirmed" });
const messageAccount = await program.account.messageAccount.fetch(
messagePda,
"confirmed"
);
console.log(JSON.stringify(messageAccount, null, 2));
console.log(
"Transaction Signature:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
});

استدعاء تعليمة الحذف

قم بتحديث الاختبار الثالث بما يلي:

anchor.test.ts
it("Delete Message Account", async () => {
const transactionSignature = await program.methods
.delete()
.accounts({
messageAccount: messagePda
})
.rpc({ commitment: "confirmed" });
const messageAccount = await program.account.messageAccount.fetchNullable(
messagePda,
"confirmed"
);
console.log("Expect Null:", JSON.stringify(messageAccount, null, 2));
console.log(
"Transaction Signature:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
});

تشغيل الاختبار

بعد إعداد اختباراتك، قم بتشغيل ملف الاختبار باستخدام test في طرفية Playground. يقوم هذا الأمر بتشغيل الاختبارات على البرنامج المنشور على شبكة devnet ويسجل روابط إلى SolanaFM لعرض تفاصيل المعاملة.

Terminal
$
test

افحص روابط SolanaFM لعرض تفاصيل المعاملة.

لاحظ أنه في هذا المثال، إذا قمت بتشغيل الاختبار مرة أخرى، فإن تعليمة create ستفشل لأن messageAccount موجود بالفعل كحساب. يمكن أن يوجد حساب واحد فقط لكل PDA معين.

Is this page helpful?