Τα on-chain προγράμματα μετακινούν tokens εκδίδοντας ένα
Cross Program Invocation (CPI) στο Token Program. Το πρόγραμμά
σας δημιουργεί μια εντολή Token Program, παρέχει τους απαραίτητους λογαριασμούς
και καλεί το invoke (ή το invoke_signed όταν υπογράφει ένα Program
Derived Address). Το Token Program εκτελεί τότε αυτή την εντολή με τα προνόμια
που του παραχωρεί το πρόγραμμά σας.
Αυτή η σελίδα καλύπτει την πλευρά που αφορά ειδικά τα tokens σε αυτή τη ροή. Για
τον ίδιο τον μηχανισμό CPI — πώς λειτουργούν τα invoke και
invoke_signed, πώς μεταδίδονται τα προνόμια υπογραφέα και εγγραφής, και το
μοντέλο κόστους ανά CPI — δείτε
Cross Program Invocation.
Συνήθη μοτίβα
Τα περισσότερα token CPI ακολουθούν το ίδιο σχήμα: δημιουργήστε την εντολή,
περάστε τα token accounts και μια αρχή, και καλέστε. Το παρακάτω παράδειγμα
μεταφέρει tokens με TransferChecked, το οποίο επαληθεύει το mint και τα
δεκαδικά ψηφία ως μέρος της μεταφοράς.
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 και το κλείσιμο λογαριασμών ακολουθούν το ίδιο μοτίβο με
διαφορετική εντολή — MintTo, Burn και CloseAccount στη
anchor_spl::token
ενότητα του Anchor, ή στους αντίστοιχους builders στο
pinocchio-token. Για πλήρη,
εκτελέσιμα προγράμματα που κάνουν CPI στο Token Program τόσο σε Anchor όσο και
σε native Rust, δείτε τα
παραδείγματα token program — και ιδιαίτερα το
παράδειγμα "Transfer Tokens".
Υπογραφή με αρχή Program Derived Address
Όταν η αρχή σε ένα token account είναι ένα
Program Derived Address που ανήκει στο πρόγραμμά σας, το
πρόγραμμα υπογράφει το ίδιο το CPI με invoke_signed, περνώντας τα seeds
του PDA ως seeds υπογραφέα. Στο Anchor, χρησιμοποιήστε
CpiContext::new_with_signer με τα seeds αντί του CpiContext::new. Η
εντολή είναι κατά τα άλλα πανομοιότυπη με τα παραπάνω παραδείγματα. Δείτε
CPIs με PDA Signers για την πλήρη λεπτομέρεια
λειτουργίας.
Ομαδοποίηση
Η εντολή Batch εκτελεί πολλές εντολές Token Program μέσα σε μία μόνο κλήση
προγράμματος. Επειδή κάθε CPI φέρει ένα σταθερό κόστος υπολογισμού, η εκτέλεση
πολλών λειτουργιών token σε μία ομαδική CPI χρησιμοποιεί λιγότερες
μονάδες υπολογισμού από το να εκδίδεται
ξεχωριστή CPI ανά λειτουργία — δείτε το
μοντέλο κόστους CPI για το πώς τα κόστη ανά CPI
αθροίζονται.
Η μειωμένη χρήση υπολογισμού μειώνει τα τέλη προτεραιότητας που πληρώνει μια συναλλαγή ανά μονάδα υπολογισμού και βελτιώνει τις πιθανότητες ολοκλήρωσής της — κάτι που έχει τη μεγαλύτερη σημασία για προγράμματα που εκτελούν πολλές λειτουργίες token σε μία μόνο εντολή. Συνήθεις χρήσεις περιλαμβάνουν τη διανομή μιας μεταφοράς σε πολλούς παραλήπτες ή την εκτέλεση μιας πολυβηματικής ροής token (για παράδειγμα συγχρονισμός, μεταφορά και κλείσιμο) σε μία μόνο CPI.
Το Batch δέχεται μόνο εντολές Token Program ως θυγατρικές, και μια ομάδα
δεν μπορεί να περιέχει άλλη ομάδα. Για τις λειτουργίες που μετακινούν tokens
(όπως μεταφορές, εκδόσεις και καύσεις), το πρόγραμμα επαληθεύει ότι οι
επηρεαζόμενοι λογαριασμοί ανήκουν στο Token Program πριν τις εκτελέσει.
Αναφορά πηγής
| Στοιχείο | Περιγραφή | Πηγή |
|---|---|---|
Batch | Η εντολή Batch (διακριτικό 255). | Πηγή |
process_batch | Λογική επεξεργαστή Batch. | Πηγή |
Κλήση Batch μέσω CPI
Το crate pinocchio-token παρέχει
ένα builder Batch κάτω από το pinocchio_token::instructions::Batch.
Τοποθετείτε κάθε θυγατρική εντολή στα buffers της ομάδας και στη συνέχεια
εκδίδετε μία μόνο CPI με το invoke.
Ένα Batch υποστηρίζεται από τρία buffers που παρέχει ο καλών: ένα για τα
σειριακά instruction data, ένα για τα account metas κάθε θυγατρικής εντολής και
ένα για τα account views που μεταβιβάζονται στο CPI. Κατασκευάστε τα ως φέτες
MaybeUninit, δώστε τα στο Batch::new, προσθέστε θυγατρικές εντολές
και στη συνέχεια καλέστε:
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 γράφει τον διακριτή της δέσμης στο πρώτο byte του buffer
δεδομένων και επιστρέφει ένα Batch που παρακολουθεί πόσο από κάθε buffer
έχει χρησιμοποιηθεί. Κάθε builder εντολής του Token Program (όπως
Transfer, TransferChecked, MintTo και Burn) υλοποιεί το
trait IntoBatch, οπότε το into_batch προσαρτά τα δεδομένα, τα
account metas και τα account views της εντολής στη δέσμη. Η κλήση του
invoke εκδίδει τη συναρμολογημένη δέσμη ως ένα CPI στο Token Program.
Τα buffers ορίζουν τη χωρητικότητα της δέσμης. Ορίστε το μέγεθος του buffer
δεδομένων σε 1 + Σ(2 + child_data_len) bytes (ο διακριτής, συν μια κεφαλίδα 2
bytes και τα σειριοποιημένα δεδομένα για κάθε παιδί) και κάθε buffer λογαριασμών
στον συνολικό αριθμό λογαριασμών σε όλα τα παιδιά. Εάν ένα παιδί δεν χωράει, το
into_batch επιστρέφει ProgramError::InvalidArgument. Για αναφορά, το
Batch::MAX_DATA_LEN είναι 10 KiB και το Batch::MAX_ACCOUNTS_LEN
ισούται με τον μέγιστο αριθμό λογαριασμών CPI του runtime.
Για δέσμες των οποίων το μέγεθος είναι γνωστό μόνο κατά την εκτέλεση,
ενεργοποιήστε το χαρακτηριστικό alloc του crate και χρησιμοποιήστε το
BatchState::new(accounts_len, data_len) για να εκχωρήσετε τα buffers στο
heap, στη συνέχεια καλέστε το as_batch για να λάβετε ένα Batch.
Όταν η αρχή μιας εντολής παιδιού είναι ένα PDA που ανήκει στο πρόγραμμά σας,
υπογράψτε το CPI με invoke_signed αντί για invoke, περνώντας τα
seeds του PDA ως καταχωρήσεις Signer.
Διάταξη instruction data
Μια δέσμη κωδικοποιεί τα παιδιά της το ένα μετά το άλλο πίσω από τον διακριτή
255. Όλοι οι λογαριασμοί μεταβιβάζονται επίπεδα, με σειρά, και τεμαχίζονται
από num_accounts για κάθε παιδί. Ο builder παράγει ακριβώς αυτή τη μορφή wire:
[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)
Η χωρητικότητα μιας δέσμης ορίζεται από τα buffers που μεταβιβάζονται στο
Batch::new: το buffer δεδομένων πρέπει να χωράει τον διακριτή συν την
κεφαλίδα και τα δεδομένα κάθε παιδιού, και τα buffers λογαριασμών πρέπει να
χωράνε τους λογαριασμούς κάθε παιδιού. Ορίστε τα μεγέθη τους για τη μεγαλύτερη
δέσμη που κατασκευάζετε, ή χρησιμοποιήστε το BatchState (με το
χαρακτηριστικό alloc) για να τα ορίσετε κατά την εκτέλεση. Το συνδυασμένο CPI
εξακολουθεί να υπόκειται στα όρια λογαριασμών και μεγέθους της συναλλαγής, οπότε
για πολύ μεγάλα fan-outs χρησιμοποιήστε Address Lookup Tables για να χωρέσουν
περισσότεροι λογαριασμοί.
Is this page helpful?