| name | rust-solana |
| description | Rust patterns specific to Solana development including serialization, compute optimization, and Solana-specific types. Load when optimizing Solana programs or working with low-level Solana features. |
Rust for Solana
Rust patterns and idioms specific to Solana program development, including serialization, time handling, signature verification, and compute optimization.
Quick Reference
Borsh Serialization
use anchor_lang::prelude::*;
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct PaymentAuthorization {
pub escrow: Pubkey,
pub mint: Pubkey,
pub recipient: Pubkey,
pub amount: u64,
pub authorization_id: u64,
pub expires_at_slot: u64,
}
Slot-Based Timing
let clock = Clock::get()?;
require!(
clock.slot >= start_slot + timeout_slots,
ErrorCode::TimeoutNotExpired
);
Byte Conversions for Seeds
let seeds = [
b"pending".as_ref(),
&authorization_id.to_le_bytes(),
];
let seeds = [
b"vault".as_ref(),
escrow.key().as_ref(),
];
Ed25519 Signature Verification
let message = PaymentAuthorization {
escrow: ctx.accounts.escrow.key(),
mint,
recipient,
amount,
authorization_id,
expires_at_slot,
};
let message_bytes = message.try_to_vec()?;
Borsh Serialization
The AnchorSerialize and AnchorDeserialize Traits
Anchor uses Borsh (Binary Object Representation Serializer for Hashing) for serialization.
For custom types:
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct CustomData {
pub value: u64,
pub active: bool,
}
For instruction arguments:
pub fn process_data(ctx: Context<Process>, data: CustomData) -> Result<()> {
msg!("Value: {}", data.value);
Ok(())
}
The #[account] macro automatically includes:
#[account]
pub struct MyAccount {
pub data: u64,
}
Manual Serialization
When you need to serialize data manually:
use anchor_lang::prelude::*;
let data = PaymentAuthorization {
escrow: escrow_pubkey,
mint: mint_pubkey,
recipient: recipient_pubkey,
amount: 1000,
authorization_id: 8372615,
expires_at_slot: 250_000,
};
let serialized = data.try_to_vec()?;
let deserialized = PaymentAuthorization::try_from_slice(&serialized)?;
Common Serialization Patterns
For PDA seeds (numbers):
&authorization_id.to_le_bytes()
For signatures (messages):
let message = authorization.try_to_vec()?;
For account data:
#[account]
pub struct Data {
pub field: u64,
}
Solana Native Types
Pubkey
Public key type (32 bytes):
use anchor_lang::solana_program::pubkey::Pubkey;
pub authority: Pubkey,
let key = ctx.accounts.escrow.key();
key.as_ref()
key.to_bytes()
Slot
Blockchain slot number (u64):
let clock = Clock::get()?;
let current_slot = clock.slot;
pub submitted_at_slot: u64,
let expired = current_slot >= start_slot + timeout_slots;
Lamports
Native SOL amount (u64):
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
let balance = ctx.accounts.account.to_account_info().lamports();
AccountInfo
Low-level account access:
use anchor_lang::prelude::*;
let account_info = ctx.accounts.my_account.to_account_info();
account_info.key
account_info.lamports
account_info.data
account_info.owner
account_info.is_signer
account_info.is_writable
Use when: Need direct account manipulation
Slot-Based Timing
Why slots instead of timestamps?
- More predictable
- Tied to chain state
- No clock drift issues
Getting Current Slot
use anchor_lang::solana_program::clock::Clock;
let clock = Clock::get()?;
let current_slot = clock.slot;
Timeout Calculations
pub fn finalize(ctx: Context<Finalize>) -> Result<()> {
let clock = Clock::get()?;
require!(
clock.slot >= ctx.accounts.pending.submitted_at_slot
+ ctx.accounts.escrow.refund_timeout_slots,
ErrorCode::RefundWindowNotExpired
);
Ok(())
}
Storing Time in Accounts
#[account]
pub struct PendingSettlement {
pub submitted_at_slot: u64,
}
pub fn submit(ctx: Context<Submit>) -> Result<()> {
let clock = Clock::get()?;
ctx.accounts.pending.submitted_at_slot = clock.slot;
Ok(())
}
Timeout Patterns
Refund window:
require!(
clock.slot < submitted_at + refund_timeout,
ErrorCode::RefundWindowExpired
);
Finalization:
require!(
clock.slot >= submitted_at + refund_timeout,
ErrorCode::RefundWindowNotExpired
);
Deadman switch:
require!(
clock.slot > last_activity + deadman_timeout,
ErrorCode::DeadmanNotExpired
);
Ed25519 Signature Verification
Why Ed25519 for Session Keys?
Session keys in escrow allow off-chain signature creation:
- Client signs authorization with session key (Ed25519 keypair)
- Facilitator submits authorization + signature on-chain
- Program verifies signature matches session key
Compute cost: ~25,000 CU per verification
Message Construction
use anchor_lang::prelude::*;
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct PaymentAuthorization {
pub escrow: Pubkey,
pub mint: Pubkey,
pub recipient: Pubkey,
pub amount: u64,
pub authorization_id: u64,
pub expires_at_slot: u64,
}
pub fn submit_authorization(
ctx: Context<SubmitAuth>,
mint: Pubkey,
recipient: Pubkey,
amount: u64,
authorization_id: u64,
expires_at_slot: u64,
signature: [u8; 64],
) -> Result<()> {
let message = PaymentAuthorization {
escrow: ctx.accounts.escrow.key(),
mint,
recipient,
amount,
authorization_id,
expires_at_slot,
};
let message_bytes = message.try_to_vec()?;
verify_ed25519_signature(
&message_bytes,
&signature,
ctx.accounts.session_key.key.as_ref(),
)?;
Ok(())
}
Signature Verification Methods
Method 1: Ed25519 Program Instruction (Recommended for Solana)
use anchor_lang::solana_program::sysvar::instructions;
pub fn submit_with_ed25519_ix(ctx: Context<Submit>) -> Result<()> {
let ix_sysvar = &ctx.accounts.instruction_sysvar;
Ok(())
}
#[derive(Accounts)]
pub struct Submit<'info> {
#[account(address = instructions::ID)]
pub instruction_sysvar: UncheckedAccount<'info>,
}
Method 2: Manual Verification (If needed)
For custom signature verification (note: requires ed25519 crate):
use ed25519_dalek::{PublicKey, Signature, Verifier};
fn verify_signature(
message: &[u8],
signature: &[u8; 64],
public_key: &[u8; 32],
) -> Result<()> {
let pubkey = PublicKey::from_bytes(public_key)
.map_err(|_| error!(ErrorCode::InvalidPublicKey))?;
let sig = Signature::from_bytes(signature);
pubkey
.verify(message, &sig)
.map_err(|_| error!(ErrorCode::InvalidSignature))?;
Ok(())
}
Note: Ed25519 verification is compute-intensive. Consider using Solana's native Ed25519 program for efficiency.
Byte Conversions
Numbers to Bytes (for PDA seeds)
Always use little-endian:
let authorization_id: u64 = 8372615;
let bytes = authorization_id.to_le_bytes();
seeds = [b"pending", &authorization_id.to_le_bytes()]
let id: u32 = 456;
let bytes = id.to_le_bytes();
Why little-endian?
- Solana uses little-endian architecture
- Consistency with on-chain data layout
- Standard in Solana ecosystem
Pubkeys to Bytes
let pubkey: Pubkey = ;
let bytes = pubkey.as_ref();
let bytes = pubkey.to_bytes();
seeds = [b"escrow", owner.key().as_ref(), &index.to_le_bytes()]
Strings to Bytes
b"user-stats"
let name = "alice";
let bytes = name.as_bytes();
Account Data Layouts
Discriminator (8 bytes)
Every #[account] struct starts with 8-byte discriminator:
Space calculation:
#[account]
pub struct MyAccount {
pub value: u64,
}
space = 8 + 8
Field Alignment
Anchor serializes fields in order:
#[account]
pub struct Example {
pub a: u8,
pub b: u64,
pub c: Pubkey,
}
No padding - fields are packed sequentially.
Dynamic Size Fields
#[account]
#[derive(InitSpace)]
pub struct Dynamic {
pub fixed: u64,
#[max_len(100)]
pub name: String,
#[max_len(10)]
pub items: Vec<u32>,
}
String: 4 bytes length + max bytes
Vec: 4 bytes length + (max count * item size)
Compute Unit Optimization
Understanding Compute Units (CU)
Solana transactions have compute budget:
- Default: 200,000 CU per transaction
- Maximum: 1,400,000 CU (with compute budget program)
Common operation costs:
- Account read: ~1,000 CU
- Ed25519 verify: ~25,000 CU
- SHA256 hash: ~500 CU
- CPI: ~1,000 CU base + callee cost
Optimization Strategies
1. Minimize allocations:
let mut buffer = Vec::with_capacity(100);
for _ in 0..100 {
let buffer = Vec::new();
}
2. Use references:
fn process(account: &Account<'info, MyAccount>) -> Result<()> { }
fn process_copy(account: Account<'info, MyAccount>) -> Result<()> { }
3. Efficient deserialization:
4. Batch operations:
const tx = new Transaction();
tx.add(instruction1, instruction2, instruction3);
Memory Management
Stack vs Heap in BPF
Solana's BPF VM has limited stack (4KB).
For large structs, use Box:
pub struct LargeData {
pub array: [u8; 1000],
}
let data = Box::new(LargeData { array: [0; 1000] });
let data = LargeData { array: [0; 1000] };
Anchor accounts are heap-allocated automatically:
#[account]
pub struct MyAccount {
pub large_array: [u8; 1000],
}
Zero-Copy Deserialization
For very large accounts (>10KB), use zero-copy:
use anchor_lang::prelude::*;
#[account(zero_copy)]
pub struct LargeAccount {
pub data: [u8; 100000],
}
Benefits:
- No deserialization cost
- Direct memory access
- Lower compute usage
Trade-offs:
- Unsafe access patterns
- Manual validation needed
Reference: Anchor Zero-Copy
Working with AccountInfo
When to Use AccountInfo
Use AccountInfo when you need:
- Direct lamport manipulation
- Raw data access
- Owner changes
- Advanced account operations
let account_info = ctx.accounts.account.to_account_info();
let balance = account_info.lamports();
**account_info.lamports.borrow_mut() += 1000;
let data = account_info.data.borrow();
if account_info.owner == &system_program::ID {
}
Safety Considerations
let mut data = account_info.data.borrow_mut();
data[0] = 42;
ctx.accounts.my_account.value = 42;
Prefer typed accounts when possible.
Common Patterns for Escrow
Authorization ID Serialization
pub fn submit_authorization(
ctx: Context<Submit>,
authorization_id: u64,
expires_at_slot: u64,
) -> Result<()> {
let seeds = [
b"pending".as_ref(),
ctx.accounts.escrow.key().as_ref(),
&authorization_id.to_le_bytes(),
];
Ok(())
}
Message Serialization for Signatures
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct PaymentAuthorization {
pub escrow: Pubkey,
pub mint: Pubkey,
pub recipient: Pubkey,
pub amount: u64,
pub authorization_id: u64,
pub expires_at_slot: u64,
}
let message = PaymentAuthorization { };
let bytes = message.try_to_vec()?;
Slot-Based Timeout Validation
pub fn finalize(ctx: Context<Finalize>) -> Result<()> {
let clock = Clock::get()?;
require!(
clock.slot >= ctx.accounts.pending.submitted_at_slot
+ ctx.accounts.escrow.refund_timeout_slots,
ErrorCode::RefundWindowNotExpired
);
Ok(())
}
Skill Loading Guidance
Load This Skill When
- Implementing signature verification
- Working with slot-based timing
- Optimizing compute usage
- Serializing complex data structures
- Working with low-level account operations
Related Skills
- anchor-core - For integration with Anchor patterns
- anchor-pdas - For byte conversions in seeds
- anchor-security - For security implications of timing
Reference Links
Official Documentation
Solana Architecture
Source Material
- Design document:
/docs/flex-solana.md - Escrow-specific patterns
- Solana Program Library examples
Acknowledgment
At the start of a session, after reviewing this skill, state: "I have reviewed the rust-solana skill and understand Solana-specific Rust patterns for program development."