Program Derived Address

W tej sekcji dowiesz się, jak zbudować podstawowy program CRUD (Create, Read, Update, Delete).

Ten przewodnik pokazuje prosty program, w którym użytkownicy mogą tworzyć, aktualizować i usuwać wiadomości. Każda wiadomość istnieje na koncie z deterministycznym adresem pochodzącym bezpośrednio z programu (Program Derived Address lub PDA).

Ten przewodnik przeprowadzi Cię przez budowanie i testowanie programu Solana przy użyciu frameworka Anchor, jednocześnie demonstrując Program Derived Addresses (PDAs). Więcej szczegółów znajdziesz na stronie Program Derived Addresses.

Dla odniesienia możesz zobaczyć ostateczny kod po ukończeniu sekcji dotyczących PDA i Cross-Program Invocation (CPI).

Kod początkowy

Rozpocznij, otwierając ten link do Solana Playground z kodem początkowym. Następnie kliknij przycisk "Import", aby dodać program do swoich projektów w Solana Playground.

ImportImport

W pliku lib.rs znajdziesz program z instrukcjami create, update i delete, które należy dodać w kolejnych krokach.

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 {}

Przed rozpoczęciem uruchom build w terminalu Playground, aby sprawdzić, czy program początkowy kompiluje się poprawnie.

Terminal
$
build

Zdefiniuj typ konta wiadomości

Najpierw zdefiniuj strukturę konta wiadomości, które program tworzy. Ta struktura określa dane do przechowywania na koncie utworzonym przez program.

W lib.rs zaktualizuj strukturę MessageAccount za pomocą następujących elementów:

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

Zbuduj program ponownie, uruchamiając build w terminalu.

Terminal
$
build

Ten kod definiuje, jakie dane należy przechowywać na koncie wiadomości. Następnie dodasz instrukcje programu.

Dodaj instrukcję tworzenia

Teraz dodaj instrukcję create, która tworzy i inicjalizuje MessageAccount.

Rozpocznij od zdefiniowania kont wymaganych dla instrukcji, aktualizując strukturę Create za pomocą następujących elementów:

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>,
}

Następnie dodaj logikę biznesową dla instrukcji create poprzez aktualizację funkcji create za pomocą następującego kodu:

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(())
}

Zbuduj program ponownie.

Terminal
$
build

Dodaj instrukcję aktualizacji

Następnie dodaj instrukcję update, aby zmienić MessageAccount na nową wiadomość.

Podobnie jak w poprzednim kroku, najpierw określ konta wymagane przez instrukcję update.

Zaktualizuj strukturę Update w następujący sposób:

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>,
}

Następnie dodaj logikę dla instrukcji 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(())
}

Przebuduj program

Terminal
$
build

Dodaj instrukcję usuwania

Następnie dodaj instrukcję delete do zamknięcia MessageAccount.

Zaktualizuj strukturę Delete o następujące elementy:

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>,
}

Następnie dodaj logikę dla instrukcji delete.

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

Przebuduj program.

Terminal
$
build

Wdróż program

Właśnie ukończyłeś podstawowy program CRUD. Wdróż program, uruchamiając deploy w terminalu Playground.

W tym przykładzie wdrożysz program na devnet, klaster Solana przeznaczony do testów deweloperskich.

Portfel Playground łączy się z devnet domyślnie. Upewnij się, że Twój portfel Playground ma devnet SOL, aby zapłacić za wdrożenie programu. Zdobądź devnet SOL z Solana Faucet.

Terminal
$
deploy

Skonfiguruj plik testowy

Kod początkowy zawiera również plik testowy w 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 () => {});
});

Dodaj poniższy kod wewnątrz describe(), ale przed sekcjami 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
);

Uruchom plik testowy, wykonując test w terminalu Playground, aby sprawdzić, czy działa zgodnie z oczekiwaniami. Kolejne kroki dodają rzeczywiste testy.

Terminal
$
test

Wywołaj instrukcję Create

Zaktualizuj pierwszy test za pomocą poniższego kodu:

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`
);
});

Wywołaj instrukcję aktualizacji

Zaktualizuj drugi test w następujący sposób:

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`
);
});

Wywołaj instrukcję usunięcia

Zaktualizuj trzeci test w następujący sposób:

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`
);
});

Uruchom test

Po przygotowaniu testów uruchom plik testowy za pomocą test w terminalu Playground. To polecenie uruchamia testy na programie wdrożonym na devnecie i loguje linki do SolanaFM, aby wyświetlić szczegóły transakcji.

Terminal
$
test

Sprawdź linki SolanaFM, aby zobaczyć szczegóły transakcji.

Zwróć uwagę, że w tym przykładzie, jeśli uruchomisz test ponownie, instrukcja create zakończy się niepowodzeniem, ponieważ messageAccount już istnieje jako konto. Tylko jedno konto może istnieć dla danego PDA.

Is this page helpful?

Spis treści

Edytuj stronę