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 명령을 다음 단계에서 추가할 프로그램을 찾을 수 있습니다.

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

시작하기 전에 Playground 터미널에서 *shellbuild*를 실행하여 시작 프로그램이 성공적으로 빌드되는지 확인하세요.

Terminal
$
build

메시지 계정 유형 정의하기

먼저, 프로그램이 생성하는 메시지 계정의 구조를 정의합니다. 이 구조는 프로그램이 생성한 계정에 저장할 데이터를 정의합니다.

lib.rs에서 MessageAccount 구조체를 다음과 같이 업데이트하세요:

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

터미널에서 *shellbuild*를 실행하여 프로그램을 다시 빌드하세요.

Terminal
$
build

이 코드는 메시지 계정에 저장할 데이터를 정의합니다. 다음으로, 프로그램 명령어를 추가할 것입니다.

생성 명령어 추가하기

이제 create 명령어를 추가하여 *rsMessageAccount*를 생성하고 초기화합니다.

먼저 다음과 같이 Create 구조체를 업데이트하여 명령어에 필요한 계정을 정의합니다:

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

다음으로, create 명령어에 대한 비즈니스 로직을 추가하기 위해 create 함수를 다음과 같이 업데이트하세요:

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

프로그램을 다시 빌드하세요.

Terminal
$
build

업데이트 명령어 추가하기

다음으로, MessageAccount를 새 메시지로 변경하기 위한 update 명령어를 추가합니다.

이전 단계와 마찬가지로, 먼저 update 명령어에 필요한 계정을 지정합니다.

다음과 같이 Update 구조체를 업데이트합니다:

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

다음으로, 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(())
}

프로그램을 다시 빌드하세요

Terminal
$
build

삭제 명령 추가하기

다음으로, delete 명령을 추가하여 *rsMessageAccount*를 닫습니다.

Delete 구조체를 다음과 같이 업데이트하세요:

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

다음으로, delete 명령어에 대한 로직을 추가하세요.

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

프로그램을 다시 빌드하세요.

Terminal
$
build

프로그램 배포하기

이제 기본 CRUD 프로그램을 완성했습니다. 플레이그라운드 터미널에서 deploy를 실행하여 프로그램을 배포하세요.

이 예제에서는 개발 테스트를 위한 Solana 클러스터인 devnet에 프로그램을 배포합니다.

플레이그라운드 지갑은 기본적으로 devnet에 연결됩니다. 프로그램 배포 비용을 지불하기 위해 플레이그라운드 지갑에 devnet SOL이 있는지 확인하세요. Solana Faucet에서 devnet SOL을 받을 수 있습니다.

Terminal
$
deploy

테스트 파일 설정하기

스타터 코드에는 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 () => {});
});

아래 코드를 describe() 내부에 추가하되, 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
);

Playground 터미널에서 *shelltest*를 실행하여 테스트 파일이 예상대로 실행되는지 확인하세요. 다음 단계에서는 실제 테스트를 추가합니다.

Terminal
$
test

Create 명령어 호출하기

첫 번째 테스트를 다음과 같이 업데이트하세요:

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

업데이트 명령 호출하기

다음과 같이 두 번째 테스트를 업데이트하세요:

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

삭제 명령 호출하기

다음과 같이 세 번째 테스트를 업데이트하세요:

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

테스트 실행

테스트를 준비한 후, Playground 터미널에서 test 명령으로 테스트 파일을 실행하세요. 이 명령은 devnet에 배포된 프로그램에 대해 테스트를 실행하고 트랜잭션 세부 정보를 볼 수 있는 SolanaFM 링크를 기록합니다.

Terminal
$
test

SolanaFM 링크를 검사하여 트랜잭션 세부 정보를 확인하세요.

이 예제에서 테스트를 다시 실행하면 messageAccount 계정이 이미 존재하기 때문에 create 명령어가 실패한다는 점에 유의하세요. 주어진 PDA에 대해 하나의 계정만 존재할 수 있습니다.

Is this page helpful?