Program Derived Address

Nesta seção, você aprenderá como construir um programa básico de Criar, Ler, Atualizar, Excluir (CRUD).

Este guia demonstra um programa simples onde os usuários podem criar, atualizar e excluir uma mensagem. Cada mensagem existe em uma conta com um endereço determinístico derivado do próprio programa (Program Derived Address ou PDA).

Este guia orienta você na construção e teste de um programa Solana usando o framework Anchor enquanto demonstra Program Derived Addresses (PDAs). Para mais detalhes, consulte a página Program Derived Addresses.

Para referência, você pode visualizar o código final após completar as seções de PDA e Cross Program Invocation (CPI).

Código inicial

Comece abrindo este link do Solana Playground com o código inicial. Em seguida, clique no botão "Import" para adicionar o programa aos seus projetos do Solana Playground.

ImportarImportar

No arquivo lib.rs, você encontrará um programa com as instruções create, update e delete para adicionar nos passos seguintes.

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

Antes de começar, execute build no terminal do Playground para verificar se o programa inicial compila com sucesso.

Terminal
$
build

Definir o tipo de conta de mensagem

Primeiro, defina a estrutura para a conta de mensagem que o programa cria. Esta estrutura define os dados a serem armazenados na conta criada pelo programa.

Em lib.rs, atualize a estrutura MessageAccount com o seguinte:

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

Compile o programa novamente executando build no terminal.

Terminal
$
build

Este código define quais dados armazenar na conta de mensagem. Em seguida, você adicionará as instruções do programa.

Adicionar instrução de criação

Agora, adicione a instrução create que cria e inicializa o MessageAccount.

Comece definindo as contas necessárias para a instrução atualizando a estrutura Create com o seguinte:

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

Em seguida, adicione a lógica de negócios para a instrução create atualizando a função create com o seguinte:

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

Reconstrua o programa.

Terminal
$
build

Adicionar instrução de atualização

Em seguida, adicione a instrução update para alterar o MessageAccount com uma nova mensagem.

Como na etapa anterior, primeiro especifique as contas necessárias para a instrução update.

Atualize a estrutura Update com o seguinte:

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

Em seguida, adicione a lógica para a instrução 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(())
}

Reconstrua o programa

Terminal
$
build

Adicionar instrução de exclusão

Em seguida, adicione a instrução delete para fechar o MessageAccount.

Atualize a estrutura Delete com o seguinte:

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

Em seguida, adicione a lógica para a instrução delete.

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

Reconstrua o programa.

Terminal
$
build

Implantar o programa

Você agora completou o programa CRUD básico. Implante o programa executando deploy no terminal do Playground.

Neste exemplo, você implantará o programa na devnet, um cluster Solana para testes de desenvolvimento.

A carteira do Playground conecta-se à devnet por padrão. Certifique-se de que sua carteira do Playground tenha SOL da devnet para pagar pela implantação do programa. Obtenha SOL da devnet no Solana Faucet.

Terminal
$
deploy

Configurar arquivo de teste

O código inicial também inclui um arquivo de teste em 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 () => {});
});

Adicione o código abaixo dentro de describe(), mas antes das seções 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
);

Execute o arquivo de teste rodando test no terminal do Playground para verificar se ele funciona como esperado. Os próximos passos adicionam os testes reais.

Terminal
$
test

Invocar a instrução Create

Atualize o primeiro teste com o seguinte:

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

Invocar instrução de atualização

Atualize o segundo teste com o seguinte:

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

Invocar instrução de exclusão

Atualize o terceiro teste com o seguinte:

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

Executar teste

Depois de preparar seus testes, execute o arquivo de teste com test no terminal do Playground. Este comando executa os testes contra o programa implantado na devnet e registra links para o SolanaFM para visualizar os detalhes da transação.

Terminal
$
test

Inspecione os links do SolanaFM para visualizar os detalhes da transação.

Observe que neste exemplo, se você executar o teste novamente, a instrução create falha porque messageAccount já existe como uma conta. Apenas uma conta pode existir para um determinado PDA.

Is this page helpful?