BARU: Helius mengakuisisi Light Protocol
membangun smart contract Solana dengan Pinocchio
Blog/Pengembangan

Cara Membangun Program Solana dengan Pinocchio

Agensi Pengembangan Solana TerkemukaExo Technologies di XExo Technologies di LinkedIn
Pendiri bersama, Exo TechnologiesTaylor Johnson di XTaylor Johnson di LinkedIn
Bacaan 12 menit

Pinocchio adalah library tanpa dependensi yang sangat dioptimalkan dan dapat digunakan untuk membangun program Solana native. Pinocchio dibuat oleh Anza, developer inti klien Agave Solana. 

Exo Tech adalah studio developer Solana terkemuka yang menjadi salah satu pengadopsi awal Pinocchio. Melalui pekerjaan untuk klien, kami telah mengembangkan beberapa program produksi menggunakan Pinocchio dan berkontribusi pada SDK untuk menambahkan fungsionalitas yang belum tersedia. 

Artikel ini membahas secara mendalam cara membangun program dengan Pinocchio serta manfaat dan komprominya. Kami ingin membekali developer dengan pengetahuan untuk menentukan apakah Pinocchio cocok bagi program mereka. Namun, perlu diperhatikan bahwa Pinocchio tidak ramah bagi pemula karena lebih mengutamakan pengoptimalan daripada pengalaman developer.

Apa itu library Pinocchio?

Library Pinocchio adalah pengganti crate solana-program yang mengoptimalkan eksekusi program dengan menggunakan tipe zero-copy secara ekstensif. zero-copy berarti data tidak perlu disalin ke alamat memori terpisah saat dibaca atau ditulis sehingga menghemat sumber daya komputasi (atau CU di Solana).

Library ini tidak memiliki dependensi dan bersifat “no_std”. Crate std milik Rust menyediakan cara umum untuk mengakses sumber daya sistem operasi serta runtime. Namun, karena Solana Virtual Machine (SVM) sendiri merupakan runtime, overhead ini tidak diperlukan.

Mengapa performa Pinocchio lebih tinggi daripada solana-program?

Setiap program Solana memerlukan entrypoint yang dipanggil runtime untuk mengeksekusi program. Library solana-program mengekspos makro entrypoint!, yang melakukan deserialisasi input program, menyiapkan heap allocator, dan membuat panic handler.

Kode
macro_rules! entrypoint {
    ($process_instruction:ident) => {
        /// # Safety
        #[no_mangle]
        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
            let (program_id, accounts, instruction_data) =
                unsafe { $crate::entrypoint::deserialize(input) };
            match $process_instruction(&program_id, &accounts, &instruction_data) {
                Ok(()) => $crate::entrypoint::SUCCESS,
                Err(error) => error.into(),
            }
        }
        $crate::custom_heap_default!();
        $crate::custom_panic_default!();
    };
}

Pinocchio mengekspor tiga makro entrypoint.

Bagi yang bermigrasi dari solana-program, makro entrypoint! akan berfungsi hampir sama dengan melakukan deserialisasi input program serta menyiapkan allocator dan handler. 

Namun, dua makro lainnya memisahkan entrypoint dari penyiapan heap allocator dan panic handler. Ini memberi developer kendali lebih besar untuk menghilangkan atau mengoptimalkan proses sebelum logika program dieksekusi. 

program_entrypoint! melakukan deserialisasi input program seperti solana-program, sedangkan lazy_program_entrypoint! hanya membungkus buffer input dan menyerahkan penanganannya kepada program sehingga kontrol atas komputasi menjadi lebih besar. 

Karena makro ini tidak menyiapkan heap allocator atau panic handler, library Pinocchio menyediakan makro default yang dapat digunakan developer.

Selain itu, jika program mengetahui bahwa memori heap tidak akan pernah diperlukan, no_allocator! menghemat compute unit (CU) dengan tidak menyiapkan memory allocator.

Bagaimana entrypoint Pinocchio melakukan deserialisasi input program Solana secara berbeda?

Kami telah menyinggung bagaimana entrypoint solana-program dan Pinocchio melakukan deserialisasi input program. Namun, penting untuk memahami perbedaan proses deserialisasinya karena dari sinilah penghematan CU yang besar berasal. 

