Skip to main content
FHE operations require significantly more memory than typical Solana programs. Privora provides a custom allocator to handle large ciphertexts.

Why a Custom Allocator?

The Problem

AllocatorHeap SizeFHE Support
Default Solana~32KBNo
Privora FHE~1MBYes
FHE ciphertexts are 10-100KB each. With the default allocator, even loading two ciphertexts would exceed available memory.

The Solution

The FheBumpAllocator provides:
  • ~1MB heap space (configurable)
  • Bump allocation (fast, no deallocation)
  • Compatible with Solana BPF environment

Setup

Basic Setup

Add at the top of your lib.rs:
use privora_sdk_program::prelude::*;

// CRITICAL: Must be at crate root
privora_sdk_program::setup_fhe_allocator!();

Custom Heap Size

For programs needing more memory:
// 2MB heap
privora_sdk_program::setup_fhe_allocator!(2 * 1024 * 1024);

How It Works

Macro Expansion

The macro expands to:
#[cfg(target_os = "solana")]
#[global_allocator]
static FHE_ALLOCATOR: FheBumpAllocator = FheBumpAllocator::new();
Key points:
  • Only active when compiling for Solana (target_os = "solana")
  • Uses #[global_allocator] to replace the default allocator
  • Static lifetime - exists for program duration

Bump Allocation

The allocator uses bump allocation:
Heap Start                                        Heap End
    |                                                 |
    v                                                 v
    +---+---+---+---+---+---+---+---+---+---+---+---+
    |AAA|BBB|CCC|   |   |   |   |   |   |   |   |   |
    +---+---+---+---+---+---+---+---+---+---+---+---+
                ^
                |
            Next allocation here
Properties:
  • Fast: O(1) allocation (just bump a pointer)
  • No deallocation: Memory is never freed during execution
  • Reset on completion: All memory reclaimed when program exits

FheBumpAllocator API

Construction

use privora_sdk_program::memory::allocator::FheBumpAllocator;

// Default ~1MB heap
let allocator = FheBumpAllocator::new();

// Custom heap size
let allocator = FheBumpAllocator::with_heap_size(2 * 1024 * 1024);

Constants

// Default heap size (~1MB)
pub const FHE_HEAP_SIZE: usize = 1024 * 1024 - 16; // 1048560 bytes

// Heap start address (Solana BPF)
const HEAP_START_ADDRESS: usize = 0x300000000;

Memory Usage Patterns

Typical Memory Usage

OperationMemory Used
Load u8 ciphertext~10KB
Load u64 ciphertext~80KB
FHE addition result~10-80KB
Comparison result~1KB

Example Program

privora_sdk_program::setup_fhe_allocator!();

pub fn match_orders(/* ... */) -> ProgramResult {
    // Load 4 u8 ciphertexts: ~40KB
    let buy_price = buy_price_ref.load()?;   // ~10KB
    let buy_qty = buy_qty_ref.load()?;       // ~10KB
    let sell_price = sell_price_ref.load()?; // ~10KB
    let sell_qty = sell_qty_ref.load()?;     // ~10KB

    // Comparison: ~1KB
    let can_match = buy_price.ge(&sell_price)?;

    // Min operation: ~10KB result
    let fill_qty = buy_qty.min(&sell_qty)?;

    // Total: ~60KB, well within 1MB
    Ok(())
}

Troubleshooting

Out of Memory Error

Error: memory allocation failed
Causes:
  1. setup_fhe_allocator!() not called
  2. Called in wrong location (not crate root)
  3. Exceeded heap size
Solutions:
// Ensure allocator is at crate root, before everything else
privora_sdk_program::setup_fhe_allocator!();

// Then imports
use privora_sdk_program::prelude::*;

// Then entrypoint
solana_program::entrypoint!(process_instruction);

Allocator Not Active

If FHE operations fail in tests:
// The allocator only activates for target_os = "solana"
// In tests, standard allocation is used

#[cfg(not(target_os = "solana"))]
// Tests use system allocator - ensure FheTestEnv is set up
let mut env = FheTestEnv::new();

Heap Size Too Small

For complex programs with many operations:
// Increase heap size
privora_sdk_program::setup_fhe_allocator!(2 * 1024 * 1024); // 2MB

Best Practices

1. Load Only What You Need

// Good: Load once, use multiple times
let price = price_ref.load()?;
let result1 = price.add(&other1)?;
let result2 = price.add(&other2)?;

// Avoid: Unnecessary loads
let result1 = price_ref.load()?.add(&other1)?;
let result2 = price_ref.load()?.add(&other2)?; // Extra 10KB!

2. Process in Batches

For large workloads:
// If processing many items, consider transaction batching
// Each transaction gets fresh memory

3. Use Appropriate Types

// Good: u8 for small values
let price: Encrypted<u8> = price_ref.load()?; // ~10KB

// Only use u64 when needed
let large_amount: Encrypted<u64> = amount_ref.load()?; // ~80KB

Memory Layout

The Solana BPF memory layout:
0x100000000 - Program text
0x200000000 - Stack
0x300000000 - Heap (FheBumpAllocator lives here)
0x400000000 - Input data
The allocator manages the heap region starting at 0x300000000.

Comparison with Default Allocator

FeatureDefaultFheBumpAllocator
Heap Size~32KB~1MB
AllocationBumpBump
DeallocationNoneNone
FHE SupportNoYes
PerformanceFastFast

Next Steps