Program on-chain memindahkan token dengan mengeluarkan
Cross Program Invocation (CPI) ke Token Program. Program Anda
membangun instruksi Token Program, menyediakan akun-akun yang diperlukan, dan
memanggil invoke (atau invoke_signed ketika Program Derived Address
menandatangani). Token Program kemudian menjalankan instruksi tersebut dengan
hak akses yang diberikan program Anda kepadanya.
Halaman ini membahas sisi khusus token dari alur tersebut. Untuk mekanisme CPI
itu sendiri — cara kerja invoke dan invoke_signed, cara hak penanda
tangan dan writable disebarkan, serta
model biaya per-CPI — lihat
Cross Program Invocation.
Pola umum
Sebagian besar CPI token mengikuti bentuk yang sama: buat instruksi, teruskan
token account dan otoritas, lalu panggil. Contoh di bawah ini mentransfer token
dengan TransferChecked, yang memverifikasi mint dan desimal sebagai bagian
dari transfer.
use anchor_lang::prelude::*;use anchor_spl::token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked};declare_id!("11111111111111111111111111111111");#[program]pub mod token_cpi {use super::*;pub fn transfer(ctx: Context<TokenTransfer>, amount: u64) -> Result<()> {let cpi_accounts = TransferChecked {from: ctx.accounts.source.to_account_info(),mint: ctx.accounts.mint.to_account_info(),to: ctx.accounts.destination.to_account_info(),authority: ctx.accounts.authority.to_account_info(),};let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(),cpi_accounts,);transfer_checked(cpi_ctx, amount, ctx.accounts.mint.decimals)?;Ok(())}}#[derive(Accounts)]pub struct TokenTransfer<'info> {#[account(mut)]pub source: InterfaceAccount<'info, TokenAccount>,pub mint: InterfaceAccount<'info, Mint>,#[account(mut)]pub destination: InterfaceAccount<'info, TokenAccount>,pub authority: Signer<'info>,pub token_program: Interface<'info, TokenInterface>,}
Minting, burning, dan penutupan akun mengikuti pola yang sama dengan instruksi
berbeda — MintTo, Burn, dan CloseAccount dalam modul
anchor_spl::token
Ankhor, atau pembangun yang sesuai di
pinocchio-token. Untuk program
lengkap yang dapat dijalankan dan melakukan CPI ke Token Program baik dengan
Anchor maupun native Rust, lihat
contoh token program — khususnya contoh
"Transfer Tokens".
Menandatangani dengan otoritas Program Derived Address
Ketika otoritas pada sebuah token account adalah
Program Derived Address yang dimiliki oleh program Anda,
program tersebut menandatangani CPI itu sendiri dengan invoke_signed,
dengan meneruskan seed Program Derived Address sebagai seed penanda tangan. Di
Anchor, gunakan CpiContext::new_with_signer dengan seed tersebut sebagai
pengganti CpiContext::new. Instruksinya identik dengan contoh-contoh di
atas. Lihat CPI dengan PDA Signer untuk mekanisme
lengkapnya.
Pengelompokan (Batching)
Instruksi Batch mengeksekusi beberapa instruksi Token Program dalam satu
panggilan program tunggal. Karena setiap CPI memiliki biaya komputasi tetap,
menjalankan beberapa operasi token dalam satu batch CPI menggunakan lebih
sedikit unit komputasi dibandingkan
mengeluarkan CPI terpisah per operasi — lihat
model biaya CPI untuk mengetahui bagaimana
biaya per-CPI terakumulasi.
Penggunaan komputasi yang lebih rendah mengurangi biaya prioritas yang dibayar transaksi per unit komputasi dan meningkatkan peluang keberhasilannya — hal ini paling penting bagi program yang melakukan banyak operasi token dalam satu instruksi. Penggunaan umum meliputi penyebaran transfer ke banyak penerima, atau menjalankan alur token bertahap (misalnya sinkronisasi, transfer, dan penutupan) dalam satu CPI.
Batch hanya menerima instruksi Token Program sebagai turunan, dan sebuah
batch tidak dapat berisi batch lain. Untuk operasi yang memindahkan token
(seperti transfer, mint, dan burn), program memverifikasi bahwa akun yang
terpengaruh dimiliki oleh Token Program sebelum mengeksekusinya.
Referensi sumber
| Item | Deskripsi | Sumber |
|---|---|---|
Batch | Instruksi Batch (diskriminator 255). | Sumber |
process_batch | Logika pemroses Batch. | Sumber |
Memanggil Batch melalui CPI
Crate pinocchio-token mengekspos
sebuah builder Batch di bawah pinocchio_token::instructions::Batch.
Anda memasukkan setiap instruksi turunan ke dalam buffer batch, lalu
mengeluarkan satu CPI dengan invoke.
Sebuah Batch didukung oleh tiga buffer yang disediakan pemanggil: satu
untuk instruction data yang diserialisasi, satu untuk meta akun instruksi
per-turunan, dan satu untuk tampilan akun yang diteruskan ke CPI. Buat sebagai
slice MaybeUninit, berikan ke Batch::new, tambahkan turunan, lalu
panggil:
use core::mem::MaybeUninit;use pinocchio::{account::AccountView, error::ProgramError, ProgramResult};use pinocchio_token::instructions::{Batch, IntoBatch, Transfer};/// Process SwapBatch.////// Performs two transfers in a single batch CPI instead of two separate/// `invoke()` calls.////// Data layout:/// [0..8] amount_a_to_b (u64 LE)/// [8..16] amount_b_to_a (u64 LE)////// Account layout:/// [0] source_a (writable) — token account A/// [1] source_b (writable) — token account B/// [2] authority_a (signer) — authority for account A/// [3] authority_b (signer) — authority for account B/// [4] token_program — SPL Token programpub fn process(accounts: &[AccountView], data: &[u8]) -> ProgramResult {if data.len() < 16 {return Err(ProgramError::InvalidInstructionData);}if accounts.len() < 5 {return Err(ProgramError::NotEnoughAccountKeys);}let amount_a_to_b = u64::from_le_bytes(data[0..8].try_into().unwrap());let amount_b_to_a = u64::from_le_bytes(data[8..16].try_into().unwrap());let source_a = &accounts[0];let source_b = &accounts[1];let authority_a = &accounts[2];let authority_b = &accounts[3];// Two child transfers, each with 3 accounts and 9 bytes of data// (1-byte discriminator + 8-byte amount).const NUM_CHILDREN: usize = 2;const CHILD_ACCOUNTS: usize = 3;const CHILD_DATA_LEN: usize = 9;// Data buffer: 1-byte batch discriminator + per child (2-byte header + data).const DATA_LEN: usize = 1 + NUM_CHILDREN * (2 + CHILD_DATA_LEN);// Account buffers: total accounts across all children.const ACCOUNTS_LEN: usize = NUM_CHILDREN * CHILD_ACCOUNTS;let mut data_buf = [MaybeUninit::uninit(); DATA_LEN];let mut ix_accounts_buf = [MaybeUninit::uninit(); ACCOUNTS_LEN];let mut accounts_buf = [MaybeUninit::uninit(); ACCOUNTS_LEN];let mut batch = Batch::new(&mut data_buf, &mut ix_accounts_buf, &mut accounts_buf)?;Transfer::new(source_a, source_b, authority_a, amount_a_to_b).into_batch(&mut batch)?;Transfer::new(source_b, source_a, authority_b, amount_b_to_a).into_batch(&mut batch)?;batch.invoke()?;Ok(())}
Batch::new menulis diskriminator batch ke byte pertama dari buffer data
dan mengembalikan Batch yang melacak seberapa banyak setiap buffer yang
telah digunakan. Setiap pembangun instruksi Token Program (seperti
Transfer, TransferChecked, MintTo, dan Burn)
mengimplementasikan trait IntoBatch, sehingga into_batch menambahkan
data instruksi tersebut, meta akun, dan tampilan akun ke dalam batch. Memanggil
invoke akan mengirimkan batch yang telah dirakit sebagai satu CPI ke Token
Program.
Buffer membatasi kapasitas batch. Ukuran buffer data ke
1 + Σ(2 + child_data_len) byte (diskriminator, ditambah header 2-byte dan data
terserialisasi untuk setiap instruksi anak) dan setiap buffer akun ke jumlah
total akun di semua instruksi anak. Jika instruksi anak tidak muat,
into_batch mengembalikan ProgramError::InvalidArgument. Sebagai
referensi, Batch::MAX_DATA_LEN adalah 10 KiB dan
Batch::MAX_ACCOUNTS_LEN sama dengan jumlah akun CPI maksimum dari runtime.
Untuk batch yang ukurannya hanya diketahui saat runtime, aktifkan fitur alloc
pada crate dan gunakan BatchState::new(accounts_len, data_len) untuk
mengalokasikan buffer di heap, lalu panggil as_batch untuk mendapatkan
Batch.
Ketika otoritas instruksi anak adalah PDA yang dimiliki oleh program Anda,
tandatangani CPI dengan invoke_signed sebagai pengganti invoke,
dengan meneruskan seed PDA sebagai entri Signer.
Tata letak instruction data
Sebuah batch mengenkode instruksi-instruksi anaknya satu per satu di belakang
diskriminator 255. Semua akun diteruskan secara datar, berurutan, dan dipotong
oleh num_accounts untuk setiap instruksi anak. Builder menghasilkan tepat
format wire ini:
[255] // Batch discriminator// For each child instruction, in order:// [num_accounts: u8] // accounts this instruction consumes// [data_len: u8] // length of this instruction's data// [data: u8; data_len]// the instruction data (begins with its own discriminator)
Kapasitas batch dibatasi oleh buffer yang diteruskan ke Batch::new: buffer
data harus menampung diskriminator ditambah header dan data setiap instruksi
anak, serta buffer akun harus menampung akun-akun setiap instruksi anak. Ukurkan
buffer untuk batch terbesar yang Anda bangun, atau gunakan BatchState
(dengan fitur alloc) untuk menentukan ukurannya saat runtime. CPI gabungan
tetap tunduk pada batas akun dan ukuran transaksi, sehingga untuk fan-out yang
sangat besar, gunakan Address Lookup Tables agar lebih banyak akun dapat
termuat.
Is this page helpful?