Daten lesen und schreiben
Beim Erstellen optimierter Solana-Programme kann eine effiziente Datenserialisierung und -deserialisierung die Leistung erheblich beeinflussen.
Obwohl Pinocchio keine Low-Level-Speicheroperationen erfordert, kann das Verständnis, wie man Kontodaten effizient liest und schreibt, Ihnen helfen, schnellere Programme zu erstellen.
Die Techniken in diesem Leitfaden funktionieren mit jedem Solana-Entwicklungs-Framework; egal ob Sie Pinocchio, Anchor oder das native SDK verwenden. Der Schlüssel liegt in der durchdachten Gestaltung Ihrer Datenstrukturen und der sicheren Handhabung der Serialisierung.
Wann unsicherer Code verwendet werden sollte
Verwenden Sie unsicheren Code nur, wenn:
Sie maximale Leistung benötigen und gemessen haben, dass sichere Alternativen zu langsam sind
Sie alle Sicherheitsinvarianten rigoros überprüfen können
Sie die Sicherheitsanforderungen klar dokumentieren
Safety Principles
Bei der Arbeit mit rohen Byte-Arrays und Speicheroperationen müssen wir vorsichtig sein, um undefiniertes Verhalten zu vermeiden. Das Verständnis dieser Prinzipien ist entscheidend für das Schreiben korrekten und zuverlässigen Codes.
Puffergrenzprüfung
Überprüfen Sie immer, ob Ihr Puffer groß genug ist, bevor Sie Lese- oder Schreiboperationen durchführen. Das Lesen oder Schreiben über den zugewiesenen Speicher hinaus führt zu undefiniertem Verhalten.
// Good: Check bounds first
if data.len() < size_of::<u64>() {
return Err(ProgramError::InvalidInstructionData);
}
let value = u64::from_le_bytes(data[0..8].try_into().unwrap());
// Bad: No bounds checking - could panic or cause UB
let value = u64::from_le_bytes(data[0..8].try_into().unwrap());Ausrichtungsanforderungen
Jeder Typ in Rust hat eine Ausrichtungsanforderung, die bestimmt, wo er im Speicher platziert werden kann. Das Lesen eines Typs aus dem Speicher, der nicht richtig ausgerichtet ist, führt zu undefiniertem Verhalten. Die meisten primitiven Typen erfordern eine Ausrichtung, die ihrer Größe entspricht:
u8: 1-Byte-Ausrichtungu16: 2-Byte-Ausrichtungu32: 4-Byte-Ausrichtungu64: 8-Byte-Ausrichtung
Das bedeutet, dass ein u64 an Speicheradressen gespeichert werden muss, die durch 8 teilbar sind, während ein u16 an geraden Adressen beginnen muss.
Wenn dies nicht beachtet wird, fügt der Compiler automatisch unsichtbare "Padding"-Bytes zwischen den Struct-Feldern ein, um sicherzustellen, dass jedes Feld seine Ausrichtungsanforderungen erfüllt.
So sieht ein schlecht angeordnetes Struct aus:
#[repr(C)]
struct BadOrder {
small: u8, // 1 byte
// padding: [u8; 7] since `big` needs to be aligned at 8 bytes.
big: u64, // 8 bytes
medium: u16, // 2 bytes
// padding: [u8; 6] since the struct size needs to be aligned to 8 bytes.
}Der Compiler fügt 7 Padding-Bytes nach small ein, weil big eine 8-Byte-Ausrichtung erfordert. Dann fügt er 6 weitere Bytes am Ende hinzu, um die Gesamtgröße (24 Bytes) zu einem Vielfachen von 8 zu machen, wodurch 13 Bytes verschwendet werden.
Eine bessere Möglichkeit wäre, die Felder des Structs so anzuordnen:
#[repr(C)]
struct GoodOrder {
big: u64, // 8 bytes
medium: u16, // 2 bytes
small: u8, // 1 byte
// padding: [u8; 5] since the struct size needs to be aligned to 8 bytes.
}Durch die Platzierung größerer Felder zuerst reduzieren wir das Padding von 13 Bytes auf nur 5 Bytes.
Es gibt eine weitere, fortgeschrittenere Methode, um Daten für maximale Platzeffizienz zu serialisieren und deserialisieren. Wir können Zero-Padding-Structs erstellen, bei denen Ausrichtungsanforderungen vollständig eliminiert werden:
#[repr(C)]
struct ByteArrayStruct {
big: [u8; 8], // represents u64
medium: [u8; 2], // represents u16
small: u8,
}Die Größe beträgt in diesem Fall genau 11 Bytes, da alles 1-Byte-ausgerichtet ist.
Gültige Bitmuster
Nicht alle Bitmuster sind für jeden Typ gültig. Typen wie bool, char und enums haben eingeschränkte gültige Werte. Das Einlesen ungültiger Bitmuster in diese Typen führt zu undefiniertem Verhalten.
Daten lesen
Es gibt verschiedene Ansätze zum Lesen von Daten aus Account-Puffern, jeder mit unterschiedlichen Vor- und Nachteilen:
Feld-für-Feld-Deserialisierung (Empfohlen)
Der sicherste Ansatz ist, jedes Feld einzeln zu deserialisieren. Dies vermeidet alle Ausrichtungsprobleme, da du mit Byte-Arrays arbeitest:
pub struct DepositInstructionData {
pub amount: u64,
pub recipient: Pubkey,
}
impl<'a> TryFrom<&'a [u8]> for DepositInstructionData {
type Error = ProgramError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
if data.len() < (size_of::<u64>() + size_of::<Pubkey>()) {
return Err(ProgramError::InvalidInstructionData);
}
// No alignment issues: we're reading bytes and converting
let amount = u64::from_le_bytes(
data[0..8].try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?
);
let recipient = Pubkey::try_from(&data[8..40])
.map_err(|_| ProgramError::InvalidInstructionData)?;
Ok(Self { amount, recipient })
}
}Zero-Copy Deserialisierung
Dies kann für maximale Leistung mit korrekt ausgerichteten Strukturen verwendet werden, erfordert jedoch sorgfältige Ausrichtungsprüfungen:
#[repr(C)]
pub struct Config {
pub authority: Pubkey,
pub mint_x: Pubkey,
pub mint_y: Pubkey,
pub seed: u64, // This field requires 8-byte alignment
pub fee: u16, // This field requires 2-byte alignment
pub state: u8,
pub config_bump: u8,
}
impl Config {
pub const LEN: usize = size_of::<Self>();
pub fn from_bytes(data: &[u8]) -> Result<&Self, ProgramError> {
if data.len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
// Critical: Check alignment for the most restrictive field (u64 in this case)
if (data.as_ptr() as usize) % core::mem::align_of::<Self>() != 0 {
return Err(ProgramError::InvalidAccountData);
}
// SAFETY: We've verified length and alignment
Ok(unsafe { &*(data.as_ptr() as *const Self) })
}
}
// Alternative: Avoid alignment issues entirely by using byte arrays for types with
// alignment requirement greater than 1 and provide accessor methods
#[repr(C)]
pub struct ConfigSafe {
pub authority: Pubkey,
pub mint_x: Pubkey,
pub mint_y: Pubkey,
seed: [u8; 8], // Convert with u64::from_le_bytes when needed
fee: [u8; 2], // Convert with u16::from_le_bytes when needed
pub state: u8,
pub config_bump: u8,
}
impl ConfigSafe {
pub fn from_bytes(data: &[u8]) -> Result<&Self, ProgramError> {
if data.len() != size_of::<Self>() {
return Err(ProgramError::InvalidAccountData);
}
// SAFETY: No alignment check needed - everything is u8 aligned
Ok(unsafe { &*(data.as_ptr() as *const Self) })
}
pub fn seed(&self) -> u64 {
u64::from_le_bytes(self.seed)
}
pub fn fee(&self) -> u16 {
u16::from_le_bytes(self.fee)
}
}Wie Sie sehen können, sind sowohl das seed- als auch das fee-Feld privat. Das liegt daran, dass wir immer Zugriffsmethoden verwenden sollten, um Daten zu lesen, da ihre Werte durch Byte-Arrays repräsentiert werden.
Wenn Sie direkt auf ein Feld zugreifen (config.seed), muss der Compiler möglicherweise eine Referenz auf die Speicheradresse dieses Feldes erstellen, wenn auch nur vorübergehend. Wenn dieses Feld nicht korrekt ausgerichtet ist, ist das Erstellen der Referenz undefiniertes Verhalten, selbst wenn Sie die Referenz nie explizit verwenden!
Zugriffsmethoden vermeiden dies, indem sie den Lesevorgang innerhalb des Methodenbereichs durchführen, wo der Compiler alle Zwischenreferenzen optimieren kann.
#[repr(C, packed)] // This can cause unaligned fields!
pub struct PackedConfig {
pub state: u8,
pub seed: u64, // This u64 might not be 8-byte aligned due to packing
}
impl PackedConfig {
pub fn seed(&self) -> u64 {
self.seed // Safe: Direct value copy, no reference created
}
}
// Usage:
let config = PackedConfig::load(account)?;
// ❌ UNDEFINED BEHAVIOR: Creates a reference to potentially unaligned field
let seed_ref = &config.seed; // Compiler must create a reference here!
// ❌ UNDEFINED BEHAVIOR: Even this can be problematic
let seed_value = config.seed; // May create temporary reference internally
// ✅ SAFE: Accessor method reads value without creating reference
let seed_value = config.seed(); // No intermediate referenceIn diesem Fall haben wir keine "speziellen Typen", aber denken Sie immer daran, dass einige Typen aufgrund ungültiger Bitmuster besondere Sorgfalt erfordern:
pub struct StateAccount {
pub is_active: bool,
pub state_type: StateType,
pub data: [u8; 32],
}
#[repr(u8)]
pub enum StateType {
Inactive = 0,
Active = 1,
Paused = 2,
}
impl StateAccount {
pub fn from_bytes(data: &[u8]) -> Result<Self, ProgramError> {
if data.len() < size_of::<Self>() {
return Err(ProgramError::InvalidAccountData);
}
// Safely handle bool (only 0 or 1 are valid)
let is_active = match data[0] {
0 => false,
1 => true,
_ => return Err(ProgramError::InvalidAccountData),
};
// Safely handle enum
let state_type = match data[1] {
0 => StateType::Inactive,
1 => StateType::Active,
2 => StateType::Paused,
_ => return Err(ProgramError::InvalidAccountData),
};
let mut data_array = [0u8; 32];
data_array.copy_from_slice(&data[2..34]);
Ok(Self {
is_active,
state_type,
data: data_array,
})
}
}Gefährliche Muster, die vermieden werden sollten
Hier sind häufige Muster, die zu undefiniertem Verhalten führen können und vermieden werden sollten:
Verwendung von
transmute()mit nicht ausgerichteten Daten
// ❌ UNDEFINED BEHAVIOR: transmute requires proper alignment
let value: u64 = unsafe { core::mem::transmute(bytes_slice) };transmute() geht davon aus, dass die Quelldaten für den Zieltyp korrekt ausgerichtet sind. Wenn Sie mit beliebigen Byte-Slices arbeiten, wird diese Annahme oft verletzt.
Pointer-Casting auf gepackte Strukturen
#[repr(C, packed)]
pub struct PackedConfig {
pub state: u8,
pub seed: u64, // This u64 is only 1-byte aligned!
pub authority: Pubkey,
}
// ❌ UNDEFINED BEHAVIOR: Creates references to unaligned fields
let config = unsafe { &*(data.as_ptr() as *const PackedConfig) };
let seed_value = config.seed; // UB: May create reference to unaligned u64Obwohl die Struktur in den Speicher passt, kann der Zugriff auf Felder mit mehreren Bytes nicht ausgerichtete Referenzen erzeugen.
Direkter Feldzugriff auf gepackte Strukturen
#[repr(C, packed)]
pub struct PackedStruct {
pub a: u8,
pub b: u64,
}
let packed = /* ... */;
// ❌ UNDEFINED BEHAVIOR: Creates reference to unaligned field
let b_ref = &packed.b;
// ❌ UNDEFINED BEHAVIOR: May create temporary reference
let b_value = packed.b;Annahme der Ausrichtung ohne Überprüfung
// ❌ UNDEFINED BEHAVIOR: No alignment check
let config = unsafe { &*(data.as_ptr() as *const Config) };Nur weil Daten passen, bedeutet das nicht, dass sie richtig ausgerichtet sind.
Falsche Verwendung von
read_unaligned()
// ❌ WRONG: read_unaligned needs proper layout, not just size
#[repr(Rust)] // Default layout - not guaranteed!
pub struct BadStruct {
pub field: u64,
}
let value = unsafe { (data.as_ptr() as *const BadStruct).read_unaligned() };read_unaligned() erfordert trotzdem, dass die Struktur ein vorhersehbares Layout hat (#[repr(C)]).
Writing Data
Das sichere Schreiben von Daten folgt ähnlichen Prinzipien wie das Lesen:
Feld-für-Feld-Serialisierung (Empfohlen)
impl Config {
pub fn write_to_buffer(&self, data: &mut [u8]) -> Result<(), ProgramError> {
if data.len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
let mut offset = 0;
// Write authority
data[offset..offset + 32].copy_from_slice(self.authority.as_ref());
offset += 32;
// Write mint_x
data[offset..offset + 32].copy_from_slice(self.mint_x.as_ref());
offset += 32;
// Write mint_y
data[offset..offset + 32].copy_from_slice(self.mint_y.as_ref());
offset += 32;
// Write seed
data[offset..offset + 8].copy_from_slice(&self.seed.to_le_bytes());
offset += 8;
// Write fee
data[offset..offset + 2].copy_from_slice(&self.fee.to_le_bytes());
offset += 2;
// Write state
data[offset] = self.state;
offset += 1;
// Write config_bump
data[offset] = self.config_bump;
Ok(())
}
}Dieser Ansatz ist die sicherste Methode, da jedes Feld explizit in den Byte-Puffer serialisiert wird:
Keine Ausrichtungsprobleme: Du schreibst in ein Byte-Array
Explizite Endianness: Du kontrollierst die Byte-Reihenfolge mit to_le_bytes()
Klares Speicherlayout: einfach zu debuggen und zu verstehen
Kein undefiniertes Verhalten: alle Operationen erfolgen auf Byte-Arrays
Direkte Mutation (Zero-Copy)
Für maximale Leistung kannst du den Byte-Puffer in eine Struktur umwandeln und Felder direkt ändern. Dies erfordert, dass die Struktur korrekt ausgerichtet ist:
impl Config {
pub fn from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, ProgramError> {
if data.len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
// Check alignment
if (data.as_ptr() as usize) % core::mem::align_of::<Self>() != 0 {
return Err(ProgramError::InvalidAccountData);
}
// SAFETY: We've verified length and alignment
Ok(unsafe { &mut *(data.as_mut_ptr() as *mut Self) })
}
}Wenn die Ausrichtung überprüft wird und die Struktur #[repr(C)] verwendet, erzeugt die direkte Feldänderung keine nicht ausgerichteten Referenzen.
Byte-Array-Ansatz mit Settern (Am sichersten + schnell)
Das Beste aus beiden Welten: Wir können intern Byte-Arrays verwenden, aber ergonomische Setter bereitstellen:
#[repr(C)]
pub struct ConfigSafe {
pub authority: Pubkey,
pub mint_x: Pubkey,
pub mint_y: Pubkey,
seed: [u8; 8],
fee: [u8; 2],
pub state: u8,
pub config_bump: u8,
}
impl ConfigSafe {
pub fn from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, ProgramError> {
if data.len() != size_of::<Self>() {
return Err(ProgramError::InvalidAccountData);
}
// No alignment check needed - everything is u8 aligned
Ok(unsafe { &mut *(data.as_mut_ptr() as *mut Self) })
}
pub fn seed(&self) -> u64 {
u64::from_le_bytes(self.seed)
}
pub fn fee(&self) -> u16 {
u16::from_le_bytes(self.fee)
}
// Setters that handle endianness correctly
pub fn set_seed(&mut self, seed: u64) {
self.seed = seed.to_le_bytes();
}
pub fn set_fee(&mut self, fee: u16) {
self.fee = fee.to_le_bytes();
}
}Dies ist ideal, weil:
Keine Ausrichtungsprobleme: alle Felder sind byte-ausgerichtet
Schnelle direkte Mutation: kein Serialisierungsaufwand nach der ersten Einrichtung
Konsistente Endianness: Setter übernehmen die Byte-Reihenfolge-Konvertierung
Typsicherheit: Setter akzeptieren die erwarteten Typen, nicht Byte-Arrays
Dynamically Sized Data
Vermeide wann immer möglich, dynamisch dimensionierte Daten direkt in Konten zu speichern. Einige Anwendungsfälle erfordern es jedoch.
Wenn dein Konto dynamische Daten enthält, platziere immer alle statisch dimensionierten Felder am Anfang deiner Struktur und hänge die dynamischen Daten am Ende an.
Einzelnes dynamisches Feld
Dies ist der einfachste Fall: ein Abschnitt mit variabler Länge am Ende Ihres Kontos:
#[repr(C)]
pub struct DynamicAccount {
pub fixed_data: [u8; 32],
pub counter: u64,
// Dynamic data follows after the struct in memory
// Layout: [fixed_data][counter][dynamic_data...]
}
impl DynamicAccount {
pub const FIXED_SIZE: usize = size_of::<Self>();
/// Safely parse account with dynamic data
pub fn from_bytes_with_dynamic(data: &[u8]) -> Result<(&Self, &[u8]), ProgramError> {
if data.len() < Self::FIXED_SIZE {
return Err(ProgramError::InvalidAccountData);
}
// SAFETY: We've verified the buffer is large enough for the fixed part
// The fixed part only contains [u8; 32] and u64, which have predictable layout
let fixed_part = unsafe { &*(data.as_ptr() as *const Self) };
// Everything after the fixed part is dynamic data
let dynamic_part = &data[Self::FIXED_SIZE..];
Ok((fixed_part, dynamic_part))
}
/// Get mutable references to both parts
pub fn from_bytes_mut_with_dynamic(data: &mut [u8]) -> Result<(&mut Self, &mut [u8]), ProgramError> {
if data.len() < Self::FIXED_SIZE {
return Err(ProgramError::InvalidAccountData);
}
// Split the buffer to avoid borrowing issues
let (fixed_bytes, dynamic_bytes) = data.split_at_mut(Self::FIXED_SIZE);
// SAFETY: We've verified the size and split safely
let fixed_part = unsafe { &mut *(fixed_bytes.as_mut_ptr() as *mut Self) };
Ok((fixed_part, dynamic_bytes))
}
}
/// Writing single dynamic field
impl DynamicAccount {
pub fn write_with_dynamic(
data: &mut [u8],
fixed_data: &[u8; 32],
counter: u64,
dynamic_data: &[u8]
) -> Result<(), ProgramError> {
let total_size = Self::FIXED_SIZE + dynamic_data.len();
if data.len() != total_size {
return Err(ProgramError::InvalidAccountData);
}
// Write fixed part field by field (safest approach)
data[0..32].copy_from_slice(fixed_data);
data[32..40].copy_from_slice(&counter.to_le_bytes());
// Write dynamic part
data[Self::FIXED_SIZE..].copy_from_slice(dynamic_data);
Ok(())
}
/// Update just the dynamic portion
pub fn update_dynamic_data(&mut self, account_data: &mut [u8], new_data: &[u8]) -> Result<(), ProgramError> {
if account_data.len() < Self::FIXED_SIZE + new_data.len() {
return Err(ProgramError::InvalidAccountData);
}
// Write new dynamic data
account_data[Self::FIXED_SIZE..Self::FIXED_SIZE + new_data.len()].copy_from_slice(new_data);
Ok(())
}
}Um undefiniertes Verhalten zu vermeiden, überprüfen Sie immer, dass der Kontodatenpuffer mindestens so groß ist wie der statisch dimensionierte Teil. Der dynamische Abschnitt kann leer sein, daher ist diese Überprüfung unerlässlich.
Dieses Layout stellt sicher, dass die Offsets für Felder mit fester Größe immer bekannt sind, unabhängig von der Länge der dynamischen Daten.
Es gibt zwei Hauptszenarien beim Lesen von dynamisch dimensionierten Daten:
Einzelnes dynamisches Feld am Ende: Sie können die Größe und den Offset der dynamischen Daten zur Laufzeit einfach so bestimmen:
const DYNAMIC_DATA_START_OFFSET: usize = size_of::<[u8; 32]>();
#[repr(C)]
pub struct DynamicallySizedAccount {
pub sized_data: [u8; 32],
// pub dynamic_data: &'info [u8], // Not part of the struct, but follows in the buffer
}
impl DynamicallySizedAccount {
/// Returns the length of the dynamic data section.
#[inline(always)]
pub fn get_dynamic_data_len(data: &[u8]) -> Result<usize, ProgramError> {
if data.len().le(&DYNAMIC_DATA_START_OFFSET) {
return Err(ProgramError::InvalidAccountData);
}
Ok(data.len() - DYNAMIC_DATA_START_OFFSET)
}
/// Returns a slice of the dynamic data.
#[inline(always)]
pub fn read_dynamic_data(data: &[u8]) -> Result<&[u8], ProgramError> {
if data.len().le(&DYNAMIC_DATA_START_OFFSET) {
return Err(ProgramError::InvalidAccountData);
}
Ok(&data[DYNAMIC_DATA_START_OFFSET..])
}
}Mehrere dynamische Felder
Dieser Ansatz ist komplexer, da wir eine Möglichkeit benötigen, die Länge jedes dynamischen Feldes außer dem letzten zu bestimmen. Der häufigste Ansatz besteht darin, jedem dynamischen Feld (außer dem letzten) seine Länge voranzustellen, damit wir den Puffer korrekt analysieren können.
Hier ist ein einfaches und robustes Muster: Speichern Sie die Länge des ersten dynamischen Feldes als u8 (oder u16 usw., wenn Sie größere Größen benötigen) unmittelbar nach den statisch dimensionierten Daten. Das erste dynamische Feld folgt, und das zweite dynamische Feld belegt den Rest des Puffers.
#[repr(C)]
pub struct MultiDynamicAccount {
pub fixed_data: [u8; 32],
pub timestamp: u64,
// Layout: [fixed_data][timestamp][len1: u8][data1][data2: remainder]
}
impl MultiDynamicAccount {
pub const FIXED_SIZE: usize = size_of::<Self>();
pub const LEN_PREFIX_SIZE: usize = size_of::<u8>();
pub const MIN_SIZE: usize = Self::FIXED_SIZE + Self::LEN_PREFIX_SIZE;
/// Parse account with two dynamic sections
pub fn parse_dynamic_fields(data: &[u8]) -> Result<(&[u8; 32], u64, &[u8], &[u8]), ProgramError> {
if data.len() < Self::MIN_SIZE {
return Err(ProgramError::InvalidAccountData);
}
// Extract fixed data safely
let fixed_data = data[..32].try_into()
.map_err(|_| ProgramError::InvalidAccountData)?;
let timestamp = u64::from_le_bytes(
data[32..40].try_into()
.map_err(|_| ProgramError::InvalidAccountData)?
);
// Read length of first dynamic field (single byte)
let len = data[Self::FIXED_SIZE] as usize;
// Validate we have enough data
if data.len() < Self::MIN_SIZE + len {
return Err(ProgramError::InvalidAccountData);
}
let data_1 = &data[Self::MIN_SIZE..Self::MIN_SIZE + len];
let data_2 = &data[Self::MIN_SIZE + len..]; // Remainder
Ok((fixed_data, timestamp, data_1, data_2))
}
/// Write account with two dynamic sections
pub fn write_with_multiple_dynamic(
buffer: &mut [u8],
fixed_data: &[u8; 32],
timestamp: u64,
data_1: &[u8],
data_2: &[u8]
) -> Result<(), ProgramError> {
let total_size = Self::MIN_SIZE + data_1.len() + data_2.len();
if buffer.len() != total_size {
return Err(ProgramError::InvalidAccountData);
}
// Validate data_1 length fits in u8
if data_1.len() > u8::MAX as usize {
return Err(ProgramError::InvalidInstructionData);
}
let mut offset = 0;
// Write fixed data
buffer[offset..offset + 32].copy_from_slice(fixed_data);
offset += 32;
buffer[offset..offset + 8].copy_from_slice(×tamp.to_le_bytes());
offset += 8;
// Write length prefix for data1 (single byte)
buffer[offset] = data_1.len() as u8;
offset += 1;
// Write data1
buffer[offset..offset + data_1.len()].copy_from_slice(data_1);
offset += data_1.len();
// Write data2 (remainder - no length prefix needed)
buffer[offset..].copy_from_slice(data_2);
Ok(())
}
}Konto in der Größe anpassen
Jedes Mal, wenn Sie ein dynamisches Feld aktualisieren und sich die Größe ändert, müssen Sie die Größe des Kontos anpassen. Hier ist eine allgemeine Funktion zum Ändern der Größe eines Kontos:
pub fn resize_account(
account: &AccountInfo,
payer: &AccountInfo,
new_size: usize,
zero_out: bool,
) -> ProgramResult {
// If the account is already the correct size, return early
if new_size == account.data_len() {
return Ok(());
}
// Calculate rent requirements
let rent = Rent::get()?;
let new_minimum_balance = rent.minimum_balance(new_size);
// Adjust lamports to meet rent-exemption requirements
match new_minimum_balance.cmp(&account.lamports()) {
core::cmp::Ordering::Greater => {
// Need more lamports for rent exemption
let lamports_diff = new_minimum_balance.saturating_sub(account.lamports());
**payer.try_borrow_mut_lamports()? -= lamports_diff;
**account.try_borrow_mut_lamports()? += lamports_diff;
}
core::cmp::Ordering::Less => {
// Return excess lamports to payer
let lamports_diff = account.lamports().saturating_sub(new_minimum_balance);
**account.try_borrow_mut_lamports()? -= lamports_diff;
**payer.try_borrow_mut_lamports()? += lamports_diff;
}
core::cmp::Ordering::Equal => {
// No lamport transfer needed
}
}
// Reallocate the account
account.resize(new_size)?;
Ok(())
}