Introduction à Solana
Avant de développer sur Solana, vous devez comprendre plusieurs concepts fondamentaux qui rendent Solana unique. Ce guide couvre les comptes, les transactions, les programmes et leurs interactions.
Comptes sur Solana
L'architecture de Solana est centrée sur les comptes : des conteneurs de données qui stockent des informations sur la blockchain. Considérez les comptes comme des fichiers individuels dans un système de fichiers, où chaque fichier possède des propriétés spécifiques et un propriétaire qui le contrôle.
Chaque compte Solana a la même structure de base :
pub struct Account {
/// lamports in the account
pub lamports: u64,
/// data held in this account
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
pub data: Vec<u8>,
/// the program that owns this account. If executable, the program that loads this account.
pub owner: Pubkey,
/// this account's data contains a loaded program (and is now read-only)
pub executable: bool,
/// the epoch at which this account will next owe rent
pub rent_epoch: Epoch,
}Chaque compte possède une adresse unique de 32 octets, affichée sous forme de chaîne encodée en base58 (par exemple, 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5). Cette adresse sert d'identifiant du compte sur la blockchain et permet de localiser des données spécifiques.
Les comptes peuvent stocker jusqu'à 10 MiB de données, qui peuvent contenir soit du code de programme exécutable, soit des données spécifiques au programme.
Tous les comptes nécessitent un dépôt de lamports proportionnel à leur taille de données pour devenir "exempts de loyer". Le terme "loyer" est historique, car les lamports étaient à l'origine déduits des comptes à chaque époque, mais cette fonctionnalité est maintenant désactivée. Aujourd'hui, le dépôt fonctionne davantage comme une caution remboursable. Tant que votre compte maintient le solde minimum pour sa taille de données, il reste exempt de loyer et persiste indéfiniment. Lorsque vous n'avez plus besoin d'un compte, vous pouvez le fermer et récupérer entièrement ce dépôt.
Chaque compte appartient à un programme, et seul ce programme propriétaire peut modifier les données du compte ou retirer ses lamports. Cependant, n'importe qui peut augmenter le solde de lamports d'un compte, ce qui est utile pour financer des opérations ou payer le loyer sans avoir à appeler le programme lui-même.
L'autorité de signature fonctionne différemment selon la propriété. Les comptes détenus par le Programme Système peuvent signer des transactions pour modifier leurs propres données, transférer la propriété ou récupérer les lamports stockés. Une fois que la propriété est transférée à un autre programme, ce programme obtient un contrôle complet sur le compte, indépendamment du fait que vous possédiez toujours la clé privée. Ce transfert de contrôle est permanent et irréversible.
Types de comptes
Le type de compte le plus courant est le Compte Système, qui stocke les lamports (la plus petite unité de SOL) et appartient au Programme Système. Ces comptes fonctionnent comme des portefeuilles de base avec lesquels les utilisateurs interagissent directement pour envoyer et recevoir du SOL.
Les Comptes de Jetons (Token Accounts) ont un objectif spécialisé, stockant les informations des jetons SPL, y compris la propriété et les métadonnées des jetons. Le Programme de Jetons possède ces comptes et gère toutes les opérations liées aux jetons dans l'écosystème Solana. Les Comptes de Jetons sont des Comptes de Données.
Les Comptes de Données stockent des informations spécifiques à l'application et appartiennent à des programmes personnalisés. Ces comptes contiennent l'état de votre application et peuvent être structurés selon les besoins de votre programme, des simples profils utilisateurs aux données financières complexes.
Enfin, les Comptes de Programme contiennent le code exécutable qui s'exécute sur Solana, où réside le contrat intelligent. Ces comptes sont marqués comme executable: true et stockent la logique du programme qui traite les instructions et gère l'état.
Travailler avec les données de compte
Voici comment les programmes interagissent avec les données de compte :
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UserAccount {
pub name: String,
pub balance: u64,
pub posts: Vec<u32>,
}
pub fn update_user_data(accounts: &[AccountInfo], new_name: String) -> ProgramResult {
let user_account = &accounts[0];
// Deserialize existing data
let mut user_data = UserAccount::try_from_slice(&user_account.data.borrow())?;
// Modify the data
user_data.name = new_name;
// Serialize back to account
user_data.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
Ok(())
}Contrairement aux bases de données où vous insérez simplement des enregistrements, les comptes Solana doivent être explicitement créés et financés avant utilisation.
Transactions sur Solana
Les transactions Solana sont des opérations atomiques qui peuvent contenir plusieurs instructions. Toutes les instructions au sein d'une transaction réussissent ensemble ou échouent ensemble : il n'y a pas d'exécution partielle.
Une transaction se compose de :
Instructions : opérations individuelles à effectuer
Comptes : comptes spécifiques que chaque instruction lira ou modifiera
Signataires : comptes qui autorisent la transaction
Transaction {
instructions: [
// Instruction 1: Transfer SOL
system_program::transfer(from_wallet, to_wallet, amount),
// Instruction 2: Update user profile
my_program::update_profile(user_account, new_name),
// Instruction 3: Log activity
my_program::log_activity(activity_account, "transfer", amount),
],
accounts: [from_wallet, to_wallet, user_account, activity_account]
signers: [user_keypair],
}Exigences et frais de transaction
Les transactions sont limitées à 1 232 octets au total, ce qui restreint le nombre d'instructions et de comptes que vous pouvez inclure.
Chaque instruction au sein d'une transaction nécessite trois composants essentiels : l'adresse du programme à invoquer, tous les comptes que l'instruction lira ou modifiera, et toutes données supplémentaires comme les arguments de fonction.
Les instructions s'exécutent séquentiellement dans l'ordre que vous spécifiez dans la transaction.
Chaque transaction entraîne des frais de base de 5 000 lamports par signature pour rémunérer les validateurs qui traitent votre transaction.
Vous pouvez également payer des frais de priorisation optionnels pour augmenter la probabilité que le leader actuel traite rapidement votre transaction. Ces frais de priorisation sont calculés comme votre limite d'unités de calcul multipliée par votre prix d'unité de calcul (mesuré en micro-lamports).
prioritization_fee = compute_unit_limit × compute_unit_priceProgrammes sur Solana
Les programmes sur Solana sont fondamentalement sans état, ce qui signifie qu'ils ne conservent aucun état interne entre les appels de fonction. Au lieu de cela, ils reçoivent des comptes en entrée, traitent les données contenues dans ces comptes et renvoient les résultats modifiés.
Cette conception sans état assure un comportement prévisible et permet de puissants modèles de composabilité.
Les programmes eux-mêmes sont stockés dans des comptes spéciaux marqués comme executable: true, contenant le code binaire compilé qui s'exécute lorsqu'il est invoqué.
Les utilisateurs interagissent avec ces programmes en envoyant des transactions qui contiennent des instructions spécifiques, chacune ciblant des fonctions particulières du programme avec les données de compte et les paramètres nécessaires.
use solana_program::prelude::*;
#[derive(BorshSerialize, BorshDeserialize)]
pub struct User {
pub name: String,
pub created_at: i64,
}
pub fn create_user(
accounts: &[AccountInfo],
name: String,
) -> ProgramResult {
let user_account = &accounts[0];
let user = User {
name,
created_at: Clock::get()?.unix_timestamp,
};
user.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
Ok(())
}Les programmes peuvent être mis à jour par leur autorité de mise à niveau désignée, permettant aux développeurs de corriger des bugs et d'ajouter des fonctionnalités après le déploiement. Cependant, la suppression de cette autorité de mise à niveau rend le programme définitivement immuable, garantissant aux utilisateurs que le code ne changera jamais.
Pour la transparence et la sécurité, les utilisateurs peuvent vérifier que les programmes sur la chaîne correspondent à leur code source public grâce à des builds vérifiables, garantissant que le bytecode déployé correspond exactement au code source publié.
Adresses dérivées de programme (PDAs)
Les PDAs sont des adresses générées de manière déterministe qui permettent des modèles de programmabilité puissants. Elles sont créées à l'aide de seeds et d'un ID de programme, produisant des adresses sans clés privées correspondantes.
Les PDAs utilisent le hachage SHA-256 avec des entrées spécifiques, notamment vos seeds personnalisées, une valeur de bump pour garantir que le résultat est hors-courbe, l'ID du programme qui possédera le PDA, et un marqueur constant.
Lorsque le hachage produit une adresse sur la courbe (ce qui se produit environ 50% du temps), le système itère du bump 255 vers 254, 253, et ainsi de suite jusqu'à trouver un résultat hors-courbe.
use solana_nostd_sha256::hashv;
const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
let pda = hashv(&[
seed_data.as_ref(), // Your custom seeds
&[bump], // Bump to ensure off-curve
program_id.as_ref(), // Program that owns this PDA
PDA_MARKER,
]);Avantages
La nature déterministe des PDAs élimine le besoin de stocker des adresses : vous pouvez les régénérer à partir des mêmes seeds chaque fois que nécessaire.
Cela crée des schémas d'adressage prévisibles qui fonctionnent comme des structures de hashmap sur la chaîne. Plus important encore, les programmes peuvent signer pour leurs propres PDAs, permettant une gestion autonome des actifs sans exposer de clés privées :
let seeds = &[b"vault", user.as_ref(), &[bump]];
invoke_signed(
&transfer_instruction,
&[from_pda, to_account, token_program],
&[&seeds[..]], // Program proves PDA control
)?;Invocation entre programmes (CPI)
Les CPIs permettent aux programmes d'appeler d'autres programmes au sein de la même transaction, créant une véritable composabilité où plusieurs programmes peuvent interagir de manière atomique sans coordination externe.
Cela permet aux développeurs de construire des applications complexes en combinant des programmes existants plutôt que de reconstruire des fonctionnalités à partir de zéro.
Les CPI suivent le même modèle que les instructions régulières, nécessitant de spécifier le programme cible, les comptes dont il a besoin et les données d'instruction, la principale différence étant qu'ils peuvent être exécutés à l'intérieur d'autres programmes.
Le programme appelant conserve le contrôle du flux tout en déléguant des opérations spécifiques à des programmes spécialisés :
let cpi_accounts = TransferAccounts {
from: source_account.clone(),
to: destination_account.clone(),
authority: authority_account.clone(),
};
let cpi_ctx = CpiContext::new(token_program.clone(), cpi_accounts);
token_program::cpi::transfer(cpi_ctx, amount)?;Contraintes et capacités
Les signataires originaux de la transaction conservent leur autorité tout au long des chaînes CPI, permettant aux programmes d'agir pour le compte des utilisateurs de manière transparente.
Cependant, les programmes ne peuvent effectuer des CPI que jusqu'à 4 niveaux de profondeur (A → B → C → D) pour éviter la récursion infinie. Les programmes peuvent également signer pour leurs PDA dans les CPI en utilisant CpiContext::new_with_signer, permettant des opérations autonomes sophistiquées.
Cette composabilité permet des opérations complexes à travers plusieurs programmes au sein d'une seule transaction atomique, rendant les applications Solana hautement modulaires et interopérables.