Program Derived Address

In questa sezione, imparerai a costruire un programma base di tipo Create, Read, Update, Delete (CRUD).

Questa guida dimostra un semplice programma in cui gli utenti possono creare, aggiornare ed eliminare un messaggio. Ogni messaggio esiste in un account con un indirizzo deterministico derivato dal programma stesso (Program Derived Address o PDA).

Questa guida ti accompagna nella costruzione e nel test di un programma Solana utilizzando il framework Anchor mentre dimostra l'uso dei Program Derived Addresses (PDAs). Per maggiori dettagli, consulta la pagina Program Derived Addresses.

Come riferimento, puoi visualizzare il codice finale dopo aver completato sia la sezione PDA che quella Cross-Program Invocation (CPI).

Codice iniziale

Inizia aprendo questo link di Solana Playground con il codice iniziale. Poi clicca sul pulsante "Import" per aggiungere il programma ai tuoi progetti di Solana Playground.

ImportaImporta

Nel file lib.rs, troverai un programma con le istruzioni create, update e delete da aggiungere nei passaggi successivi.

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

Prima di iniziare, esegui build nel terminale di Playground per verificare che il programma iniziale si compili correttamente.

Terminal
$
build

Definire il tipo di account per i messaggi

Per prima cosa, definisci la struttura per l'account del messaggio che il programma creerà. Questa struttura definisce i dati da memorizzare nell'account creato dal programma.

In lib.rs, aggiorna la struttura MessageAccount con quanto segue:

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

Compila nuovamente il programma eseguendo build nel terminale.

Terminal
$
build

Questo codice definisce quali dati memorizzare nell'account del messaggio. Successivamente, aggiungerai le istruzioni del programma.

Aggiungi istruzione di creazione

Ora, aggiungi l'istruzione create che crea e inizializza MessageAccount.

Inizia definendo gli account richiesti per l'istruzione aggiornando la struct Create con quanto segue:

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

Successivamente, aggiungi la logica di business per l'istruzione create aggiornando la funzione create con quanto segue:

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

Ricompila il programma.

Terminal
$
build

Aggiungi l'istruzione di aggiornamento

Successivamente, aggiungi l'istruzione update per modificare il MessageAccount con un nuovo messaggio.

Come nel passaggio precedente, specifica prima gli account richiesti dall'istruzione update.

Aggiorna la struttura Update con quanto segue:

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

Successivamente, aggiungi la logica per l'istruzione 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(())
}

Ricompila il programma

Terminal
$
build

Aggiungi l'istruzione Delete

Successivamente, aggiungi l'istruzione delete per chiudere MessageAccount.

Aggiorna la struttura Delete con quanto segue:

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

Successivamente, aggiungi la logica per l'istruzione delete.

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

Ricompila il programma.

Terminal
$
build

Distribuisci il programma

Hai ora completato il programma CRUD di base. Distribuisci il programma eseguendo deploy nel terminale di Playground.

In questo esempio, distribuirai il programma sulla devnet, un cluster Solana per i test di sviluppo.

Il wallet di Playground si connette alla devnet per impostazione predefinita. Assicurati che il tuo wallet di Playground abbia SOL della devnet per pagare la distribuzione del programma. Ottieni SOL della devnet dal Solana Faucet.

Terminal
$
deploy

Configura il file di test

Il codice iniziale include anche un file di test in 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 () => {});
});

Aggiungi il codice seguente all'interno di describe(), ma prima delle sezioni 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
);

Esegui il file di test lanciando test nel terminale di Playground per verificare che funzioni come previsto. I prossimi passaggi aggiungono i test effettivi.

Terminal
$
test

Invoca l'istruzione Create

Aggiorna il primo test con quanto segue:

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

Invoca l'istruzione di aggiornamento

Aggiorna il secondo test con quanto segue:

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

Invoca l'istruzione Delete

Aggiorna il terzo test con quanto segue:

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

Esegui il test

Dopo aver preparato i test, esegui il file di test con test nel terminale di Playground. Questo comando esegue i test sul programma distribuito sulla devnet e registra i link a SolanaFM per visualizzare i dettagli della transazione.

Terminal
$
test

Esamina i link di SolanaFM per visualizzare i dettagli della transazione.

Nota che in questo esempio, se esegui nuovamente il test, l'istruzione create fallisce perché messageAccount esiste già come account. Solo un account può esistere per un determinato PDA.

Is this page helpful?