Program Derived Address

Σε αυτή την ενότητα, θα μάθετε πώς να δημιουργήσετε ένα βασικό πρόγραμμα Δημιουργίας, Ανάγνωσης, Ενημέρωσης, Διαγραφής (CRUD).

Αυτός ο οδηγός παρουσιάζει ένα απλό πρόγραμμα όπου οι χρήστες μπορούν να δημιουργούν, να ενημερώνουν και να διαγράφουν ένα μήνυμα. Κάθε μήνυμα υπάρχει σε έναν λογαριασμό με ντετερμινιστική διεύθυνση που προέρχεται από το ίδιο το πρόγραμμα (Program Derived Address ή PDA).

Αυτός ο οδηγός σας καθοδηγεί στη δημιουργία και τον έλεγχο ενός προγράμματος Solana χρησιμοποιώντας το πλαίσιο Anchor, ενώ παράλληλα επιδεικνύει τα Program Derived Addresses (PDAs). Για περισσότερες λεπτομέρειες, ανατρέξτε στη σελίδα Program Derived Addresses.

Για αναφορά, μπορείτε να δείτε τον τελικό κώδικα αφού ολοκληρώσετε τόσο τις ενότητες PDA όσο και Cross-Program Invocation (CPI).

Αρχικός Κώδικας

Ξεκινήστε ανοίγοντας αυτόν τον σύνδεσμο Solana Playground με τον αρχικό κώδικα. Στη συνέχεια, κάντε κλικ στο κουμπί "Import" για να προσθέσετε το πρόγραμμα στα έργα σας στο Solana Playground.

ΕισαγωγήΕισαγωγή

Στο αρχείο lib.rs, θα βρείτε ένα πρόγραμμα με τις εντολές create_message, update_message, και delete_message που θα προσθέσετε στα επόμενα βήματα.

use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
pub mod message_program {
use super::*;
pub fn create_message(ctx: Context<CreateMessage>, content: String) -> Result<()> {
// TODO: Implement create message
Ok(())
}
pub fn update_message(ctx: Context<UpdateMessage>, content: String) -> Result<()> {
// TODO: Implement update message
Ok(())
}
pub fn delete_message(ctx: Context<DeleteMessage>) -> Result<()> {
// TODO: Implement delete message
Ok(())
}
}

Πριν ξεκινήσετε, εκτελέστε anchor build στο τερματικό του Playground για να ελέγξετε ότι το αρχικό πρόγραμμα μεταγλωττίζεται με επιτυχία.

$ anchor build
BuildOptions {
skip_lint: false,
mode: Release,
features: "",
}
Compiling proc-macro2 v1.0.66
Compiling unicode-ident v1.0.11
Compiling quote v1.0.33
Compiling syn v1.0.109
Compiling thiserror v1.0.48
Compiling typenum v1.16.0
Compiling version_check v0.9.4
Compiling subtle v2.5.0
Compiling serde v1.0.188
Compiling keccak v0.1.4
Compiling serde_derive v1.0.188
Compiling sha2 v0.10.7
Compiling bs58 v0.4.0
Compiling toml_datetime v0.6.3
Compiling winnow v0.5.15
Compiling generic-array v0.14.7
Compiling block-buffer v0.10.4
Compiling serde_json v1.0.107
Compiling digest v0.10.7
Compiling crypto-common v0.1.6
Compiling cfg-if v1.0.0
Compiling cpufeatures v0.2.9
Compiling itoa v1.0.9
Compiling ryu v1.0.15
Compiling toml_edit v0.19.14
Compiling ahash v0.7.6
Compiling once_cell v1.18.0
Compiling memchr v2.6.3
Compiling regex-syntax v0.7.5
Compiling regex-automata v0.3.8
Compiling regex v1.9.5
Compiling hashbrown v0.12.3
Compiling solana-frozen-abi-macro v1.16.13
Compiling solana-program v1.16.13
Compiling thiserror-impl v1.0.48
Compiling num-traits v0.2.16
Compiling num-integer v0.1.45
Compiling num-bigint v0.4.4
Compiling solana-frozen-abi v1.16.13
Compiling anchor-attribute-constant v0.28.0
Compiling anchor-attribute-error v0.28.0
Compiling anchor-attribute-interface v0.28.0
Compiling anchor-attribute-event v0.28.0
Compiling anchor-attribute-account v0.28.0
Compiling anchor-derive-accounts v0.28.0
Compiling anchor-attribute-program v0.28.0
Compiling anchor-attribute-access-control v0.28.0
Compiling anchor-attribute-state v0.28.0
Compiling anchor-syn v0.28.0
Compiling anchor-lang v0.28.0
Compiling message_program v0.1.0 (/workspace/message_program/programs/message_program)
Finished release [optimized] target(s) in 1m 01s

Ορισμός Τύπου Λογαριασμού Μηνύματος

Πρώτα, ορίστε τη δομή για τον λογαριασμό μηνύματος που δημιουργεί το πρόγραμμα. Αυτή η δομή καθορίζει τα δεδομένα που θα αποθηκευτούν στον λογαριασμό που δημιουργείται από το πρόγραμμα.

Στο 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, ένα Solana cluster για δοκιμές ανάπτυξης.

Το πορτοφόλι του Playground συνδέεται στο devnet από προεπιλογή. Βεβαιωθείτε ότι το πορτοφόλι του Playground έχει devnet SOL για να πληρώσει για την ανάπτυξη του προγράμματος. Αποκτήστε devnet SOL από το Solana Faucet.

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 στο τερματικό του Playground για να ελέγξετε ότι εκτελείται όπως αναμένεται. Τα επόμενα βήματα προσθέτουν τις πραγματικές δοκιμές.

Terminal
$
test

Κλήση της εντολής Create

Ενημερώστε την πρώτη δοκιμή με τα εξής:

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?