Các Account
Như chúng ta đã thấy trong phần trước, xác thực account với Pinocchio khác với Anchor vì chúng ta không thể sử dụng Account Type tự động thực hiện kiểm tra owner, signature và discriminator.
Trong Native Rust, chúng ta cần thực hiện các xác thực này thủ công. Mặc dù điều này làm nhiều chi tiết hơn, nhưng nó khá đơn giản để triển khai:
// SignerAccount type
if !account.is_signer() {
return Err(PinocchioError::NotSigner.into());
}
Hoặc đối với kiểm tra owner:
// SystemAccount type
if !account.is_owned_by(&pinocchio_system::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
Bằng cách bao bọc tất cả các xác thực trong triển khai TryFrom
mà chúng ta đã đề cập trước đó, chúng ta có thể dễ dàng xác định các kiểm tra bị thiếu và đảm bảo rằng chúng ta đang viết code an toàn.
Tuy nhiên, việc viết những kiểm tra này cho mỗi instruction có thể trở nên lặp đi lặp lại. Để giải quyết vấn đề này, chúng tôi đã tạo một file helper.rs
định nghĩa các kiểu tương tự như của Anchor để hợp lý hóa các xác thực này.
Common Interface và Trait
Đối với file helper.rs
của chúng tôi, chúng tôi đã tận dụng hai khái niệm cơ bản của Rust: Common Interface và Trait.
Chúng tôi chọn cách tiếp cận này thay vì giải pháp dựa trên macro (như của Anchor) vì một số lý do chính:
- Trait và Interface cung cấp code rõ ràng, tường minh mà người đọc có thể theo dõi mà không cần phải "mở rộng" macro trong đầu
- Trình biên dịch có thể xác minh các triển khai trait, cho phép phát hiện lỗi tốt hơn, suy luận kiểu, auto-completion và công cụ refactoring
- Trait cho phép các triển khai generic có thể được tái sử dụng mà không cần nhân bản code, trong khi Procedural macro nhân bản code cho mỗi lần sử dụng
- Những trait này có thể được đóng gói thành một crate có thể tái sử dụng, trong khi các API được tạo bởi macro thường bị giới hạn trong crate mà chúng được định nghĩa
Bây giờ bạn đã hiểu quyết định thiết kế của chúng tôi, hãy khám phá cú pháp và chức năng của những khái niệm này.
Trait và Common Interface là gì?
Nếu bạn quen thuộc với các ngôn ngữ lập trình khác, bạn có thể nhận ra trait tương tự như "interface"; chúng định nghĩa các hợp đồng chỉ định những phương thức mà một kiểu phải triển khai.
Trong Rust, một trait hoạt động như một bản thiết kế tuyên bố "bất kỳ kiểu nào triển khai điều này phải cung cấp những hàm cụ thể này."
Đây là một ví dụ đơn giản:
// 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(())
}
}
Điều tuyệt vời ở đây là bây giờ bất kỳ kiểu account nào triển khai AccountCheck
đều có thể được sử dụng theo cùng một cách; chúng ta có thể gọi .check()
trên bất kỳ cái nào trong số chúng, và mỗi kiểu xử lý logic xác thực có ý nghĩa đối với nó.
Đây là những gì chúng ta có ý là "common interface": các kiểu khác nhau chia sẻ cùng các method signature.
Bây giờ hãy xem cách chúng ta áp dụng điều này vào các kiểm tra bảo mật account của chúng ta:
Signer và System Account
Như chúng ta đã thấy trong các ví dụ trước, kiểm tra SystemAccount
và SignerAccount
rất đơn giản và không yêu cầu bất kỳ xác thực bổ sung nào, vì vậy chúng ta sẽ thêm những điều sau vào helper.rs
của chúng ta:
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(())
}
}
Ở đây chúng ta chỉ đơn giản kiểm tra xem account có phải là signer hay nó có được sở hữu bởi system program không. Chú ý cách cả hai struct đều cung cấp cùng một phương thức check, mang lại cho chúng ta common interface mà chúng ta đã nói đến.
Mint và Token Account
Bây giờ mọi thứ trở nên thú vị hơn. Chúng ta bắt đầu với trait AccountCheck
thông thường, nhưng chúng ta cũng thêm các trait cụ thể khác để cung cấp các helper bổ sung giống với các macro Anchor như init
và 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(())
}
}
Đối với chức năng init
và init_if_needed
, chúng ta tạo một trait khác gọi là MintInit
mà chúng ta sử dụng cụ thể cho mục đích này vì các trường duy nhất được yêu cầu. Sau đó chúng ta sử dụng CPI CreateAccount
và InitializeMint2
để khởi tạo account 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),
}
}
}
Sau đó chúng ta làm chính xác như vậy cho 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
Bạn có thể đã nhận thấy rằng đối với Legacy SPL Token Program, chúng ta chỉ thực hiện kiểm tra độ dài trên Mint
và TokenAccount
. Cách tiếp cận này hoạt động vì khi bạn chỉ có hai kiểu account với kích thước cố định, bạn có thể phân biệt giữa chúng chỉ bằng độ dài của chúng.
Đối với Token2022, cách tiếp cận đơn giản này không hoạt động. Kích thước Mint
có thể tăng lên và có thể vượt quá kích thước TokenAccount
khi các token extension được thêm trực tiếp vào dữ liệu Mint
. Điều này có nghĩa là chúng ta không thể chỉ dựa vào kích thước để phân biệt giữa các kiểu account.
Đối với Token2022, chúng ta có thể phân biệt giữa Mint
và TokenAccount
theo hai cách:
- Theo kích thước: Tương tự như Legacy Token Program (khi các account có kích thước tiêu chuẩn)
- Theo discriminator: Một byte đặc biệt nằm ở vị trí 165 (lớn hơn một byte so với legacy TokenAccount để tránh xung đột)
Điều này dẫn đến các kiểm tra xác thực được sửa đổi:
// 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),
}
}
}
Token Interface
Vì chúng ta muốn khiến cho việc làm việc với cả Token2022 và Legacy Token Program trở nên dễ dàng mà không cần phân biệt giữa chúng, chúng ta đã tạo một helper tuân theo cùng nguyên tắc cơ bản:
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
Chúng ta có thể tạo một số kiểm tra cho Associated Token Program. Chúng rất giống với các kiểm tra Token Program thông thường, nhưng chúng bao gồm một kiểm tra derivation bổ sung để đảm bảo account được derive một cách chính xác.
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),
}
}
}
Các tài khoản của chương trình
Cuối cùng, chúng ta triển khai các kiểm tra và helper cho các tài khoản của chương trình, bao gồm chức năng init
và close
.
Bạn có thể nhận thấy điều gì đó thú vị trong triển khai close
của chúng ta: chúng ta thay đổi kích thước account thành gần như không có gì, chỉ để lại byte đầu tiên và đặt nó thành 255. Đây là một biện pháp bảo mật để ngăn chặn các cuộc tấn công tái khởi tạo.
Một cuộc tấn công tái khởi tạo xảy ra khi kẻ tấn công cố gắng tái sử dụng một account đã đóng bằng cách khởi tạo lại nó với dữ liệu độc hại. Bằng cách đặt byte đầu tiên thành 255 và thu nhỏ account xuống gần như kích thước bằng không, chúng ta làm cho account không thể bị nhầm lẫn với bất kỳ kiểu account hợp lệ nào trong tương lai. Đây là một pattern bảo mật phổ biến trong các chương trình 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()
}
}
Tối ưu hóa việc truy cập dữ liệu của Account
Mặc dù chúng ta có thể triển khai một Trait
tổng quát để đọc từ ProgramAccount
, nhưng hiệu quả hơn là tạo các reader
và setter
cụ thể chỉ truy cập các trường cần thiết thay vì deserialize toàn bộ account. Cách tiếp cận này giảm overhead tính toán và chi phí gas.
Đây là một ví dụ về cách triển khai tối ưu hóa này:
#[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)
}
}
Triển khai này cung cấp ba phương thức để truy cập dữ liệu account:
from_account_info
: Một phương thức an toàn thực hiện xác thực đầy đủ và kiểm tra borrowfrom_account_info_unchecked
: Một phương thức unsafe bỏ qua kiểm tra borrow nhưng vẫn xác thực các thuộc tính accountfrom_bytes
: Một phương thức unsafe để truy cập byte trực tiếp, được sử dụng nội bộ bởi các phương thức khác
Chúng ta cũng có thể triển khai một helper set_inner
để cập nhật dữ liệu account:
#[inline(always)]
pub fn set_inner(&mut self, seed: u64, bump: [u8;1]) {
self.seed = seed;
self.bump = bump;
}
Để kiểm soát chi tiết hơn và hiệu quả hơn, chúng ta có thể triển khai các getter và setter cụ thể sử dụng offset cố định:
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();
})
}
Triển khai này cung cấp:
- Một hằng số
SEED_OFFSET
để theo dõi vị trí của dữ liệu seed - Một hàm xác thực
check_program_id_and_discriminator
- Các phiên bản an toàn và không an toàn của getter và setter
- Sử dụng inline để có hiệu suất tốt hơn
Các phiên bản không an toàn (unsafe) bỏ qua kiểm tra xác thực để có hiệu suất tốt hơn khi bạn chắc chắn account hợp lệ, trong khi các phiên bản an toàn đảm bảo xác thực thích hợp trước khi truy cập dữ liệu.