Sekilas, input hasil deserialisasi yang diteruskan ke instruction handler program terlihat sama:

Kode
/// solana-program and pinocchio both look the same
process_instruction(
         program_id: &Pubkey,
         accounts: &[AccountInfo],
         instruction_data: &[u8],
     ) -> ProgramResult

Perbedaan utamanya terletak pada implementasi AccountInfo. 

Sementara solana-program menulis data ke struct AccountInfo yang memiliki data tersebut, struct AccountInfo milik Pinocchio hanyalah pointer ke data input dasar yang merepresentasikan account. Hal ini mengurangi jumlah data yang perlu disalin sehingga menghemat banyak CU.

Bagaimana Pinocchio memungkinkan developer mengoptimalkan CU?

Karena instruction processor menerima referensi ke pointer, developer yang menggunakan library Pinocchio akan mendapati bahwa logika mereka hampir tidak pernah memiliki kepemilikan atas data yang sedang diproses. 

Hal ini mudah terlihat saat mencoba mengakses nilai pada AccountInfo. Membaca public key account dengan metode key() akan mengembalikan referensi ke Pubkey. Dengan demikian, pembacaan informasi account selama eksekusi program dan mutasi data account menjadi lebih murah.

Contoh Pengoptimalan CU Pinocchio: P-token

Contoh bagus yang terus memanfaatkan zero-copy untuk pengoptimalan adalah program p-token.

Program ini dibuat sebagai pengganti SPL Token Program kanonis, tetapi menggunakan Pinocchio untuk mengurangi jumlah compute unit secara drastis pada setiap transaksi.

Anda akan segera melihat bahwa semua state diakses melalui pointer.

Alih-alih melakukan deserialisasi token account, data dari AccountInfo diperiksa, lalu sebuah pointer dikembalikan.

Setiap properti diakses melalui fungsi, dan semua nilai yang bukan primitive mengembalikan referensi yang mempertahankan zero-copy. 

Untuk mengetahui lebih lanjut mengapa cara ini mengurangi penggunaan CU secara drastis, baca artikel tentang pengoptimalan CU ini.

Pinocchio vs. Anchor

Anchor adalah framework opinionated yang sangat populer untuk mengembangkan program Solana. Anchor dianggap memiliki level abstraksi lebih tinggi daripada Pinocchio karena tidak memiliki logika untuk mengekspos struktur dasar seperti AccountInfo.

Sebaliknya, Anchor bergantung pada crate solana-program yang telah disebutkan dan mengekspos trait serta makro untuk menyederhanakan proses pengembangan program. Anchor menyediakan pola instruction discriminator dan logika deserialisasi account. Logika deserialisasi tersebut mengandalkan Borsh, yang memerlukan penyalinan data ke alamat memori lain karena tidak menggunakan zero-copy. 

Meskipun kemudahan Anchor mempercepat proses pengembangan program Solana, konsekuensinya adalah penggunaan CU yang lebih besar.

Di sisi lain, Pinocchio adalah library yang dirancang untuk menggantikan solana-program ketika developer memerlukan kontrol penggunaan komputasi yang mendetail. Library ini sama sekali tidak opinionated dan memungkinkan developer menyusun program dengan cara apa pun yang dianggap sesuai. Tata letak setiap proyek Pinocchio dapat terlihat benar-benar berbeda, sedangkan proyek Anchor memiliki struktur yang ditetapkan dengan jelas. 

Library Pinocchio tidak menangani binding atau implementasi klien apa pun. Di sisi lain, Anchor menyediakan dukungan penuh untuk pembuatan IDL, yang dapat digunakan di sisi klien untuk berinteraksi dengan program.

Developer yang menggunakan Pinocchio harus menulisnya sendiri atau menggunakan alat lain seperti Shank dan Codama, yang kami uraikan di bawah dalam bagian Alat Pelengkap untuk Membangun dengan Pinocchio.

Pinocchio vs. Steel

Steel adalah framework lain untuk menulis program Solana. Saat ini dibangun di atas solana-program, Steel mengekspos makro, fungsi, dan pola yang memudahkan penulisan program yang aman dan ekspresif.

