diff --git a/apps/kv-store/src/__private.rs b/apps/kv-store/src/__private.rs index 471b84c67..0ddeb6178 100644 --- a/apps/kv-store/src/__private.rs +++ b/apps/kv-store/src/__private.rs @@ -1,7 +1,6 @@ -use calimero_sdk::borsh::{from_slice, to_vec}; +use calimero_sdk::borsh::from_slice; use calimero_sdk::{app, env}; -use calimero_storage::collections::unordered_map::{Entry, UnorderedMap}; -use calimero_storage::entities::Data; +use calimero_storage::address::Id; use calimero_storage::integration::Comparison; use calimero_storage::interface::{Action, Interface, StorageError}; use calimero_storage::sync::{self, SyncArtifact}; @@ -16,66 +15,36 @@ impl KvStore { let artifact = from_slice::(&args).map_err(StorageError::DeserializationError)?; - let this = Interface::root::()?; - match artifact { SyncArtifact::Actions(actions) => { for action in actions { let _ignored = match action { - Action::Add { type_id, .. } | Action::Update { type_id, .. } => { - match type_id { - 1 => Interface::apply_action::(action)?, - 254 => Interface::apply_action::>(action)?, - 255 => { - Interface::apply_action::>(action)? - } - _ => return Err(StorageError::UnknownType(type_id)), - } - } - Action::Delete { .. } => { - todo!("how are we supposed to identify the entity to delete???????") + Action::Compare { id } => { + sync::push_comparison(Comparison { + data: Interface::find_by_id_raw(id)?, + comparison_data: Interface::generate_comparison_data(Some(id))?, + }); } - Action::Compare { .. } => { - todo!("how are we supposed to compare when `Comparison` needs `type_id`???????") + Action::Add { .. } | Action::Update { .. } | Action::Delete { .. } => { + Interface::apply_action(action)?; } }; } - - if let Some(this) = this { - return Interface::commit_root(this); - } } SyncArtifact::Comparisons(comparisons) => { if comparisons.is_empty() { sync::push_comparison(Comparison { - type_id: ::type_id(), - data: this - .as_ref() - .map(to_vec) - .transpose() - .map_err(StorageError::SerializationError)?, - comparison_data: Interface::generate_comparison_data(this.as_ref())?, + data: Interface::find_by_id_raw(Id::root())?, + comparison_data: Interface::generate_comparison_data(None)?, }); } for Comparison { - type_id, data, comparison_data, } in comparisons { - match type_id { - 1 => Interface::compare_affective::(data, comparison_data)?, - 254 => Interface::compare_affective::>( - data, - comparison_data, - )?, - 255 => Interface::compare_affective::>( - data, - comparison_data, - )?, - _ => return Err(StorageError::UnknownType(type_id)), - }; + Interface::compare_affective(data, comparison_data)?; } } } diff --git a/apps/kv-store/src/lib.rs b/apps/kv-store/src/lib.rs index ebe163683..476727983 100644 --- a/apps/kv-store/src/lib.rs +++ b/apps/kv-store/src/lib.rs @@ -2,22 +2,18 @@ use std::collections::BTreeMap; +use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; use calimero_sdk::types::Error; use calimero_sdk::{app, env}; use calimero_storage::collections::UnorderedMap; -use calimero_storage::entities::Element; -use calimero_storage::AtomicUnit; mod __private; #[app::state(emits = for<'a> Event<'a>)] -#[derive(AtomicUnit, Clone, Debug, PartialEq, PartialOrd)] -#[root] -#[type_id(1)] +#[derive(Clone, Debug, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] +#[borsh(crate = "calimero_sdk::borsh")] pub struct KvStore { items: UnorderedMap, - #[storage] - storage: Element, } #[app::event] @@ -33,8 +29,7 @@ impl KvStore { #[app::init] pub fn init() -> KvStore { KvStore { - items: UnorderedMap::new().unwrap(), - storage: Element::root(), + items: UnorderedMap::new(), } } @@ -93,7 +88,10 @@ impl KvStore { app::emit!(Event::Removed { key }); - self.items.remove(key).map_err(Into::into) + self.items + .remove(key) + .map(|v| v.is_some()) + .map_err(Into::into) } pub fn clear(&mut self) -> Result<(), Error> { diff --git a/crates/sdk/macros/src/logic/method.rs b/crates/sdk/macros/src/logic/method.rs index 4f3b83e2d..b41bd4ad8 100644 --- a/crates/sdk/macros/src/logic/method.rs +++ b/crates/sdk/macros/src/logic/method.rs @@ -1,5 +1,6 @@ use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::spanned::Spanned; use syn::{Error as SynError, GenericParam, Ident, ImplItemFn, Path, ReturnType, Visibility}; use crate::errors::{Errors, ParseError}; @@ -61,12 +62,14 @@ impl ToTokens for PublicLogicMethod<'_> { let input_lifetime = if self.has_refs { let lifetime = lifetimes::input(); - quote! { <#lifetime> } + quote_spanned! { name.span()=> + <#lifetime> + } } else { quote! {} }; - quote! { + quote_spanned! {name.span()=> #[derive(::calimero_sdk::serde::Deserialize)] #[serde(crate = "::calimero_sdk::serde")] struct #input_ident #input_lifetime { @@ -94,38 +97,41 @@ impl ToTokens for PublicLogicMethod<'_> { let (def, mut call) = match &self.self_type { Some(type_) => ( { - let mutability = match type_ { - SelfType::Mutable(_) => Some(quote! {mut}), - SelfType::Owned(_) | SelfType::Immutable(_) => None, + let (mutability, ty) = match type_ { + SelfType::Mutable(ty) => (Some(quote! {mut}), ty), + SelfType::Owned(ty) | SelfType::Immutable(ty) => (None, ty), }; - quote! { - let Some(#mutability app) = ::calimero_storage::interface::Interface::root::<#self_>().ok().flatten() + quote_spanned! {ty.span()=> + let Some(#mutability app) = ::calimero_storage::collections::Root::<#self_>::fetch() else { ::calimero_sdk::env::panic_str("Failed to find or read app state") }; } }, - quote! { app.#name(#(#arg_idents),*); }, + quote_spanned! {name.span()=> + app.#name(#(#arg_idents),*) + }, ), None => ( if init_method { - quote! { - if let Some(mut app) = ::calimero_storage::interface::Interface::root::<#self_>().ok().flatten() { + quote_spanned! {name.span()=> + if let Some(mut app) = ::calimero_storage::collections::Root::<#self_>::fetch() { ::calimero_sdk::env::panic_str("Cannot initialize over already existing state.") }; - let mut app: #self_ = + let mut app = } } else { quote! {} }, - quote! { <#self_>::#name(#(#arg_idents),*); }, + quote_spanned! {name.span()=> + <#self_>::#name(#(#arg_idents),*) + }, ), }; - if let (Some(_), false) = (&self.ret, init_method) { - //only when it's not init - call = quote! { + if let (Some(ret), false) = (&self.ret, init_method) { + call = quote_spanned! {ret.ty.span()=> let output = #call; let output = { #[expect(unused_imports)] @@ -140,22 +146,24 @@ impl ToTokens for PublicLogicMethod<'_> { ), } }; - ::calimero_sdk::env::value_return(&output); - }; + ::calimero_sdk::env::value_return(&output) + } } let state_finalizer = match (&self.self_type, init_method) { (Some(SelfType::Mutable(_)), _) | (_, true) => quote! { - if let Err(_) = ::calimero_storage::interface::Interface::commit_root(app) { - ::calimero_sdk::env::panic_str("Failed to commit app state") - } + app.commit(); }, _ => quote! {}, }; // todo! when generics are present, strip them let init_impl = if init_method { - quote! { + call = quote_spanned! {name.span()=> + ::calimero_storage::collections::Root::new(|| #call) + }; + + quote_spanned! {name.span()=> impl ::calimero_sdk::state::AppStateInit for #self_ { type Return = #ret; } @@ -164,7 +172,7 @@ impl ToTokens for PublicLogicMethod<'_> { quote! {} }; - quote! { + quote_spanned! {name.span()=> #[cfg(target_arch = "wasm32")] #[no_mangle] pub extern "C" fn #name() { @@ -176,7 +184,7 @@ impl ToTokens for PublicLogicMethod<'_> { #def - #call + #call; #state_finalizer } diff --git a/crates/storage/src/collections.rs b/crates/storage/src/collections.rs index 1686eaf26..bc90ed095 100644 --- a/crates/storage/src/collections.rs +++ b/crates/storage/src/collections.rs @@ -14,7 +14,8 @@ pub mod unordered_set; pub use unordered_set::UnorderedSet; pub mod vector; pub use vector::Vector; -pub mod root; +mod root; +#[doc(hidden)] pub use root::Root; pub mod error; pub use error::StoreError; @@ -323,6 +324,5 @@ where { fn drop(&mut self) { self.collection.element_mut().update(); - let _ignored = Interface::save(self.collection); } } diff --git a/crates/storage/src/collections/root.rs b/crates/storage/src/collections/root.rs index 2d5d8823c..0a7ea7020 100644 --- a/crates/storage/src/collections/root.rs +++ b/crates/storage/src/collections/root.rs @@ -2,7 +2,9 @@ use std::cell::RefCell; use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; use std::ptr; +use std::sync::LazyLock; use borsh::{BorshDeserialize, BorshSerialize}; @@ -10,10 +12,11 @@ use super::{Collection, Entry}; use crate::address::Id; use crate::entities::Data; use crate::env; +use crate::interface::Interface; /// Thing. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct RootHandle { +pub(super) struct RootHandle { /// The ID of the root collection. pub id: Id, _priv: PhantomData, @@ -41,12 +44,23 @@ thread_local! { pub static ROOT: RefCell>>> = RefCell::new(None); } +static ID: LazyLock = LazyLock::new(|| Id::new(env::context_id())); + +/// Prepares the root collection. +fn employ_root_guard() { + let old = ROOT.with(|root| root.borrow_mut().replace(RootHandle::new(*ID))); + + if old.is_some() { + panic!("root collection already defined"); + } +} + /// A set collection that stores unqiue values once. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Debug)] pub struct Root { - id: Id, inner: Collection, value: RefCell>, + dirty: bool, } impl Root @@ -54,40 +68,86 @@ where T: BorshSerialize + BorshDeserialize, { /// Creates a new root collection with the given value. - pub fn new(value: T) -> Self { - let id = Id::new(env::context_id()); + pub fn new T>(f: F) -> Self { + employ_root_guard(); - let old = ROOT.with(|root| root.borrow_mut().replace(RootHandle::new(id))); + let mut inner = Collection::new(Some(*ID)); - if old.is_some() { - panic!("root collection already defined"); - } - - let mut inner = Collection::new(Some(id)); + let id = Self::entry_id(); - let value = inner.insert(Some(id), value).unwrap(); + let value = inner.insert(Some(id), f()).unwrap(); Self { - id, inner, + dirty: false, value: RefCell::new(Some(value)), } } - /// Gets the value of the root collection. - pub fn get(&self) -> &T { - self.get_mut() + fn entry_id() -> Id { + Id::new([118; 32]) } - /// Gets the value of the root collection mutably. - pub fn get_mut(&self) -> &mut T { + fn get(&self) -> &mut T { let mut value = self.value.borrow_mut(); - let value = value.get_or_insert_with(|| self.inner.get(self.id).unwrap().unwrap()); + let id = Self::entry_id(); + + let value = value.get_or_insert_with(|| self.inner.get(id).unwrap().unwrap()); #[expect(unsafe_code, reason = "necessary for caching")] let value = unsafe { &mut *ptr::from_mut(value) }; value } + + /// Fetches the root collection. + pub fn fetch() -> Option { + employ_root_guard(); + + let inner = Interface::root().unwrap()?; + + Some(Self { + inner, + dirty: false, + value: RefCell::new(None), + }) + } + + /// Commits the root collection. + pub fn commit(mut self) { + let _ignored = ROOT.with(|root| root.borrow_mut().take()); + + if self.dirty { + if let Some(value) = self.value.into_inner() { + if let Some(mut entry) = self.inner.get_mut(Self::entry_id()).unwrap() { + *entry = value; + } + } + } + + Interface::commit_root(self.inner).unwrap(); + } +} + +impl Deref for Root +where + T: BorshSerialize + BorshDeserialize, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl DerefMut for Root +where + T: BorshSerialize + BorshDeserialize, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.dirty = true; + + self.get() + } } diff --git a/crates/storage/src/collections/unordered_map.rs b/crates/storage/src/collections/unordered_map.rs index d122baf3c..005a5a057 100644 --- a/crates/storage/src/collections/unordered_map.rs +++ b/crates/storage/src/collections/unordered_map.rs @@ -202,9 +202,7 @@ mod tests { #[test] fn test_unordered_map_basic_operations() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -253,9 +251,7 @@ mod tests { #[test] fn test_unordered_map_insert_and_get() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key1".to_string(), "value1".to_string()) @@ -278,9 +274,7 @@ mod tests { #[test] fn test_unordered_map_update_value() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -299,9 +293,7 @@ mod tests { #[test] fn test_remove() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -317,9 +309,7 @@ mod tests { #[test] fn test_clear() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key1".to_string(), "value1".to_string()) @@ -338,9 +328,7 @@ mod tests { #[test] fn test_unordered_map_len() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert_eq!(map.len().expect("len failed"), 0); @@ -369,9 +357,7 @@ mod tests { #[test] fn test_unordered_map_contains() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key".to_string(), "value".to_string()) @@ -384,9 +370,7 @@ mod tests { #[test] fn test_unordered_map_entries() { - let _root = Root::new(()); - - let mut map = UnorderedMap::::new(); + let mut map = Root::new(|| UnorderedMap::new()); assert!(map .insert("key1".to_string(), "value1".to_string()) diff --git a/crates/storage/src/collections/unordered_set.rs b/crates/storage/src/collections/unordered_set.rs index cb36a2599..195dcdb1c 100644 --- a/crates/storage/src/collections/unordered_set.rs +++ b/crates/storage/src/collections/unordered_set.rs @@ -175,9 +175,7 @@ mod tests { #[test] fn test_unordered_set_operations() { - let _root = Root::new(()); - - let mut set = UnorderedSet::::new(); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); @@ -205,9 +203,7 @@ mod tests { #[test] fn test_unordered_set_len() { - let _root = Root::new(()); - - let mut set = UnorderedSet::::new(); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); assert!(set.insert("value2".to_string()).expect("insert failed")); @@ -222,9 +218,7 @@ mod tests { #[test] fn test_unordered_set_clear() { - let _root = Root::new(()); - - let mut set = UnorderedSet::::new(); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); assert!(set.insert("value2".to_string()).expect("insert failed")); @@ -240,9 +234,7 @@ mod tests { #[test] fn test_unordered_set_entries() { - let _root = Root::new(()); - - let mut set = UnorderedSet::::new(); + let mut set = Root::new(|| UnorderedSet::new()); assert!(set.insert("value1".to_string()).expect("insert failed")); assert!(set.insert("value2".to_string()).expect("insert failed")); diff --git a/crates/storage/src/collections/vector.rs b/crates/storage/src/collections/vector.rs index c3310e095..701193be9 100644 --- a/crates/storage/src/collections/vector.rs +++ b/crates/storage/src/collections/vector.rs @@ -11,6 +11,7 @@ use crate::collections::error::StoreError; /// A vector collection that stores key-value pairs. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct Vector { + // Borrow/ToOwned inner: Collection, } @@ -163,9 +164,8 @@ mod tests { #[test] fn test_vector_push() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value = "test_data".to_string(); let result = vector.push(value.clone()); assert!(result.is_ok()); @@ -174,9 +174,8 @@ mod tests { #[test] fn test_vector_get() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); let retrieved_value = vector.get(0).unwrap(); @@ -185,9 +184,8 @@ mod tests { #[test] fn test_vector_update() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value1 = "test_data1".to_string(); let value2 = "test_data2".to_string(); let _ = vector.push(value1.clone()).unwrap(); @@ -199,9 +197,8 @@ mod tests { #[test] fn test_vector_get_non_existent() { - let _root = Root::new(()); + let vector = Root::new(|| Vector::::new()); - let vector: Vector = Vector::new(); match vector.get(0) { Ok(retrieved_value) => assert_eq!(retrieved_value, None), Err(e) => panic!("Error occurred: {:?}", e), @@ -210,9 +207,8 @@ mod tests { #[test] fn test_vector_pop() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); let popped_value = vector.pop().unwrap(); @@ -222,9 +218,8 @@ mod tests { #[test] fn test_vector_entries() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value1 = "test_data1".to_string(); let value2 = "test_data2".to_string(); let _ = vector.push(value1.clone()).unwrap(); @@ -235,9 +230,8 @@ mod tests { #[test] fn test_vector_contains() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); assert!(vector.contains(&value).unwrap()); @@ -247,9 +241,8 @@ mod tests { #[test] fn test_vector_clear() { - let _root = Root::new(()); + let mut vector = Root::new(|| Vector::new()); - let mut vector: Vector = Vector::new(); let value = "test_data".to_string(); let _ = vector.push(value.clone()).unwrap(); vector.clear().unwrap(); diff --git a/crates/storage/src/env.rs b/crates/storage/src/env.rs index 44e74874f..1423e57a8 100644 --- a/crates/storage/src/env.rs +++ b/crates/storage/src/env.rs @@ -163,7 +163,7 @@ mod mocked { /// Return the context id. pub(super) const fn context_id() -> [u8; 32] { - [0; 32] + [236; 32] } /// Gets the current time. diff --git a/crates/storage/src/integration.rs b/crates/storage/src/integration.rs index b9281b1c2..aa3a0452e 100644 --- a/crates/storage/src/integration.rs +++ b/crates/storage/src/integration.rs @@ -8,9 +8,6 @@ use crate::interface::ComparisonData; #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[expect(clippy::exhaustive_structs, reason = "Exhaustive")] pub struct Comparison { - /// The type of the entity. - pub type_id: u8, - /// The serialised data of the entity. pub data: Option>,