Program Derived Address
En esta sección, aprenderás a construir un programa básico de Crear, Leer, Actualizar, Eliminar (CRUD).
Esta guía demuestra un programa simple donde los usuarios pueden crear, actualizar y eliminar un mensaje. Cada mensaje existe en una cuenta con una dirección determinista derivada del propio programa (Program Derived Address o PDA).
Esta guía te lleva a través de la construcción y prueba de un programa Solana utilizando el framework Anchor mientras demuestra Program Derived Addresses (PDAs). Para más detalles, consulta la página Program Derived Addresses.
Como referencia, puedes ver el código final después de completar ambas secciones de PDA y Cross Program Invocation (CPI).
Código inicial
Comienza abriendo este enlace de Solana Playground con el código inicial. Luego haz clic en el botón "Import" para añadir el programa a tus proyectos de Solana Playground.
Importar
En el archivo lib.rs
, encontrarás un programa con las instrucciones
create
, update
, y delete
para añadir en los siguientes pasos.
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 {}
Antes de comenzar, ejecuta build
en la terminal de Playground para
verificar que el programa inicial se compila correctamente.
$build
Definir el tipo de cuenta de mensaje
Primero, define la estructura para la cuenta de mensaje que el programa crea. Esta estructura define los datos a almacenar en la cuenta creada por el programa.
En lib.rs
, actualiza la estructura MessageAccount
con lo siguiente:
#[account]pub struct MessageAccount {pub user: Pubkey,pub message: String,pub bump: u8,}
Compila el programa nuevamente ejecutando build
en la terminal.
$build
Este código define qué datos almacenar en la cuenta de mensajes. A continuación, añadirás las instrucciones del programa.
Añadir instrucción de creación
Ahora, añade la instrucción create
que crea e inicializa el
MessageAccount
.
Comienza definiendo las cuentas requeridas para la instrucción actualizando la
estructura Create
con lo siguiente:
#[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>,}
A continuación, añade la lógica de negocio para la instrucción create
actualizando la función create
con lo siguiente:
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(())}
Reconstruye el programa.
$build
Añadir instrucción de actualización
A continuación, añade la instrucción update
para cambiar el MessageAccount
con un nuevo mensaje.
Como en el paso anterior, primero especifica las cuentas requeridas por la
instrucción update
.
Actualiza la estructura Update
con lo siguiente:
#[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>,}
A continuación, añade la lógica para la instrucción update
.
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(())}
Reconstruye el programa
$build
Añadir instrucción de eliminación
A continuación, añade la instrucción delete
para cerrar la
MessageAccount
.
Actualiza la estructura Delete
con lo siguiente:
#[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>,}
A continuación, añade la lógica para la instrucción delete
.
pub fn delete(_ctx: Context<Delete>) -> Result<()> {msg!("Delete Message");Ok(())}
Reconstruye el programa.
$build
Desplegar el programa
Has completado el programa CRUD básico. Despliega el programa ejecutando
deploy
en la terminal de Playground.
En este ejemplo, desplegarás el programa en la devnet, un clúster de Solana para pruebas de desarrollo.
La cartera de Playground se conecta a la devnet por defecto. Asegúrate de que tu cartera de Playground tenga SOL de devnet para pagar el despliegue del programa. Obtén SOL de devnet del Solana Faucet.
$deploy
Configurar el archivo de prueba
El código inicial también incluye un archivo de prueba en 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 () => {});});
Añade el código siguiente dentro de describe()
, pero antes de las
secciones it()
.
const program = pg.program;const wallet = pg.wallet;const [messagePda, messageBump] = PublicKey.findProgramAddressSync([Buffer.from("message"), wallet.publicKey.toBuffer()],program.programId);
Ejecuta el archivo de prueba usando test
en la terminal de Playground
para comprobar que funciona como se espera. Los siguientes pasos añaden las
pruebas reales.
$test
Invocar la instrucción Create
Actualiza la primera prueba con lo siguiente:
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`);});
Invocar instrucción de actualización
Actualiza la segunda prueba con lo siguiente:
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`);});
Invocar instrucción de eliminación
Actualiza la tercera prueba con lo siguiente:
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`);});
Ejecutar prueba
Después de preparar tus pruebas, ejecuta el archivo de prueba con test
en la terminal de Playground. Este comando ejecuta las pruebas contra el
programa desplegado en la devnet y registra enlaces a SolanaFM para ver los
detalles de la transacción.
$test
Inspecciona los enlaces de SolanaFM para ver los detalles de la transacción.
Ten en cuenta que en este ejemplo, si ejecutas la prueba nuevamente, la
instrucción create
fallará porque messageAccount
ya existe como una
cuenta. Solo una cuenta puede existir para una PDA determinada.
Is this page helpful?