솔라나 문서LiteSVMRust프로그램 테스트

프로그램 배포하기

시작하기

let mut svm = LiteSVM::new();

이것은 기본 litesvm 테스트 인스턴스를 생성하며, 활성화된 모든 런타임 기능, 기본 시스템 변수, 사전 컴파일된 항목, spl 프로그램, 서명 검증, 그리고 System Program을 포함한 모든 내장 프로그램이 포함됩니다.

테스트에서 다른 프로그램과 상호작용하려면, 해당 프로그램을 테스트 환경에 배포해야 합니다.

  1. 테스트하려는 프로그램을 빌드하고 배포하세요

  2. 위 프로그램이 CPI 호출을 하는 모든 프로그램을 찾아 해당 프로그램들도 배포하세요

예시: 프로그램이 Pyth의 데이터 피드를 사용하는 경우, 테스트 인스턴스에 Pyth 프로그램이 배포되어 있어야 합니다.

기본 프로그램 배포

방법 1: 바이트 배열로 배포

가장 빠른 테스트 실행을 위해 바이트 배열에서 직접 프로그램을 배포하세요:

use litesvm::LiteSVM;
use solana_sdk::signature::{read_keypair_file, Signer};
let mut svm = LiteSVM::new();
// Read the program's keypair to get correct ID
let program_keypair = read_keypair_file("target/deploy/my_program-keypair.json")
.expect("Program keypair file not found");
let program_id = program_keypair.pubkey();
// Include bytes at compile time
let program_bytes = include_bytes!("../target/deploy/my_program.so");
// Deploy from bytes
svm.add_program(program_id, program_bytes)
.expect("Failed to deploy program");
// Verify deployment
assert!(svm.get_account(&program_id).is_some(), "Program account not created");
assert!(svm.get_account(&program_id).unwrap().executable, "Program not executable");

include_bytes!는 컴파일 시점에 바이너리를 테스트 실행 파일에 직접 삽입하여, 테스트 실행 중 I/O를 제거합니다.

장점

  • 런타임 속도 향상 (디스크 읽기 없음)
  • 자체 완결형 테스트 바이너리
  • 컴파일 후 .so 파일이 삭제되어도 정상 동작

단점

  • 테스트 바이너리 크기 증가
  • .so 파일 변경 시 테스트를 다시 컴파일해야 함

방법 2: 파일(.so)에서 배포

파일이 자주 변경될 때 파일 시스템에서 프로그램을 로드합니다:

use litesvm::LiteSVM;
use solana_sdk::signature::{read_keypair_file, Signer};
let mut svm = LiteSVM::new();
// Read keypair for correct program ID
let program_keypair = read_keypair_file("target/deploy/program-keypair.json").unwrap();
let program_id = program_keypair.pubkey();
// Deploy from file
svm.add_program_from_file(program_id, "target/deploy/program.so")
.expect("Failed to deploy program from file");
// Always verify
assert!(svm.get_account(&program_id).unwrap().executable);

장점

  • 더 작은 테스트 바이너리
  • 테스트를 재컴파일하지 않고 .so 변경 사항 반영 가능
  • CI/CD 파이프라인에 더 유연하게 적용 가능

단점

  • 느림 (런타임 시 디스크 I/O 발생)
  • 테스트 실행 시점에 .so 파일이 존재해야 함
  • 파일 경로가 테스트 실행 디렉터리 기준으로 정확해야 함

메인넷/데브넷에서 프로그램 가져오기

코드를 보유하지 않은 프로그램이 필요한 경우, Solana CLI를 사용하여 라이브 클러스터에서 프로그램을 덤프하고 테스트에 활용하세요:

# Dump a program from mainnet
solana program dump gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s pyth.so --url mainnet-beta
# Dump from devnet
solana program dump YourProgramID program.so --url devnet

그런 다음 테스트에서 로드합니다:

svm.add_program_from_file(
pubkey!("gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"),
"pyth.so"
).unwrap();

모범 사례

1. 올바른 배포 방법 선택

  • add_program() 사용 시:

    • 컴파일 시점에 프로그램이 테스트 바이너리에 내장되는 경우
    • 더 빠른 테스트 실행을 원하는 경우 (런타임 I/O 없음)
    • 독립적인 테스트가 필요한 경우
  • add_program_from_file() 사용 시:

    • 프로그램이 별도로 빌드된 경우 (예: devnet에서 가져오는 경우)
    • 개발 중 프로그램이 자주 변경되는 경우 (재컴파일 없이 테스트 재실행 가능)
    • .so 파일이 아티팩트인 CI/CD 환경이 있는 경우

