Buat Program Solana Pertama Anda

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:

Terminal
$
anchor --version

Buat Proyek

Jalankan perintah berikut di terminal Anda:

Terminal
$
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.

Anchor.toml
Cargo.toml
Cargo.toml
lib.rs
constants.rs
error.rs
instructions.rs
initialize.rs
increment.rs
state.rs
test_initialize.rs

Build Program

Jalankan anchor build untuk mengompilasi program awal:

Terminal
$
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:

Terminal
$
anchor test

Template ini Anchor.toml menggunakan perintah pengujian Rust:

Anchor.toml
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:

Terminal
$
solana airdrop 2 --url devnet
Terminal
$
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.

programs/my-program/src/lib.rs
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)
}
}
Anchor.toml
[programs.localnet]
my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
lib.rs
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.

programs/my-program/src/constants.rs
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;
initialize.rs
#[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_SEED mengambil alamat PDA counter.
  • HELLO_WORLD_LAMPORTS menetapkan jumlah yang ditransfer dari payer ke akun counter.
increment.rs
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.

programs/my-program/src/state.rs
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}
initialize.rs
#[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(())
}
increment.rs
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_SPACE menentukan ukuran akun untuk field yang didefinisikan dalam state.rs.
  • count dan authority adalah nilai field yang ditulis saat akun diinisialisasi.
  • count += 1 memperbarui 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.

programs/my-program/src/error.rs
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,
}
increment.rs
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::Unauthorized dikembalikan ketika penandatangan bukan authority yang tersimpan di akun counter.
  • ErrorCode::CounterOverflow dikembalikan ketika counter sudah mencapai MAX_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.

programs/my-program/src/instructions.rs
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.

programs/my-program/src/instructions/initialize.rs
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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

initialize.rs
#[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.

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.

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.

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.

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.

System Program

Akun system_program diperlukan karena pembuatan akun baru menggunakan System Program.

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.

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.

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.

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.

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.

Catat Pesan

msg! menulis pesan ke log program. Ok(()) menandakan bahwa instruksi berhasil dikembalikan.

initialize.rs
#[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>,
}

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.

programs/my-program/src/instructions/increment.rs
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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

increment.rs
#[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.

Counter Account

Akun counter menyimpan data Counter. Batasan mut memungkinkan handler memperbarui hitungan yang tersimpan, dan batasan seeds serta bump memverifikasi alamat PDA counter.

Authority Signer

Akun authority harus menandatangani transaksi. Handler kemudian memeriksa bahwa penanda tangan ini cocok dengan otoritas yang tersimpan di akun counter.

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.

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.

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.

Perbarui Hitungan

Hanya setelah kedua pemeriksaan berhasil, handler memperbarui data akun. Baris ini menambahkan satu ke nilai counter yang tersimpan.

Pesan Log

msg! menulis hitungan yang diperbarui ke log program. Ok(()) menunjukkan bahwa instruksi berhasil dikembalikan.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}

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.

initialize.rs
#[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>,
}
test_initialize.rs
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:

  • payer diteruskan sebagai payer: payer.pubkey().
  • counter diteruskan sebagai counter.
  • system_program diteruskan sebagai system_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.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
test_initialize.rs
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:

  • counter diteruskan sebagai counter.
  • authority diteruskan sebagai authority: 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.

programs/my-program/tests/test_initialize.rs
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.

Anchor.toml
skip_local_validator = true
[toolchain]
[features]
resolution = true
skip-lint = false
[programs.localnet]
my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "cargo test"
[hooks]

Is this page helpful?

Daftar Isi

Edit Halaman
© 2026 Yayasan Solana. Semua hak dilindungi.