Add custom storage items
For the collectibles
pallet to be useful, it needs to store information about the number of collectibles created and who owns each collectible.
After you decide the information you want to store, you need to decide how it should be stored: as a single value or in a storage map.
For the workshop, you'll create three custom storage items to track the state:
- A simple single value—
CollectiblesCount
—to keep track of the total number of collectibles in the pallet. - A simple map of key-value pairs—
CollectiblesMap
—to map the properties associated with each collectible to its unique identifier. - A simple map of key-value pairs—
OwnerOfCollectibles
—to map collectibles to the user account that owns them.
For a closer look at the storage architecture and abstractions that Substrate uses, see State transitions and storage.
Store a single value
The FRAME storage module provides a StorageValue
trait to store single values in the runtime.
In this workshop, you'll use a StorageValue
for the CollectiblesCount
—to keep track of the total number of collectibles in the pallet. The StorageValue
keeps track of a 64-bit unsigned integer (u64) value that is incremented each time you generate a new collectible, up to maximum of 18446744073709551615 unique collectibles.
#[pallet::storage]
pub(super) type CollectiblesCount<T: Config> = StorageValue<_, u64, ValueQuery>;
The ValueQuery
in this declaration specifies what a query should return if there's no value in storage.
There are three possible settings for handling what the query returns:
OptionQuery
, ResultQuery
, or ValueQuery
.
We use ValueQuery
here so that if there is no value in storage—for example when you first start the network—the query should return the value zero (0) rather than an OptionQuery
value of None
or a ResultQuery
value of Err
.
Map collectibles to their properties
The FRAME storage module provides a StorageMap
trait to store single key maps in the runtime.
A StorageMap named CollectiblesMap
maps each collectible to its unique information.
The key for the CollectiblesMap
map is the unique_id
of the collectible.
/// Maps the Collectible struct to the unique_id.
#[pallet::storage]
pub(super) type CollectibleMap<T: Config> = StorageMap<_, Twox64Concat, [u8; 16], Collectible<T>>;
The Twox64Concat
in this declaration specifies the hashing algorithm to use to create this storage value.
By allowing you to specify the hashing algorithm to use, storage maps allow you to control the level of security appropriate to the type of information being stored.
For example, you might choose a more performant but less secure hashing algorithm to store information about collectibles and a less performant but more secure hashing algorithm to store more sensitive information.
For information about the hashing algorithms Substrate supports and the security they provide, see Hashing algorithms.
Map owners to their collectibles
A StorageMap named OwnerOfCollectibles
maps user accounts to the collectibles they own.
The key for this storage map is a user account: T::AccountID
.
The value for this storage map is a BoundedVec
data type with the unique_id
of each collectible that each user account owns.
This map makes it easy to look up each individual collectible for its information since the unique_id
is used as the key for the collectiblesMap
map.
By using a BoundedVec, you can ensure that each storage item has a maximum length, which is important for managing limits within the runtime.
/// Track the collectibles owned by each account.
#[pallet::storage]
pub(super) type OwnerOfCollectibles<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
BoundedVec<[u8; 16], T::MaximumOwned>,
ValueQuery,
>;
In order for the code to compile, you'll need to annotate the Collectible
data structure with this derive macro:
#[scale_info(skip_type_params(T))]
Verify that your program compiles by running the following command:
cargo build --package collectibles
You can ignore the compiler warnings for now.