Akun
Seperti yang kita lihat di bagian sebelumnya, validasi akun dengan Pinocchio berbeda dari Anchor karena kita tidak dapat menggunakan Tipe Akun yang secara otomatis melakukan pemeriksaan pemilik, penandatangan, dan diskriminator.
Dalam Native Rust, kita perlu melakukan validasi ini secara manual. Meskipun ini memerlukan perhatian lebih pada detail, implementasinya cukup mudah:
// SignerAccount type
if !account.is_signer() {
return Err(PinocchioError::NotSigner.into());
}Atau untuk pemeriksaan pemilik:
// SystemAccount type
if !account.is_owned_by(&pinocchio_system::ID) {
return Err(PinocchioError::InvalidOwner.into());
}Dengan membungkus semua validasi dalam implementasi TryFrom yang telah kita bahas sebelumnya, kita dapat dengan mudah mengidentifikasi pemeriksaan yang hilang dan memastikan kita menulis kode yang aman.
Namun, menulis pemeriksaan ini untuk setiap instruksi bisa menjadi berulang-ulang. Untuk mengatasi hal ini, kami membuat file helper.rs yang mendefinisikan tipe-tipe serupa dengan Anchor untuk menyederhanakan validasi ini.
Antarmuka Umum dan Trait
Untuk file helper.rs kami, kami memanfaatkan dua konsep dasar Rust: Antarmuka Umum dan Trait.
Kami memilih pendekatan ini daripada solusi berbasis makro (seperti Anchor) karena beberapa alasan utama:
Trait dan Antarmuka menyediakan kode yang jelas dan eksplisit yang dapat diikuti pembaca tanpa harus "mengembangkan" makro secara mental
Kompiler dapat memverifikasi implementasi trait, memungkinkan deteksi kesalahan yang lebih baik, inferensi tipe, pelengkapan otomatis, dan alat refaktorisasi
Trait memungkinkan implementasi generik yang dapat digunakan kembali tanpa duplikasi kode, sementara makro Prosedural menduplikasi kode untuk setiap penggunaan
Trait ini dapat dikemas menjadi crate yang dapat digunakan kembali, sedangkan API yang dihasilkan makro biasanya terbatas pada crate tempat mereka didefinisikan
Sekarang Anda memahami keputusan desain kami, mari kita jelajahi sintaks dan fungsionalitas dari konsep-konsep ini.
Apa itu Trait dan Antarmuka Umum?
Jika Anda familiar dengan bahasa pemrograman lain, Anda mungkin mengenali trait mirip dengan "interface"; mereka mendefinisikan kontrak yang menentukan metode apa yang harus diimplementasikan oleh suatu tipe.
Dalam Rust, sebuah trait bertindak sebagai cetak biru yang menyatakan "tipe apa pun yang mengimplementasikan ini harus menyediakan fungsi-fungsi spesifik ini."
Berikut contoh sederhana:
// Define a Trait
pub trait AccountCheck {
fn check(account: &AccountInfo) -> Result<(), ProgramError>;
}
// Define a Type
pub struct SignerAccount;
// Implement the trait for different Types
impl AccountCheck for SignerAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_signer() {
return Err(PinocchioError::NotSigner.into());
}
Ok(())
}
}
pub struct SystemAccount;
impl AccountCheck for SystemAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&pinocchio_system::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
Ok(())
}
}Keindahannya di sini adalah bahwa sekarang tipe akun apa pun yang mengimplementasikan AccountCheck dapat digunakan dengan cara yang sama; kita dapat memanggil .check() pada semuanya, dan setiap tipe menangani logika validasi yang masuk akal untuknya.
Inilah yang kita maksud dengan "antarmuka umum": tipe-tipe berbeda yang berbagi tanda tangan metode yang sama.
Sekarang mari kita lihat bagaimana kita menerapkan ini pada pemeriksaan keamanan akun kita:
Signer and System Account
Seperti yang kita lihat pada contoh sebelumnya, pemeriksaan SystemAccount dan SignerAccount cukup sederhana dan tidak memerlukan validasi tambahan, jadi kita akan menambahkan hal berikut ke helper.rs kita:
pub trait AccountCheck {
fn check(account: &AccountInfo) -> Result<(), ProgramError>;
}
pub struct SignerAccount;
impl AccountCheck for SignerAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_signer() {
return Err(PinocchioError::NotSigner.into());
}
Ok(())
}
}
pub struct SystemAccount;
impl AccountCheck for SystemAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&pinocchio_system::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
Ok(())
}
}Di sini kita hanya memeriksa apakah akun adalah penandatangan atau jika dimiliki oleh program sistem. Perhatikan bagaimana kedua struct menyediakan metode pemeriksaan yang sama, memberikan kita antarmuka umum yang kita bicarakan.
Mint and Token Accounts
Sekarang hal-hal menjadi lebih menarik. Kita mulai dengan trait AccountCheck seperti biasa, tetapi kita juga menambahkan trait spesifik lainnya untuk menyediakan helper tambahan yang menyerupai makro Anchor seperti init dan init_if_needed.
pub struct MintAccount;
impl AccountCheck for MintAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&pinocchio_token::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
if account.data_len() != pinocchio_token::state::Mint::LEN {
return Err(PinocchioError::InvalidAccountData.into());
}
Ok(())
}
}Untuk fungsionalitas init dan init_if_needed, kita membuat trait lain yang disebut MintInit yang kita gunakan khusus untuk tujuan ini karena bidang unik yang diperlukan. Kemudian kita menggunakan CPI CreateAccount dan InitializeMint2 untuk menginisialisasi akun Mint:
pub trait MintInit {
fn init(account: &AccountInfo, payer: &AccountInfo, decimals: u8, mint_authority: &[u8; 32], freeze_authority: Option<&[u8; 32]>) -> ProgramResult;
fn init_if_needed(account: &AccountInfo, payer: &AccountInfo, decimals: u8, mint_authority: &[u8; 32], freeze_authority: Option<&[u8; 32]>) -> ProgramResult;
}
impl MintInit for MintAccount {
fn init(account: &AccountInfo, payer: &AccountInfo, decimals: u8, mint_authority: &[u8; 32], freeze_authority: Option<&[u8; 32]>) -> ProgramResult {
// Get required lamports for rent
let lamports = Rent::get()?.minimum_balance(pinocchio_token::state::Mint::LEN);
// Fund the account with the required lamports
CreateAccount {
from: payer,
to: account,
lamports,
space: pinocchio_token::state::Mint::LEN as u64,
owner: &pinocchio_token::ID,
}.invoke()?;
InitializeMint2 {
mint: account,
decimals,
mint_authority,
freeze_authority,
}.invoke()
}
fn init_if_needed(account: &AccountInfo, payer: &AccountInfo, decimals: u8, mint_authority: &[u8; 32], freeze_authority: Option<&[u8; 32]>) -> ProgramResult {
match Self::check(account) {
Ok(_) => Ok(()),
Err(_) => Self::init(account, payer, decimals, mint_authority, freeze_authority),
}
}
}Kemudian kita melakukan hal yang sama persis untuk TokenAccount:
pub struct TokenAccount;
impl AccountCheck for TokenAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&pinocchio_token::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
if account.data_len().ne(&pinocchio_token::state::TokenAccount::LEN) {
return Err(PinocchioError::InvalidAccountData.into());
}
Ok(())
}
}
pub trait AccountInit {
fn init(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &[u8; 32]) -> ProgramResult;
fn init_if_needed(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &[u8; 32]) -> ProgramResult;
}
impl AccountInit for TokenAccount {
fn init(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &[u8; 32]) -> ProgramResult {
// Get required lamports for rent
let lamports = Rent::get()?.minimum_balance(pinocchio_token::state::TokenAccount::LEN);
// Fund the account with the required lamports
CreateAccount {
from: payer,
to: account,
lamports,
space: pinocchio_token::state::TokenAccount::LEN as u64,
owner: &pinocchio_token::ID,
}.invoke()?;
// Initialize the Token Account
InitializeAccount3 {
account,
mint,
owner,
}.invoke()
}
fn init_if_needed(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &[u8; 32]) -> ProgramResult {
match Self::check(account) {
Ok(_) => Ok(()),
Err(_) => Self::init(account, mint, payer, owner),
}
}
}Token2022
Anda mungkin telah memperhatikan bahwa untuk Program SPL Token Legacy, kita hanya melakukan pemeriksaan panjang pada Mint dan TokenAccount. Pendekatan ini berhasil karena ketika Anda hanya memiliki dua jenis akun dengan ukuran tetap, Anda dapat membedakannya hanya dengan menggunakan panjangnya.
Untuk Token2022, pendekatan sederhana ini tidak berfungsi. Ukuran Mint dapat bertambah dan berpotensi melebihi ukuran TokenAccount ketika ekstensi token ditambahkan langsung ke data Mint. Ini berarti kita tidak dapat hanya mengandalkan ukuran untuk membedakan antara jenis akun.
Untuk Token2022, kita dapat membedakan antara Mint dan TokenAccount dengan dua cara:
Berdasarkan ukuran: Mirip dengan Program Token Legacy (ketika akun memiliki ukuran standar)
Berdasarkan diskriminator: Byte khusus yang terletak pada posisi 165 (satu byte lebih besar dari TokenAccount legacy untuk menghindari konflik)
Ini mengarah pada modifikasi pemeriksaan validasi:
// TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
pub const TOKEN_2022_PROGRAM_ID: [u8; 32] = [
0x06, 0xdd, 0xf6, 0xe1, 0xee, 0x75, 0x8f, 0xde, 0x18, 0x42, 0x5d, 0xbc, 0xe4, 0x6c, 0xcd, 0xda,
0xb6, 0x1a, 0xfc, 0x4d, 0x83, 0xb9, 0x0d, 0x27, 0xfe, 0xbd, 0xf9, 0x28, 0xd8, 0xa1, 0x8b, 0xfc,
];
const TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET: usize = 165;
pub const TOKEN_2022_MINT_DISCRIMINATOR: u8 = 0x01;
pub const TOKEN_2022_TOKEN_ACCOUNT_DISCRIMINATOR: u8 = 0x02;
pub struct Mint2022Account;
impl AccountCheck for Mint2022Account {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&TOKEN_2022_PROGRAM_ID) {
return Err(PinocchioError::InvalidOwner.into());
}
let data = account.try_borrow_data()?;
if data.len().ne(&pinocchio_token::state::Mint::LEN) {
if data.len().le(&TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET) {
return Err(PinocchioError::InvalidAccountData.into());
}
if data[TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET].ne(&TOKEN_2022_MINT_DISCRIMINATOR) {
return Err(PinocchioError::InvalidAccountData.into());
}
}
Ok(())
}
}
impl MintInit for Mint2022Account {
fn init(account: &AccountInfo, payer: &AccountInfo, decimals: u8, mint_authority: &[u8; 32], freeze_authority: Option<&[u8; 32]>) -> ProgramResult {
// Get required lamports for rent
let lamports = Rent::get()?.minimum_balance(pinocchio_token::state::Mint::LEN);
// Fund the account with the required lamports
CreateAccount {
from: payer,
to: account,
lamports,
space: pinocchio_token::state::Mint::LEN as u64,
owner: &TOKEN_2022_PROGRAM_ID,
}.invoke()?;
InitializeMint2 {
mint: account,
decimals,
mint_authority,
freeze_authority,
}.invoke()
}
fn init_if_needed(account: &AccountInfo, payer: &AccountInfo, decimals: u8, mint_authority: &[u8; 32], freeze_authority: Option<&[u8; 32]>) -> ProgramResult {
match Self::check(account) {
Ok(_) => Ok(()),
Err(_) => Self::init(account, payer, decimals, mint_authority, freeze_authority),
}
}
}
pub struct TokenAccount2022Account;
impl AccountCheck for TokenAccount2022Account {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&TOKEN_2022_PROGRAM_ID) {
return Err(PinocchioError::InvalidOwner.into());
}
let data = account.try_borrow_data()?;
if data.len().ne(&pinocchio_token::state::TokenAccount::LEN) {
if data.len().le(&TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET) {
return Err(PinocchioError::InvalidAccountData.into());
}
if data[TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET].ne(&TOKEN_2022_TOKEN_ACCOUNT_DISCRIMINATOR) {
return Err(PinocchioError::InvalidAccountData.into());
}
}
Ok(())
}
}
impl AccountInit for TokenAccount2022Account {
fn init(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &[u8; 32]) -> ProgramResult {
// Get required lamports for rent
let lamports = Rent::get()?.minimum_balance(pinocchio_token::state::TokenAccount::LEN);
// Fund the account with the required lamports
CreateAccount {
from: payer,
to: account,
lamports,
space: pinocchio_token::state::TokenAccount::LEN as u64,
owner: &TOKEN_2022_PROGRAM_ID,
}.invoke()?;
InitializeAccount3 {
account,
mint,
owner,
}.invoke()
}
fn init_if_needed(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &[u8; 32]) -> ProgramResult {
match Self::check(account) {
Ok(_) => Ok(()),
Err(_) => Self::init(account, mint, payer, owner),
}
}
}Antarmuka Token
Karena kita ingin memudahkan penggunaan Program Token2022 dan Token Legacy tanpa harus membedakan di antara keduanya, kita membuat helper yang mengikuti prinsip dasar yang sama:
pub struct MintInterface;
impl AccountCheck for MintInterface {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&TOKEN_2022_PROGRAM_ID) {
if !account.is_owned_by(&pinocchio_token::ID) {
return Err(PinocchioError::InvalidOwner.into());
} else {
if account.data_len().ne(&pinocchio_token::state::Mint::LEN) {
return Err(PinocchioError::InvalidAccountData.into());
}
}
} else {
let data = account.try_borrow_data()?;
if data.len().ne(&pinocchio_token::state::Mint::LEN) {
if data.len().le(&TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET) {
return Err(PinocchioError::InvalidAccountData.into());
}
if data[TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET].ne(&TOKEN_2022_MINT_DISCRIMINATOR) {
return Err(PinocchioError::InvalidAccountData.into());
}
}
}
Ok(())
}
}
pub struct TokenAccountInterface;
impl AccountCheck for TokenAccountInterface {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&TOKEN_2022_PROGRAM_ID) {
if !account.is_owned_by(&pinocchio_token::ID) {
return Err(PinocchioError::InvalidOwner.into());
} else {
if account.data_len().ne(&pinocchio_token::state::TokenAccount::LEN) {
return Err(PinocchioError::InvalidAccountData.into());
}
}
} else {
let data = account.try_borrow_data()?;
if data.len().ne(&pinocchio_token::state::TokenAccount::LEN) {
if data.len().le(&TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET) {
return Err(PinocchioError::InvalidAccountData.into());
}
if data[TOKEN_2022_ACCOUNT_DISCRIMINATOR_OFFSET]
.ne(&TOKEN_2022_TOKEN_ACCOUNT_DISCRIMINATOR)
{
return Err(PinocchioError::InvalidAccountData.into());
}
}
}
Ok(())
}
}Associated Token Account
Kita dapat membuat beberapa pemeriksaan untuk Program Associated Token. Pemeriksaan ini sangat mirip dengan pemeriksaan Program Token normal, tetapi mencakup pemeriksaan derivasi tambahan untuk memastikan akun diturunkan dengan benar.
pub struct AssociatedTokenAccount;
impl AssociatedTokenAccountCheck for AssociatedTokenAccount {
fn check(
account: &AccountInfo,
authority: &AccountInfo,
mint: &AccountInfo,
token_program: &AccountInfo,
) -> Result<(), ProgramError> {
TokenAccount::check(account)?;
if find_program_address(
&[authority.key(), token_program.key(), mint.key()],
&pinocchio_associated_token_account::ID,
)
.0
.ne(account.key())
{
return Err(PinocchioError::InvalidAddress.into());
}
Ok(())
}
}
impl AssociatedTokenAccountInit for AssociatedTokenAccount {
fn init(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &AccountInfo, system_program: &AccountInfo, token_program: &AccountInfo) -> ProgramResult {
Create {
funding_account: payer,
account,
wallet: owner,
mint,
system_program,
token_program,
}.invoke()
}
fn init_if_needed(account: &AccountInfo, mint: &AccountInfo, payer: &AccountInfo, owner: &AccountInfo, system_program: &AccountInfo, token_program: &AccountInfo) -> ProgramResult {
match Self::check(account, payer, mint) {
Ok(_) => Ok(()),
Err(_) => Self::init(account, mint, payer, owner, system_program, token_program),
}
}
}Program Accounts
Akhirnya, kita mengimplementasikan pemeriksaan dan helper untuk akun program, termasuk fungsionalitas init dan close.
Anda mungkin memperhatikan sesuatu yang menarik dalam implementasi close kita: kita mengubah ukuran akun menjadi hampir tidak ada, hanya menyisakan byte pertama dan menetapkannya ke 255. Ini adalah langkah keamanan untuk mencegah serangan reinisialisasi.
Serangan reinisialisasi terjadi ketika penyerang mencoba menggunakan kembali akun yang sudah ditutup dengan menginisialisasi ulang menggunakan data berbahaya. Dengan menetapkan byte pertama ke 255 dan mengecilkan ukuran akun hingga mendekati nol, kita membuat akun tersebut tidak mungkin dianggap sebagai tipe akun yang valid di masa depan. Ini adalah pola keamanan umum dalam program Solana.
pub struct ProgramAccount;
impl AccountCheck for ProgramAccount {
fn check(account: &AccountInfo) -> Result<(), ProgramError> {
if !account.is_owned_by(&crate::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
if account.data_len().ne(&crate::state::ProgramAccount::LEN) {
return Err(PinocchioError::InvalidAccountData.into());
}
Ok(())
}
}
pub trait ProgramAccountInit {
fn init<'a, T: Sized>(
payer: &AccountInfo,
account: &AccountInfo,
seeds: &[Seed<'a>],
space: usize,
) -> ProgramResult;
}
impl ProgramAccountInit for ProgramAccount {
fn init<'a, T: Sized>(
payer: &AccountInfo,
account: &AccountInfo,
seeds: &[Seed<'a>],
space: usize,
) -> ProgramResult {
// Get required lamports for rent
let lamports = Rent::get()?.minimum_balance(space);
// Create signer with seeds slice
let signer = [Signer::from(seeds)];
// Create the account
CreateAccount {
from: payer,
to: account,
lamports,
space: space as u64,
owner: &crate::ID,
}
.invoke_signed(&signer)?;
Ok(())
}
}
pub trait AccountClose {
fn close(account: &AccountInfo, destination: &AccountInfo) -> ProgramResult;
}
impl AccountClose for ProgramAccount {
fn close(account: &AccountInfo, destination: &AccountInfo) -> ProgramResult {
{
let mut data = account.try_borrow_mut_data()?;
data[0] = 0xff;
}
*destination.try_borrow_mut_lamports()? += *account.try_borrow_lamports()?;
account.realloc(1, true)?;
account.close()
}
}Mengoptimalkan Akses Data Akun
Meskipun kita dapat mengimplementasikan Trait yang umum untuk membaca dari ProgramAccount, lebih efisien untuk membuat readers dan setters spesifik yang hanya mengakses bidang yang diperlukan tanpa mendeserialkan seluruh akun. Pendekatan ini mengurangi beban komputasi dan biaya gas.
Berikut contoh cara mengimplementasikan optimasi ini:
#[repr(C)]
pub struct AccountExample {
pub seed: u64,
pub bump: [u8; 1]
}
impl AccountExample {
/// The length of the `AccountExample` account data.
pub const LEN: usize = size_of::<u64>() + size_of::<[u8; 1]>();
/// Return an `AccountExample` from the given account info.
///
/// This method performs owner and length validation on `AccountInfo`, safe borrowing
/// the account data.
#[inline]
pub fn from_account_info(account_info: &AccountInfo) -> Result<Ref<AccountExample>, ProgramError> {
if account_info.data_len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
if account_info.owner() != &crate::ID {
return Err(ProgramError::InvalidAccountOwner);
}
Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
Self::from_bytes(data)
}))
}
/// Return a `AccountExample` from the given account info.
///
/// This method performs owner and length validation on `AccountInfo`, but does not
/// perform the borrow check.
///
/// # Safety
///
/// The caller must ensure that it is safe to borrow the account data – e.g., there are
/// no mutable borrows of the account data.
#[inline]
pub unsafe fn from_account_info_unchecked(
account_info: &AccountInfo,
) -> Result<&Self, ProgramError> {
if account_info.data_len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
if account_info.owner() != &crate::ID {
return Err(ProgramError::InvalidAccountOwner);
}
Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
}
/// Return a `AccountExample` from the given bytes.
///
/// # Safety
///
/// The caller must ensure that `bytes` contains a valid representation of `AccountExample`.
#[inline(always)]
pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
&*(bytes.as_ptr() as *const AccountExample)
}
}Implementasi ini menyediakan tiga metode untuk mengakses data akun:
from_account_info: Metode aman yang melakukan validasi penuh dan pemeriksaan peminjamanfrom_account_info_unchecked: Metode tidak aman yang melewati pemeriksaan peminjaman tetapi masih memvalidasi properti akunfrom_bytes: Metode tidak aman untuk akses byte langsung, digunakan secara internal oleh metode lain
Kita juga dapat mengimplementasikan pembantu set_inner untuk memperbarui data akun:
#[inline(always)]
pub fn set_inner(&mut self, seed: u64, bump: [u8;1]) {
self.seed = seed;
self.bump = bump;
}Untuk kontrol yang lebih terperinci dan efisiensi, kita dapat mengimplementasikan getter dan setter spesifik menggunakan offset tetap:
const SEED_OFFSET: usize = 0;
#[inline(always)]
pub fn check_program_id_and_discriminator(
account_info: &AccountInfo,
) -> Result<(), ProgramError> {
// Check Program ID
if unsafe { account_info.owner().ne(&crate::ID) } {
return Err(ProgramError::IncorrectProgramId);
}
// Check length
if account_info.data_len().ne(Self::LEN) {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn get_seeds(account_info: &AccountInfo) -> Result<u64, ProgramError> {
Self::check_program_id_and_discriminator(account_info);
let data = account_info.try_borrow_data()?;
Ok(u64::from_le_bytes(data[SEED_OFFSET..SEED_OFFSET + size_of::<u64>()].try_into().unwrap()))
}
#[inline(always)]
pub unsafe fn get_seeds_unchecked(account_info: &AccountInfo) -> Result<u64, ProgramError> {
let data = account_info.try_borrow_data()?;
Ok(u64::from_le_bytes(data[SEED_OFFSET..SEED_OFFSET + size_of::<u64>()].try_into().unwrap()))
}
#[inline(always)]
pub fn set_seeds(account_info: &AccountInfo, seed: u64) -> Result<(), ProgramError> {
Self::check_program_id_and_discriminator(account_info);
let data = account_info.try_borrow_mut_data()?;
Ok(unsafe {
*(data.as_mut_ptr() as *mut [u8; 8]) = seed.to_le_bytes();
})
}
#[inline(always)]
pub fn set_seeds_unchecked(account_info: &AccountInfo, seed: u64) -> Result<(), ProgramError> {
let data = account_info.try_borrow_mut_data()?;
Ok(unsafe {
*(data.as_mut_ptr() as *mut [u8; 8]) = seed.to_le_bytes();
})
}Implementasi ini menyediakan:
Konstanta
SEED_OFFSETuntuk melacak posisi data seedFungsi validasi
check_program_id_and_discriminatorVersi aman dan tidak aman dari getter dan setter
Optimasi inline untuk performa yang lebih baik
Versi tidak aman melewati pemeriksaan validasi untuk performa yang lebih baik ketika Anda yakin akun tersebut valid, sementara versi aman memastikan validasi yang tepat sebelum mengakses data.