Comptes
Comme nous l'avons vu dans la section précédente, la validation des comptes avec Pinocchio diffère de celle avec Anchor car nous ne pouvons pas utiliser les types de comptes qui effectuent automatiquement les vérifications du propriétaire, de la signature et du discriminateur.
En Rust natif, nous devons effectuer ces validations manuellement. Bien que cela nécessite une plus grande attention aux détails, son implémentation est simple :
// SignerAccount type
if !account.is_signer() {
return Err(PinocchioError::NotSigner.into());
}
Ou pour une vérification du propriétaire :
// SystemAccount type
if !account.is_owned_by(&pinocchio_system::ID) {
return Err(PinocchioError::InvalidOwner.into());
}
En intégrant toutes les validations dans l'implémentation TryFrom
que nous avons abordée précédemment, nous pouvons facilement identifier les vérifications manquantes et nous assurer que nous écrivons un code sécurisé.
Cependant, l'écriture de ces vérifications pour chaque instruction peut devenir répétitive. Pour remédier à cela, nous créons un fichier helper.rs
qui définit des types similaires à ceux d'Anchor afin de faciliter ces validations.
Interfaces Communes et Traits
Pour notre fichier helper.rs
, nous exploitons deux concepts fondamentaux de Rust : Interfaces Communes et Traits.
Nous avons préféré cette approche à une solution basée sur des macros (comme celle d'Anchor) pour plusieurs raisons essentielles :
- Les Traits et les Interfaces fournissent un code clair et explicite que les lecteurs peuvent suivre sans avoir à "développer" mentalement les macros
- Le compilateur peut vérifier les implémentations des traits offrant ainsi une meilleure détection des erreurs, une meilleure inférence de types, une meilleure saisie automatique et de meilleurs outils de refactorisation
- Les traits permettent des implémentations génériques qui peuvent être réutilisées sans duplication de code, tandis que les macros procédurales dupliquent le code pour chaque utilisation
- Ces traits peuvent être regroupés dans une crate réutilisable, tandis que les APIs générées par des macros sont généralement limitées à la crate dans laquelle elles sont définies
Maintenant que vous comprenez notre choix de conception, explorons la syntaxe et les fonctionnalités de ces concepts.
Que sont les Traits et les Interfaces Communes ?
Si vous êtes familier avec d'autres langages de programmation, vous trouverez peut-être que les traits sont similaires aux "interfaces". Ils définissent des contrats qui spécifient les méthodes qu'un type doit implémenter.
Dans Rust, un trait agit comme un modèle qui déclare "tout type implémentant ceci doit fournir ces fonctions précises".
Voici un exemple simple :
// 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(())
}
}
L'avantage ici est que désormais tout type de compte qui implémente AccountCheck
peut être utilisé de la même manière. Nous pouvons appeler .check()
sur n'importe lequel d'entre eux, et chaque type gère la logique de validation qui lui convient.
C'est ce que nous entendons par "interface commune" : différents types partageant les mêmes signatures de méthode.
Voyons maintenant comment appliquer cela à nos contrôles de sécurité des comptes :
Signataire et Compte Système
Comme nous l'avons vu dans les exemples précédents, les vérifications SystemAccount
et SignerAccount
sont simples et ne nécessitent aucune validation supplémentaire. Nous allons donc ajouter ce qui suit à notre fichier helper.rs
:
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(())
}
}
Ici, nous vérifions simplement si le compte est un signataire ou s'il appartient au Programme Système. Remarquez que les deux structures fournissent la même méthode de vérification ce qui nous donne l'interface commune dont nous avons parlé.
Comptes de Mint et Comptes de Jetons
Les choses deviennent maintenant plus intéressantes. Nous commençons par le trait habituel AccountCheck
mais nous ajoutons également d'autres traits spécifiques afin de fournir des fonctions d'aide supplémentaires qui ressemblent aux macros Anchor telles que init
et 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(())
}
}
Pour les fonctionnalités init
et init_if_needed
, nous créons un autre trait appelé MintInit
que nous utilisons spécifiquement à cet effet en raison des champs uniques requis. Nous utilisons ensuite les CPIs CreateAccount
et InitializeMint2
pour initialiser le compte de 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),
}
}
}
Nous faisons ensuite exactement la même chose pour le 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
Vous avez peut-être remarqué que pour le Programme Legacy SPL-Token, nous n'avons effectué qu'une vérification de la longueur du Mint
et du TokenAccount
. Cette approche fonctionne car lorsque vous n'avez que deux types de comptes avec des tailles fixes, vous pouvez les distinguer simplement en vous basant sur leur longueur.
Pour Token2022, cette approche simple ne fonctionne pas. En effet, la taille du Mint
peut augmenter et potentiellement dépasser la taille du TokenAccount
lorsque des extensions de jetons sont ajoutées directement aux données du Mint
. Cela signifie que nous ne pouvons pas nous baser uniquement sur la taille pour différencier les types de comptes.
Pour Token2022, nous pouvons distinguer un Mint
d'un TokenAccount
de deux manières :
- Par la taille: Similaire au Programme Legacy Token (lorsque les comptes ont des tailles standard)
- Par le discriminateur: Un octet spécial situé à la position 165 (un octet de plus que l'ancien
TokenAccount
afin d'éviter les conflits)
Cela conduit à des contrôles de validation modifiés :
// 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),
}
}
}
Interface de Jeton
Comme nous voulons faciliter l'utilisation des Programmes Token2022 et Legacy Token sans avoir à faire de distinction entre eux, nous avons créé une fonction d'aide qui suit le même principe de base :
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(())
}
}
Compte de Jetons Associé
Nous pouvons créer des contrôles pour le Programme de Jetons Associés. Ces vérifications sont très similaires aux vérifications du Programme de Jetons mais elles incluent une vérification de dérivation supplémentaire afin de s'assurer que le compte est correctement dérivé.
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),
}
}
}
Comptes de Programme
Enfin, nous implémentons des vérifications et des fonctions d'aide pour les comptes de programme, y compris les fonctionnalités init
et close
.
Vous remarquerez peut-être quelque chose d'intéressant dans notre implémentation de close
: nous redimensionnons le compte pour qu'il ne contienne presque plus rien en ne laissant que le premier octet et en le définissant sur 255. Il s'agit d'une mesure de sécurité visant à prévenir les attaques par réinitialisation.
Une attaque par réinitialisation se produit lorsqu'un attaquant tente de réutiliser un compte fermé en le réinitialisant avec des données malveillantes. En définissant le premier octet sur 255 et en réduisant le compte à une taille proche de zéro, nous rendons impossible toute confusion future entre ce compte et un type de compte valide. Il s'agit d'un pattern de sécurité courant dans les programmes 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()
}
}
Optimisation de l'Accès aux Données du Compte
Bien que nous puissions implémenter un Trait
généralisé pour lire à partir du ProgramAccount
, il est plus efficace de créer des readers
("lecteurs") et des setters
("définisseurs") spécifiques qui accèdent uniquement aux champs nécessaires plutôt que de désérialiser l'intégralité du compte. Cette approche réduit la charge de calcul et les coûts liés au gaz.
Voici un exemple illustrant comment mettre en œuvre cette optimisation :
#[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)
}
}
Cette implémentation fournit trois méthodes pour accéder aux données du compte :
from_account_info
: Une méthode sûre qui effectue une validation complète et une vérification des empruntsfrom_account_info_unchecked
: Une méthode non sûre (unsafe) qui ignore la vérification des emprunts mais valide tout de même les propriétés du comptefrom_bytes
: Une méthode non sûre pour un accès direct aux octets, utilisée en interne par les autres méthodes
Nous pouvons également implémenter une fonction d'aide set_inner
pour mettre à jour les données du compte :
#[inline(always)]
pub fn set_inner(&mut self, seed: u64, bump: [u8;1]) {
self.seed = seed;
self.bump = bump;
}
Pour un contrôle plus précis et une plus grande efficacité, nous pouvons implémenter des getters
et des setters
spécifiques à l'aide de décalages (offsets) fixes :
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();
})
}
Cette implémentation fournit :
- Une constante
SEED_OFFSET
pour suivre la position des données de seed - Une fonction de validation
check_program_id_and_discriminator
- Versions sûres et non sûres des
getters
etsetters
- Optimisations inline pour de meilleures performances
Les versions non sûres ignorent les contrôles de validation pour améliorer les performances lorsque vous êtes certain que le compte est valide, tandis que les versions sûres garantissent une validation correcte avant d'accéder aux données.