Testowanie programów

Testowanie niestandardowego programu Solana z poziomu Go oznacza załadowanie skompilowanego pliku .so do SVM, skonfigurowanie oczekiwanych kont i wysyłanie instrukcji do programu. litesvm-go obsługuje to wszystko w procesie — nie jest potrzebny żaden validator.

Ładowanie programu

package mytest
import (
"os"
"testing"
litesvm "github.com/LiteSVM/litesvm-go"
solana "github.com/gagliardetto/solana-go"
)
func TestLoadProgram(t *testing.T) {
svm, err := litesvm.New()
if err != nil {
t.Fatal(err)
}
defer svm.Close()
// Configure for program testing. Setters are infallible in practice;
// discarding the return keeps the example readable.
_ = svm.SetSigverify(false)
_ = svm.SetBlockhashCheck(false)
_ = svm.SetSysvars()
_ = svm.SetBuiltins()
// Load program from .so file.
programID := solana.NewWallet().PublicKey()
soPath := "./target/deploy/my_program.so"
if _, err := os.Stat(soPath); err != nil {
t.Skipf("program file not found: %s (build with: cargo build-sbf)", soPath)
}
if err := svm.AddProgramFromFile(programID, soPath); err != nil {
t.Fatal(err)
}
t.Logf("program loaded: %s", programID)
// Verify the program account.
acct := svm.GetAccount(programID)
if acct == nil {
t.Fatal("program account missing after load")
}
defer acct.Close()
t.Logf(" executable: %v", acct.Executable())
t.Logf(" data size: %d bytes", len(acct.Data()))
}

Kompletna konfiguracja testu programu

package mytest
import (
"os"
"testing"
litesvm "github.com/LiteSVM/litesvm-go"
solana "github.com/gagliardetto/solana-go"
)
func TestProgramEndToEnd(t *testing.T) {
svm, err := litesvm.New()
if err != nil {
t.Fatal(err)
}
defer svm.Close()
// Wide-open config: faster tests, less plumbing. Setters are infallible
// in practice; discarding the return keeps the example readable.
_ = svm.SetSigverify(false)
_ = svm.SetBlockhashCheck(false)
_ = svm.SetSysvars()
_ = svm.SetBuiltins()
_ = svm.SetPrecompiles()
_ = svm.SetTransactionHistory(100)
// Load the program.
programID := solana.NewWallet().PublicKey()
soPath := "./target/deploy/my_program.so"
if _, err := os.Stat(soPath); err != nil {
t.Skipf("missing %s", soPath)
}
if err := svm.AddProgramFromFile(programID, soPath); err != nil {
t.Fatal(err)
}
// Seed any data accounts the program expects.
dataAddr := solana.NewWallet().PublicKey()
dataPayload := make([]byte, 100)
rent, err := svm.MinimumBalanceForRentExemption(len(dataPayload))
if err != nil {
t.Fatal(err)
}
acct, err := litesvm.NewAccount(rent, dataPayload, programID, false, 0)
if err != nil {
t.Fatal(err)
}
defer acct.Close()
if err := svm.SetAccount(dataAddr, acct); err != nil {
t.Fatal(err)
}
// Fund a payer that will sign the test instruction.
priv, err := solana.NewRandomPrivateKey()
if err != nil {
t.Fatal(err)
}
payer := priv.PublicKey()
if err := svm.Airdrop(payer, 1_000_000_000); err != nil {
t.Fatal(err)
}
// Build an instruction against the program.
bh, err := svm.LatestBlockhash()
if err != nil {
t.Fatal(err)
}
ix := &solana.GenericInstruction{
ProgID: programID,
AccountValues: solana.AccountMetaSlice{
{PublicKey: dataAddr, IsWritable: true, IsSigner: false},
{PublicKey: payer, IsWritable: true, IsSigner: true},
},
DataBytes: []byte{0x01 /* discriminator + args */},
}
tx, err := solana.NewTransaction(
[]solana.Instruction{ix},
bh,
solana.TransactionPayer(payer),
)
if err != nil {
t.Fatal(err)
}
if _, err := tx.Sign(func(k solana.PublicKey) *solana.PrivateKey {
if k.Equals(payer) {
return &priv
}
return nil
}); err != nil {
t.Fatal(err)
}
txBytes, err := tx.MarshalBinary()
if err != nil {
t.Fatal(err)
}
// Execute and inspect.
out, err := svm.SendLegacyTransaction(txBytes)
if err != nil {
t.Fatal(err)
}
defer out.Close()
if !out.IsOk() {
t.Fatalf("program failed: %s\nlogs: %v", out.Error(), out.Logs())
}
t.Logf("logs: %v", out.Logs())
t.Logf("compute: %d CU", out.ComputeUnits())
// Verify state changes by reading the data account back.
updated := svm.GetAccount(dataAddr)
if updated == nil {
t.Fatal("data account missing after execution")
}
defer updated.Close()
// Decode `updated.Data()` and assert as needed.
}

Korzystanie z domyślnych programów

Do testów wymagających standardowych programów (SPL Token, Memo, ...):

svm, err := litesvm.New()
if err != nil {
t.Fatal(err)
}
defer svm.Close()
if err := svm.SetDefaultPrograms(); err != nil { // SPL Token, Memo, etc.
t.Fatal(err)
}
if err := svm.WithNativeMints(); err != nil { // wrapped-SOL mint
t.Fatal(err)
}

Skompiluj swój program Solana za pomocą cargo build-sbf, aby wygenerować plik .so.

Schemat jest zawsze taki sam:

  1. Konfiguracja — włącz sysvary, wbudowane funkcje i prekompilacje według potrzeb.
  2. Ładowanie programuAddProgramFromFile (lub AddProgram dla bajtów w pamięci).
  3. Konfiguracja kont — wstępnie wypełnij konta danych za pomocą SetAccount.
  4. Wysyłanie — zbuduj instrukcję za pomocą solana-go, serializuj i prześlij przez SendLegacyTransaction.
  5. Weryfikacja — odczytaj konta za pomocą GetAccount i sprawdź logi, jednostki obliczeniowe oraz stan końcowy.

Gdy ten szkielet będzie już działać, rozbuduj go, dodając więcej instrukcji, więcej kont i pomocnika do obsługi powtarzalnego kodu.

Is this page helpful?

Spis treści

Edytuj stronę