#[doc(hidden)] pub fn serai_db_key( db_dst: &'static [u8], item_dst: &'static [u8], key: impl AsRef<[u8]>, ) -> Vec { let db_len = u8::try_from(db_dst.len()).unwrap(); let dst_len = u8::try_from(item_dst.len()).unwrap(); [[db_len].as_ref(), db_dst, [dst_len].as_ref(), item_dst, key.as_ref()].concat() } /// Creates a series of structs which provide namespacing for keys /// /// # Description /// /// Creates a unit struct and a default implementation for the `key`, `get`, and `set`. The macro /// uses a syntax similar to defining a function. Parameters are concatenated to produce a key, /// they must be `scale` encodable. The return type is used to auto encode and decode the database /// value bytes using `borsh`. /// /// # Arguments /// /// * `db_name` - A database name /// * `field_name` - An item name /// * `args` - Comma separated list of key arguments /// * `field_type` - The return type /// /// # Example /// /// ```ignore /// create_db!( /// TributariesDb { /// AttemptsDb: (key_bytes: &[u8], attempt_id: u32) -> u64, /// ExpiredDb: (genesis: [u8; 32]) -> Vec /// } /// ) /// ``` #[macro_export] macro_rules! create_db { ($db_name: ident { $( $field_name: ident: $(<$($generic_name: tt: $generic_type: tt),+>)?( $($arg: ident: $arg_type: ty),* ) -> $field_type: ty$(,)? )* }) => { $( #[derive(Clone, Debug)] pub(crate) struct $field_name; impl $field_name { pub(crate) fn key$(<$($generic_name: $generic_type),+>)?($($arg: $arg_type),*) -> Vec { use scale::Encode; $crate::serai_db_key( stringify!($db_name).as_bytes(), stringify!($field_name).as_bytes(), ($($arg),*).encode() ) } pub(crate) fn set$(<$($generic_name: $generic_type),+>)?( txn: &mut impl DbTxn $(, $arg: $arg_type)*, data: &$field_type ) { let key = $field_name::key$(::<$($generic_name),+>)?($($arg),*); txn.put(&key, borsh::to_vec(data).unwrap()); } pub(crate) fn get$(<$($generic_name: $generic_type),+>)?( getter: &impl Get, $($arg: $arg_type),* ) -> Option<$field_type> { getter.get($field_name::key$(::<$($generic_name),+>)?($($arg),*)).map(|data| { borsh::from_slice(data.as_ref()).unwrap() }) } // Returns a PhantomData of all generic types so if the generic was only used in the value, // not the keys, this doesn't have unused generic types #[allow(dead_code)] pub(crate) fn del$(<$($generic_name: $generic_type),+>)?( txn: &mut impl DbTxn $(, $arg: $arg_type)* ) -> core::marker::PhantomData<($($($generic_name),+)?)> { txn.del(&$field_name::key$(::<$($generic_name),+>)?($($arg),*)); core::marker::PhantomData } pub(crate) fn take$(<$($generic_name: $generic_type),+>)?( txn: &mut impl DbTxn $(, $arg: $arg_type)* ) -> Option<$field_type> { let key = $field_name::key$(::<$($generic_name),+>)?($($arg),*); let res = txn.get(&key).map(|data| borsh::from_slice(data.as_ref()).unwrap()); if res.is_some() { txn.del(key); } res } } )* }; } #[macro_export] macro_rules! db_channel { ($db_name: ident { $($field_name: ident: $(<$($generic_name: tt: $generic_type: tt),+>)?( $($arg: ident: $arg_type: ty),* ) -> $field_type: ty$(,)? )* }) => { $( create_db! { $db_name { $field_name: $(<$($generic_name: $generic_type),+>)?( $($arg: $arg_type,)* index: u32 ) -> $field_type } } impl $field_name { pub(crate) fn send$(<$($generic_name: $generic_type),+>)?( txn: &mut impl DbTxn $(, $arg: $arg_type)* , value: &$field_type ) { // Use index 0 to store the amount of messages let messages_sent_key = $field_name::key$(::<$($generic_name),+>)?($($arg,)* 0); let messages_sent = txn.get(&messages_sent_key).map(|counter| { u32::from_le_bytes(counter.try_into().unwrap()) }).unwrap_or(0); txn.put(&messages_sent_key, (messages_sent + 1).to_le_bytes()); // + 2 as index 1 is used for the amount of messages read // Using distinct counters enables send to be called without mutating anything recv may // at the same time let index_to_use = messages_sent + 2; $field_name::set$(::<$($generic_name),+>)?(txn, $($arg,)* index_to_use, value); } pub(crate) fn try_recv$(<$($generic_name: $generic_type),+>)?( txn: &mut impl DbTxn $(, $arg: $arg_type)* ) -> Option<$field_type> { let messages_recvd_key = $field_name::key$(::<$($generic_name),+>)?($($arg,)* 1); let messages_recvd = txn.get(&messages_recvd_key).map(|counter| { u32::from_le_bytes(counter.try_into().unwrap()) }).unwrap_or(0); let index_to_read = messages_recvd + 2; let res = $field_name::get$(::<$($generic_name),+>)?(txn, $($arg,)* index_to_read); if res.is_some() { $field_name::del$(::<$($generic_name),+>)?(txn, $($arg,)* index_to_read); txn.put(&messages_recvd_key, (messages_recvd + 1).to_le_bytes()); } res } } )* }; }