Program Derived Address
이 섹션에서는 기본적인 생성, 읽기, 업데이트, 삭제(CRUD) 프로그램을 구축하는 방법을 배우게 됩니다.
이 가이드는 사용자가 메시지를 생성, 업데이트 및 삭제할 수 있는 간단한 프로그램을 보여줍니다. 각 메시지는 프로그램 자체에서 파생된 결정론적 주소(Program Derived Address 또는 PDA)를 가진 계정에 존재합니다.
이 가이드는 Anchor 프레임워크를 사용하여 Solana 프로그램을 구축하고 테스트하는 과정을 안내하면서 Program Derived Addresses(PDA)를 보여줍니다. 자세한 내용은 Program Derived Addresses 페이지를 참조하세요.
참고로, PDA와 Cross-Program Invocation(CPI) 섹션을 모두 완료한 후의 최종 코드를 볼 수 있습니다.
시작 코드
시작 코드가 포함된 Solana Playground 링크를 열어 시작하세요. 그런 다음 "Import" 버튼을 클릭하여 프로그램을 Solana Playground 프로젝트에 추가하세요.
가져오기
lib.rs
파일에서 다음 단계에서 추가할 create
,
update
, 그리고 delete
명령이 있는 프로그램을
찾을 수 있습니다.
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 {}
시작하기 전에 Playground 터미널에서 *shellbuild
*를 실행하여 시작 프로그램이
성공적으로 빌드되는지 확인하세요.
$build
메시지 계정 유형 정의하기
먼저, 프로그램이 생성하는 메시지 계정의 구조를 정의합니다. 이 구조는 프로그램이 생성한 계정에 저장할 데이터를 정의합니다.
lib.rs
에서 MessageAccount
구조체를 다음과 같이 업데이트하세요:
#[account]pub struct MessageAccount {pub user: Pubkey,pub message: String,pub bump: u8,}
터미널에서 *shellbuild
*를 실행하여 프로그램을 다시 빌드하세요.
$build
이 코드는 메시지 계정에 저장할 데이터를 정의합니다. 다음으로, 프로그램 명령어를 추가할 것입니다.
생성 명령어 추가하기
이제 create
명령어를 추가하여 *rsMessageAccount
*를 생성하고
초기화합니다.
먼저 다음과 같이 Create
구조체를 업데이트하여 명령어에 필요한 계정을
정의합니다:
#[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
함수를 다음과 같이 업데이트하세요:
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(())}
프로그램을 다시 빌드하세요.
$build
업데이트 명령어 추가하기
다음으로, 새 메시지로 MessageAccount
를 변경하는 update
명령어를 추가합니다.
이전 단계와 마찬가지로, 먼저 update
명령어에 필요한 계정을 지정합니다.
Update
구조체를 다음과 같이 업데이트하세요:
#[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
명령어에 대한 로직을 추가합니다.
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(())}
프로그램을 다시 빌드합니다
$build
삭제 명령 추가하기
다음으로, *rsMessageAccount
*를 닫기 위한 delete
명령을 추가합니다.
Delete
구조체를 다음과 같이 업데이트합니다:
#[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
명령어에 대한 로직을 추가하세요.
pub fn delete(_ctx: Context<Delete>) -> Result<()> {msg!("Delete Message");Ok(())}
프로그램을 다시 빌드하세요.
$build
프로그램 배포하기
이제 기본 CRUD 프로그램을 완성했습니다. 플레이그라운드 터미널에서 deploy
를
실행하여 프로그램을 배포하세요.
이 예제에서는 개발 테스트를 위한 Solana 클러스터인 devnet에 프로그램을 배포합니다.
플레이그라운드 지갑은 기본적으로 devnet에 연결됩니다. 프로그램 배포 비용을 지불하기 위해 플레이그라운드 지갑에 devnet SOL이 있는지 확인하세요. Solana Faucet에서 devnet SOL을 받을 수 있습니다.
$deploy
테스트 파일 설정하기
스타터 코드에는 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()
섹션 이전에 추가하세요.
const program = pg.program;const wallet = pg.wallet;const [messagePda, messageBump] = PublicKey.findProgramAddressSync([Buffer.from("message"), wallet.publicKey.toBuffer()],program.programId);
Playground 터미널에서 *shelltest
*를 실행하여 테스트 파일이 예상대로 실행되는지
확인하세요. 다음 단계에서는 실제 테스트를 추가합니다.
$test
Create 명령 호출하기
다음과 같이 첫 번째 테스트를 업데이트하세요:
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`);});
업데이트 명령 호출하기
다음과 같이 두 번째 테스트를 업데이트하세요:
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`);});
Delete 명령어 호출하기
다음과 같이 세 번째 테스트를 업데이트하세요:
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
명령으로 테스트
파일을 실행하세요. 이 명령은 devnet에 배포된 프로그램에 대해 테스트를 실행하고
트랜잭션 세부 정보를 볼 수 있는 SolanaFM 링크를 기록합니다.
$test
SolanaFM 링크를 검사하여 트랜잭션 세부 정보를 확인하세요.
이 예제에서 테스트를 다시 실행하면 messageAccount
계정이 이미 존재하기
때문에 create
명령이 실패한다는 점에 유의하세요. 주어진 PDA에 대해 하나의
계정만 존재할 수 있습니다.
Is this page helpful?