Sifat Steel yang opinionated membuatnya mudah dibaca sekaligus tetap modular. Developer dapat memilih untuk hanya menggunakan komponen Steel yang diperlukan, tidak seperti Anchor yang merupakan framework serba-atau-tidak-sama-sekali.

Makro account! milik Steel menggunakan bytemuck untuk mem-parsing struktur account, sedangkan Pinocchio sama sekali tidak menangani parsing account. Steel juga mencakup parser dan assertion yang dapat dirangkai sehingga validasi kustom mudah ditambahkan. Pinocchio tidak menyediakan pola semacam ini secara bawaan dan mengharuskan developer menulis pola validasi sendiri.

Namun, untuk cross-program invocation (CPI) umum seperti System Program dan Token Program, Pinocchio dan Steel sama-sama mengekspos pola yang memudahkan invocation tersebut.

Pinocchio sangat dioptimalkan, tetapi menyerahkan setiap detail kepada developer. Steel adalah wrapper modular yang baik untuk library solana-program dan dirancang guna meningkatkan pengalaman developer.

Cara Membuat Token Menggunakan Pinocchio

Untuk mendemonstrasikan program yang ditulis dengan Pinocchio, kami akan menulis ulang program pembuatan token dari contoh developer Solana.

Ini adalah program sederhana dengan satu instruction yang membuat mint token Token2022 dan menggunakan ekstensi token Metadata untuk menyimpan informasi tentang token. Metadata akan diberikan melalui instruction data yang berisi nama, simbol, dan uri.

1. Tentukan Entrypoint

Mari mulai dengan menentukan entrypoint program kita.

Kita menggunakan makro entrypoint lengkap karena ingin memakai allocator dan penanganan panic default dari Pinocchio.

Kode
entrypoint!(process_instruction);

fn process_instruction(
   _program_id: &Pubkey,
   accounts: &[AccountInfo],
   instruction_data: &[u8],
) -> ProgramResult {
   Ok(())
}

2. Tentukan Struktur Instruction Data

Berikutnya, kita menentukan struktur instruction data agar sesuai dengan program contoh lainnya. Untuk menghemat waktu pengembangan, kita akan menggunakan Borsh untuk deserialisasi dan membahas metode deserialisasi yang lebih optimal dalam artikel lain.

Kode
#[derive(BorshDeserialize, Debug)]
pub struct CreateTokenArgs {
   pub name: String,
   pub symbol: String,
   pub uri: String,
   pub decimals: u8,
}

3. Parsing Account dan Instruction Data

Sekarang, mari tulis logika di dalam instruction processor.

Hal pertama yang harus dilakukan adalah mendestrukturisasi account dari daftar account dan melakukan deserialisasi instruction data ke CreateTokenArgs kita.

Kode
let [mint_account, mint_authority, payer, token_program, _system_program] = accounts else {
       return Err(ProgramError::NotEnoughAccountKeys);
   };

   let args = CreateTokenArgs::try_from_slice(instruction_data)
       .map_err(|_| ProgramError::InvalidInstructionData)?;

4. Buat Mint Account Token2022

Setelah account dan instruction data di-parsing, kita memanggil instruction CreateAccount milik System program.

