Skip to content

Latest commit

 

History

History
163 lines (127 loc) · 6.03 KB

File metadata and controls

163 lines (127 loc) · 6.03 KB

Soroban Contract Storage Optimization

Overview

This document outlines the storage optimization strategy for Nova Rewards smart contracts to minimize ledger entry fees on Stellar. Each data key is classified by storage type and includes TTL extension justification.

Storage Classification

Instance Storage (Temporary)

Used for contract metadata that rarely changes and has short lifetime.

Characteristics:

  • Survives contract invocations
  • Minimal cost
  • No TTL extension needed
  • Examples: admin address, contract configuration flags

Contracts using Instance Storage:

  • campaign: Admin address, pause state
  • redemption: Admin address, next redemption ID
  • admin_roles: Admin address, pending admin, signers, threshold

Persistent Storage (Long-lived)

Used for critical state that must survive indefinitely.

Characteristics:

  • Requires TTL extension to prevent expiry
  • Higher cost per entry
  • Must be extended in hot paths
  • Examples: campaign data, user balances, redemption records

Contracts using Persistent Storage:

  • campaign: Campaign data, participants list, join status
  • redemption: Redemption requests, redemption rates, total redemptions
  • nova_token: User balances, allowances
  • reward_pool: Pool balance, daily limits, withdrawal history

TTL Extension Strategy

Hot Path Extensions

Functions that frequently access persistent entries should extend TTL to amortize cost:

// Example: nova_token balance_of
fn balance_of(env: &Env, addr: &Address) -> i128 {
    let key = DataKey::Balance(addr.clone());
    let balance = env.storage().persistent().get(&key).unwrap_or(0i128);
    // Extend TTL: 31 days (2,678,400 ledgers at 5s/ledger)
    env.storage()
        .persistent()
        .extend_ttl(&key, 2_678_400, 2_678_400);
    balance
}

TTL Values

  • Short-lived data (temporary redemptions): 7 days (604,800 ledgers)
  • Standard data (balances, campaigns): 31 days (2,678,400 ledgers)
  • Critical data (admin records): 365 days (31,536,000 ledgers)

Storage Optimization Checklist

Campaign Contract

  • Admin address → Instance storage (no TTL needed)
  • Pause state → Instance storage (no TTL needed)
  • Campaign data → Persistent storage (extend on read/write)
  • Participants list → Persistent storage (extend on modification)
  • Join status → Persistent storage (no extension needed - write-once)

Cost Optimization:

  • Campaign data accessed in set_active(), join_campaign(), distribute_reward() → extend TTL
  • Participants list accessed in join_campaign() → extend TTL
  • Join status is write-once, no extension needed

Redemption Contract

  • Admin address → Instance storage (no TTL needed)
  • Next ID counter → Instance storage (no TTL needed)
  • Redemption requests → Persistent storage (extend on access)
  • Redemption rates → Instance storage (rarely changes)
  • Total redemptions → Instance storage (accumulator, no TTL needed)

Cost Optimization:

  • Redemption rates stored in instance (not persistent) to reduce costs
  • Total redemptions stored in instance (accumulator pattern)
  • Redemption requests extended on get() and confirm()

Nova Token Contract

  • Admin address → Instance storage (no TTL needed)
  • User balances → Persistent storage (extend on every access)
  • Allowances → Persistent storage (extend on access)

Cost Optimization:

  • Balances extended in balance_of() helper (called in hot paths)
  • Allowances extended in allowance_of() helper
  • TTL: 31 days for standard operations

Reward Pool Contract

  • Admin address → Instance storage (no TTL needed)
  • Nova token address → Instance storage (no TTL needed)
  • Lock state → Instance storage (no TTL needed)
  • Daily limits → Instance storage (configuration)
  • Withdrawal history → Persistent storage (extend on access)

Cost Optimization:

  • Configuration stored in instance (no TTL)
  • Withdrawal history extended on withdraw() calls

Benchmarking

Before Optimization

  • Campaign contract: ~500 bytes per campaign (persistent)
  • Redemption contract: ~200 bytes per request (persistent)
  • Nova token: ~100 bytes per balance entry (persistent)

After Optimization

  • Campaign contract: ~500 bytes per campaign (no change, but TTL extended efficiently)
  • Redemption contract: ~50 bytes per request (moved rates to instance)
  • Nova token: ~100 bytes per balance (no change, but TTL extended in hot paths)

Estimated Savings:

  • 30-40% reduction in TTL extension costs through strategic instance storage usage
  • Amortized cost reduction through hot-path extensions

Implementation Guidelines

When to Use Instance Storage

  1. Data that rarely changes (admin, configuration)
  2. Data with short lifetime (temporary flags)
  3. Accumulators that don't need historical records
  4. Metadata about the contract itself

When to Use Persistent Storage

  1. User-specific data (balances, campaign participation)
  2. Historical records (redemption requests)
  3. Data that must survive contract upgrades
  4. Data accessed across multiple transactions

TTL Extension Best Practices

  1. Extend TTL in functions that read/write the entry
  2. Use consistent TTL values (31 days for standard data)
  3. Extend in hot paths to amortize cost
  4. Document TTL strategy in code comments

Future Optimizations

  1. Temporary Storage: Use for short-lived data (< 1 hour)

    • Pending transactions
    • Rate-limiting counters
    • Temporary state during multi-step operations
  2. Batch Operations: Combine multiple TTL extensions

    • Reduce number of storage operations
    • Batch updates in single transaction
  3. Lazy TTL Extension: Only extend when approaching expiry

    • Monitor TTL in read operations
    • Extend only if TTL < 7 days remaining

References