From efa4a687f43243a90defaac77eae78d759c7810e Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 16 Dec 2024 16:50:44 +0100 Subject: [PATCH 1/5] wip: Storage for RefMut --- examples/demo/app/src/counter_storage/mod.rs | 41 ++++++++++++++++++++ examples/demo/app/src/lib.rs | 9 +++++ examples/demo/app/tests/fixture/mod.rs | 6 ++- examples/demo/app/tests/gtest.rs | 30 ++++++++++++++ examples/demo/client/demo.idl | 9 +++++ rs/src/gstd/mod.rs | 1 + rs/src/gstd/storage.rs | 35 +++++++++++++++++ rs/src/prelude.rs | 5 ++- 8 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 examples/demo/app/src/counter_storage/mod.rs create mode 100644 rs/src/gstd/storage.rs diff --git a/examples/demo/app/src/counter_storage/mod.rs b/examples/demo/app/src/counter_storage/mod.rs new file mode 100644 index 000000000..56c1812b3 --- /dev/null +++ b/examples/demo/app/src/counter_storage/mod.rs @@ -0,0 +1,41 @@ +use sails_rs::prelude::*; + +pub struct Service { + storage: BoxedStorage, +} + +impl Service { + pub fn new(storage: impl Storage + 'static) -> Self { + Self { + storage: Box::new(storage), + } + } + + pub fn from_accessor>() -> Self { + Self { + storage: T::boxed(), + } + } +} + +#[service(events = Event)] +impl Service { + pub fn bump(&mut self) { + let state = self.storage.get_mut(); + + *state = state.saturating_add(1); + + self.notify_on(Event::Bumped).expect("unable to emit event"); + } + + pub fn get(&self) -> u128 { + *self.storage.get() + } +} + +#[derive(Clone, Debug, Encode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +enum Event { + Bumped, +} diff --git a/examples/demo/app/src/lib.rs b/examples/demo/app/src/lib.rs index 7c12a7f8c..4ef5a19c4 100644 --- a/examples/demo/app/src/lib.rs +++ b/examples/demo/app/src/lib.rs @@ -4,6 +4,7 @@ use demo_walker as walker; use sails_rs::{cell::RefCell, prelude::*}; mod counter; +mod counter_storage; mod dog; mod mammal; mod ping; @@ -30,6 +31,7 @@ pub struct DemoProgram { // Counter data has the same lifetime as the program itself, i.e. it will // live as long as the program is available on the network. counter_data: RefCell, + counter_storage: RefCell, } #[program] @@ -45,6 +47,7 @@ impl DemoProgram { } Self { counter_data: RefCell::new(counter::CounterData::new(Default::default())), + counter_storage: RefCell::new(0u128), } } @@ -60,6 +63,7 @@ impl DemoProgram { } Ok(Self { counter_data: RefCell::new(counter::CounterData::new(counter.unwrap_or_default())), + counter_storage: RefCell::new(0u128), }) } @@ -74,6 +78,11 @@ impl DemoProgram { counter::CounterService::new(&self.counter_data) } + pub fn counter_storage(&'static self) -> counter_storage::Service { + let data = self.counter_storage.borrow_mut(); + counter_storage::Service::new(data) + } + // Exposing yet another service pub fn dog(&self) -> dog::DogService { dog::DogService::new(walker::WalkerService::new(dog_data())) diff --git a/examples/demo/app/tests/fixture/mod.rs b/examples/demo/app/tests/fixture/mod.rs index 21a304ae7..dc217c592 100644 --- a/examples/demo/app/tests/fixture/mod.rs +++ b/examples/demo/app/tests/fixture/mod.rs @@ -1,7 +1,7 @@ use demo_client::{ counter::{self, events::CounterEvents}, dog::{self, events::DogEvents}, - Counter, DemoFactory, Dog, References, ValueFee, + Counter, CounterStorage, DemoFactory, Dog, References, ValueFee, }; use sails_rs::{events::Listener, gtest::calls::*, gtest::System, prelude::*}; @@ -45,6 +45,10 @@ impl Fixture { counter::events::listener(self.program_space.clone()) } + pub(crate) fn counter_storage_client(&self) -> CounterStorage { + CounterStorage::new(self.program_space.clone()) + } + pub(crate) fn dog_client(&self) -> Dog { Dog::new(self.program_space.clone()) } diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index ceac6ca10..ad1869706 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -483,3 +483,33 @@ async fn value_fee_works() { && initial_balance - balance < 10_100_000_000_000 ); } + +#[tokio::test] +async fn counter_storage_works() { + // Arrange + let fixture = Fixture::new(); + + let demo_factory = fixture.demo_factory(); + + // Use generated client code for activating Demo program + // using the `new` constructor and the `send_recv` method + let demo_program_id = demo_factory + .new(Some(42), None) + .send_recv(fixture.demo_code_id(), "123") + .await + .unwrap(); + + let mut counter_client = fixture.counter_storage_client(); + + // Act + counter_client + .bump() + .send_recv(demo_program_id) + .await + .unwrap(); + + let result = counter_client.get().recv(demo_program_id).await.unwrap(); + + // Asert + assert_eq!(result, 1); +} diff --git a/examples/demo/client/demo.idl b/examples/demo/client/demo.idl index d7cc67282..b269c5573 100644 --- a/examples/demo/client/demo.idl +++ b/examples/demo/client/demo.idl @@ -44,6 +44,15 @@ service Counter { } }; +service CounterStorage { + Bump : () -> null; + query Get : () -> u128; + + events { + Bumped; + } +}; + service Dog { MakeSound : () -> str; Walk : (dx: i32, dy: i32) -> null; diff --git a/rs/src/gstd/mod.rs b/rs/src/gstd/mod.rs index 4da72b504..7d5a7b734 100644 --- a/rs/src/gstd/mod.rs +++ b/rs/src/gstd/mod.rs @@ -12,6 +12,7 @@ use core::cell::OnceCell; pub mod calls; pub mod events; pub mod services; +pub mod storage; // TODO: To be renamed into SysCalls or something similar pub trait ExecContext { diff --git a/rs/src/gstd/storage.rs b/rs/src/gstd/storage.rs new file mode 100644 index 000000000..90265cae7 --- /dev/null +++ b/rs/src/gstd/storage.rs @@ -0,0 +1,35 @@ +use crate::boxed::Box; +use core::{ + cell::RefMut, + ops::{Deref, DerefMut}, +}; + +pub type BoxedStorage = Box>; + +pub trait Storage { + type Item; + + fn get(&self) -> &Self::Item; + + fn get_mut(&mut self) -> &mut Self::Item; +} + +pub trait StorageAccessor { + fn get() -> impl Storage + 'static; + + fn boxed() -> BoxedStorage { + Box::new(Self::get()) + } +} + +impl Storage for RefMut<'_, T> { + type Item = T; + + fn get(&self) -> &Self::Item { + self.deref() + } + + fn get_mut(&mut self) -> &mut Self::Item { + self.deref_mut() + } +} diff --git a/rs/src/prelude.rs b/rs/src/prelude.rs index 45e104c1d..bf719d0dc 100644 --- a/rs/src/prelude.rs +++ b/rs/src/prelude.rs @@ -42,7 +42,10 @@ pub mod ffi { } #[cfg(feature = "gstd")] -pub use crate::gstd::{export, program, route, service, CommandReply}; +pub use crate::gstd::{ + export, program, route, service, storage::BoxedStorage, storage::Storage, + storage::StorageAccessor, CommandReply, +}; pub use crate::types::*; pub use parity_scale_codec::{self as scale_codec, Decode, Encode, EncodeLike}; From fcab5198fc1c6e71fbe1e01f706bfe74f59159e8 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 16 Dec 2024 19:23:05 +0100 Subject: [PATCH 2/5] wip: StorageAccessor & SyncUnsafeCell --- examples/demo/app/src/counter_storage/mod.rs | 4 +- examples/demo/app/src/lib.rs | 7 ++ examples/demo/app/tests/fixture/mod.rs | 6 +- examples/demo/app/tests/gtest.rs | 30 +++++ examples/demo/client/demo.idl | 9 ++ rs/src/gstd/storage.rs | 109 ++++++++++++++++++- rs/src/prelude.rs | 2 +- 7 files changed, 159 insertions(+), 8 deletions(-) diff --git a/examples/demo/app/src/counter_storage/mod.rs b/examples/demo/app/src/counter_storage/mod.rs index 56c1812b3..ce33c1252 100644 --- a/examples/demo/app/src/counter_storage/mod.rs +++ b/examples/demo/app/src/counter_storage/mod.rs @@ -11,9 +11,9 @@ impl Service { } } - pub fn from_accessor>() -> Self { + pub fn from_accessor>(accessor: &'static T) -> Self { Self { - storage: T::boxed(), + storage: accessor.boxed(), } } } diff --git a/examples/demo/app/src/lib.rs b/examples/demo/app/src/lib.rs index 4ef5a19c4..5c4810f02 100644 --- a/examples/demo/app/src/lib.rs +++ b/examples/demo/app/src/lib.rs @@ -17,6 +17,7 @@ mod value_fee; // of using a global variable here. It is just a demonstration of how to use global variables. static mut DOG_DATA: Option> = None; static mut REF_DATA: u8 = 42; +static STORAGE_CELL: SyncUnsafeCell = SyncUnsafeCell::new(0u128); #[allow(static_mut_refs)] fn dog_data() -> &'static RefCell { @@ -81,6 +82,12 @@ impl DemoProgram { pub fn counter_storage(&'static self) -> counter_storage::Service { let data = self.counter_storage.borrow_mut(); counter_storage::Service::new(data) + // can be simplified to + //counter_storage::Service::from_accessor(&self.counter_storage) + } + + pub fn counter_storage_cell(&'static self) -> counter_storage::Service { + counter_storage::Service::from_accessor(&STORAGE_CELL) } // Exposing yet another service diff --git a/examples/demo/app/tests/fixture/mod.rs b/examples/demo/app/tests/fixture/mod.rs index dc217c592..1ba59aafc 100644 --- a/examples/demo/app/tests/fixture/mod.rs +++ b/examples/demo/app/tests/fixture/mod.rs @@ -1,7 +1,7 @@ use demo_client::{ counter::{self, events::CounterEvents}, dog::{self, events::DogEvents}, - Counter, CounterStorage, DemoFactory, Dog, References, ValueFee, + Counter, CounterStorage, CounterStorageCell, DemoFactory, Dog, References, ValueFee, }; use sails_rs::{events::Listener, gtest::calls::*, gtest::System, prelude::*}; @@ -49,6 +49,10 @@ impl Fixture { CounterStorage::new(self.program_space.clone()) } + pub(crate) fn counter_storage_cell_client(&self) -> CounterStorageCell { + CounterStorageCell::new(self.program_space.clone()) + } + pub(crate) fn dog_client(&self) -> Dog { Dog::new(self.program_space.clone()) } diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index ad1869706..405e423d7 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -513,3 +513,33 @@ async fn counter_storage_works() { // Asert assert_eq!(result, 1); } + +#[tokio::test] +async fn counter_storage_cell_works() { + // Arrange + let fixture = Fixture::new(); + + let demo_factory = fixture.demo_factory(); + + // Use generated client code for activating Demo program + // using the `new` constructor and the `send_recv` method + let demo_program_id = demo_factory + .new(Some(42), None) + .send_recv(fixture.demo_code_id(), "123") + .await + .unwrap(); + + let mut counter_client = fixture.counter_storage_cell_client(); + + // Act + counter_client + .bump() + .send_recv(demo_program_id) + .await + .unwrap(); + + let result = counter_client.get().recv(demo_program_id).await.unwrap(); + + // Asert + assert_eq!(result, 1); +} diff --git a/examples/demo/client/demo.idl b/examples/demo/client/demo.idl index b269c5573..f896dfa09 100644 --- a/examples/demo/client/demo.idl +++ b/examples/demo/client/demo.idl @@ -53,6 +53,15 @@ service CounterStorage { } }; +service CounterStorageCell { + Bump : () -> null; + query Get : () -> u128; + + events { + Bumped; + } +}; + service Dog { MakeSound : () -> str; Walk : (dx: i32, dy: i32) -> null; diff --git a/rs/src/gstd/storage.rs b/rs/src/gstd/storage.rs index 90265cae7..34f4b40e8 100644 --- a/rs/src/gstd/storage.rs +++ b/rs/src/gstd/storage.rs @@ -1,7 +1,8 @@ use crate::boxed::Box; use core::{ - cell::RefMut, + cell::{RefCell, RefMut, UnsafeCell}, ops::{Deref, DerefMut}, + ptr::NonNull, }; pub type BoxedStorage = Box>; @@ -15,10 +16,22 @@ pub trait Storage { } pub trait StorageAccessor { - fn get() -> impl Storage + 'static; + fn get(&'static self) -> impl Storage + 'static; - fn boxed() -> BoxedStorage { - Box::new(Self::get()) + fn boxed(&'static self) -> BoxedStorage { + Box::new(self.get()) + } +} + +impl Storage for &mut T { + type Item = T; + + fn get(&self) -> &Self::Item { + self + } + + fn get_mut(&mut self) -> &mut Self::Item { + self } } @@ -33,3 +46,91 @@ impl Storage for RefMut<'_, T> { self.deref_mut() } } + +impl Storage for NonNull { + type Item = T; + + fn get(&self) -> &Self::Item { + unsafe { self.as_ref() } + } + + fn get_mut(&mut self) -> &mut Self::Item { + unsafe { self.as_mut() } + } +} + +impl StorageAccessor for RefCell { + fn get(&'static self) -> impl Storage + 'static { + self.borrow_mut() + } +} + +#[repr(transparent)] +pub struct SyncUnsafeCell { + value: UnsafeCell, +} + +unsafe impl Sync for SyncUnsafeCell {} + +impl SyncUnsafeCell { + /// Constructs a new instance of `SyncUnsafeCell` which will wrap the specified value. + #[inline] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } +} + +impl SyncUnsafeCell { + /// Gets a mutable pointer to the wrapped value. + /// + /// This can be cast to a pointer of any kind. + /// Ensure that the access is unique (no active references, mutable or not) + /// when casting to `&mut T`, and ensure that there are no mutations + /// or mutable aliases going on when casting to `&T` + #[inline] + pub const fn get(&self) -> *mut T { + self.value.get() + } + + /// Returns a mutable reference to the underlying data. + /// + /// This call borrows the `SyncUnsafeCell` mutably (at compile-time) which + /// guarantees that we possess the only reference. + #[inline] + pub const fn get_mut(&mut self) -> &mut T { + self.value.get_mut() + } + + /// Gets a mutable pointer to the wrapped value. + /// + /// See [`UnsafeCell::get`] for details. + #[inline] + pub const fn raw_get(this: *const Self) -> *mut T { + // We can just cast the pointer from `SyncUnsafeCell` to `T` because + // of #[repr(transparent)] on both SyncUnsafeCell and UnsafeCell. + // See UnsafeCell::raw_get. + this as *const T as *mut T + } +} + +impl Default for SyncUnsafeCell { + /// Creates an `SyncUnsafeCell`, with the `Default` value for T. + fn default() -> SyncUnsafeCell { + SyncUnsafeCell::new(Default::default()) + } +} + +impl From for SyncUnsafeCell { + /// Creates a new `SyncUnsafeCell` containing the given value. + fn from(t: T) -> SyncUnsafeCell { + SyncUnsafeCell::new(t) + } +} + +impl StorageAccessor for SyncUnsafeCell { + fn get(&'static self) -> impl Storage + 'static { + unsafe { NonNull::new_unchecked(self.get()) } + } +} diff --git a/rs/src/prelude.rs b/rs/src/prelude.rs index bf719d0dc..6cf744845 100644 --- a/rs/src/prelude.rs +++ b/rs/src/prelude.rs @@ -44,7 +44,7 @@ pub mod ffi { #[cfg(feature = "gstd")] pub use crate::gstd::{ export, program, route, service, storage::BoxedStorage, storage::Storage, - storage::StorageAccessor, CommandReply, + storage::StorageAccessor, storage::SyncUnsafeCell, CommandReply, }; pub use crate::types::*; From 6c926d3ed51cac09b2c49d8b02660cff1bfa5e61 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 17 Dec 2024 12:01:30 +0100 Subject: [PATCH 3/5] replace static lifetime with 'a --- examples/demo/app/src/counter_storage/mod.rs | 12 ++++++------ examples/demo/app/src/lib.rs | 4 ++-- rs/src/gstd/storage.rs | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/demo/app/src/counter_storage/mod.rs b/examples/demo/app/src/counter_storage/mod.rs index ce33c1252..ad17570fc 100644 --- a/examples/demo/app/src/counter_storage/mod.rs +++ b/examples/demo/app/src/counter_storage/mod.rs @@ -1,17 +1,17 @@ use sails_rs::prelude::*; -pub struct Service { - storage: BoxedStorage, +pub struct Service<'a> { + storage: Box + 'a>, } -impl Service { - pub fn new(storage: impl Storage + 'static) -> Self { +impl<'a> Service<'a> { + pub fn new(storage: impl Storage + 'a) -> Self { Self { storage: Box::new(storage), } } - pub fn from_accessor>(accessor: &'static T) -> Self { + pub fn from_accessor>(accessor: &'a T) -> Self { Self { storage: accessor.boxed(), } @@ -19,7 +19,7 @@ impl Service { } #[service(events = Event)] -impl Service { +impl Service<'_> { pub fn bump(&mut self) { let state = self.storage.get_mut(); diff --git a/examples/demo/app/src/lib.rs b/examples/demo/app/src/lib.rs index 5c4810f02..9cedcda52 100644 --- a/examples/demo/app/src/lib.rs +++ b/examples/demo/app/src/lib.rs @@ -79,14 +79,14 @@ impl DemoProgram { counter::CounterService::new(&self.counter_data) } - pub fn counter_storage(&'static self) -> counter_storage::Service { + pub fn counter_storage(&self) -> counter_storage::Service<'_> { let data = self.counter_storage.borrow_mut(); counter_storage::Service::new(data) // can be simplified to //counter_storage::Service::from_accessor(&self.counter_storage) } - pub fn counter_storage_cell(&'static self) -> counter_storage::Service { + pub fn counter_storage_cell(&self) -> counter_storage::Service<'_> { counter_storage::Service::from_accessor(&STORAGE_CELL) } diff --git a/rs/src/gstd/storage.rs b/rs/src/gstd/storage.rs index 34f4b40e8..2f0eebda2 100644 --- a/rs/src/gstd/storage.rs +++ b/rs/src/gstd/storage.rs @@ -15,10 +15,10 @@ pub trait Storage { fn get_mut(&mut self) -> &mut Self::Item; } -pub trait StorageAccessor { - fn get(&'static self) -> impl Storage + 'static; +pub trait StorageAccessor<'a, T> { + fn get(&'a self) -> impl Storage + 'a; - fn boxed(&'static self) -> BoxedStorage { + fn boxed(&'a self) -> Box + 'a> { Box::new(self.get()) } } @@ -59,8 +59,8 @@ impl Storage for NonNull { } } -impl StorageAccessor for RefCell { - fn get(&'static self) -> impl Storage + 'static { +impl<'a, T> StorageAccessor<'a, T> for RefCell { + fn get(&'a self) -> impl Storage + 'a { self.borrow_mut() } } @@ -129,8 +129,8 @@ impl From for SyncUnsafeCell { } } -impl StorageAccessor for SyncUnsafeCell { - fn get(&'static self) -> impl Storage + 'static { +impl<'a, T> StorageAccessor<'a, T> for SyncUnsafeCell { + fn get(&'a self) -> impl Storage + 'a { unsafe { NonNull::new_unchecked(self.get()) } } } From bb67cf727544b27cb60384666b0e9180ed7e0b06 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 8 Jan 2025 19:37:58 +0100 Subject: [PATCH 4/5] add static_storage macros, tests --- examples/demo/app/src/counter_storage/mod.rs | 17 ++++--- examples/demo/app/src/lib.rs | 13 +++-- examples/demo/app/tests/fixture/mod.rs | 7 ++- examples/demo/app/tests/gclient.rs | 44 +++++++++++++++++ examples/demo/app/tests/gtest.rs | 52 +++++++++++++++++++- examples/demo/client/demo.idl | 9 ++++ rs/src/gstd/storage.rs | 43 ++++++++++++++++ 7 files changed, 171 insertions(+), 14 deletions(-) diff --git a/examples/demo/app/src/counter_storage/mod.rs b/examples/demo/app/src/counter_storage/mod.rs index ad17570fc..462c6df51 100644 --- a/examples/demo/app/src/counter_storage/mod.rs +++ b/examples/demo/app/src/counter_storage/mod.rs @@ -1,17 +1,21 @@ -use sails_rs::prelude::*; +use sails_rs::{prelude::*, static_storage}; + +#[derive(Default)] +pub struct Data(pub u128); +static_storage!(Data, Data(0u128)); pub struct Service<'a> { - storage: Box + 'a>, + storage: Box + 'a>, } impl<'a> Service<'a> { - pub fn new(storage: impl Storage + 'a) -> Self { + pub fn new(storage: impl Storage + 'a) -> Self { Self { storage: Box::new(storage), } } - pub fn from_accessor>(accessor: &'a T) -> Self { + pub fn from_accessor>(accessor: &'a T) -> Self { Self { storage: accessor.boxed(), } @@ -22,14 +26,13 @@ impl<'a> Service<'a> { impl Service<'_> { pub fn bump(&mut self) { let state = self.storage.get_mut(); - - *state = state.saturating_add(1); + state.0 = state.0.saturating_add(1); self.notify_on(Event::Bumped).expect("unable to emit event"); } pub fn get(&self) -> u128 { - *self.storage.get() + self.storage.get().0 } } diff --git a/examples/demo/app/src/lib.rs b/examples/demo/app/src/lib.rs index 9cedcda52..83a518d52 100644 --- a/examples/demo/app/src/lib.rs +++ b/examples/demo/app/src/lib.rs @@ -17,7 +17,8 @@ mod value_fee; // of using a global variable here. It is just a demonstration of how to use global variables. static mut DOG_DATA: Option> = None; static mut REF_DATA: u8 = 42; -static STORAGE_CELL: SyncUnsafeCell = SyncUnsafeCell::new(0u128); +static STORAGE_CELL: SyncUnsafeCell = + SyncUnsafeCell::new(counter_storage::Data(0u128)); #[allow(static_mut_refs)] fn dog_data() -> &'static RefCell { @@ -32,7 +33,7 @@ pub struct DemoProgram { // Counter data has the same lifetime as the program itself, i.e. it will // live as long as the program is available on the network. counter_data: RefCell, - counter_storage: RefCell, + counter_storage: RefCell, } #[program] @@ -48,7 +49,7 @@ impl DemoProgram { } Self { counter_data: RefCell::new(counter::CounterData::new(Default::default())), - counter_storage: RefCell::new(0u128), + counter_storage: RefCell::new(counter_storage::Data(0u128)), } } @@ -64,7 +65,7 @@ impl DemoProgram { } Ok(Self { counter_data: RefCell::new(counter::CounterData::new(counter.unwrap_or_default())), - counter_storage: RefCell::new(0u128), + counter_storage: RefCell::new(counter_storage::Data(0u128)), }) } @@ -90,6 +91,10 @@ impl DemoProgram { counter_storage::Service::from_accessor(&STORAGE_CELL) } + pub fn counter_storage_static(&self) -> counter_storage::Service<'_> { + counter_storage::Service::new(counter_storage::Data::storage()) + } + // Exposing yet another service pub fn dog(&self) -> dog::DogService { dog::DogService::new(walker::WalkerService::new(dog_data())) diff --git a/examples/demo/app/tests/fixture/mod.rs b/examples/demo/app/tests/fixture/mod.rs index 1ba59aafc..0eca12b6d 100644 --- a/examples/demo/app/tests/fixture/mod.rs +++ b/examples/demo/app/tests/fixture/mod.rs @@ -1,7 +1,8 @@ use demo_client::{ counter::{self, events::CounterEvents}, dog::{self, events::DogEvents}, - Counter, CounterStorage, CounterStorageCell, DemoFactory, Dog, References, ValueFee, + Counter, CounterStorage, CounterStorageCell, CounterStorageStatic, DemoFactory, Dog, + References, ValueFee, }; use sails_rs::{events::Listener, gtest::calls::*, gtest::System, prelude::*}; @@ -53,6 +54,10 @@ impl Fixture { CounterStorageCell::new(self.program_space.clone()) } + pub(crate) fn counter_storage_static_client(&self) -> CounterStorageStatic { + CounterStorageStatic::new(self.program_space.clone()) + } + pub(crate) fn dog_client(&self) -> Dog { Dog::new(self.program_space.clone()) } diff --git a/examples/demo/app/tests/gclient.rs b/examples/demo/app/tests/gclient.rs index 3dd2d24ea..5595147c2 100644 --- a/examples/demo/app/tests/gclient.rs +++ b/examples/demo/app/tests/gclient.rs @@ -278,6 +278,50 @@ async fn value_fee_works() { ); } +#[tokio::test] +#[ignore = "requires run gear node on GEAR_PATH"] +async fn counter_storage_static_works() { + // Arrange + + let (remoting, demo_code_id, gas_limit, ..) = spin_up_node_with_demo_code().await; + + let demo_factory = demo_client::DemoFactory::new(remoting.clone()); + + // Use generated client code for activating Demo program + // using the `new` constructor and the `send_recv` method + let demo_program_id = demo_factory + .new(Some(42), None) + .with_gas_limit(gas_limit) + .send_recv(demo_code_id, "123") + .await + .unwrap(); + + let mut counter_client = demo_client::CounterStorageStatic::new(remoting.clone()); + + // Act + + // Use generated client code for calling Counter service + // using the `send_recv` method + counter_client + .bump() + .with_gas_limit(gas_limit) + .send_recv(demo_program_id) + .await + .unwrap(); + + counter_client + .bump() + .with_gas_limit(gas_limit) + .send_recv(demo_program_id) + .await + .unwrap(); + + let result = counter_client.get().recv(demo_program_id).await.unwrap(); + + // Asert + assert_eq!(result, 2); +} + async fn spin_up_node_with_demo_code() -> (GClientRemoting, CodeId, GasUnit, GearApi) { let gear_path = option_env!("GEAR_PATH"); if gear_path.is_none() { diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index 405e423d7..96b20b97c 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -508,10 +508,16 @@ async fn counter_storage_works() { .await .unwrap(); + counter_client + .bump() + .send_recv(demo_program_id) + .await + .unwrap(); + let result = counter_client.get().recv(demo_program_id).await.unwrap(); // Asert - assert_eq!(result, 1); + assert_eq!(result, 2); } #[tokio::test] @@ -538,8 +544,50 @@ async fn counter_storage_cell_works() { .await .unwrap(); + counter_client + .bump() + .send_recv(demo_program_id) + .await + .unwrap(); + + let result = counter_client.get().recv(demo_program_id).await.unwrap(); + + // Asert + assert_eq!(result, 2); +} + +#[tokio::test] +async fn counter_storage_static_works() { + // Arrange + let fixture = Fixture::new(); + + let demo_factory = fixture.demo_factory(); + + // Use generated client code for activating Demo program + // using the `new` constructor and the `send_recv` method + let demo_program_id = demo_factory + .new(Some(42), None) + .send_recv(fixture.demo_code_id(), "123") + .await + .unwrap(); + + let mut counter_client = fixture.counter_storage_static_client(); + + // Act + counter_client + .bump() + .send_recv(demo_program_id) + .await + .unwrap(); + + counter_client + .bump() + .send_recv(demo_program_id) + .await + .unwrap(); + let result = counter_client.get().recv(demo_program_id).await.unwrap(); // Asert - assert_eq!(result, 1); + assert_eq!(result, 2); } diff --git a/examples/demo/client/demo.idl b/examples/demo/client/demo.idl index f896dfa09..e119c87d4 100644 --- a/examples/demo/client/demo.idl +++ b/examples/demo/client/demo.idl @@ -62,6 +62,15 @@ service CounterStorageCell { } }; +service CounterStorageStatic { + Bump : () -> null; + query Get : () -> u128; + + events { + Bumped; + } +}; + service Dog { MakeSound : () -> str; Walk : (dx: i32, dy: i32) -> null; diff --git a/rs/src/gstd/storage.rs b/rs/src/gstd/storage.rs index 2f0eebda2..afc199cb4 100644 --- a/rs/src/gstd/storage.rs +++ b/rs/src/gstd/storage.rs @@ -59,6 +59,18 @@ impl Storage for NonNull { } } +impl Storage for Option { + type Item = T; + + fn get(&self) -> &Self::Item { + self.as_ref().expect("storage is not initialized") + } + + fn get_mut(&mut self) -> &mut Self::Item { + self.as_mut().expect("storage is not initialized") + } +} + impl<'a, T> StorageAccessor<'a, T> for RefCell { fn get(&'a self) -> impl Storage + 'a { self.borrow_mut() @@ -134,3 +146,34 @@ impl<'a, T> StorageAccessor<'a, T> for SyncUnsafeCell { unsafe { NonNull::new_unchecked(self.get()) } } } + +#[macro_export] +macro_rules! static_storage { + ($type:ty, $init:expr) => { + impl $type { + pub(crate) fn storage() -> impl Storage + 'static { + static mut STORAGE: $type = $init; + unsafe { &mut *core::ptr::addr_of_mut!(STORAGE) } + } + } + }; +} + +#[macro_export] +macro_rules! static_option_storage { + ($type:ty) => { + static mut STORAGE: Option<$type> = None; + + impl $type { + pub(crate) fn init(init_value: $type) { + unsafe { + STORAGE = Some(init_value); + }; + } + + pub(crate) fn storage() -> impl Storage + 'static { + unsafe { STORAGE.as_mut().expect("storage is not initialized") } + } + } + }; +} From 098283d03ee353cd69d83f47e4b0fd22120e9c2b Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 13 Jan 2025 16:43:34 +0100 Subject: [PATCH 5/5] wip: static_storage experiments --- examples/demo/app/src/counter_storage/mod.rs | 3 +- examples/demo/app/src/lib.rs | 10 ++++- rs/src/gstd/storage.rs | 47 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/examples/demo/app/src/counter_storage/mod.rs b/examples/demo/app/src/counter_storage/mod.rs index 462c6df51..2a59aa28b 100644 --- a/examples/demo/app/src/counter_storage/mod.rs +++ b/examples/demo/app/src/counter_storage/mod.rs @@ -1,8 +1,7 @@ -use sails_rs::{prelude::*, static_storage}; +use sails_rs::prelude::*; #[derive(Default)] pub struct Data(pub u128); -static_storage!(Data, Data(0u128)); pub struct Service<'a> { storage: Box + 'a>, diff --git a/examples/demo/app/src/lib.rs b/examples/demo/app/src/lib.rs index 83a518d52..1cb6897f6 100644 --- a/examples/demo/app/src/lib.rs +++ b/examples/demo/app/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use demo_walker as walker; -use sails_rs::{cell::RefCell, prelude::*}; +use sails_rs::{cell::RefCell, prelude::*, static_storage2}; mod counter; mod counter_storage; @@ -20,6 +20,10 @@ static mut REF_DATA: u8 = 42; static STORAGE_CELL: SyncUnsafeCell = SyncUnsafeCell::new(counter_storage::Data(0u128)); +static_storage2!( + counter_data: counter_storage::Data, +); + #[allow(static_mut_refs)] fn dog_data() -> &'static RefCell { unsafe { @@ -47,6 +51,7 @@ impl DemoProgram { Default::default(), ))); } + static_storage::init_storage(counter_storage::Data(0u128)); Self { counter_data: RefCell::new(counter::CounterData::new(Default::default())), counter_storage: RefCell::new(counter_storage::Data(0u128)), @@ -63,6 +68,7 @@ impl DemoProgram { dog_position.1, ))); } + static_storage::init_storage(counter_storage::Data(0u128)); Ok(Self { counter_data: RefCell::new(counter::CounterData::new(counter.unwrap_or_default())), counter_storage: RefCell::new(counter_storage::Data(0u128)), @@ -92,7 +98,7 @@ impl DemoProgram { } pub fn counter_storage_static(&self) -> counter_storage::Service<'_> { - counter_storage::Service::new(counter_storage::Data::storage()) + counter_storage::Service::new(static_storage::counter_data()) } // Exposing yet another service diff --git a/rs/src/gstd/storage.rs b/rs/src/gstd/storage.rs index afc199cb4..eff5cfe96 100644 --- a/rs/src/gstd/storage.rs +++ b/rs/src/gstd/storage.rs @@ -177,3 +177,50 @@ macro_rules! static_option_storage { } }; } + +#[macro_export] +macro_rules! static_storage2 { + ($($id:ident: $type:ty $(,)?)*) => { + mod static_storage { + use super::*; + use core::mem::MaybeUninit; + + struct InnerStorage { + $($id: $type,)* + } + static mut __STORAGE: MaybeUninit = MaybeUninit::uninit(); + + pub(crate) fn init_storage($($id: $type,)*) { + unsafe { + let ptr = &mut *core::ptr::addr_of_mut!(__STORAGE); + ptr.write(InnerStorage { $($id,) * }) + }; + } + + $(pub(crate) fn $id() -> &'static mut $type { unsafe { let ptr = &mut *core::ptr::addr_of_mut!(__STORAGE); &mut *core::ptr::addr_of_mut!((*ptr.assume_init_mut()).$id) } })* + } + }; +} + +#[macro_export] +macro_rules! static_storage3 { + ($($id:ident: $type:ty = $init:expr $(,)?)*) => { + mod static_storage { + use super::*; + + $(pub(crate) fn $id() -> &'static mut $type { static mut $id: $type = $init; unsafe { &mut *core::ptr::addr_of_mut!($id) } })* + } + }; +} + +mod tests { + use super::*; + + #[test] + fn static_storage2_test() { + static_storage2!( + a: u32, + b: u64 + ); + } +}