//! Contract storage abstraction. //! //! Provides a key-value storage interface for contracts with support for: //! - Merkle proofs //! - Storage rent (optional) //! - Efficient updates use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSerialize}; use synor_types::Hash256; use crate::ContractId; /// Storage key (256-bit). #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, BorshSerialize, BorshDeserialize, )] pub struct StorageKey(pub [u8; 32]); impl StorageKey { /// Creates a new storage key. pub fn new(bytes: [u8; 32]) -> Self { StorageKey(bytes) } /// Creates from a hash. pub fn from_hash(hash: &Hash256) -> Self { StorageKey(*hash.as_bytes()) } /// Creates from a string key (hashed). pub fn from_string_key(key: &str) -> Self { let hash: [u8; 32] = blake3::hash(key.as_bytes()).into(); StorageKey(hash) } /// Creates from bytes (padded or hashed). pub fn from_bytes(bytes: &[u8]) -> Self { if bytes.len() == 32 { let mut arr = [0u8; 32]; arr.copy_from_slice(bytes); StorageKey(arr) } else if bytes.len() < 32 { let mut arr = [0u8; 32]; arr[..bytes.len()].copy_from_slice(bytes); StorageKey(arr) } else { let hash: [u8; 32] = blake3::hash(bytes).into(); StorageKey(hash) } } /// Creates a key for a map entry (key + index). pub fn map_key(base: &StorageKey, index: &[u8]) -> Self { let mut input = Vec::with_capacity(32 + index.len()); input.extend_from_slice(&base.0); input.extend_from_slice(index); let hash: [u8; 32] = blake3::hash(&input).into(); StorageKey(hash) } /// Returns as bytes. pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } } /// Storage value (variable length). #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct StorageValue(pub Vec); impl StorageValue { /// Creates a new storage value. pub fn new(data: Vec) -> Self { StorageValue(data) } /// Creates from a u64. pub fn from_u64(value: u64) -> Self { StorageValue(value.to_le_bytes().to_vec()) } /// Creates from a u128. pub fn from_u128(value: u128) -> Self { StorageValue(value.to_le_bytes().to_vec()) } /// Parses as u64. pub fn as_u64(&self) -> Option { if self.0.len() == 8 { let mut arr = [0u8; 8]; arr.copy_from_slice(&self.0); Some(u64::from_le_bytes(arr)) } else { None } } /// Parses as u128. pub fn as_u128(&self) -> Option { if self.0.len() == 16 { let mut arr = [0u8; 16]; arr.copy_from_slice(&self.0); Some(u128::from_le_bytes(arr)) } else { None } } /// Returns the raw bytes. pub fn as_bytes(&self) -> &[u8] { &self.0 } /// Returns the length. pub fn len(&self) -> usize { self.0.len() } /// Checks if empty. pub fn is_empty(&self) -> bool { self.0.is_empty() } } /// Storage access type for metering. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StorageAccess { /// Read an existing value. Read, /// Write a new value. Write, /// Update an existing value. Update, /// Delete a value. Delete, } /// Contract storage interface. pub trait ContractStorage: Send + Sync { /// Gets a value from storage. fn get(&self, contract: &ContractId, key: &StorageKey) -> Option; /// Sets a value in storage. fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue); /// Deletes a value from storage. fn delete(&mut self, contract: &ContractId, key: &StorageKey) -> Option; /// Checks if a key exists. fn contains(&self, contract: &ContractId, key: &StorageKey) -> bool; /// Gets the storage root hash for a contract. fn root(&self, contract: &ContractId) -> Hash256; /// Commits pending changes. fn commit(&mut self); /// Rolls back pending changes. fn rollback(&mut self); } /// In-memory storage implementation. #[derive(Clone, Debug, Default)] pub struct MemoryStorage { /// Storage data by contract. data: HashMap>, /// Pending changes (for rollback). pending: HashMap>>, } impl MemoryStorage { /// Creates a new in-memory storage. pub fn new() -> Self { MemoryStorage { data: HashMap::new(), pending: HashMap::new(), } } /// Gets all keys for a contract. pub fn keys(&self, contract: &ContractId) -> Vec { self.data .get(contract) .map(|m| m.keys().copied().collect()) .unwrap_or_default() } /// Gets all values for a contract. pub fn values(&self, contract: &ContractId) -> Vec<(StorageKey, StorageValue)> { self.data .get(contract) .map(|m| m.iter().map(|(k, v)| (*k, v.clone())).collect()) .unwrap_or_default() } /// Clears all storage for a contract. pub fn clear(&mut self, contract: &ContractId) { self.data.remove(contract); self.pending.remove(contract); } /// Takes pending changes and returns them as StorageChange entries. /// /// This consumes the pending changes without committing them. /// Use this to collect storage changes for execution results. pub fn take_pending_changes(&mut self) -> Vec { let mut changes = Vec::new(); for (contract, pending_changes) in self.pending.drain() { for (key, new_value) in pending_changes { // Get old value from committed data let old_value = self.data.get(&contract).and_then(|m| m.get(&key)).cloned(); changes.push(crate::StorageChange { contract, key, old_value, new_value, }); } } changes } /// Returns pending changes without consuming them. pub fn pending_changes(&self) -> Vec { let mut changes = Vec::new(); for (contract, pending_changes) in &self.pending { for (key, new_value) in pending_changes { // Get old value from committed data let old_value = self.data.get(contract).and_then(|m| m.get(key)).cloned(); changes.push(crate::StorageChange { contract: *contract, key: *key, old_value, new_value: new_value.clone(), }); } } changes } } impl ContractStorage for MemoryStorage { fn get(&self, contract: &ContractId, key: &StorageKey) -> Option { // Check pending first if let Some(contract_pending) = self.pending.get(contract) { if let Some(pending_value) = contract_pending.get(key) { return pending_value.clone(); } } // Then check committed self.data.get(contract).and_then(|m| m.get(key)).cloned() } fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) { self.pending .entry(*contract) .or_default() .insert(key, Some(value)); } fn delete(&mut self, contract: &ContractId, key: &StorageKey) -> Option { let old_value = self.get(contract, key); self.pending .entry(*contract) .or_default() .insert(*key, None); old_value } fn contains(&self, contract: &ContractId, key: &StorageKey) -> bool { self.get(contract, key).is_some() } fn root(&self, contract: &ContractId) -> Hash256 { // Simple merkle root of sorted key-value pairs let mut entries: Vec<_> = self .data .get(contract) .map(|m| m.iter().collect()) .unwrap_or_default(); entries.sort_by_key(|(k, _)| *k); if entries.is_empty() { return Hash256::default(); } let mut hasher_input = Vec::new(); for (key, value) in entries { hasher_input.extend_from_slice(&key.0); hasher_input.extend_from_slice(&value.0); } Hash256::from_bytes(blake3::hash(&hasher_input).into()) } fn commit(&mut self) { for (contract, changes) in self.pending.drain() { let contract_data = self.data.entry(contract).or_default(); for (key, value) in changes { match value { Some(v) => { contract_data.insert(key, v); } None => { contract_data.remove(&key); } } } } } fn rollback(&mut self) { self.pending.clear(); } } /// Storage with change tracking. #[derive(Debug)] pub struct TrackedStorage { /// Underlying storage. inner: S, /// Tracked reads. reads: Vec<(ContractId, StorageKey)>, /// Tracked writes. writes: Vec<(ContractId, StorageKey, Option)>, } impl TrackedStorage { /// Creates a new tracked storage wrapper. pub fn new(inner: S) -> Self { TrackedStorage { inner, reads: Vec::new(), writes: Vec::new(), } } /// Gets recorded reads. pub fn reads(&self) -> &[(ContractId, StorageKey)] { &self.reads } /// Gets recorded writes. pub fn writes(&self) -> &[(ContractId, StorageKey, Option)] { &self.writes } /// Clears tracking data. pub fn clear_tracking(&mut self) { self.reads.clear(); self.writes.clear(); } /// Unwraps the inner storage. pub fn into_inner(self) -> S { self.inner } } impl ContractStorage for TrackedStorage { fn get(&self, contract: &ContractId, key: &StorageKey) -> Option { // Note: Can't track reads here due to &self, would need interior mutability self.inner.get(contract, key) } fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) { self.writes.push((*contract, key, Some(value.clone()))); self.inner.set(contract, key, value); } fn delete(&mut self, contract: &ContractId, key: &StorageKey) -> Option { self.writes.push((*contract, *key, None)); self.inner.delete(contract, key) } fn contains(&self, contract: &ContractId, key: &StorageKey) -> bool { self.inner.contains(contract, key) } fn root(&self, contract: &ContractId) -> Hash256 { self.inner.root(contract) } fn commit(&mut self) { self.inner.commit(); } fn rollback(&mut self) { self.inner.rollback(); } } /// Snapshot of storage state for rollback. #[derive(Clone, Debug)] pub struct StorageSnapshot { /// Contract data at snapshot time. data: HashMap>, } impl StorageSnapshot { /// Creates a snapshot from memory storage. pub fn from_memory(storage: &MemoryStorage) -> Self { StorageSnapshot { data: storage.data.clone(), } } /// Restores memory storage from snapshot. pub fn restore(&self, storage: &mut MemoryStorage) { storage.data = self.data.clone(); storage.pending.clear(); } } #[cfg(test)] mod tests { use super::*; fn test_contract_id() -> ContractId { ContractId::from_bytes([0x42; 32]) } #[test] fn test_storage_key() { let key1 = StorageKey::from_string_key("balance"); let key2 = StorageKey::from_string_key("balance"); assert_eq!(key1, key2); let key3 = StorageKey::from_string_key("other"); assert_ne!(key1, key3); } #[test] fn test_storage_value() { let value = StorageValue::from_u64(12345); assert_eq!(value.as_u64(), Some(12345)); let value = StorageValue::from_u128(u128::MAX); assert_eq!(value.as_u128(), Some(u128::MAX)); } #[test] fn test_map_key() { let base = StorageKey::from_string_key("balances"); let index = [1u8; 32]; let key1 = StorageKey::map_key(&base, &index); let key2 = StorageKey::map_key(&base, &[2u8; 32]); assert_ne!(key1, key2); } #[test] fn test_memory_storage() { let mut storage = MemoryStorage::new(); let contract = test_contract_id(); let key = StorageKey::from_string_key("test"); // Write storage.set(&contract, key, StorageValue::from_u64(42)); // Read pending assert_eq!(storage.get(&contract, &key).unwrap().as_u64(), Some(42)); // Commit storage.commit(); // Read committed assert_eq!(storage.get(&contract, &key).unwrap().as_u64(), Some(42)); // Delete storage.delete(&contract, &key); storage.commit(); assert!(storage.get(&contract, &key).is_none()); } #[test] fn test_storage_rollback() { let mut storage = MemoryStorage::new(); let contract = test_contract_id(); let key = StorageKey::from_string_key("test"); storage.set(&contract, key, StorageValue::from_u64(42)); storage.commit(); storage.set(&contract, key, StorageValue::from_u64(100)); storage.rollback(); // Should still be 42 assert_eq!(storage.get(&contract, &key).unwrap().as_u64(), Some(42)); } #[test] fn test_storage_root() { let mut storage = MemoryStorage::new(); let contract = test_contract_id(); let root1 = storage.root(&contract); storage.set( &contract, StorageKey::from_string_key("a"), StorageValue::from_u64(1), ); storage.commit(); let root2 = storage.root(&contract); assert_ne!(root1, root2); storage.set( &contract, StorageKey::from_string_key("b"), StorageValue::from_u64(2), ); storage.commit(); let root3 = storage.root(&contract); assert_ne!(root2, root3); } }