From 66721ca134cd20295446f8a7884454ee1e0d7c71 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Tue, 20 Feb 2024 18:24:59 +0200 Subject: [PATCH 1/2] feat: object(generic type) to id mapper --- .../basic-features/src/basic_features_main.rs | 2 + .../src/storage_mapper_object_to_id.rs | 56 ++++++ framework/base/src/storage/mappers.rs | 2 + .../storage/mappers/object_to_id_mapper.rs | 187 ++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs create mode 100644 framework/base/src/storage/mappers/object_to_id_mapper.rs diff --git a/contracts/feature-tests/basic-features/src/basic_features_main.rs b/contracts/feature-tests/basic-features/src/basic_features_main.rs index 3375fda456..fd4af1e31c 100644 --- a/contracts/feature-tests/basic-features/src/basic_features_main.rs +++ b/contracts/feature-tests/basic-features/src/basic_features_main.rs @@ -28,6 +28,7 @@ pub mod storage_mapper_linked_list; pub mod storage_mapper_map; pub mod storage_mapper_map_storage; pub mod storage_mapper_non_fungible_token; +pub mod storage_mapper_object_to_id; pub mod storage_mapper_queue; pub mod storage_mapper_set; pub mod storage_mapper_single; @@ -78,6 +79,7 @@ pub trait BasicFeatures: + non_zero_features::TypeFeatures + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + storage_mapper_get_at_address::StorageMapperGetAtAddress + + storage_mapper_object_to_id::ObjectToIdMapperFeatures { #[init] fn init(&self) {} diff --git a/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs b/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs new file mode 100644 index 0000000000..a7e386b231 --- /dev/null +++ b/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs @@ -0,0 +1,56 @@ +use crate::types::ExampleStructManaged; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ObjectToIdMapperFeatures { + #[endpoint] + fn object_to_id_mapper_get_id(&self, object: ExampleStructManaged) -> AddressId { + self.object_ids().get_id(&object) + } + + #[endpoint] + fn object_to_id_mapper_get_object( + &self, + object_id: ObjectId, + ) -> Option> { + self.object_ids().get_object(object_id) + } + + #[endpoint] + fn object_to_id_mapper_contains(&self, object_id: ObjectId) -> bool { + self.object_ids().contains_id(object_id) + } + + #[endpoint] + fn object_to_id_mapper_set(&self, object: &ExampleStructManaged) -> AddressId { + self.object_ids().insert_new(object) + } + + #[endpoint] + fn object_to_id_mapper_get_id_or_insert( + &self, + object: ExampleStructManaged, + ) -> AddressId { + self.object_ids().get_id_or_insert(object) + } + + #[endpoint] + fn object_to_id_mapper_remove_by_id( + &self, + object_id: ObjectId, + ) -> Option> { + self.object_ids().remove_by_id(object_id) + } + + #[endpoint] + fn address_to_id_mapper_remove_by_address( + &self, + object: ExampleStructManaged, + ) -> AddressId { + self.object_ids().remove_by_object(&object) + } + + #[storage_mapper("object_ids")] + fn object_ids(&self) -> ObjectToIdMapper>; +} diff --git a/framework/base/src/storage/mappers.rs b/framework/base/src/storage/mappers.rs index 5b220073b8..d7d70c4086 100644 --- a/framework/base/src/storage/mappers.rs +++ b/framework/base/src/storage/mappers.rs @@ -4,6 +4,7 @@ mod linked_list_mapper; mod map_mapper; mod map_storage_mapper; mod mapper; +mod object_to_id_mapper; mod queue_mapper; mod set_mapper; mod single_value_mapper; @@ -20,6 +21,7 @@ pub use linked_list_mapper::{LinkedListMapper, LinkedListNode}; pub use map_mapper::MapMapper; pub use map_storage_mapper::MapStorageMapper; pub use mapper::{StorageClearable, StorageMapper}; +pub use object_to_id_mapper::{ObjectId, ObjectToIdMapper}; pub use queue_mapper::QueueMapper; pub use set_mapper::SetMapper; pub use single_value_mapper::{SingleValue, SingleValueMapper}; diff --git a/framework/base/src/storage/mappers/object_to_id_mapper.rs b/framework/base/src/storage/mappers/object_to_id_mapper.rs new file mode 100644 index 0000000000..f56a59c6b2 --- /dev/null +++ b/framework/base/src/storage/mappers/object_to_id_mapper.rs @@ -0,0 +1,187 @@ +use core::{borrow::Borrow, marker::PhantomData}; + +use multiversx_sc_codec::{NestedDecode, NestedEncode, TopDecode, TopEncode}; + +use crate::{ + api::StorageMapperApi, + imports::{ErrorApiImpl, ManagedType}, + storage::StorageKey, + storage_clear, storage_set, +}; + +use super::{ + set_mapper::{CurrentStorage, StorageAddress}, + StorageMapper, +}; + +static ID_SUFFIX: &[u8] = b"id"; +static OBJECT_SUFFIX: &[u8] = b"object"; +static LAST_ID_SUFFIX: &[u8] = b"lastId"; + +pub type ObjectId = u64; +pub const NULL_ID: ObjectId = 0; + +pub struct ObjectToIdMapper +where + SA: StorageMapperApi, + A: StorageAddress, + T: TopEncode + TopDecode + NestedEncode + NestedDecode + 'static, +{ + _phantom_api: PhantomData, + _phantom_item: PhantomData, + address: A, + base_key: StorageKey, +} + +impl StorageMapper for ObjectToIdMapper +where + SA: StorageMapperApi, + T: TopEncode + TopDecode + NestedEncode + NestedDecode, +{ + #[inline] + fn new(base_key: StorageKey) -> Self { + ObjectToIdMapper { + _phantom_api: PhantomData, + _phantom_item: PhantomData, + address: CurrentStorage, + base_key, + } + } +} + +impl ObjectToIdMapper +where + SA: StorageMapperApi, + A: StorageAddress, + T: TopEncode + TopDecode + NestedEncode + NestedDecode, +{ + pub fn contains_id(&self, id: ObjectId) -> bool { + let key = self.id_to_object_key(id); + self.address.address_storage_get_len(key.as_ref()) != 0 + } + + pub fn get_id(&self, object: BT) -> ObjectId + where + BT: Borrow, + { + let key = self.object_to_id_key(object); + self.address.address_storage_get(key.as_ref()) + } + + pub fn get_object(&self, id: ObjectId) -> Option { + let key = self.id_to_object_key(id); + if self.address.address_storage_get_len(key.as_ref()) == 0 { + return None; + } + let object = self.address.address_storage_get(key.as_ref()); + Some(object) + } + + fn id_to_object_key(&self, id: ObjectId) -> StorageKey { + let mut item_key = self.base_key.clone(); + item_key.append_bytes(ID_SUFFIX); + item_key.append_item(&id); + + item_key + } + + fn object_to_id_key(&self, object: BT) -> StorageKey + where + BT: Borrow, + { + let mut item_key = self.base_key.clone(); + item_key.append_bytes(OBJECT_SUFFIX); + item_key.append_item(object.borrow()); + + item_key + } + + fn last_id_key(&self) -> StorageKey { + let mut item_key = self.base_key.clone(); + item_key.append_bytes(LAST_ID_SUFFIX); + + item_key + } + + pub fn get_last_id(&self) -> ObjectId { + self.address + .address_storage_get(self.last_id_key().as_ref()) + } +} + +impl ObjectToIdMapper +where + SA: StorageMapperApi, + T: TopEncode + TopDecode + NestedEncode + NestedDecode, +{ + pub fn get_id_or_insert(&self, object: T) -> ObjectId { + let current_id = self + .address + .address_storage_get(self.object_to_id_key(&object).as_ref()); + if current_id != 0 { + return current_id; + } + + self.insert_object(object) + } + + pub fn insert_new(&self, object: BT) -> ObjectId + where + BT: Borrow, + { + let existing_id = self.get_id(object.borrow()); + if existing_id != NULL_ID { + SA::error_api_impl().signal_error(b"Object already registered"); + } + + self.insert_object(object) + } + + pub fn remove_by_id(&self, id: ObjectId) -> Option { + let object = self.get_object(id)?; + self.remove_entry(id, &object); + + Some(object) + } + + pub fn remove_by_object(&self, object: BT) -> ObjectId + where + BT: Borrow, + { + let current_id = self.get_id(object.borrow()); + if current_id != NULL_ID { + self.remove_entry(current_id, object); + } + + current_id + } + + fn insert_object(&self, object: BT) -> ObjectId + where + BT: Borrow, + { + let new_id = self.get_last_id() + 1; + storage_set(self.id_to_object_key(new_id).as_ref(), &object.borrow()); + storage_set(self.object_to_id_key(object).as_ref(), &new_id); + + self.set_last_id(new_id); + + new_id + } + + fn set_last_id(&self, last_id: ObjectId) { + if last_id == 0 { + SA::error_api_impl().signal_error(b"ID Overflow"); + } + + storage_set(self.last_id_key().as_ref(), &last_id); + } + + fn remove_entry(&self, id: ObjectId, object: BT) + where + BT: Borrow, + { + storage_clear(self.object_to_id_key(object).as_ref()); + storage_clear(self.id_to_object_key(id).as_ref()); + } +} From d7e944b46b7de6152ad8cd652e55ad41f1e1502a Mon Sep 17 00:00:00 2001 From: Bucur David Date: Fri, 15 Mar 2024 08:43:06 +0200 Subject: [PATCH 2/2] feat: get id or error --- .../src/storage_mapper_object_to_id.rs | 8 ++++++++ .../src/storage/mappers/object_to_id_mapper.rs | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs b/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs index a7e386b231..b1fea81df9 100644 --- a/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs +++ b/contracts/feature-tests/basic-features/src/storage_mapper_object_to_id.rs @@ -9,6 +9,14 @@ pub trait ObjectToIdMapperFeatures { self.object_ids().get_id(&object) } + #[endpoint] + fn object_to_id_mapper_get_id_non_zero( + &self, + object: ExampleStructManaged, + ) -> AddressId { + self.object_ids().get_id_non_zero(&object) + } + #[endpoint] fn object_to_id_mapper_get_object( &self, diff --git a/framework/base/src/storage/mappers/object_to_id_mapper.rs b/framework/base/src/storage/mappers/object_to_id_mapper.rs index f56a59c6b2..ec32be57e8 100644 --- a/framework/base/src/storage/mappers/object_to_id_mapper.rs +++ b/framework/base/src/storage/mappers/object_to_id_mapper.rs @@ -16,6 +16,7 @@ use super::{ static ID_SUFFIX: &[u8] = b"id"; static OBJECT_SUFFIX: &[u8] = b"object"; +static UNKNOW_OBJECT_ERR_MSG: &[u8] = b"Unknown object"; static LAST_ID_SUFFIX: &[u8] = b"lastId"; pub type ObjectId = u64; @@ -68,6 +69,17 @@ where self.address.address_storage_get(key.as_ref()) } + pub fn get_id_non_zero(&self, object: BT) -> ObjectId + where + BT: Borrow, + { + let id = self.get_id(object); + if id == NULL_ID { + SA::error_api_impl().signal_error(UNKNOW_OBJECT_ERR_MSG); + } + id + } + pub fn get_object(&self, id: ObjectId) -> Option { let key = self.id_to_object_key(id); if self.address.address_storage_get_len(key.as_ref()) == 0 { @@ -131,7 +143,7 @@ where { let existing_id = self.get_id(object.borrow()); if existing_id != NULL_ID { - SA::error_api_impl().signal_error(b"Object already registered"); + SA::error_api_impl().signal_error(b"Object already exists"); } self.insert_object(object)