Skip to main content

truthlinked_sdk/
backend.rs

1//! Storage backend abstraction for contract state persistence.
2//!
3//! This module provides a trait-based abstraction over storage operations,
4//! enabling both production (on-chain) and testing (in-memory) backends.
5//!
6//! # Storage Backends
7//!
8//! - **`HostStorage`**: Production backend that delegates to WASM host imports.
9//!   Used when contracts run on-chain.
10//! - **`MemoryStorage`**: In-memory backend using `BTreeMap` for testing.
11//!   Enables fast, isolated unit tests without WASM runtime.
12//!
13//! # Example
14//!
15//! ```ignore
16//! use truthlinked_sdk::backend::{StorageBackend, MemoryStorage};
17//!
18//! // Testing with in-memory storage
19//! let mut storage = MemoryStorage::new();
20//! let slot = [1u8; 32];
21//! storage.write_32(slot, [42u8; 32])?;
22//! let value = storage.read_32(&slot)?;
23//! assert_eq!(value[0], 42);
24//! ```
25
26extern crate alloc;
27
28use alloc::collections::BTreeMap;
29
30use crate::env;
31use crate::error::Result;
32
33/// Trait for storage backends that support 32-byte slot read/write operations.
34///
35/// All storage in TruthLinked contracts operates on fixed 32-byte slots.
36/// This trait abstracts the underlying storage mechanism, allowing contracts
37/// to work with both on-chain storage (via WASM host) and in-memory storage (for testing).
38pub trait StorageBackend {
39    /// Reads a 32-byte value from the specified storage slot.
40    ///
41    /// # Arguments
42    ///
43    /// * `key` - The 32-byte storage slot address
44    ///
45    /// # Returns
46    ///
47    /// The 32-byte value stored at the slot, or `[0u8; 32]` if uninitialized.
48    fn read_32(&self, key: &[u8; 32]) -> Result<[u8; 32]>;
49
50    /// Writes a 32-byte value to the specified storage slot.
51    ///
52    /// # Arguments
53    ///
54    /// * `key` - The 32-byte storage slot address
55    /// * `value` - The 32-byte value to store
56    fn write_32(&mut self, key: [u8; 32], value: [u8; 32]) -> Result<()>;
57}
58
59/// Storage backend that delegates to WASM host imports.
60///
61/// This is a zero-sized type that forwards all storage operations to the
62/// TruthLinked runtime via host function calls. Used when contracts execute on-chain.
63///
64/// # Example
65///
66/// ```ignore
67/// use truthlinked_sdk::backend::{HostStorage, StorageBackend};
68///
69/// let storage = HostStorage;
70/// let value = storage.read_32(&slot)?;
71/// ```
72#[derive(Clone, Copy, Debug, Default)]
73pub struct HostStorage;
74
75impl StorageBackend for HostStorage {
76    fn read_32(&self, key: &[u8; 32]) -> Result<[u8; 32]> {
77        env::storage_read_32(key)
78    }
79
80    fn write_32(&mut self, key: [u8; 32], value: [u8; 32]) -> Result<()> {
81        env::storage_write_32(&key, &value)
82    }
83}
84
85/// In-memory storage backend for testing and development.
86///
87/// Uses a `BTreeMap` to store slot values in memory. This enables fast,
88/// isolated unit tests without requiring a WASM runtime or blockchain.
89///
90/// # Example
91///
92/// ```ignore
93/// use truthlinked_sdk::backend::MemoryStorage;
94///
95/// let mut storage = MemoryStorage::new()
96///     .with_slot([1u8; 32], [42u8; 32])
97///     .with_slot([2u8; 32], [99u8; 32]);
98///
99/// let snapshot = storage.snapshot(); // Clone current state
100/// ```
101#[derive(Clone, Debug, Default)]
102pub struct MemoryStorage {
103    slots: BTreeMap<[u8; 32], [u8; 32]>,
104}
105
106impl MemoryStorage {
107    /// Creates a new empty in-memory storage backend.
108    pub fn new() -> Self {
109        Self {
110            slots: BTreeMap::new(),
111        }
112    }
113
114    /// Builder method to pre-populate a storage slot.
115    ///
116    /// Useful for setting up test fixtures with initial state.
117    ///
118    /// # Arguments
119    ///
120    /// * `key` - Storage slot address
121    /// * `value` - Initial value for the slot
122    ///
123    /// # Example
124    ///
125    /// ```ignore
126    /// let storage = MemoryStorage::new()
127    ///     .with_slot([1u8; 32], [42u8; 32]);
128    /// ```
129    pub fn with_slot(mut self, key: [u8; 32], value: [u8; 32]) -> Self {
130        self.slots.insert(key, value);
131        self
132    }
133
134    /// Creates a snapshot of the current storage state.
135    ///
136    /// Returns a clone of the internal slot map. Useful for:
137    /// - Saving state before mutations
138    /// - Comparing state changes in tests
139    /// - Debugging storage operations
140    ///
141    /// # Returns
142    ///
143    /// A `BTreeMap` containing all current slot values.
144    pub fn snapshot(&self) -> BTreeMap<[u8; 32], [u8; 32]> {
145        self.slots.clone()
146    }
147}
148
149impl StorageBackend for MemoryStorage {
150    fn read_32(&self, key: &[u8; 32]) -> Result<[u8; 32]> {
151        Ok(self.slots.get(key).copied().unwrap_or([0u8; 32]))
152    }
153
154    fn write_32(&mut self, key: [u8; 32], value: [u8; 32]) -> Result<()> {
155        self.slots.insert(key, value);
156        Ok(())
157    }
158}