프로그램 keypair 이해하기

Solana 프로그램을 빌드할 때:

  1. Anchortarget/deploy/program-keypair.json를 생성합니다
  2. 네이티브 빌드는 .so 파일과 함께 keypair 파일을 생성합니다
  3. 프로그램의 온체인 주소는 이 keypair의 공개 키이며, 프로그램 ID라고도 합니다.
  4. PDA의 경우, find_program_address()는 프로그램 ID를 seed로 요구합니다
  5. CPI의 경우, 프로그램 ID는 다른 프로그램을 호출하는 데 사용됩니다

일반적인 문제와 해결 방법

자동 배포 실패

문제: 프로그램이 배포된 것처럼 보이지만 트랜잭션이 "InvalidProgramForExecution" 오류와 함께 실패합니다.

원인: 프로그램 ID가 keypair와 일치하지 않습니다.

해결 방법:

// Always use the keypair-derived ID
let keypair = read_keypair_file("target/deploy/program-keypair.json").unwrap();
let program_id = keypair.pubkey();
svm.add_program(program_id, &program_bytes).unwrap();
// Verify with detailed checks
let account = svm.get_account(&program_id)
.expect("Program account doesn't exist");
assert!(account.executable, "Program not executable");
assert_eq!(account.owner, solana_sdk::bpf_loader::id(), "Wrong owner");
assert!(!account.data.is_empty(), "Program data is empty");
println!("✅ Program verified at {}", program_id);

프로그램을 찾을 수 없음 오류

오류: TransactionError::InvalidProgramForExecution

일반적인 원인:

  1. 프로그램이 배포되지 않음
  2. 명령어에 잘못된 프로그램 ID 사용
  3. 프로그램 ID와 keypair 불일치

디버깅 단계:

// Step 1: Check if program exists
if let Some(account) = svm.get_account(&program_id) {
println!("Program exists");
println!(" Executable: {}", account.executable);
println!(" Owner: {}", account.owner);
println!(" Data length: {}", account.data.len());
} else {
println!("Program does not exist at {}", program_id);
}
// Step 2: Verify the ID matches keypair
let expected_keypair = read_keypair_file("target/deploy/program-keypair.json").unwrap();
let expected_id = expected_keypair.pubkey();
if program_id != expected_id {
println!(" ID mismatch!");
println!(" Used: {}", program_id);
println!(" Expected: {}", expected_id);
}
// Step 3: Check instruction program ID
println!("Instruction program_id: {}", instruction.program_id);
assert_eq!(instruction.program_id, program_id, "Instruction uses wrong program ID");

배포되지 않은 프로그램에 대한 CPI

문제: 프로그램이 배포되지 않은 프로그램에 CPI 호출을 시도합니다.

해결 방법: 모든 의존성 프로그램을 배포하세요:

// Find all dependency programs and make sure they are deployed
svm.add_program_from_file(
pubkey!("gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"),
"pyth.so"
).unwrap();

클러스터에서 프로그램을 가져와야 하는 경우, 이 섹션을 참조하세요.

배포를 위한 lamport 부족

참고: LiteSVM은 프로그램의 렌트 면제 잔액을 자동으로 처리합니다.

// No need to manually fund - LiteSVM handles this
svm.add_program(program_id, &program_bytes).unwrap();
// But you can check the rent if needed
let program_len = program_bytes.len();
let required_lamports = svm.minimum_balance_for_rent_exemption(program_len);
println!("Program requires {} lamports for rent exemption", required_lamports);

배포 검증 체크리스트

프로그램 keypair 읽기

let keypair = read_keypair_file("target/deploy/program-keypair.json").unwrap();
let program_id = keypair.pubkey();

일치하는 ID로 배포

svm.add_program(program_id, &program_bytes).unwrap();

배포 확인

assert!(svm.get_account(&program_id).unwrap().executable);

프로그램이 CPI를 통해 호출하는 모든 프로그램을 litesvm 테스트 인스턴스에 배포하세요

다음 단계

다음 섹션에서는 프로그램 명령어를 실행하고 복잡한 트랜잭션 패턴을 처리하는 방법을 알아봅니다.

Is this page helpful?