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

삭제 명령 추가하기

다음으로, *rsMessageAccount*를 닫기 위한 delete 명령을 추가합니다.

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

Delete 명령어 호출하기

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

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

테스트 실행

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

Terminal
$
test

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

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

Is this page helpful?