Di bawah ini, kita menggunakan struct CreateAccount dari `pinocchio_system crate as it makes it very convenient to CPI by setting values of the struct and calling invoke.

Tidak seperti saat membuat mint SPL Token biasa, kita harus menentukan ruang tambahan yang diperlukan oleh ekstensi token yang digunakan.

Ukuran ekstensi Metadata Pointer bersifat statis, sedangkan ekstensi Token Metadata harus dihitung secara dinamis berdasarkan argumen yang diberikan.

Kode
 /// [4 (extension discriminator) + 32 (update_authority) + 32 (metadata)]
   const METADATA_POINTER_SIZE: usize = 4 + 32 + 32;
   /// [4 (extension discriminator) + 32 (update_authority) + 32 (mint) + 4 (size of name ) + 4 (size of symbol) + 4 (size of uri) + 4 (size of additional_metadata)]
   const METADATA_EXTENSION_BASE_SIZE: usize = 4 + 32 + 32 + 4 + 4 + 4 + 4;
   /// Padding used so that Mint and Account extensions start at the same index
   const EXTENSIONS_PADDING_AND_OFFSET: usize = 84;

   /* within `process_instruction` */
   let extension_size = METADATA_POINTER_SIZE
       + METADATA_EXTENSION_BASE_SIZE
       + args.name.len()
       + args.symbol.len()
       + args.uri.len();
   let total_mint_size = Mint::LEN + EXTENSIONS_PADDING_AND_OFFSET + extension_size;

   let rent = Rent::get()?;
   // Create the account for the Mint
   CreateAccount {
       from: payer,
       to: mint_account,
       owner: token2022_program.key(),
       lamports: rent.minimum_balance(Mint::LEN),
       space: Mint::LEN as u64,
   }
   .invoke()?;

Setelah CreateAccount dipanggil, SystemProgram mendaftarkan program Token2022 sebagai pemilik mint Account.

5. Inisialisasi Ekstensi, Account, dan Nilai Metadata

Berikutnya, kita harus menetapkan data account dengan menginisialisasi ekstensi Metadata Pointer, menginisialisasi Mint account menggunakan program Token2022, serta menginisialisasi nilai metadata yang diterima program kita sebagai argumen. 

CPI berikut berasal dari sebuah branch crate pinocchio_token yang sedang dikembangkan secara aktif. Karena itu, perlu diperhatikan bahwa kode ini kemungkinan akan usang karena fungsionalitas Token2022 direncanakan untuk dipisahkan dari crate SPL Token.

Kode
// Initialize MetadataPointer extension pointing to the Mint account
   InitializeMetadataPointer {
       mint: mint_account,
       authority: Some(*payer.key()),
       metadata_address: Some(*mint_account.key()),
   }
   .invoke()?;

   // Now initialize that account as a Token2022 Mint
   InitializeMint2 {
       mint: mint_account,
       decimals: args.decimals,
       mint_authority: mint_authority.key(),
       freeze_authority: None,
   }
   .invoke(TokenProgramVariant::Token2022)?;

   // Set the metadata within the Mint account
   InitializeTokenMetadata {
       metadata: mint_account,
       update_authority: payer,
       mint: mint_account,
       mint_authority: payer,
       name: &args.name,
       symbol: &args.symbol,
       uri: &args.uri,
   }
   .invoke()?;

Selesai! 

Sekarang kita memiliki mint token dengan metadata mandiri menggunakan Token2022 yang ditulis dengan Pinocchio.

Kode ini masih dapat ditingkatkan untuk memastikan pengoptimalan maksimal, tetapi kami berharap contoh ini memberi Anda pemahaman tentang cara menulis program dengan Pinocchio.

Alat Pelengkap untuk Membangun dengan Pinocchio

Alat khusus untuk Pinocchio masih terbatas, tetapi terus berkembang.

Bytemuck untuk (De)serialisasi Account

(De)serialisasi account harus diimplementasikan oleh developer program Pinocchio. Jika dilakukan secara manual, proses ini membosankan dan rentan terhadap kesalahan. Bytemuck adalah library yang sangat baik untuk memudahkan pembacaan dan penulisan byte array sebagai struct. Artinya, library ini cukup optimal karena membatasi jumlah data yang perlu disalin ke memori.

Borsh adalah solusi lain saat bekerja dengan account yang ukurannya tidak tetap. Namun, Borsh kurang efisien dari sisi komputasi dan menjadi salah satu alasan orang memilih Pinocchio daripada Anchor.

Shank untuk Membuat IDL

Karena Pinocchio adalah library, Pinocchio tidak memiliki pembuatan IDL bawaan seperti Anchor. IDL (Interface Definition Language) adalah file JSON yang mendefinisikan antarmuka publik program Solana, termasuk instruction, struktur account, dan kode error. IDL memungkinkan interaksi terstandar dan menyederhanakan pengembangan di sisi klien.

Untuk membuat IDL, kami menyarankan penggunaan Shank. Crate ini sangat memudahkan developer untuk menganotasi kode dan menggunakan CLI guna menghasilkan IDL yang valid. Menambahkan makro ShankAccount ke derive statement sebuah struct menunjukkan bahwa struct tersebut adalah Account yang harus dapat di-(de)serialisasi. Setelah shank CLI dijalankan, struktur ini akan menjadi typed account dalam IDL yang kemudian dapat digunakan untuk membuat klien.

Makro penting lainnya adalah ShankInstruction untuk enum instruction program. Makro ini memungkinkan penggunaan atribut #[account] guna menunjukkan indeks dan izin setiap account dalam daftar untuk instruction tertentu.

Lihat repositori shank-macro untuk informasi lebih lanjut tentang anotasi kode berguna yang memudahkan pembuatan IDL bagi program non-Anchor.

Codama untuk Membuat Klien

Setelah memiliki IDL, pembuatan klien menjadi mudah dengan Codama. Jika kode yang dihasilkan tidak sesuai dengan kebutuhan, Anda harus menulis klien secara manual.

Di Exo Tech, kami membuat template proyek Pinocchio untuk membantu kami menyiapkan repositori program Solana dengan cepat. Silakan mencobanya dan buka pull request untuk setiap peningkatan!

Masa Depan Pinocchio

Meskipun dirancang sebagai pengganti langsung untuk solana-program, Pinocchio belum memiliki kesetaraan fitur. Beberapa sysvar belum didukung, sementara crate non-core belum memiliki dukungan penuh atau bahkan belum tersedia. Misalnya, banyak signer tidak didukung dalam crate program Token Pinocchio. Token2022 juga belum didukung, meskipun sedang dalam pengembangan.

Salah satu kelemahan yang lebih signifikan dari penggunaan Pinocchio adalah semua SDK yang dikembangkan untuk program Solana lain menggunakan crate solana-program. Artinya, setiap SDK memerlukan kepemilikan atas AccountInfo atau data yang diteruskan. Hal ini membuat interoperabilitas dengan program yang dikembangkan menggunakan Pinocchio menjadi sangat sulit. 

Saat mengintegrasikan program pihak ketiga, sangat umum untuk harus menulis logika CPI kustom bagi setiap instruction. Masalah ini mungkin pada akhirnya dapat diatasi dengan generator kode seperti Codama, tetapi saat ini belum sepenuhnya terwujud.

Penting untuk diperhatikan bahwa Pinocchio masih aktif dikembangkan dan belum diaudit. Komunitas masih berupaya menambahkan sysvar lainnya ke SDK serta meningkatkan dukungan untuk program SPL penting seperti Token dan Token2022.

Cara Berkontribusi pada Pinocchio

Ada banyak kontribusi sederhana yang dapat diberikan kepada Pinocchio.

Terdapat issue terbuka dan pull request yang sudah ada dan membutuhkan dukungan tambahan. Bergabunglah dalam percakapan atau cukup buka pull request untuk ditinjau oleh maintainer!

Kesimpulan

Pinocchio adalah library dengan performa yang jauh lebih tinggi untuk menulis program Solana dibandingkan solusi sebelumnya. Dengan memberi developer fleksibilitas lebih besar atas entrypoint program dan menggunakan zero-copy untuk mengakses input program, Pinocchio dapat membantu developer mengurangi penggunaan CU. Namun, Pinocchio masih merupakan library baru dan fiturnya belum lengkap. Saat artikel ini ditulis, library tersebut belum diaudit. Karena itu, gunakan dengan hati-hati.

Saat menilai apakah akan menggunakan Pinocchio, penting untuk mempertimbangkan komprominya dibandingkan library dan framework lain.

Framework opinionated seperti Anchor akan mempercepat pengembangan program dan lebih mudah dipelihara sehingga menjadi pilihan tepat ketika kecepatan masuk pasar merupakan hal penting.

Setelah produk Anda stabil dan menerima transaksi dalam volume besar, pengoptimalan program Solana menggunakan library seperti Pinocchio mungkin lebih sesuai.

Sumber Daya Tambahan

Untuk informasi lebih lanjut, tonton presentasi Febo di Solana Accelerate 2025 dan jelajahi sumber daya edukasi berikut:

Berlangganan Helius

Ikuti perkembangan terbaru dalam pengembangan Solana dan dapatkan pembaruan saat kami memublikasikan postingan

Gambar diperbesar