Skip to content

Commit

Permalink
add example dummy storage value pallet
Browse files Browse the repository at this point in the history
  • Loading branch information
Sacha Lansky committed Sep 29, 2023
1 parent 9659e60 commit 5037b55
Show file tree
Hide file tree
Showing 5 changed files with 471 additions and 0 deletions.
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ members = [
"substrate/frame/elections-phragmen",
"substrate/frame/examples",
"substrate/frame/examples/basic",
"substrate/frame/examples/dummy-storage-value",
"substrate/frame/examples/default-config",
"substrate/frame/examples/dev-mode",
"substrate/frame/examples/kitchensink",
Expand Down
58 changes: 58 additions & 0 deletions substrate/frame/examples/dummy-storage-value/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
[package]
name = "pallet-example-dummy-storage-value"
version = "4.0.0-dev"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage = "https://substrate.io"
repository.workspace = true
description = "A basic example of using a storage value in a FRAME pallet."
readme = "README.md"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true}
frame-support = { path = "../../support", default-features = false}
frame-system = { path = "../../system", default-features = false}
pallet-balances = { path = "../../balances", default-features = false}
sp-io = { path = "../../../primitives/io", default-features = false}
sp-runtime = { path = "../../../primitives/runtime", default-features = false}
sp-std = { path = "../../../primitives/std", default-features = false}
docify = "0.2.4"

[dev-dependencies]
sp-core = { path = "../../../primitives/core", default-features = false}

[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-balances/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
238 changes: 238 additions & 0 deletions substrate/frame/examples/dummy-storage-value/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # Dummy Storage Value Example
//!
//! A pallet demonstrating basic usage of FRAME's
//! [`StorageValue`](frame_support::storage::types::StorageValue) API alongside other commonly used
//! FRAME features, such as ensuring the origin of a call and configuring the genesis storage of the
//! pallet.
//!
//! **WARNING: This pallet is not meant to be used in production.** The pallet is in `dev_mode`, so we don't need to care about specifying call indices or call
//! weights. Read more about configuring weights [here](https://docs.substrate.io/test/benchmark/).
//!
//! ## Overview
//!
//! We demonstrate the [`StorageValue`](frame_support::storage::types::StorageValue) API by showing
//! it's use of `mutate` on:
//! - A storage item that stores some `Balance` and uses the default
//! [`OptionQuery`](frame_support::storage::types::OptionQuery) which will always either return
//! `Option<T>` or `None` when queried
#![doc = docify::embed!("src/tests.rs", accumulate_dummy_works)]
//! - A storage item that stores a `u32` and uses
//! [`ValueQuery`](frame_support::storage::types::ValueQuery) to always return `T` or
//! `Default::default` if the stored value is removed, which in this case will be `0`
//! (`u32::default()`)
#![doc = docify::embed!("src/tests.rs", accumulate_dummy_value_query_works)]
//! We provide an example of a call that can only set a new value in Dummy if it is called by the root origin:
#![doc = docify::embed!("src/tests.rs", set_dummy_works)]
//! We also configure our pallet's genesis state using [`GenesisConfig`] which we demonstrate in the mock runtime
//! environment to run our tests against:
#![doc = docify::embed!("src/tests.rs", new_test_ext)]

// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{dispatch::DispatchResult, sp_runtime::Saturating};
use frame_system::ensure_signed;

// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;

#[cfg(test)]
mod tests;

/// A type alias for the balance type from this pallet's point of view.
type BalanceOf<T> = <T as pallet_balances::Config>::Balance;

// Definition of the pallet logic, to be aggregated in a chain's runtime definition.
// Note: we're using this palelt in `dev_mode`.
#[frame_support::pallet(dev_mode)]
pub mod pallet {
// Import various types used to declare pallet in scope.
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

/// Our pallet's configuration trait.
///
/// Because all FRAME pallets depend on some core utility types from the System pallet,
/// [`frame_system::Config`] should always be included. This pallet example uses the `Balances`
/// type from `pallet_balances` which we make available by bounding this trait with
/// `pallet_balances::Config`.
#[pallet::config]
pub trait Config: pallet_balances::Config + frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}

// The `Pallet` type is a placeholder we use to implement traits and methods for the pallet.
#[pallet::pallet]
pub struct Pallet<T>(_);

/// An example storage item to store a single value, in our case, some Balance.
/// This storage item uses [`OptionQuery`] by default which will return what is in actual state
/// provided by [`sp_io::storage`]. If a value `v` exists in state, it returns `Some(v)`,
/// otherwise it returns `None`.
///
/// The getter attribute generates a function on the `Pallet` struct that we can use to
/// conveniently retrieve the current value stored.
#[doc = docify::embed!("src/tests.rs", accumulate_dummy_works)]
#[pallet::storage]
#[pallet::getter(fn dummy)]
pub(super) type Dummy<T: Config> = StorageValue<_, T::Balance>;

/// An example storage item that stores a `u32` value.
/// Here, we're using [`ValueQuery`] instead of the default [`OptionQuery`]. If a value exists
/// in state, it will return that raw `u32` value, otherwise it will return `u32::default()`.
#[doc = docify::embed!("src/tests.rs", accumulate_dummy_value_query_works)]
#[pallet::storage]
#[pallet::getter(fn dummy_value_query)]
pub(super) type DummyValueQuery<T: Config> = StorageValue<_, u32, ValueQuery>;

/// The optional genesis configuration type, which we use here to demonstrate how to configure
/// building the genesis storage for this pallet.
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub dummy: T::Balance,
pub dummy_value_query: u32,
}

