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 00000000..2a59aa28 --- /dev/null +++ b/examples/demo/app/src/counter_storage/mod.rs @@ -0,0 +1,43 @@ +use sails_rs::prelude::*; + +#[derive(Default)] +pub struct Data(pub u128); + +pub struct Service<'a> { + storage: Box + 'a>, +} + +impl<'a> Service<'a> { + pub fn new(storage: impl Storage + 'a) -> Self { + Self { + storage: Box::new(storage), + } + } + + pub fn from_accessor>(accessor: &'a T) -> Self { + Self { + storage: accessor.boxed(), + } + } +} + +#[service(events = Event)] +impl Service<'_> { + pub fn bump(&mut self) { + let state = self.storage.get_mut(); + 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().0 + } +} + +#[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 7c12a7f8..1cb6897f 100644 --- a/examples/demo/app/src/lib.rs +++ b/examples/demo/app/src/lib.rs @@ -1,9 +1,10 @@ #![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; mod dog; mod mammal; mod ping; @@ -16,6 +17,12 @@ 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(counter_storage::Data(0u128)); + +static_storage2!( + counter_data: counter_storage::Data, +); #[allow(static_mut_refs)] fn dog_data() -> &'static RefCell { @@ -30,6 +37,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] @@ -43,8 +51,10 @@ 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)), } } @@ -58,8 +68,10 @@ 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)), }) } @@ -74,6 +86,21 @@ impl DemoProgram { counter::CounterService::new(&self.counter_data) } + 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(&self) -> counter_storage::Service<'_> { + counter_storage::Service::from_accessor(&STORAGE_CELL) + } + + pub fn counter_storage_static(&self) -> counter_storage::Service<'_> { + counter_storage::Service::new(static_storage::counter_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 98e66a0c..81cfc7ef 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, DemoFactory, Dog, References, ValueFee, + Counter, CounterStorage, CounterStorageCell, CounterStorageStatic, DemoFactory, Dog, + References, ValueFee, }; use sails_rs::{events::Listener, gtest::calls::*, gtest::System, prelude::*}; @@ -45,6 +46,18 @@ 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 counter_storage_cell_client(&self) -> CounterStorageCell { + 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 3dd2d24e..5595147c 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 aa2f9665..63fd5193 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -522,3 +522,111 @@ 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(); + + 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_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(); + + 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, 2); +} diff --git a/examples/demo/client/demo.idl b/examples/demo/client/demo.idl index d7cc6728..e119c87d 100644 --- a/examples/demo/client/demo.idl +++ b/examples/demo/client/demo.idl @@ -44,6 +44,33 @@ service Counter { } }; +service CounterStorage { + Bump : () -> null; + query Get : () -> u128; + + events { + Bumped; + } +}; + +service CounterStorageCell { + Bump : () -> null; + query Get : () -> u128; + + events { + Bumped; + } +}; + +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/mod.rs b/rs/src/gstd/mod.rs index 4da72b50..7d5a7b73 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 00000000..eff5cfe9 --- /dev/null +++ b/rs/src/gstd/storage.rs @@ -0,0 +1,226 @@ +use crate::boxed::Box; +use core::{ + cell::{RefCell, RefMut, UnsafeCell}, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +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<'a, T> { + fn get(&'a self) -> impl Storage + 'a; + + fn boxed(&'a self) -> Box + 'a> { + 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 + } +} + +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() + } +} + +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 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() + } +} + +#[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<'a, T> StorageAccessor<'a, T> for SyncUnsafeCell { + fn get(&'a self) -> impl Storage + 'a { + 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") } + } + } + }; +} + +#[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 + ); + } +} diff --git a/rs/src/prelude.rs b/rs/src/prelude.rs index 45e104c1..6cf74484 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, storage::SyncUnsafeCell, CommandReply, +}; pub use crate::types::*; pub use parity_scale_codec::{self as scale_codec, Decode, Encode, EncodeLike};