Storage Values
The most basic storage type for a blockchain is a single StorageValue
.
A StorageValue
is used to place a single object into the blockchain storage.
A single object can be as simple as a single type like a u32
, or more complex structures, or even vectors.
What is most important to understand is that a StorageValue
places a single entry into the merkle trie. So when you read data, you read all of it. When you write data, you write all of it. This is in contrast to a StorageMap
, which you will learn about next.
Construction
We constructed a simple StorageValue
for you in the code, but let's break it down:
#![allow(unused)] fn main() { #[pallet::storage] pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32>; }
As you can see, our storage is a type alias for a new instance of StorageValue
.
Our storage value has a parameter Value
where we can define the type we want to place in storage. In this case, it is a simple u32
.
You will also notice CountForKitties
is generic over <T: Config>
. All of our storage must be generic over <T: Config>
even if we are not using it directly. Macros use this generic parameter to fill in behind the scene details to make the StorageValue
work. Think about all the code behind the scenes which actually sticks this storage into a merkle trie in the database of our blockchain.
Visibility of the type is up to you and your needs, but you need to remember that blockchains are public databases. So pub
in this case is only about Rust, and allowing other modules to access this storage and its APIs directly.
You cannot make storage on a blockchain "private", and even if you make this storage without pub
, there are low level ways to manipulate the storage in the database.
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; pub use pallet::*; #[frame::pallet(dev_mode)] pub mod pallet { use super::*; #[pallet::pallet] pub struct Pallet<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; } /* 🚧 TODO 🚧: - Create a new `StorageValue` named `CountForKitties`. - `CountForKitties` should be generic over `<T: Config>`. - Set `Value` to `u32` to store that type. */ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> {} #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; Self::mint(who)?; Ok(()) } } } }
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; pub use pallet::*; #[frame::pallet(dev_mode)] pub mod pallet { use super::*; #[pallet::pallet] pub struct Pallet<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; } #[pallet::storage] pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> {} #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; Self::mint(who)?; Ok(()) } } } }
#![allow(unused)] fn main() { // Tests for the Kitties Pallet. // // Normally this file would be split into two parts: `mock.rs` and `tests.rs`. // The `mock.rs` file would contain all the setup code for our `TestRuntime`. // Then `tests.rs` would only have the tests for our pallet. // However, to minimize the project, these have been combined into this single file. // // Learn more about creating tests for Pallets: // https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/your_first_pallet/index.html // This flag tells rust to only run this file when running `cargo test`. #![cfg(test)] use crate as pallet_kitties; use crate::*; use frame::deps::sp_io; use frame::runtime::prelude::*; use frame::testing_prelude::*; use frame::traits::fungible::*; type Balance = u64; type Block = frame_system::mocking::MockBlock<TestRuntime>; // In our "test runtime", we represent a user `AccountId` with a `u64`. // This is just a simplification so that we don't need to generate a bunch of proper cryptographic // public keys when writing tests. It is just easier to say "user 1 transfers to user 2". // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; // Our blockchain tests only need 3 Pallets: // 1. System: Which is included with every FRAME runtime. // 2. PalletBalances: Which is manages your blockchain's native currency. (i.e. DOT on Polkadot) // 3. PalletKitties: The pallet you are building in this tutorial! construct_runtime! { pub struct TestRuntime { System: frame_system, PalletBalances: pallet_balances, PalletKitties: pallet_kitties, } } // Normally `System` would have many more configurations, but you can see that we use some macro // magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { type Block = Block; type AccountData = pallet_balances::AccountData<Balance>; } // Normally `pallet_balances` would have many more configurations, but you can see that we use some // macro magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for TestRuntime { type AccountStore = System; type Balance = Balance; } // This is the configuration of our Pallet! If you make changes to the pallet's `trait Config`, you // will also need to update this configuration to represent that. impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; } // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` // It simulates the blockchain database backend for our tests. // If you forget to include this and try to access your Pallet storage, you will get an error like: // "`get_version_1` called outside of an Externalities-provided environment." pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::<TestRuntime>::default() .build_storage() .unwrap() .into() } #[test] fn starting_template_is_sane() { new_test_ext().execute_with(|| { let event = Event::<TestRuntime>::Created { owner: ALICE }; let _runtime_event: RuntimeEvent = event.into(); let _call = Call::<TestRuntime>::create_kitty {}; let result = PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)); assert_ok!(result); }); } #[test] fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { // We often need to set `System` to block 1 so that we can see events. System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); }); } #[test] fn create_kitty_checks_signed() { new_test_ext().execute_with(|| { // The `create_kitty` extrinsic should work when being called by a user. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // The `create_kitty` extrinsic should fail when being called by an unsigned message. assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); }) } #[test] fn create_kitty_emits_event() { new_test_ext().execute_with(|| { // We need to set block number to 1 to view events. System::set_block_number(1); // Execute our call, and ensure it is successful. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // Assert the last event by our blockchain is the `Created` event with the correct owner. System::assert_last_event(Event::<TestRuntime>::Created { owner: 1 }.into()); }) } #[test] fn count_for_kitties_created_correctly() { new_test_ext().execute_with(|| { // Querying storage before anything is set will return `None`. assert_eq!(CountForKitties::<TestRuntime>::get(), None); // You can `set` the value using an `Option<u32>`. CountForKitties::<TestRuntime>::set(Some(1337u32)); // You can `put` the value directly with a `u32`. CountForKitties::<TestRuntime>::put(1337u32); // Check that the value is now in storage. assert_eq!(CountForKitties::<TestRuntime>::get(), Some(1337u32)); }) } }
diff --git a/src/lib.rs b/src/lib.rs
index ae8a09b..7d88fff 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,6 +18,12 @@ pub mod pallet {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
+ /* 🚧 TODO 🚧:
+ - Create a new `StorageValue` named `CountForKitties`.
+ - `CountForKitties` should be generic over `<T: Config>`.
+ - Set `Value` to `u32` to store that type.
+ */
+
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
diff --git a/src/lib.rs b/src/lib.rs
index 7d88fff..90242f6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,11 +18,8 @@ pub mod pallet {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
- /* 🚧 TODO 🚧:
- - Create a new `StorageValue` named `CountForKitties`.
- - `CountForKitties` should be generic over `<T: Config>`.
- - Set `Value` to `u32` to store that type.
- */
+ #[pallet::storage]
+ pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
diff --git a/src/tests.rs b/src/tests.rs
index 2526de8..7713826 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -117,3 +117,17 @@ fn create_kitty_emits_event() {
System::assert_last_event(Event::<TestRuntime>::Created { owner: 1 }.into());
})
}
+
+#[test]
+fn count_for_kitties_created_correctly() {
+ new_test_ext().execute_with(|| {
+ // Querying storage before anything is set will return `None`.
+ assert_eq!(CountForKitties::<TestRuntime>::get(), None);
+ // You can `set` the value using an `Option<u32>`.
+ CountForKitties::<TestRuntime>::set(Some(1337u32));
+ // You can `put` the value directly with a `u32`.
+ CountForKitties::<TestRuntime>::put(1337u32);
+ // Check that the value is now in storage.
+ assert_eq!(CountForKitties::<TestRuntime>::get(), Some(1337u32));
+ })
+}