/// The genesis build for this pallet.
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
Dummy::<T>::put(&self.dummy);
DummyValueQuery::<T>::put(&self.dummy_value_query);
}
}

/// Events are a simple means of providing some metadata about specific state changes that have
/// been made that can be useful to the outside world (for e.g. apps or chain explorers).
///
/// All events are stored in the System pallet at the end of each block.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// The Dummy value has been accumulated.
AccumulateDummy {
/// The amount `Dummy` has been increased by.
increase_by: BalanceOf<T>,
},
/// The Dummy value has been set.
SetDummy {
/// The new value of `Dummy`.
new_balance: BalanceOf<T>,
},
}

/// The call declaration block of our pallet.
impl<T: Config> Pallet<T> {
/// A public call that increases the value in `Dummy`.
///
/// This can be called by any signed origin. The example uses the [`StorageValue::mutate`]
/// method to demonstrate a safe and elegant way to accumulate the stored value.
pub fn accumulate_dummy(origin: OriginFor<T>, increase_by: T::Balance) -> DispatchResult {
// we ensure that the origin is signed
let _sender = ensure_signed(origin)?;

// using `mutate`, we can query the value in storage and update it in just a few lines
Dummy::<T>::mutate(|dummy| {
// we use `saturating_add` instead of a regular `+` to avoid overflowing
let new_dummy = dummy.map_or(increase_by, |d| d.saturating_add(increase_by));
*dummy = Some(new_dummy);
});

// Here's another way to accumulate the value in `Dummy`:
// read the value from storage using the generated getter function
// let dummy = Self::dummy();
// calculate the new value
// let new_dummy = dummy.map_or(increase_by, |dummy| dummy.saturating_add(increase_by));
// put the new value into storage
// Dummy::<T>::put(new_dummy);

// deposit an event to let the outside world know this storage update has
// happened
Self::deposit_event(Event::AccumulateDummy { increase_by });

// no errors returned
Ok(())
}

/// A privileged call that can set the value in `Dummy` to a new value.
///
/// This must be called with a `Root` origin, implying that only the system as a whole has
/// decided to execute this call. Different runtimes have different reasons to allow
/// privileged calls to be executed - we don't need to care why. Because it's privileged, we
/// can assume it's a one-off operation and substantial processing/storage/memory can be
/// used without worrying about gameability or attack scenarios.
pub fn set_dummy(origin: OriginFor<T>, new_balance: T::Balance) -> DispatchResult {
// we ensure that the caller is a root origin
ensure_root(origin)?;

// put the new value into storage
Dummy::<T>::put(new_balance);

// deposit an event
Self::deposit_event(Event::SetDummy { new_balance });

// no errors returned
Ok(())
}
}
}

/// The main implementation block for our pallet.
impl<T: Config> Pallet<T> {
/// Removes the values in our Dummy and DummyValueQuery storage items.
///
/// We use this function in our unit tests to showcase the behavior of `OptionQuery` and
/// `ValueQuery`.
#[warn(dead_code)]
fn do_reset_dummy(origin: T::RuntimeOrigin) -> DispatchResult {
let _sender = ensure_signed(origin)?;

Dummy::<T>::kill();
DummyValueQuery::<T>::kill();

assert_eq!(Self::dummy(), None);
assert_eq!(Self::dummy_value_query(), u32::default());

Ok(())
}

/// Accumulates the value in [`DummyValueQuery`].
///
/// This demonstrates using the `mutate` method from the
/// [`StorageValue`](frame_support::storage::types::StorageValue) API on
/// [`ValueQuery`](frame_support::storage::types::ValueQuery)
#[warn(dead_code)]
fn accumulate_value_query(origin: T::RuntimeOrigin, increase_by: u32) -> DispatchResult {
let _sender = ensure_signed(origin)?;

let prev = DummyValueQuery::<T>::get();
// because DummyValueQuery uses [`ValueQuery`], 'value' is the raw type
let result = DummyValueQuery::<T>::mutate(|value| {
*value = value.saturating_add(increase_by);
*value
});
assert!(prev + increase_by == result);

Ok(())
}
}
Loading

0 comments on commit 5037b55

Please sign in to comment.