Quickstart ini menggunakan proyek awal
framework Anchor yang dihasilkan oleh
anchor init. Anda akan membuat proyek secara lokal, menjalankan pengujiannya,
mem-build program, dan menelusuri kode program yang dihasilkan oleh Anchor.
Prasyarat
Sebelum memulai, instal perangkat pengembangan Solana. Instalasi mencakup Rust, Solana CLI, dan Anchor CLI.
Gunakan Anchor CLI versi 1.1.2 atau lebih tinggi untuk template ini. Periksa versi yang terinstal:
$anchor --version
Buat Proyek
Jalankan perintah berikut di terminal Anda:
$anchor init my-program$cd my-program
Proyek awal mencakup satu program Solana di bawah programs/my-program. Program
ini mencakup dua instruksi: satu untuk menginisialisasi akun counter dan satu
lagi untuk menambah nilai counter.
Beberapa bagian dari template ini mendemonstrasikan pola umum program Solana: menurunkan alamat akun PDA, melakukan Cross Program Invocation (CPI) untuk mentransfer SOL, dan menggunakan pemeriksaan error kustom untukmenghentikan instruksi ketika suatu kondisi gagal.
Build Program
Jalankan anchor build untuk mengompilasi program awal:
$anchor build
Program yang telah dikompilasi ditulis ke target/deploy/my_program.so. Ketika
program di-deploy, isi dari file .so ini disimpan dalam sebuah akun di
onchain.
Jalankan Pengujian
Jalankan pengujian default:
$anchor test
Template ini Anchor.toml menggunakan perintah pengujian Rust:
skip_local_validator = true[scripts]test = "cargo test"
Pengujian memuat program yang telah dikompilasi ke dalam
LiteSVM, membuat payer, mengirim instruksi
initialize dan increment, lalu memeriksa status akun counter.
Menjalankan anchor test juga mengompilasi program, sehingga Anda tidak perlu
menjalankan anchor build terlebih dahulu saat pengujian secara lokal.
Deploy Program
Pengujian lokal adalah siklus umpan balik tercepat. Ketika Anda siap untuk melakukan deploy ke suatu jaringan, misalnya devnet, build terlebih dahulu, lalu deploy ke cluster.
Men-deploy program Solana membutuhkan SOL karena program disimpan dalam sebuah account, dan account tersebut harus membayar untuk ruang yang digunakannya. Di devnet, minta SOL devnet gratis dari Solana Faucet atau dengan Solana CLI:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
File Sumber
Direktori src berisi program Solana. Dokumentasi
Struktur Program
dari Anchor menjelaskan makro inti yang digunakan di sini, termasuk
declare_id!, #[program], #[derive(Accounts)], dan
#[account]. Bagian ini memandu Anda melalui file-file template.
lib.rs
lib.rs adalah titik masuk program. Ini menghubungkan file-file sumber,
mendefinisikan alamat program, dan mendefinisikan instruksi program yang dapat
dipanggil oleh pengguna.
pub mod constants;pub mod error;pub mod instructions;pub mod state;use anchor_lang::prelude::*;pub use constants::*;pub use instructions::*;pub use state::*;declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");#[program]pub mod my_program {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {crate::instructions::initialize::handle_initialize(ctx)}pub fn increment(ctx: Context<Increment>) -> Result<()> {crate::instructions::increment::handle_increment(ctx)}}
[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");
Alamat program yang sama muncul di konfigurasi dan kode. Anchor.toml memberi
tahu Anchor alamat mana yang akan di-deploy atau dipanggil untuk sebuah cluster.
declare_id! mendefinisikan alamat program dalam program untuk pemeriksaan
keamanan.
constants.rs
constants.rs menyimpan nilai-nilai bersama di satu tempat. Dalam template ini,
COUNTER_SEED menurunkan PDA counter, HELLO_WORLD_LAMPORTS ditransfer
saat inisialisasi, dan MAX_COUNT diperiksa sebelum menambah nilai.
use anchor_lang::prelude::*;#[constant]pub const COUNTER_SEED: &[u8] = b"counter";#[constant]pub const HELLO_WORLD_LAMPORTS: u64 = 1;#[constant]pub const MAX_COUNT: u64 = 10;
#[derive(Accounts)]pub struct Initialize<'info> {// ...#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {// ...anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;Ok(())}
initialize.rs menggunakan dua konstanta:
COUNTER_SEEDmengambil alamat PDA counter.HELLO_WORLD_LAMPORTSmenetapkan jumlah yang ditransfer dari payer ke akun counter.
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;Ok(())}
increment.rs menggunakan MAX_COUNT sebagai batas
atas counter. Jika nilai count saat ini sudah mencapai maksimum, require!
mengembalikan CounterOverflow dan data akun tidak berubah.
state.rs
state.rs mendefinisikan tipe data kustom untuk akun yang dibuat dan dimiliki
oleh program. Program mendefinisikan instruksi untuk membuat, menginisialisasi,
dan memperbarui data tersebut, namun data counter tidak disimpan di dalam
program itu sendiri. Data tersebut disimpan dalam akun terpisah dengan alamatnya
sendiri.
use anchor_lang::prelude::*;#[account]#[derive(InitSpace)]pub struct Counter {pub count: u64,pub authority: Pubkey,}
#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();Ok(())}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...ctx.accounts.counter.count += 1;Ok(())}
state.rs mendefinisikan data akun Counter. File instruksi menggunakan
tipe tersebut saat membuat dan memperbarui akun:
Counter::INIT_SPACEmenentukan ukuran akun untuk field yang didefinisikan dalamstate.rs.countdanauthorityadalah nilai field yang ditulis saat akun diinisialisasi.count += 1memperbarui nilai counter yang tersimpan setelah validasi berhasil.
error.rs
error.rs mendefinisikan error kustom program. Dalam template ini, error-error
tersebut mendemonstrasikan bagaimana handler instruksi berhenti ketika pemanggil
tidak diizinkan untuk memperbarui counter atau counter sudah mencapai
MAX_COUNT.
use anchor_lang::prelude::*;#[error_code]pub enum ErrorCode {#[msg("Only the counter authority can update this counter")]Unauthorized,#[msg("Counter has reached the maximum value")]CounterOverflow,}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
error.rs menamai error yang dapat dikembalikan oleh instruksi:
ErrorCode::Unauthorizeddikembalikan ketika penandatangan bukan authority yang tersimpan di akun counter.ErrorCode::CounterOverflowdikembalikan ketika counter sudah mencapaiMAX_COUNT.
instructions.rs
instructions.rs menghubungkan file-file instruksi ke crate program sehingga
lib.rs dapat mengakses kode instruksi initialize dan increment.
Setiap file instruksi mendefinisikan akun-akun yang diperlukan oleh instruksi
tersebut dan logika handler yang berjalan setelah Anchor memvalidasi akun-akun
tersebut.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs mendefinisikan akun-akun yang diperlukan untuk membuat akun
counter, lalu menulis nilai awal akun tersebut. Struct #[derive(Accounts)]
menggunakan Anchor
account constraints
untuk menentukan akun mana yang diperlukan dan bagaimana akun counter baru
dibuat.
use anchor_lang::prelude::*;use crate::{constants::*, state::Counter};#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Account Context
Struct Initialize mendefinisikan akun-akun yang harus disertakan saat
pengguna memanggil instruksi initialize. Anchor memeriksa akun-akun ini
sebelum handler dijalankan.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Payer Account
Akun payer membayar untuk membuat akun counter. Tipe Signer<'info>
berarti pembayar harus menandatangani transaksi, dan #[account(mut)]
berarti akun pembayar dapat diubah karena lamport akan dipotong.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Counter Account
Akun counter menyimpan data Counter dari state.rs. init
memberitahu Anchor untuk membuat akun ini sebelum handler dijalankan, dan
payer = payer memberitahu Anchor akun mana yang membayar biaya pembuatan.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Ukuran Akun
Batasan space memberi tahu Anchor berapa banyak data akun yang perlu
dialokasikan. Anchor menyimpan diskriminator 8-byte terlebih dahulu, kemudian
byte yang dibutuhkan untuk bidang Counter. Diskriminator memungkinkan
Anchor mengenali akun ini sebagai akun Counter sebelum mendeserialisasi
data akun.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Alamat Counter
Batasan seeds dan bump mendefinisikan alamat PDA yang diharapkan
untuk akun counter. Anchor memverifikasi bahwa akun counter yang diberikan
sesuai dengan alamat tersebut. Template ini menggunakan PDA sehingga pengguna
dapat menurunkan alamat counter dari ID program dan seed, sehingga alamat
counter menjadi deterministik.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
System Program
Akun system_program diperlukan karena pembuatan akun baru menggunakan
System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Fungsi Handler
Fungsi handle_initialize berjalan setelah Anchor memvalidasi akun-akun
dalam Initialize. Nilai ctx memberi handler akses ke akun-akun yang
telah diverifikasi tersebut.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Data Awal
Handler menulis nilai-nilai pertama ke dalam akun counter baru. Hitungan dimulai
dari 0, dan pembayar menjadi otoritas yang diizinkan untuk menambah
counter nantinya.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Akun Transfer
CPI transfer ini disertakan hanya untuk mendemonstrasikan bagaimana CPI
meneruskan akun ke program lain. Struct Transfer mencantumkan akun-akun
yang digunakan oleh transfer System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Konteks CPI
CpiContext::new menggabungkan program yang dipanggil dengan akun-akun yang
diteruskan ke program tersebut. Ini adalah bentuk dasar dari CPI: pilih program
yang akan dipanggil, kumpulkan akun-akun yang diharapkan program tersebut, lalu
teruskan keduanya ke dalam pemanggilan. Di sini, program yang dipanggil adalah
System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Panggil Transfer
anchor_lang::system_program::transfer memanggil instruksi transfer System
Program. Dalam template ini, transfer adalah contoh sederhana dari pemanggilan
program lain dari program Anda. Jika CPI transfer gagal, instruksi
initialize juga akan gagal.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Catat Pesan
msg! menulis pesan ke log program. Ok(()) menandakan bahwa instruksi
berhasil dikembalikan.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
increment.rs
increment.rs mendefinisikan akun-akun yang diperlukan untuk memperbarui akun
counter yang sudah ada. Handler memeriksa bahwa penanda tangan adalah otoritas
yang tersimpan, memeriksa bahwa hitungan belum mencapai MAX_COUNT yang
ditentukan, lalu menambah hitungan.
use anchor_lang::prelude::*;use crate::{constants::*, error::ErrorCode, state::Counter};#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Account Context
Struct Increment mendefinisikan akun-akun yang harus disertakan ketika
pengguna memanggil instruksi increment. Anchor memeriksa akun-akun ini
sebelum handler dijalankan.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Counter Account
Akun counter menyimpan data Counter. Batasan mut memungkinkan
handler memperbarui hitungan yang tersimpan, dan batasan seeds serta
bump memverifikasi alamat PDA counter.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Authority Signer
Akun authority harus menandatangani transaksi. Handler kemudian memeriksa
bahwa penanda tangan ini cocok dengan otoritas yang tersimpan di akun counter.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Fungsi Handler
Fungsi handle_increment berjalan setelah Anchor memvalidasi akun-akun di
Increment. Nilai ctx memberikan akses handler ke akun-akun yang
telah diperiksa tersebut.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Pemeriksaan Otoritas
Pemeriksaan pertama memastikan bahwa penandatangan diizinkan untuk memperbarui
counter ini. Jika alamat penandatangan tidak cocok dengan
counter.authority, instruksi berhenti dengan
ErrorCode::Unauthorized. Ini mendemonstrasikan otorisasi di tingkat
aplikasi: program memiliki data counter, tetapi mengimplementasikan aturan
tentang penandatangan mana yang boleh mengubah data tersebut.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Pemeriksaan Jumlah Maksimum
Pemeriksaan kedua mencegah counter melampaui MAX_COUNT. Jika counter sudah
mencapai batas, instruksi berhenti dengan ErrorCode::CounterOverflow.
Batas ini adalah aturan buatan dalam template agar Anda dapat melihat bagaimana
error kustom menghentikan instruksi sebelum data akun diubah.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Perbarui Hitungan
Hanya setelah kedua pemeriksaan berhasil, handler memperbarui data akun. Baris ini menambahkan satu ke nilai counter yang tersimpan.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Pesan Log
msg! menulis hitungan yang diperbarui ke log program. Ok(())
menunjukkan bahwa instruksi berhasil dikembalikan.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
File pengujian
programs/my-program/tests/test_initialize.rs adalah pengujian integrasi Rust.
Ia tidak menjalankan validator lokal. Sebaliknya, ia memuat file .so yang
telah dikompilasi ke dalam LiteSVM, membangun transaksi yang memanggil program,
dan membaca akun counter setelah setiap transaksi. Pengujian ini membangun
instruksi untuk transaksi Solana dengan menentukan ID program yang akan
dipanggil, menyediakan instruction data, dan meneruskan akun yang diperlukan.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);
Konteks akun Initialize mendefinisikan akun-akun yang diperlukan oleh
instruksi initialize. Pengujian meneruskan akun-akun yang sama tersebut ke
helper my_program::accounts::Initialize yang dihasilkan:
payerditeruskan sebagaipayer: payer.pubkey().counterditeruskan sebagaicounter.system_programditeruskan sebagaisystem_program::ID.
my_program::instruction::Initialize {}.data()
membuat instruction data. Di sinilah argumen instruksi akan dikodekan, namun
instruksi initialize ini tidak memerlukan argumen apa pun.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);
Konteks akun Increment mendefinisikan akun-akun yang diperlukan oleh
instruksi increment. Pengujian meneruskan akun-akun yang sama tersebut ke
helper my_program::accounts::Increment yang dihasilkan:
counterditeruskan sebagaicounter.authorityditeruskan sebagaiauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
membuat instruction data. Di sinilah argumen instruksi akan dikodekan, namun
instruksi increment ini tidak memerlukan argumen apa pun.
use {anchor_lang::{prelude::Pubkey,solana_program::{instruction::Instruction, system_program},AccountDeserialize, InstructionData, ToAccountMetas,},litesvm::LiteSVM,solana_keypair::Keypair,solana_message::{Message, VersionedMessage},solana_signer::Signer,solana_transaction::versioned::VersionedTransaction,};#[test]fn test_initialize() {let program_id = my_program::id();let payer = Keypair::new();let counter = Pubkey::find_program_address(&[my_program::constants::COUNTER_SEED],&program_id,).0;let mut svm = LiteSVM::new();let bytes = include_bytes!(concat!(env!("CARGO_TARGET_TMPDIR"),"/../deploy/my_program.so"));svm.add_program(program_id, bytes).unwrap();svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 0);assert_eq!(counter_state.authority, payer.pubkey());let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 1);assert_eq!(counter_state.authority, payer.pubkey());}
Konfigurasi proyek
File proyek root memberi tahu Anchor dan Cargo cara membangun, menguji, dan men-deploy program. Untuk referensi lengkap, lihat dokumentasi Anchor untuk konfigurasi Anchor.toml dan Anchor CLI.
skip_local_validator = true[toolchain][features]resolution = trueskip-lint = false[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"[provider]cluster = "localnet"wallet = "~/.config/solana/id.json"[scripts]test = "cargo test"[hooks]
Is this page helpful?