Skip to main content

topsoil_core/traits/
storage.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Traits for encoding data related to pallet's storage items.
8
9use alloc::{collections::btree_set::BTreeSet, vec, vec::Vec};
10use codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, MaxEncodedLen};
11use core::{marker::PhantomData, mem, ops::Drop};
12use impl_trait_for_tuples::impl_for_tuples;
13use scale_info::TypeInfo;
14pub use subsoil::core::storage::TrackedStorageKey;
15use subsoil::core::Get;
16use subsoil::runtime::{
17	traits::{Convert, Member},
18	Debug, DispatchError,
19};
20use topsoil_core::CloneNoBound;
21
22/// An instance of a pallet in the storage.
23///
24/// It is required that these instances are unique, to support multiple instances per pallet in the
25/// same runtime!
26///
27/// E.g. for module MyModule default instance will have prefix "MyModule" and other instances
28/// "InstanceNMyModule".
29pub trait Instance: 'static {
30	/// Unique module prefix. E.g. "InstanceNMyModule" or "MyModule"
31	const PREFIX: &'static str;
32	/// Unique numerical identifier for an instance.
33	const INDEX: u8;
34}
35
36// Dummy implementation for `()`.
37impl Instance for () {
38	const PREFIX: &'static str = "";
39	const INDEX: u8 = 0;
40}
41
42/// An instance of a storage in a pallet.
43///
44/// Define an instance for an individual storage inside a pallet.
45/// The pallet prefix is used to isolate the storage between pallets, and the storage prefix is
46/// used to isolate storages inside a pallet.
47///
48/// NOTE: These information can be used to define storages in pallet such as a `StorageMap` which
49/// can use keys after `twox_128(pallet_prefix())++twox_128(STORAGE_PREFIX)`
50pub trait StorageInstance {
51	/// Prefix of a pallet to isolate it from other pallets.
52	fn pallet_prefix() -> &'static str;
53
54	/// Return the prefix hash of pallet instance.
55	///
56	/// NOTE: This hash must be `twox_128(pallet_prefix())`.
57	/// Should not impl this function by hand. Only use the default or macro generated impls.
58	fn pallet_prefix_hash() -> [u8; 16] {
59		subsoil::io::hashing::twox_128(Self::pallet_prefix().as_bytes())
60	}
61
62	/// Prefix given to a storage to isolate from other storages in the pallet.
63	const STORAGE_PREFIX: &'static str;
64
65	/// Return the prefix hash of storage instance.
66	///
67	/// NOTE: This hash must be `twox_128(STORAGE_PREFIX)`.
68	fn storage_prefix_hash() -> [u8; 16] {
69		subsoil::io::hashing::twox_128(Self::STORAGE_PREFIX.as_bytes())
70	}
71
72	/// Return the prefix hash of instance.
73	///
74	/// NOTE: This hash must be `twox_128(pallet_prefix())++twox_128(STORAGE_PREFIX)`.
75	/// Should not impl this function by hand. Only use the default or macro generated impls.
76	fn prefix_hash() -> [u8; 32] {
77		let mut final_key = [0u8; 32];
78		final_key[..16].copy_from_slice(&Self::pallet_prefix_hash());
79		final_key[16..].copy_from_slice(&Self::storage_prefix_hash());
80
81		final_key
82	}
83}
84
85/// Metadata about storage from the runtime.
86#[derive(Debug, codec::Encode, codec::Decode, Eq, PartialEq, Clone, scale_info::TypeInfo)]
87pub struct StorageInfo {
88	/// Encoded string of pallet name.
89	pub pallet_name: Vec<u8>,
90	/// Encoded string of storage name.
91	pub storage_name: Vec<u8>,
92	/// The prefix of the storage. All keys after the prefix are considered part of this storage.
93	pub prefix: Vec<u8>,
94	/// The maximum number of values in the storage, or none if no maximum specified.
95	pub max_values: Option<u32>,
96	/// The maximum size of key/values in the storage, or none if no maximum specified.
97	pub max_size: Option<u32>,
98}
99
100/// A trait to give information about storage.
101///
102/// It can be used to calculate PoV worst case size.
103pub trait StorageInfoTrait {
104	fn storage_info() -> Vec<StorageInfo>;
105}
106
107#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
108#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
109#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
110impl StorageInfoTrait for Tuple {
111	fn storage_info() -> Vec<StorageInfo> {
112		let mut res = vec![];
113		for_tuples!( #( res.extend_from_slice(&Tuple::storage_info()); )* );
114		res
115	}
116}
117
118/// Similar to [`StorageInfoTrait`], a trait to give partial information about storage.
119///
120/// This is useful when a type can give some partial information with its generic parameter doesn't
121/// implement some bounds.
122pub trait PartialStorageInfoTrait {
123	fn partial_storage_info() -> Vec<StorageInfo>;
124}
125
126/// Allows a pallet to specify storage keys to whitelist during benchmarking.
127/// This means those keys will be excluded from the benchmarking performance
128/// calculation.
129pub trait WhitelistedStorageKeys {
130	/// Returns a [`Vec<TrackedStorageKey>`] indicating the storage keys that
131	/// should be whitelisted during benchmarking. This means that those keys
132	/// will be excluded from the benchmarking performance calculation.
133	fn whitelisted_storage_keys() -> Vec<TrackedStorageKey>;
134}
135
136#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
137#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
138#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
139impl WhitelistedStorageKeys for Tuple {
140	fn whitelisted_storage_keys() -> Vec<TrackedStorageKey> {
141		// de-duplicate the storage keys
142		let mut combined_keys: BTreeSet<TrackedStorageKey> = BTreeSet::new();
143		for_tuples!( #(
144			for storage_key in Tuple::whitelisted_storage_keys() {
145				combined_keys.insert(storage_key);
146			}
147		 )* );
148		combined_keys.into_iter().collect::<Vec<_>>()
149	}
150}
151
152/// The resource footprint of a bunch of blobs. We assume only the number of blobs and their total
153/// size in bytes matter.
154#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
155pub struct Footprint {
156	/// The number of blobs.
157	pub count: u64,
158	/// The total size of the blobs in bytes.
159	pub size: u64,
160}
161
162impl Footprint {
163	/// Construct a footprint directly from `items` and `len`.
164	pub fn from_parts(items: usize, len: usize) -> Self {
165		Self { count: items as u64, size: len as u64 }
166	}
167
168	/// Construct a footprint with one item, and size equal to the encoded size of `e`.
169	pub fn from_encodable(e: impl Encode) -> Self {
170		Self::from_parts(1, e.encoded_size())
171	}
172
173	/// Construct a footprint with one item, and size equal to the max encoded length of `E`.
174	pub fn from_mel<E: MaxEncodedLen>() -> Self {
175		Self::from_parts(1, E::max_encoded_len())
176	}
177}
178
179/// A storage price that increases linearly with the number of elements and their size.
180pub struct LinearStoragePrice<Base, Slope, Balance>(PhantomData<(Base, Slope, Balance)>);
181impl<Base, Slope, Balance> Convert<Footprint, Balance> for LinearStoragePrice<Base, Slope, Balance>
182where
183	Base: Get<Balance>,
184	Slope: Get<Balance>,
185	Balance: From<u64> + subsoil::runtime::Saturating,
186{
187	fn convert(a: Footprint) -> Balance {
188		let s: Balance = (a.count.saturating_mul(a.size)).into();
189		s.saturating_mul(Slope::get()).saturating_add(Base::get())
190	}
191}
192
193/// Constant `Price` regardless of the given [`Footprint`].
194pub struct ConstantStoragePrice<Price, Balance>(PhantomData<(Price, Balance)>);
195impl<Price, Balance> Convert<Footprint, Balance> for ConstantStoragePrice<Price, Balance>
196where
197	Price: Get<Balance>,
198	Balance: From<u64> + subsoil::runtime::Saturating,
199{
200	fn convert(_: Footprint) -> Balance {
201		Price::get()
202	}
203}
204
205/// Placeholder marking functionality disabled. Useful for disabling various (sub)features.
206#[derive(CloneNoBound, Debug, Encode, Eq, Decode, TypeInfo, MaxEncodedLen, PartialEq)]
207pub struct Disabled;
208impl<A, F> Consideration<A, F> for Disabled {
209	fn new(_: &A, _: F) -> Result<Self, DispatchError> {
210		Err(DispatchError::Other("Disabled"))
211	}
212	fn update(self, _: &A, _: F) -> Result<Self, DispatchError> {
213		Err(DispatchError::Other("Disabled"))
214	}
215	fn drop(self, _: &A) -> Result<(), DispatchError> {
216		Ok(())
217	}
218	#[cfg(feature = "runtime-benchmarks")]
219	fn ensure_successful(_: &A, _: F) {}
220}
221
222/// Some sort of cost taken from account temporarily in order to offset the cost to the chain of
223/// holding some data [`Footprint`] in state.
224///
225/// The cost may be increased, reduced or dropped entirely as the footprint changes.
226///
227/// A single ticket corresponding to some particular datum held in storage. This is an opaque
228/// type, but must itself be stored and generally it should be placed alongside whatever data
229/// the ticket was created for.
230///
231/// While not technically a linear type owing to the need for `FullCodec`, *this should be
232/// treated as one*. Don't type to duplicate it, and remember to drop it when you're done with
233/// it.
234#[must_use]
235pub trait Consideration<AccountId, Footprint>:
236	Member + FullCodec + TypeInfo + MaxEncodedLen
237{
238	/// Create a ticket for the `new` footprint attributable to `who`. This ticket *must* ultimately
239	/// be consumed through `update` or `drop` once the footprint changes or is removed.
240	fn new(who: &AccountId, new: Footprint) -> Result<Self, DispatchError>;
241
242	/// Optionally consume an old ticket and alter the footprint, enforcing the new cost to `who`
243	/// and returning the new ticket (or an error if there was an issue).
244	///
245	/// For creating tickets and dropping them, you can use the simpler `new` and `drop` instead.
246	fn update(self, who: &AccountId, new: Footprint) -> Result<Self, DispatchError>;
247
248	/// Consume a ticket for some `old` footprint attributable to `who` which should now been freed.
249	fn drop(self, who: &AccountId) -> Result<(), DispatchError>;
250
251	/// Consume a ticket for some `old` footprint attributable to `who` which should be sacrificed.
252	///
253	/// This is infallible. In the general case (and it is left unimplemented), then it is
254	/// equivalent to the consideration never being dropped. Cases which can handle this properly
255	/// should implement, but it *MUST* rely on the loss of the consideration to the owner.
256	fn burn(self, _: &AccountId) {
257		let _ = self;
258	}
259	/// Ensure that creating a ticket for a given account and footprint will be successful if done
260	/// immediately after this call.
261	#[cfg(feature = "runtime-benchmarks")]
262	fn ensure_successful(who: &AccountId, new: Footprint);
263}
264
265impl<A, F> Consideration<A, F> for () {
266	fn new(_: &A, _: F) -> Result<Self, DispatchError> {
267		Ok(())
268	}
269	fn update(self, _: &A, _: F) -> Result<(), DispatchError> {
270		Ok(())
271	}
272	fn drop(self, _: &A) -> Result<(), DispatchError> {
273		Ok(())
274	}
275	#[cfg(feature = "runtime-benchmarks")]
276	fn ensure_successful(_: &A, _: F) {}
277}
278
279#[cfg(feature = "experimental")]
280/// An extension of the [`Consideration`] trait that allows for the management of tickets that may
281/// represent no cost. While the [`MaybeConsideration`] still requires proper handling, it
282/// introduces the ability to determine if a ticket represents no cost and can be safely forgotten
283/// without any side effects.
284pub trait MaybeConsideration<AccountId, Footprint>: Consideration<AccountId, Footprint> {
285	/// Returns `true` if this [`Consideration`] represents a no-cost ticket and can be forgotten
286	/// without any side effects.
287	fn is_none(&self) -> bool;
288}
289
290#[cfg(feature = "experimental")]
291impl<A, F> MaybeConsideration<A, F> for () {
292	fn is_none(&self) -> bool {
293		true
294	}
295}
296
297macro_rules! impl_incrementable {
298	($($type:ty),+) => {
299		$(
300			impl Incrementable for $type {
301				fn increment(&self) -> Option<Self> {
302					self.checked_add(1)
303				}
304
305				fn initial_value() -> Option<Self> {
306					Some(0)
307				}
308			}
309		)+
310	};
311}
312
313/// A trait representing an incrementable type.
314///
315/// The `increment` and `initial_value` functions are fallible.
316/// They should either both return `Some` with a valid value, or `None`.
317pub trait Incrementable
318where
319	Self: Sized,
320{
321	/// Increments the value.
322	///
323	/// Returns `Some` with the incremented value if it is possible, or `None` if it is not.
324	fn increment(&self) -> Option<Self>;
325
326	/// Returns the initial value.
327	///
328	/// Returns `Some` with the initial value if it is available, or `None` if it is not.
329	fn initial_value() -> Option<Self>;
330}
331
332impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
333
334/// Wrap a type so that is `Drop` impl is never called.
335///
336/// Useful when storing types like `Imbalance` which would trigger their `Drop`
337/// implementation whenever they are written to storage as they are dropped after
338/// being serialized.
339#[derive(Default, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo)]
340pub struct NoDrop<T: Default>(T);
341
342impl<T: Default> Drop for NoDrop<T> {
343	fn drop(&mut self) {
344		mem::forget(mem::take(&mut self.0))
345	}
346}
347
348/// Sealed trait that marks a type with a suppressed Drop implementation.
349///
350/// Useful for constraining your storage items types by this bound to make
351/// sure they won't run drop when stored.
352pub trait SuppressedDrop: sealed::Sealed {
353	/// The wrapped whose drop function is ignored.
354	type Inner;
355
356	fn new(inner: Self::Inner) -> Self;
357	fn as_ref(&self) -> &Self::Inner;
358	fn as_mut(&mut self) -> &mut Self::Inner;
359	fn into_inner(self) -> Self::Inner;
360}
361
362impl SuppressedDrop for () {
363	type Inner = ();
364
365	fn new(inner: Self::Inner) -> Self {
366		inner
367	}
368
369	fn as_ref(&self) -> &Self::Inner {
370		self
371	}
372
373	fn as_mut(&mut self) -> &mut Self::Inner {
374		self
375	}
376
377	fn into_inner(self) -> Self::Inner {
378		self
379	}
380}
381
382impl<T: Default> SuppressedDrop for NoDrop<T> {
383	type Inner = T;
384
385	fn as_ref(&self) -> &Self::Inner {
386		&self.0
387	}
388
389	fn as_mut(&mut self) -> &mut Self::Inner {
390		&mut self.0
391	}
392
393	fn into_inner(mut self) -> Self::Inner {
394		mem::take(&mut self.0)
395	}
396
397	fn new(inner: Self::Inner) -> Self {
398		Self(inner)
399	}
400}
401
402mod sealed {
403	pub trait Sealed {}
404	impl Sealed for () {}
405	impl<T: Default> Sealed for super::NoDrop<T> {}
406}
407
408#[cfg(test)]
409mod tests {
410	use super::*;
411	use crate::BoundedVec;
412	use subsoil::core::{ConstU32, ConstU64};
413
414	#[test]
415	fn incrementable_works() {
416		assert_eq!(0u8.increment(), Some(1));
417		assert_eq!(1u8.increment(), Some(2));
418
419		assert_eq!(u8::MAX.increment(), None);
420	}
421
422	#[test]
423	fn linear_storage_price_works() {
424		type Linear = LinearStoragePrice<ConstU64<7>, ConstU64<3>, u64>;
425		let p = |count, size| Linear::convert(Footprint { count, size });
426
427		assert_eq!(p(0, 0), 7);
428		assert_eq!(p(0, 1), 7);
429		assert_eq!(p(1, 0), 7);
430
431		assert_eq!(p(1, 1), 10);
432		assert_eq!(p(8, 1), 31);
433		assert_eq!(p(1, 8), 31);
434
435		assert_eq!(p(u64::MAX, u64::MAX), u64::MAX);
436	}
437
438	#[test]
439	fn footprint_from_mel_works() {
440		let footprint = Footprint::from_mel::<(u8, BoundedVec<u8, ConstU32<9>>)>();
441		let expected_size = BoundedVec::<u8, ConstU32<9>>::max_encoded_len() as u64;
442		assert_eq!(expected_size, 10);
443		assert_eq!(footprint, Footprint { count: 1, size: expected_size + 1 });
444
445		let footprint = Footprint::from_mel::<(u8, BoundedVec<u8, ConstU32<999>>)>();
446		let expected_size = BoundedVec::<u8, ConstU32<999>>::max_encoded_len() as u64;
447		assert_eq!(expected_size, 1001);
448		assert_eq!(footprint, Footprint { count: 1, size: expected_size + 1 });
449	}
450}