feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
[package]
name = "pallet-examples"
version = "4.0.0-dev"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "The single package with examples of various types of FRAME pallets"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
pallet-default-config-example = { workspace = true }
pallet-dev-mode = { workspace = true }
pallet-example-authorization-tx-extension = { workspace = true }
pallet-example-basic = { workspace = true }
pallet-example-frame-crate = { workspace = true }
pallet-example-kitchensink = { workspace = true }
pallet-example-offchain-worker = { workspace = true }
pallet-example-single-block-migrations = { workspace = true }
pallet-example-split = { workspace = true }
pallet-example-tasks = { workspace = true }
pallet-example-view-functions = { workspace = true }
[features]
default = ["std"]
std = [
"pallet-default-config-example/std",
"pallet-dev-mode/std",
"pallet-example-authorization-tx-extension/std",
"pallet-example-basic/std",
"pallet-example-frame-crate/std",
"pallet-example-kitchensink/std",
"pallet-example-offchain-worker/std",
"pallet-example-single-block-migrations/std",
"pallet-example-split/std",
"pallet-example-tasks/std",
"pallet-example-view-functions/std",
]
try-runtime = [
"pallet-default-config-example/try-runtime",
"pallet-dev-mode/try-runtime",
"pallet-example-authorization-tx-extension/try-runtime",
"pallet-example-basic/try-runtime",
"pallet-example-kitchensink/try-runtime",
"pallet-example-offchain-worker/try-runtime",
"pallet-example-single-block-migrations/try-runtime",
"pallet-example-split/try-runtime",
"pallet-example-tasks/try-runtime",
"pallet-example-view-functions/try-runtime",
]
runtime-benchmarks = [
"pallet-default-config-example/runtime-benchmarks",
"pallet-dev-mode/runtime-benchmarks",
"pallet-example-authorization-tx-extension/runtime-benchmarks",
"pallet-example-basic/runtime-benchmarks",
"pallet-example-frame-crate/runtime-benchmarks",
"pallet-example-kitchensink/runtime-benchmarks",
"pallet-example-offchain-worker/runtime-benchmarks",
"pallet-example-single-block-migrations/runtime-benchmarks",
"pallet-example-split/runtime-benchmarks",
"pallet-example-tasks/runtime-benchmarks",
"pallet-example-view-functions/runtime-benchmarks",
]
@@ -0,0 +1,64 @@
[package]
name = "pallet-example-authorization-tx-extension"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example authorization transaction extension pallet"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
docify = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame-benchmarking = { optional = true, workspace = true }
frame-support = { features = ["experimental"], workspace = true }
frame-system = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
pallet-verify-signature = { workspace = true }
sp-core = { workspace = true }
sp-keyring = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-verify-signature/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-verify-signature/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-keyring/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-verify-signature/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,139 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use core::{fmt, marker::PhantomData};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::{pallet_prelude::TransactionSource, traits::OriginTrait, Parameter};
use scale_info::TypeInfo;
use sp_runtime::{
impl_tx_ext_default,
traits::{
DispatchInfoOf, DispatchOriginOf, IdentifyAccount, TransactionExtension, ValidateResult,
Verify,
},
transaction_validity::{InvalidTransaction, ValidTransaction},
};
use crate::pallet_coownership::{Config, Origin};
/// Helper struct to organize the data needed for signature verification of both parties involved.
#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
pub struct AuthCredentials<Signer, Signature> {
first: (Signer, Signature),
second: (Signer, Signature),
}
/// Extension that, if activated by providing a pair of signers and signatures, will authorize a
/// coowner origin of the two signers. Both signers have to construct their signatures on all of the
/// data that follows this extension in the `TransactionExtension` pipeline, their implications and
/// the call. Essentially re-sign the transaction from this point onwards in the pipeline by using
/// the `inherited_implication`, as shown below.
#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct AuthorizeCoownership<T, Signer, Signature> {
inner: Option<AuthCredentials<Signer, Signature>>,
_phantom: PhantomData<T>,
}
impl<T: Config, Signer, Signature> Default for AuthorizeCoownership<T, Signer, Signature> {
fn default() -> Self {
Self { inner: None, _phantom: Default::default() }
}
}
impl<T: Config, Signer, Signature> AuthorizeCoownership<T, Signer, Signature> {
/// Creates an active extension that will try to authorize the coownership origin.
pub fn new(first: (Signer, Signature), second: (Signer, Signature)) -> Self {
Self { inner: Some(AuthCredentials { first, second }), _phantom: Default::default() }
}
}
impl<T: Config, Signer, Signature> fmt::Debug for AuthorizeCoownership<T, Signer, Signature> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "AuthorizeCoownership")
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
Ok(())
}
}
impl<T: Config + Send + Sync, Signer, Signature> TransactionExtension<T::RuntimeCall>
for AuthorizeCoownership<T, Signer, Signature>
where
Signer: IdentifyAccount<AccountId = T::AccountId> + Parameter + Send + Sync + 'static,
Signature: Verify<Signer = Signer> + Parameter + Send + Sync + 'static,
{
const IDENTIFIER: &'static str = "AuthorizeCoownership";
type Implicit = ();
type Val = ();
type Pre = ();
fn validate(
&self,
mut origin: DispatchOriginOf<T::RuntimeCall>,
_call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
_len: usize,
_self_implicit: Self::Implicit,
inherited_implication: &impl codec::Encode,
_source: TransactionSource,
) -> ValidateResult<Self::Val, T::RuntimeCall> {
// If the extension is inactive, just move on in the pipeline.
let Some(auth) = &self.inner else {
return Ok((ValidTransaction::default(), (), origin));
};
let first_account = auth.first.0.clone().into_account();
let second_account = auth.second.0.clone().into_account();
// Construct the payload to sign using the `inherited_implication`.
let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
// Both parties' signatures must be correct for the origin to be authorized.
// In a prod environment, we're just return a `InvalidTransaction::BadProof` if the
// signature isn't valid, but we return these custom errors to be able to assert them in
// tests.
if !auth.first.1.verify(&msg[..], &first_account) {
Err(InvalidTransaction::Custom(100))?
}
if !auth.second.1.verify(&msg[..], &second_account) {
Err(InvalidTransaction::Custom(200))?
}
// Construct a `pallet_coownership::Origin`.
let local_origin = Origin::Coowners(first_account, second_account);
// Turn it into a local `PalletsOrigin`.
let local_origin = <T as Config>::PalletsOrigin::from(local_origin);
// Then finally into a pallet `RuntimeOrigin`.
let local_origin = <T as Config>::RuntimeOrigin::from(local_origin);
// Which the `set_caller_from` function will convert into the overarching `RuntimeOrigin`
// created by `construct_runtime!`.
origin.set_caller_from(local_origin);
// Make sure to return the new origin.
Ok((ValidTransaction::default(), (), origin))
}
// We're not doing any special logic in `TransactionExtension::prepare`, so just impl a default.
impl_tx_ext_default!(T::RuntimeCall; weight prepare);
}
@@ -0,0 +1,174 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Authorization Transaction Extension Example Pallet
//!
//! **This pallet serves as an example and is not meant to be used in production.**
//!
//! FRAME Transaction Extension reference implementation, origin mutation, origin authorization and
//! integration in a `TransactionExtension` pipeline.
//!
//! The [TransactionExtension](sp_runtime::traits::TransactionExtension) used in this example is
//! [AuthorizeCoownership](extensions::AuthorizeCoownership). If activated, the extension will
//! authorize 2 signers as coowners, with a [coowner origin](pallet_coownership::Origin) specific to
//! the [coownership example pallet](pallet_coownership), by validating a signature of the rest of
//! the transaction from each party. This means any extensions after ours in the pipeline, their
//! implicits and the actual call. The extension pipeline used in our example checks the genesis
//! hash, transaction version and mortality of the transaction after the `AuthorizeCoownership` runs
//! as we want these transactions to run regardless of what origin passes through them and/or we
//! want their implicit data in any signature authorization happening earlier in the pipeline.
//!
//! In this example, aside from the [AuthorizeCoownership](extensions::AuthorizeCoownership)
//! extension, we use the following pallets:
//! - [pallet_coownership] - provides a coowner origin and the functionality to authorize it.
//! - [pallet_assets] - a dummy asset pallet that tracks assets, identified by an
//! [AssetId](pallet_assets::AssetId), and their respective owners, which can be either an
//! [account](pallet_assets::Owner::Single) or a [pair of owners](pallet_assets::Owner::Double).
//!
//! Assets are created in [pallet_assets] using the
//! [create_asset](pallet_assets::Call::create_asset) call, which accepts traditionally signed
//! origins (a single account) or coowner origins, authorized through the
//! [CoownerOrigin](pallet_assets::Config::CoownerOrigin) type.
//!
//! ### Example runtime setup
#![doc = docify::embed!("src/mock.rs", example_runtime)]
//!
//! ### Example usage
#![doc = docify::embed!("src/tests.rs", create_coowned_asset_works)]
//!
//! This example does not focus on any pallet logic or syntax, but rather on `TransactionExtension`
//! functionality. The pallets used are just skeletons to provide storage state and custom origin
//! choices and requirements, as shown in the examples. Any weight and/or
//! transaction fee is out of scope for this example.
#![cfg_attr(not(feature = "std"), no_std)]
pub mod extensions;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
extern crate alloc;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[frame_support::pallet(dev_mode)]
pub mod pallet_coownership {
use super::*;
use frame_support::traits::OriginTrait;
#[pallet::config]
pub trait Config: frame_system::Config {
/// The aggregated origin which the dispatch will take.
type RuntimeOrigin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
+ From<Self::PalletsOrigin>
+ IsType<<Self as frame_system::Config>::RuntimeOrigin>;
/// The caller origin, overarching type of all pallets origins.
type PalletsOrigin: From<Origin<Self>> + TryInto<Origin<Self>, Error = Self::PalletsOrigin>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
/// Origin that this pallet can authorize. For the purposes of this example, it's just two
/// accounts that own something together.
#[pallet::origin]
#[derive(
Clone,
PartialEq,
Eq,
RuntimeDebug,
Encode,
Decode,
DecodeWithMemTracking,
MaxEncodedLen,
TypeInfo,
)]
pub enum Origin<T: Config> {
Coowners(T::AccountId, T::AccountId),
}
}
#[frame_support::pallet(dev_mode)]
pub mod pallet_assets {
use super::*;
pub type AssetId = u32;
/// Type that describes possible owners of a particular asset.
#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum Owner<AccountId> {
Single(AccountId),
Double(AccountId, AccountId),
}
#[pallet::config]
pub trait Config: frame_system::Config {
/// Type that can authorize an account pair coowner origin.
type CoownerOrigin: EnsureOrigin<
Self::RuntimeOrigin,
Success = (Self::AccountId, Self::AccountId),
>;
}
/// Map that holds the owner information for each asset it manages.
#[pallet::storage]
pub type AssetOwners<T> =
StorageMap<_, Blake2_128Concat, AssetId, Owner<<T as frame_system::Config>::AccountId>>;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::error]
pub enum Error<T> {
/// Asset already exists.
AlreadyExists,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Simple call that just creates an asset with a specific `AssetId`. This call will fail if
/// there is already an asset with the same `AssetId`.
///
/// The origin is either a single account (traditionally signed origin) or a coowner origin.
#[pallet::call_index(0)]
pub fn create_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
let owner: Owner<T::AccountId> = match T::CoownerOrigin::try_origin(origin) {
Ok((first, second)) => Owner::Double(first, second),
Err(origin) => ensure_signed(origin).map(|account| Owner::Single(account))?,
};
AssetOwners::<T>::try_mutate(asset_id, |maybe_owner| {
if maybe_owner.is_some() {
return Err(Error::<T>::AlreadyExists);
}
*maybe_owner = Some(owner);
Ok(())
})?;
Ok(())
}
}
}
@@ -0,0 +1,134 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::*;
pub(crate) use example_runtime::*;
use extensions::AuthorizeCoownership;
use frame_support::derive_impl;
use frame_system::{CheckEra, CheckGenesis, CheckNonce, CheckTxVersion};
use pallet_verify_signature::VerifySignature;
use sp_runtime::{
generic,
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
BuildStorage, MultiSignature, MultiSigner,
};
#[docify::export]
mod example_runtime {
use super::*;
/// Our `TransactionExtension` fit for general transactions.
pub type TxExtension = (
// Validate the signature of regular account transactions (substitutes the old signed
// transaction).
VerifySignature<Runtime>,
// Nonce check (and increment) for the caller.
CheckNonce<Runtime>,
// If activated, will mutate the origin to a `pallet_coownership` origin of 2 accounts that
// own something.
AuthorizeCoownership<Runtime, MultiSigner, MultiSignature>,
// Some other extensions that we want to run for every possible origin and we want captured
// in any and all signature and authorization schemes (such as the traditional account
// signature or the double signature in `pallet_coownership`).
CheckGenesis<Runtime>,
CheckTxVersion<Runtime>,
CheckEra<Runtime>,
);
/// Convenience type to more easily construct the signature to be signed in case
/// `AuthorizeCoownership` is activated.
pub type InnerTxExtension = (CheckGenesis<Runtime>, CheckTxVersion<Runtime>, CheckEra<Runtime>);
pub type UncheckedExtrinsic =
generic::UncheckedExtrinsic<AccountId, RuntimeCall, Signature, TxExtension>;
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
pub type Signature = MultiSignature;
pub type BlockNumber = u32;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Runtime
{
System: frame_system,
VerifySignaturePallet: pallet_verify_signature,
Assets: pallet_assets,
Coownership: pallet_coownership,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type AccountId = AccountId;
type Block = Block;
type Lookup = IdentityLookup<Self::AccountId>;
}
impl pallet_verify_signature::Config for Runtime {
type Signature = MultiSignature;
type AccountIdentifier = MultiSigner;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}
/// Type that enables any pallet to ask for a coowner origin.
pub struct EnsureCoowner;
impl EnsureOrigin<RuntimeOrigin> for EnsureCoowner {
type Success = (AccountId, AccountId);
fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
match o.clone().into() {
Ok(pallet_coownership::Origin::<Runtime>::Coowners(first, second)) =>
Ok((first, second)),
_ => Err(o),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
unimplemented!()
}
}
impl pallet_assets::Config for Runtime {
type CoownerOrigin = EnsureCoowner;
}
impl pallet_coownership::Config for Runtime {
type RuntimeOrigin = RuntimeOrigin;
type PalletsOrigin = OriginCaller;
}
}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = RuntimeGenesisConfig {
// We use default for brevity, but you can configure as desired if needed.
system: Default::default(),
}
.build_storage()
.unwrap();
t.into()
}
@@ -0,0 +1,280 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Tests for pallet-example-authorization-tx-extension.
use codec::Encode;
use frame_support::{
assert_noop,
dispatch::GetDispatchInfo,
pallet_prelude::{InvalidTransaction, TransactionValidityError},
};
use pallet_verify_signature::VerifySignature;
use sp_keyring::Sr25519Keyring;
use sp_runtime::{
generic::ExtensionVersion,
traits::{Applyable, Checkable, IdentityLookup, TransactionExtension},
MultiSignature, MultiSigner,
};
use crate::{extensions::AuthorizeCoownership, mock::*, pallet_assets};
#[test]
fn create_asset_works() {
new_test_ext().execute_with(|| {
let alice_keyring = Sr25519Keyring::Alice;
let alice_account = AccountId::from(alice_keyring.public());
// Simple call to create asset with Id `42`.
let create_asset_call =
RuntimeCall::Assets(pallet_assets::Call::create_asset { asset_id: 42 });
let ext_version: ExtensionVersion = 0;
// Create extension that will be used for dispatch.
let initial_nonce = 23;
let tx_ext = (
frame_system::CheckNonce::<Runtime>::from(initial_nonce),
AuthorizeCoownership::<Runtime, MultiSigner, MultiSignature>::default(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create the transaction signature, to be used in the top level `VerifyMultiSignature`
// extension.
let tx_sign = MultiSignature::Sr25519(
(&(ext_version, &create_asset_call), &tx_ext, tx_ext.implicit().unwrap())
.using_encoded(|e| alice_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Add the signature to the extension.
let tx_ext = (
VerifySignature::new_with_signature(tx_sign, alice_account.clone()),
frame_system::CheckNonce::<Runtime>::from(initial_nonce),
AuthorizeCoownership::<Runtime, MultiSigner, MultiSignature>::default(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create the transaction and we're ready for dispatch.
let uxt = UncheckedExtrinsic::new_transaction(create_asset_call, tx_ext);
// Check Extrinsic validity and apply it.
let uxt_info = uxt.get_dispatch_info();
let uxt_len = uxt.using_encoded(|e| e.len());
// Manually pay for Alice's nonce.
frame_system::Account::<Runtime>::mutate(&alice_account, |info| {
info.nonce = initial_nonce;
info.providers = 1;
});
// Check should pass.
let xt = <UncheckedExtrinsic as Checkable<IdentityLookup<AccountId>>>::check(
uxt,
&Default::default(),
)
.unwrap();
// Apply the extrinsic.
let res = xt.apply::<Runtime>(&uxt_info, uxt_len).unwrap();
// Asserting the results.
assert_eq!(frame_system::Account::<Runtime>::get(&alice_account).nonce, initial_nonce + 1);
assert_eq!(
pallet_assets::AssetOwners::<Runtime>::get(42),
Some(pallet_assets::Owner::<AccountId>::Single(alice_account))
);
assert!(res.is_ok());
});
}
#[docify::export]
#[test]
fn create_coowned_asset_works() {
new_test_ext().execute_with(|| {
let alice_keyring = Sr25519Keyring::Alice;
let bob_keyring = Sr25519Keyring::Bob;
let charlie_keyring = Sr25519Keyring::Charlie;
let alice_account = AccountId::from(alice_keyring.public());
let bob_account = AccountId::from(bob_keyring.public());
let charlie_account = AccountId::from(charlie_keyring.public());
// Simple call to create asset with Id `42`.
let create_asset_call =
RuntimeCall::Assets(pallet_assets::Call::create_asset { asset_id: 42 });
let ext_version: ExtensionVersion = 0;
// Create the inner transaction extension, to be signed by our coowners, Alice and Bob.
let inner_ext: InnerTxExtension = (
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create the payload Alice and Bob need to sign.
let inner_payload =
(&(ext_version, &create_asset_call), &inner_ext, inner_ext.implicit().unwrap());
// Create Alice's signature.
let alice_inner_sig = MultiSignature::Sr25519(
inner_payload.using_encoded(|e| alice_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Create Bob's signature.
let bob_inner_sig = MultiSignature::Sr25519(
inner_payload.using_encoded(|e| bob_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Create the transaction extension, to be signed by the submitter of the extrinsic, let's
// have it be Charlie.
let initial_nonce = 23;
let tx_ext = (
frame_system::CheckNonce::<Runtime>::from(initial_nonce),
AuthorizeCoownership::<Runtime, MultiSigner, MultiSignature>::new(
(alice_keyring.into(), alice_inner_sig.clone()),
(bob_keyring.into(), bob_inner_sig.clone()),
),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create Charlie's transaction signature, to be used in the top level
// `VerifyMultiSignature` extension.
let tx_sign = MultiSignature::Sr25519(
(&(ext_version, &create_asset_call), &tx_ext, tx_ext.implicit().unwrap())
.using_encoded(|e| charlie_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Add the signature to the extension.
let tx_ext = (
VerifySignature::new_with_signature(tx_sign, charlie_account.clone()),
frame_system::CheckNonce::<Runtime>::from(initial_nonce),
AuthorizeCoownership::<Runtime, MultiSigner, MultiSignature>::new(
(alice_keyring.into(), alice_inner_sig),
(bob_keyring.into(), bob_inner_sig),
),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create the transaction and we're ready for dispatch.
let uxt = UncheckedExtrinsic::new_transaction(create_asset_call, tx_ext);
// Check Extrinsic validity and apply it.
let uxt_info = uxt.get_dispatch_info();
let uxt_len = uxt.using_encoded(|e| e.len());
// Manually pay for Charlie's nonce.
frame_system::Account::<Runtime>::mutate(&charlie_account, |info| {
info.nonce = initial_nonce;
info.providers = 1;
});
// Check should pass.
let xt = <UncheckedExtrinsic as Checkable<IdentityLookup<AccountId>>>::check(
uxt,
&Default::default(),
)
.unwrap();
// Apply the extrinsic.
let res = xt.apply::<Runtime>(&uxt_info, uxt_len).unwrap();
// Asserting the results.
assert!(res.is_ok());
assert_eq!(frame_system::Account::<Runtime>::get(charlie_account).nonce, initial_nonce + 1);
assert_eq!(
pallet_assets::AssetOwners::<Runtime>::get(42),
Some(pallet_assets::Owner::<AccountId>::Double(alice_account, bob_account))
);
});
}
#[test]
fn inner_authorization_works() {
new_test_ext().execute_with(|| {
let alice_keyring = Sr25519Keyring::Alice;
let bob_keyring = Sr25519Keyring::Bob;
let charlie_keyring = Sr25519Keyring::Charlie;
let charlie_account = AccountId::from(charlie_keyring.public());
// Simple call to create asset with Id `42`.
let create_asset_call =
RuntimeCall::Assets(pallet_assets::Call::create_asset { asset_id: 42 });
let ext_version: ExtensionVersion = 0;
// Create the inner transaction extension, to be signed by our coowners, Alice and Bob. They
// are going to sign this transaction as a mortal one.
let inner_ext: InnerTxExtension = (
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
// Sign with mortal era check.
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::mortal(4, 0)),
);
// Create the payload Alice and Bob need to sign.
let inner_payload = (&create_asset_call, &inner_ext, inner_ext.implicit().unwrap());
// Create Alice's signature.
let alice_inner_sig = MultiSignature::Sr25519(
inner_payload.using_encoded(|e| alice_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Create Bob's signature.
let bob_inner_sig = MultiSignature::Sr25519(
inner_payload.using_encoded(|e| bob_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Create the transaction extension, to be signed by the submitter of the extrinsic, let's
// have it be Charlie.
let initial_nonce = 23;
let tx_ext = (
frame_system::CheckNonce::<Runtime>::from(initial_nonce),
AuthorizeCoownership::<Runtime, MultiSigner, MultiSignature>::new(
(alice_keyring.into(), alice_inner_sig.clone()),
(bob_keyring.into(), bob_inner_sig.clone()),
),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
// Construct the transaction as immortal with a different era check.
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create Charlie's transaction signature, to be used in the top level
// `VerifyMultiSignature` extension.
let tx_sign = MultiSignature::Sr25519(
(&(ext_version, &create_asset_call), &tx_ext, tx_ext.implicit().unwrap())
.using_encoded(|e| charlie_keyring.sign(&sp_io::hashing::blake2_256(e))),
);
// Add the signature to the extension that Charlie signed.
let tx_ext = (
VerifySignature::new_with_signature(tx_sign, charlie_account.clone()),
frame_system::CheckNonce::<Runtime>::from(initial_nonce),
AuthorizeCoownership::<Runtime, MultiSigner, MultiSignature>::new(
(alice_keyring.into(), alice_inner_sig),
(bob_keyring.into(), bob_inner_sig),
),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
// Construct the transaction as immortal with a different era check.
frame_system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::immortal()),
);
// Create the transaction and we're ready for dispatch.
let uxt = UncheckedExtrinsic::new_transaction(create_asset_call, tx_ext);
// Check Extrinsic validity and apply it.
let uxt_info = uxt.get_dispatch_info();
let uxt_len = uxt.using_encoded(|e| e.len());
// Manually pay for Charlie's nonce.
frame_system::Account::<Runtime>::mutate(charlie_account, |info| {
info.nonce = initial_nonce;
info.providers = 1;
});
// Check should pass.
let xt = <UncheckedExtrinsic as Checkable<IdentityLookup<AccountId>>>::check(
uxt,
&Default::default(),
)
.unwrap();
// The extrinsic should fail as the signature for the `AuthorizeCoownership` doesn't work
// for the provided payload with the changed transaction mortality.
assert_noop!(
xt.apply::<Runtime>(&uxt_info, uxt_len),
TransactionValidityError::Invalid(InvalidTransaction::Custom(100))
);
});
}
+60
View File
@@ -0,0 +1,60 @@
[package]
name = "pallet-example-basic"
version = "27.0.0"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example pallet"
readme = "README.md"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
pallet-balances = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-core = { workspace = true }
[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",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
+240
View File
@@ -0,0 +1,240 @@
<!-- markdown-link-check-disable -->
# Basic Example Pallet
<!-- Original author of paragraph: @gavofyork -->
The Example: A simple example of a FRAME pallet demonstrating
concepts, APIs and structures common to most FRAME runtimes.
Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation.
**This pallet serves as an example and is not meant to be used in production.**
## Documentation Guidelines
<!-- Original author of paragraph: Various. Based on collation of review comments to PRs addressing issues with -->
<!-- label 'S3-FRAME' in https://github.com/paritytech/substrate-developer-hub/issues -->
<ul>
<li>Documentation comments (i.e. <code>/// comment</code>) - should
accompany pallet functions and be restricted to the pallet interface,
not the internals of the pallet implementation. Only state inputs,
outputs, and a brief description that mentions whether calling it
requires root, but without repeating the source code details.
Capitalize the first word of each documentation comment and end it with
a full stop. See
<a href="https://github.com/paritytech/substrate#72-contributing-to-documentation-for-substrate-packages"
target="_blank"> Generic example of annotating source code with documentation comments</a></li>
<li>Self-documenting code - Try to refactor code to be self-documenting.</li>
<li>Code comments - Supplement complex code with a brief explanation, not every line of code.</li>
<li>Identifiers - surround by backticks (i.e. <code>INHERENT_IDENTIFIER</code>, <code>InherentType</code>,
<code>u64</code>)</li>
<li>Usage scenarios - should be simple doctests. The compiler should ensure they stay valid.</li>
<li>Extended tutorials - should be moved to external files and refer to.</li>
<!-- Original author of paragraph: @AmarRSingh -->
<li>Mandatory - include all of the sections/subsections where <b>MUST</b> is specified.</li>
<li>Optional - optionally include sections/subsections where <b>CAN</b> is specified.</li>
</ul>
### Documentation Template:<br>
Copy and paste this template from frame/examples/basic/src/lib.rs into file
`frame/<INSERT_CUSTOM_PALLET_NAME>/src/lib.rs` of your own custom pallet and complete it.
<details><p><pre>
// Add heading with custom pallet name
\# <INSERT_CUSTOM_PALLET_NAME> Pallet
// Add simple description
// Include the following links that shows what trait needs to be implemented to use the pallet
// and the supported dispatchables that are documented in the Call enum.
- \[`<INSERT_CUSTOM_PALLET_NAME>::Config`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/trait.Config.html)
- \[`Call`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/enum.Call.html)
- \[`Module`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/struct.Module.html)
\## Overview
<!-- Original author of paragraph: Various. See https://github.com/paritytech/substrate-developer-hub/issues/44 -->
// Short description of pallet's purpose.
// Links to Traits that should be implemented.
// What this pallet is for.
// What functionality the pallet provides.
// When to use the pallet (use case examples).
// How it is used.
// Inputs it uses and the source of each input.
// Outputs it produces.
<!-- Original author of paragraph: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
<!-- and comment https://github.com/paritytech/substrate-developer-hub/issues/44#issuecomment-471982710 -->
\## Terminology
// Add terminology used in the custom pallet. Include concepts, storage items, or actions that you think
// deserve to be noted to give context to the rest of the documentation or pallet usage. The author needs to
// use some judgment about what is included. We don't want a list of every storage item nor types - the user
// can go to the code for that. For example, "transfer fee" is obvious and should not be included, but
// "free balance" and "reserved balance" should be noted to give context to the pallet.
// Please do not link to outside resources. The reference docs should be the ultimate source of truth.
<!-- Original author of heading: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
\## Goals
// Add goals that the custom pallet is designed to achieve.
<!-- Original author of heading: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
\### Scenarios
<!-- Original author of paragraph: @Kianenigma. Based on PR https://github.com/paritytech/substrate/pull/1951 -->
\#### <INSERT_SCENARIO_NAME>
// Describe requirements prior to interacting with the custom pallet.
// Describe the process of interacting with the custom pallet for this scenario and public API functions used.
\## Interface
\### Supported Origins
// What origins are used and supported in this pallet (root, signed, none)
// i.e. root when <code>\`ensure_root\`</code> used
// i.e. none when <code>\`ensure_none\`</code> used
// i.e. signed when <code>\`ensure_signed\`</code> used
<code>\`inherent\`</code> <INSERT_DESCRIPTION>
<!-- Original author of paragraph: @Kianenigma in comment -->
<!-- https://github.com/paritytech/substrate-developer-hub/issues/44#issuecomment-471982710 -->
\### Types
// Type aliases. Include any associated types and where the user would typically define them.
<code>\`ExampleType\`</code> <INSERT_DESCRIPTION>
<!-- Original author of paragraph: ??? -->
// Reference documentation of aspects such as `storageItems` and `dispatchable` functions should only be
// included in the https://docs.rs Rustdocs for Substrate and not repeated in the README file.
\### Dispatchable Functions
<!-- Original author of paragraph: @AmarRSingh & @joepetrowski -->
// A brief description of dispatchable functions and a link to the rustdoc with their actual documentation.
// <b>MUST</b> have link to Call enum
// <b>MUST</b> have origin information included in function doc
// <b>CAN</b> have more info up to the user
\### Public Functions
<!-- Original author of paragraph: @joepetrowski -->
// A link to the rustdoc and any notes about usage in the pallet, not for specific functions.
// For example, in the Balances Pallet: "Note that when using the publicly exposed functions,
// you (the runtime developer) are responsible for implementing any necessary checks
// (e.g. that the sender is the signer) before calling a function that will affect storage."
<!-- Original author of paragraph: @AmarRSingh -->
// It is up to the writer of the respective pallet (with respect to how much information to provide).
\#### Public Inspection functions - Immutable (getters)
// Insert a subheading for each getter function signature
\##### <code>\`example_getter_name()\`</code>
// What it returns
// Why, when, and how often to call it
// When it could panic or error
// When safety issues to consider
\#### Public Mutable functions (changing state)
// Insert a subheading for each setter function signature
\##### <code>\`example_setter_name(origin, parameter_name: T::ExampleType)\`</code>
// What state it changes
// Why, when, and how often to call it
// When it could panic or error
// When safety issues to consider
// What parameter values are valid and why
\### Storage Items
// Explain any storage items included in this pallet
\### Digest Items
// Explain any digest items included in this pallet
\### Inherent Data
// Explain what inherent data (if any) is defined in the pallet and any other related types
\### Events:
// Insert events for this pallet if any
\### Errors:
// Explain what generates errors
\## Usage
// Insert 2-3 examples of usage and code snippets that show how to
// use <INSERT_CUSTOM_PALLET_NAME> Pallet in a custom pallet.
\### Prerequisites
// Show how to include necessary imports for <INSERT_CUSTOM_PALLET_NAME> and derive
// your pallet configuration trait with the `INSERT_CUSTOM_PALLET_NAME` trait.
\```rust
use <INSERT_CUSTOM_PALLET_NAME>;
pub trait Config: <INSERT_CUSTOM_PALLET_NAME>::Config { }
\```
\### Simple Code Snippet
// Show a simple example (e.g. how to query a public getter function of <INSERT_CUSTOM_PALLET_NAME>)
\### Example from FRAME
// Show a usage example in an actual runtime
// See:
// - Substrate TCR https://github.com/parity-samples/substrate-tcr
// - Substrate Kitties https://shawntabrizi.github.io/substrate-collectables-workshop/#/
\## Genesis Config
<!-- Original author of paragraph: @joepetrowski -->
\## Dependencies
// Dependencies on other FRAME pallets and the genesis config should be mentioned,
// but not the Rust Standard Library.
// Genesis configuration modifications that may be made to incorporate this pallet
// Interaction with other pallets
<!-- Original author of heading: @AmarRSingh -->
\## Related Pallets
// Interaction with other pallets in the form of a bullet point list
\## References
<!-- Original author of paragraph: @joepetrowski -->
// Links to reference material, if applicable. For example, Phragmen, W3F research, etc.
// that the implementation is based on.
</pre></p></details>
License: MIT-0
@@ -0,0 +1,124 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Benchmarking for `pallet-example-basic`.
// Only enable this module for benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use crate::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the
// runtime and compile it with `runtime-benchmarks` feature. The detail procedures are
// documented at:
// https://docs.pezkuwichain.io/reference/how-to-guides/weights/add-benchmarks/
//
// The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file.
// The exact command of how the estimate generated is printed at the top of the file.
// Details on using the benchmarks macro can be seen at:
// https://docs.pezkuwichain.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks
#[benchmarks]
mod benchmarks {
use super::*;
// This will measure the execution time of `set_dummy`.
#[benchmark]
fn set_dummy_benchmark() {
// This is the benchmark setup phase.
// `set_dummy` is a constant time function, hence we hard-code some random value here.
let value = 1000u32.into();
#[extrinsic_call]
set_dummy(RawOrigin::Root, value); // The execution phase is just running `set_dummy` extrinsic call
// This is the optional benchmark verification phase, asserting certain states.
assert_eq!(Dummy::<T>::get(), Some(value))
}
// An example method that returns a Result that can be called within a benchmark
fn example_result_method() -> Result<(), BenchmarkError> {
Ok(())
}
// This will measure the execution time of `accumulate_dummy`.
// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
// The benchmark verification phase is omitted.
#[benchmark]
fn accumulate_dummy() -> Result<(), BenchmarkError> {
let value = 1000u32.into();
// The caller account is whitelisted for DB reads/write by the benchmarking macro.
let caller: T::AccountId = whitelisted_caller();
// an example of calling something result-based within a benchmark using the ? operator
// this necessitates specifying the `Result<(), BenchmarkError>` return type
example_result_method()?;
// You can use `_` if the name of the Call matches the benchmark name.
#[extrinsic_call]
_(RawOrigin::Signed(caller), value);
// need this to be compatible with the return type
Ok(())
}
/// You can write helper functions in here since its a normal Rust module.
fn setup_vector(len: u32) -> Vec<u32> {
let mut vector = Vec::<u32>::new();
for i in (0..len).rev() {
vector.push(i);
}
vector
}
// This will measure the execution time of sorting a vector.
//
// Define `x` as a linear component with range `[0, =10_000]`. This means that the benchmarking
// will assume that the weight grows at a linear rate depending on `x`.
#[benchmark]
fn sort_vector(x: Linear<0, 10_000>) {
let mut vector = setup_vector(x);
// The benchmark execution phase could also be a closure with custom code:
#[block]
{
vector.sort();
}
// Check that it was sorted correctly. This will not be benchmarked and is just for
// verification.
vector.windows(2).for_each(|w| assert!(w[0] <= w[1]));
}
// This line generates test cases for benchmarking, and could be run by:
// `cargo test -p pallet-example-basic --all-features`, you will see one line per case:
// `test benchmarking::bench_sort_vector ... ok`
// `test benchmarking::bench_accumulate_dummy ... ok`
// `test benchmarking::bench_set_dummy_benchmark ... ok` in the result.
//
// The line generates three steps per benchmark, with repeat=1 and the three steps are
// [low, mid, high] of the range.
impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test);
}
+536
View File
@@ -0,0 +1,536 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Basic Example Pallet
//!
//! A pallet demonstrating concepts, APIs and structures common to most FRAME runtimes.
//!
//! **This pallet serves as an example and is not meant to be used in production.**
//!
//! > Made with *Substrate*, for *Pezkuwi*.
//!
//! [![github]](https://github.com/pezkuwichain/pezkuwi-sdk/tree/master/substrate/frame/examples/basic)
//! [![pezkuwi]](https://pezkuwichain.io)
//!
//! [pezkuwi]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//!
//! ## Pallet API
//!
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
//! including its configuration trait, dispatchables, storage items, events and errors.
//!
//! ## Overview
//!
//! This pallet provides basic examples of using:
//!
//! - A custom weight calculator able to classify a call's dispatch class (see:
//! [`frame_support::dispatch::DispatchClass`])
//! - Pallet hooks to implement some custom logic that's executed before and after a block is
//! imported (see: [`frame_support::traits::Hooks`])
//! - Inherited weight annotation for pallet calls, used to create less repetition for calls that
//! use the [`Config::WeightInfo`] trait to calculate call weights. This can also be overridden,
//! as demonstrated by [`Call::set_dummy`].
//! - A private function that performs a storage update.
//! - A simple transaction extension implementation (see:
//! [`sp_runtime::traits::TransactionExtension`]) which increases the priority of the
//! [`Call::set_dummy`] if it's present and drops any transaction with an encoded length higher
//! than 200 bytes.
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::vec::Vec;
use codec::{Decode, DecodeWithMemTracking, Encode};
use core::marker::PhantomData;
use frame_support::{
dispatch::{ClassifyDispatch, DispatchClass, DispatchResult, Pays, PaysFee, WeighData},
pallet_prelude::TransactionSource,
traits::IsSubType,
weights::Weight,
};
use frame_system::ensure_signed;
use log::info;
use scale_info::TypeInfo;
use sp_runtime::{
impl_tx_ext_default,
traits::{
Bounded, DispatchInfoOf, DispatchOriginOf, SaturatedConversion, Saturating,
TransactionExtension, ValidateResult,
},
transaction_validity::{InvalidTransaction, ValidTransaction},
};
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
#[cfg(test)]
mod tests;
mod benchmarking;
pub mod weights;
pub use weights::*;
/// A type alias for the balance type from this pallet's point of view.
type BalanceOf<T> = <T as pallet_balances::Config>::Balance;
const MILLICENTS: u32 = 1_000_000_000;
// A custom weight calculator tailored for the dispatch call `set_dummy()`. This actually examines
// the arguments and makes a decision based upon them.
//
// The `WeightData<T>` trait has access to the arguments of the dispatch that it wants to assign a
// weight to. Nonetheless, the trait itself cannot make any assumptions about what the generic type
// of the arguments (`T`) is. Based on our needs, we could replace `T` with a more concrete type
// while implementing the trait. The `pallet::weight` expects whatever implements `WeighData<T>` to
// replace `T` with a tuple of the dispatch arguments. This is exactly how we will craft the
// implementation below.
//
// The rules of `WeightForSetDummy` are as follows:
// - The final weight of each dispatch is calculated as the argument of the call multiplied by the
// parameter given to the `WeightForSetDummy`'s constructor.
// - assigns a dispatch class `operational` if the argument of the call is more than 1000.
//
// More information can be read at:
// - https://docs.pezkuwichain.io/main-docs/build/tx-weights-fees/
//
// Manually configuring weight is an advanced operation and what you really need may well be
// fulfilled by running the benchmarking toolchain. Refer to `benchmarking.rs` file.
struct WeightForSetDummy<T: pallet_balances::Config>(BalanceOf<T>);
impl<T: pallet_balances::Config> WeighData<(&BalanceOf<T>,)> for WeightForSetDummy<T> {
fn weigh_data(&self, target: (&BalanceOf<T>,)) -> Weight {
let multiplier = self.0;
// *target.0 is the amount passed into the extrinsic
let cents = *target.0 / <BalanceOf<T>>::from(MILLICENTS);
Weight::from_parts((cents * multiplier).saturated_into::<u64>(), 0)
}
}
impl<T: pallet_balances::Config> ClassifyDispatch<(&BalanceOf<T>,)> for WeightForSetDummy<T> {
fn classify_dispatch(&self, target: (&BalanceOf<T>,)) -> DispatchClass {
if *target.0 > <BalanceOf<T>>::from(1000u32) {
DispatchClass::Operational
} else {
DispatchClass::Normal
}
}
}
impl<T: pallet_balances::Config> PaysFee<(&BalanceOf<T>,)> for WeightForSetDummy<T> {
fn pays_fee(&self, _target: (&BalanceOf<T>,)) -> Pays {
Pays::Yes
}
}
// Definition of the pallet logic, to be aggregated at runtime definition through
// `construct_runtime`.
#[frame_support::pallet]
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. All our types and constants go in here. If the
/// pallet is dependent on specific other pallets, then their configuration traits
/// should be added to our implied traits list.
///
/// `frame_system::Config` should always be included.
#[pallet::config]
pub trait Config: pallet_balances::Config + frame_system::Config {
// Setting a constant config parameter from the runtime
#[pallet::constant]
type MagicNumber: Get<Self::Balance>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
}
// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
// method.
#[pallet::pallet]
pub struct Pallet<T>(_);
// This pallet implements the [`frame_support::traits::Hooks`] trait to define some logic to
// execute in some context.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
// `on_initialize` is executed at the beginning of the block before any extrinsic are
// dispatched.
//
// This function must return the weight consumed by `on_initialize` and `on_finalize`.
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
// Anything that needs to be done at the start of the block.
// We don't do anything here.
Weight::zero()
}
// `on_finalize` is executed at the end of block after all extrinsic are dispatched.
fn on_finalize(_n: BlockNumberFor<T>) {
// Perform necessary data/state clean up here.
}
// A runtime code run after every block and have access to extended set of APIs.
//
// For instance you can generate extrinsics for the upcoming produced block.
fn offchain_worker(_n: BlockNumberFor<T>) {
// We don't do anything here.
// but we could dispatch extrinsic (transaction/unsigned/inherent) using
// sp_io::submit_extrinsic.
// To see example on offchain worker, please refer to example-offchain-worker pallet
// accompanied in this repository.
}
}
// The call declaration. This states the entry points that we handle. The
// macro takes care of the marshalling of arguments and dispatch.
//
// Anyone can have these functions execute by signing and submitting
// an extrinsic. Ensure that calls into each of these execute in a time, memory and
// using storage space proportional to any costs paid for by the caller or otherwise the
// difficulty of forcing the call to happen.
//
// Generally you'll want to split these into three groups:
// - Public calls that are signed by an external account.
// - Root calls that are allowed to be made only by the governance system.
// - Unsigned calls that can be of two kinds:
// * "Inherent extrinsics" that are opinions generally held by the block authors that build
// child blocks.
// * Unsigned Transactions that are of intrinsic recognizable utility to the network, and are
// validated by the runtime.
//
// Information about where this dispatch initiated from is provided as the first argument
// "origin". As such functions must always look like:
//
// `fn foo(origin: OriginFor<T>, bar: Bar, baz: Baz) -> DispatchResultWithPostInfo { ... }`
//
// The `DispatchResultWithPostInfo` is required as part of the syntax (and can be found at
// `pallet_prelude::DispatchResultWithPostInfo`).
//
// There are three entries in the `frame_system::Origin` enum that correspond
// to the above bullets: `::Signed(AccountId)`, `::Root` and `::None`. You should always match
// against them as the first thing you do in your function. There are three convenience calls
// in system that do the matching for you and return a convenient result: `ensure_signed`,
// `ensure_root` and `ensure_none`.
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
/// This is your public interface. Be extremely careful.
/// This is just a simple example of how to interact with the pallet from the external
/// world.
// This just increases the value of `Dummy` by `increase_by`.
//
// Since this is a dispatched function there are two extremely important things to
// remember:
//
// - MUST NOT PANIC: Under no circumstances (save, perhaps, storage getting into an
// irreparably damaged state) must this function panic.
// - NO SIDE-EFFECTS ON ERROR: This function must either complete totally (and return
// `Ok(())` or it must have no side-effects on storage and return `Err('Some reason')`.
//
// The first is relatively easy to audit for - just ensure all panickers are removed from
// logic that executes in production (which you do anyway, right?!). To ensure the second
// is followed, you should do all tests for validity at the top of your function. This
// is stuff like checking the sender (`origin`) or that state is such that the operation
// makes sense.
//
// Once you've determined that it's all good, then enact the operation and change storage.
// If you can't be certain that the operation will succeed without substantial computation
// then you have a classic blockchain attack scenario. The normal way of managing this is
// to attach a bond to the operation. As the first major alteration of storage, reserve
// some value from the sender's account (`Balances` Pallet has a `reserve` function for
// exactly this scenario). This amount should be enough to cover any costs of the
// substantial execution in case it turns out that you can't proceed with the operation.
//
// If it eventually transpires that the operation is fine and, therefore, that the
// expense of the checks should be borne by the network, then you can refund the reserved
// deposit. If, however, the operation turns out to be invalid and the computation is
// wasted, then you can burn it or repatriate elsewhere.
//
// Security bonds ensure that attackers can't game it by ensuring that anyone interacting
// with the system either progresses it or pays for the trouble of faffing around with
// no progress.
//
// If you don't respect these rules, it is likely that your chain will be attackable.
//
// Each transaction must define a `#[pallet::weight(..)]` attribute to convey a set of
// static information about its dispatch. FRAME System and FRAME Executive pallet then use
// this information to properly execute the transaction, whilst keeping the total load of
// the chain in a moderate rate.
//
// The parenthesized value of the `#[pallet::weight(..)]` attribute can be any type that
// implements a set of traits, namely [`WeighData`], [`ClassifyDispatch`], and
// [`PaysFee`]. The first conveys the weight (a numeric representation of pure
// execution time and difficulty) of the transaction and the second demonstrates the
// [`DispatchClass`] of the call, the third gives whereas extrinsic must pay fees or not.
// A higher weight means a larger transaction (less of which can be placed in a single
// block).
//
// The weight for this extrinsic we rely on the auto-generated `WeightInfo` from the
// benchmark toolchain.
#[pallet::call_index(0)]
pub fn accumulate_dummy(origin: OriginFor<T>, increase_by: T::Balance) -> DispatchResult {
// This is a public call, so we ensure that the origin is some signed account.
let _sender = ensure_signed(origin)?;
// Read the value of dummy from storage.
// let dummy = Dummy::<T>::get();
// Calculate the new value.
// let new_dummy = dummy.map_or(increase_by, |dummy| dummy + increase_by);
// Put the new value into storage.
// <Dummy<T>>::put(new_dummy);
// Will also work with a reference:
// <Dummy<T>>::put(&new_dummy);
// Here's the new one of read and then modify the value.
<Dummy<T>>::mutate(|dummy| {
// Using `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);
});
// Let's deposit an event to let the outside world know this happened.
Self::deposit_event(Event::AccumulateDummy { balance: increase_by });
// All good, no refund.
Ok(())
}
/// A privileged call; in this case it resets our dummy value to something new.
// Implementation of a privileged call. The `origin` parameter is ROOT because
// it's not (directly) from an extrinsic, but rather the system as a whole has decided
// to execute it. Different runtimes have different reasons for 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.
//
// The weight for this extrinsic we use our own weight object `WeightForSetDummy` to
// determine its weight
#[pallet::call_index(1)]
#[pallet::weight(WeightForSetDummy::<T>(<BalanceOf<T>>::from(100u32)))]
pub fn set_dummy(
origin: OriginFor<T>,
#[pallet::compact] new_value: T::Balance,
) -> DispatchResult {
ensure_root(origin)?;
// Print out log or debug message in the console via log::{error, warn, info, debug,
// trace}, accepting format strings similar to `println!`.
// https://docs.pezkuwichain.io/substrate/master/sp_io/logging/fn.log.html
// https://docs.pezkuwichain.io/substrate/master/frame_support/constant.LOG_TARGET.html
info!("New value is now: {:?}", new_value);
// Put the new value into storage.
<Dummy<T>>::put(new_value);
Self::deposit_event(Event::SetDummy { balance: new_value });
// All good, no refund.
Ok(())
}
}
/// Events are a simple means of reporting specific conditions and
/// circumstances that have happened that users, Dapps and/or chain explorers would find
/// interesting and otherwise difficult to detect.
#[pallet::event]
/// This attribute generate the function `deposit_event` to deposit one of this pallet event,
/// it is optional, it is also possible to provide a custom implementation.
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// Just a normal `enum`, here's a dummy event to ensure it compiles.
/// Dummy event, just here so there's a generic type that's used.
AccumulateDummy {
balance: BalanceOf<T>,
},
SetDummy {
balance: BalanceOf<T>,
},
SetBar {
account: T::AccountId,
balance: BalanceOf<T>,
},
}
// pallet::storage attributes allow for type-safe usage of the Substrate storage database,
// so you can keep things around between blocks.
//
// Any storage must be one of `StorageValue`, `StorageMap` or `StorageDoubleMap`.
// The first generic holds the prefix to use and is generated by the macro.
// The query kind is either `OptionQuery` (the default) or `ValueQuery`.
// - for `type Foo<T> = StorageValue<_, u32, OptionQuery>`:
// - `Foo::put(1); Foo::get()` returns `Some(1)`;
// - `Foo::kill(); Foo::get()` returns `None`.
// - for `type Foo<T> = StorageValue<_, u32, ValueQuery>`:
// - `Foo::put(1); Foo::get()` returns `1`;
// - `Foo::kill(); Foo::get()` returns `0` (u32::default()).
#[pallet::storage]
pub(super) type Dummy<T: Config> = StorageValue<_, T::Balance>;
// A map that has enumerable entries.
#[pallet::storage]
pub(super) type Bar<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance>;
// this one uses the query kind: `ValueQuery`, we'll demonstrate the usage of 'mutate' API.
#[pallet::storage]
pub(super) type Foo<T: Config> = StorageValue<_, T::Balance, ValueQuery>;
#[pallet::storage]
pub type CountedMap<T> = CountedStorageMap<_, Blake2_128Concat, u8, u16>;
// The genesis config type.
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub dummy: T::Balance,
pub bar: Vec<(T::AccountId, T::Balance)>,
pub foo: T::Balance,
}
// The build of genesis for the pallet.
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
<Dummy<T>>::put(&self.dummy);
for (a, b) in &self.bar {
<Bar<T>>::insert(a, b);
}
<Foo<T>>::put(&self.foo);
}
}
}
// The main implementation block for the pallet. Functions here fall into three broad
// categories:
// - Public interface. These are functions that are `pub` and generally fall into inspector
// functions that do not write to storage and operation functions that do.
// - Private functions. These are your usual private utilities unavailable to other pallets.
impl<T: Config> Pallet<T> {
// Add public immutables and private mutables.
#[allow(dead_code)]
fn accumulate_foo(origin: T::RuntimeOrigin, increase_by: T::Balance) -> DispatchResult {
let _sender = ensure_signed(origin)?;
let prev = Foo::<T>::get();
// Because Foo has 'default', the type of 'foo' in closure is the raw type instead of an
// Option<> type.
let result = Foo::<T>::mutate(|foo| {
*foo = foo.saturating_add(increase_by);
*foo
});
assert!(prev + increase_by == result);
Ok(())
}
}
// Similar to other FRAME pallets, your pallet can also define a transaction extension and perform
// some checks and [pre/post]processing [before/after] the transaction. A transaction extension can
// be any decodable type that implements `TransactionExtension`. See the trait definition for the
// full list of bounds. As a convention, you can follow this approach to create an extension for
// your pallet:
// - If the extension does not carry any data, then use a tuple struct with just a `marker`
// (needed for the compiler to accept `T: Config`) will suffice.
// - Otherwise, create a tuple struct which contains the external data. Of course, for the entire
// struct to be decodable, each individual item also needs to be decodable.
//
// Note that a transaction extension can also indicate that a particular data must be present in the
// _signing payload_ of a transaction by providing an implementation for the `implicit` method. This
// example will not cover this type of extension. See `CheckSpecVersion` in [FRAME
// System](https://github.com/pezkuwichain/pezkuwi-sdk/tree/master/substrate/frame/system#signed-extensions)
// for an example.
//
// Using the extension, you can add some hooks to the life cycle of each transaction. Note that by
// default, an extension is applied to all `Call` functions (i.e. all transactions). the `Call` enum
// variant is given to each function of `TransactionExtension`. Hence, you can filter based on
// pallet or a particular call if needed.
//
// Some extra information, such as encoded length, some static dispatch info like weight and the
// sender of the transaction (if signed) are also provided.
//
// The full list of hooks that can be added to a transaction extension can be found in the
// `TransactionExtension` trait definition.
//
// The transaction extensions are aggregated in the runtime file of a substrate chain. All
// extensions should be aggregated in a tuple and passed to the `CheckedExtrinsic` and
// `UncheckedExtrinsic` types defined in the runtime. Lookup `pub type TxExtension = (...)` in
// `node/runtime` and `node-template` for an example of this.
/// A simple transaction extension that checks for the `set_dummy` call. In that case, it increases
/// the priority and prints some log.
///
/// Additionally, it drops any transaction with an encoded length higher than 200 bytes. No
/// particular reason why, just to demonstrate the power of transaction extensions.
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct WatchDummy<T: Config + Send + Sync>(PhantomData<T>);
impl<T: Config + Send + Sync> core::fmt::Debug for WatchDummy<T> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "WatchDummy")
}
}
impl<T: Config + Send + Sync> TransactionExtension<<T as frame_system::Config>::RuntimeCall>
for WatchDummy<T>
where
<T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
{
const IDENTIFIER: &'static str = "WatchDummy";
type Implicit = ();
type Pre = ();
type Val = ();
fn validate(
&self,
origin: DispatchOriginOf<<T as frame_system::Config>::RuntimeCall>,
call: &<T as frame_system::Config>::RuntimeCall,
_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
len: usize,
_self_implicit: Self::Implicit,
_inherited_implication: &impl Encode,
_source: TransactionSource,
) -> ValidateResult<Self::Val, <T as frame_system::Config>::RuntimeCall> {
// if the transaction is too big, just drop it.
if len > 200 {
return Err(InvalidTransaction::ExhaustsResources.into());
}
// check for `set_dummy`
let validity = match call.is_sub_type() {
Some(Call::set_dummy { .. }) => {
sp_runtime::print("set_dummy was received.");
let valid_tx =
ValidTransaction { priority: Bounded::max_value(), ..Default::default() };
valid_tx
},
_ => Default::default(),
};
Ok((validity, (), origin))
}
impl_tx_ext_default!(<T as frame_system::Config>::RuntimeCall; weight prepare);
}
+195
View File
@@ -0,0 +1,195 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Tests for pallet-example-basic.
use crate::*;
use frame_support::{
assert_ok, derive_impl,
dispatch::{DispatchInfo, GetDispatchInfo},
traits::{ConstU64, OnInitialize},
};
use sp_core::H256;
// The testing primitives are very useful for avoiding having to work with signatures
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
use sp_runtime::{
traits::{BlakeTwo256, DispatchTransaction, IdentityLookup},
transaction_validity::TransactionSource::External,
BuildStorage,
};
// Reexport crate as its pallet name for construct_runtime.
use crate as pallet_example_basic;
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Example: pallet_example_basic,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Nonce = u64;
type Hash = H256;
type RuntimeCall = RuntimeCall;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
}
impl Config for Test {
type MagicNumber = ConstU64<1_000_000_000>;
type WeightInfo = ();
}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = RuntimeGenesisConfig {
// We use default for brevity, but you can configure as desired if needed.
system: Default::default(),
balances: Default::default(),
example: pallet_example_basic::GenesisConfig {
dummy: 42,
// we configure the map with (key, value) pairs.
bar: alloc::vec![(1, 2), (2, 3)],
foo: 24,
},
}
.build_storage()
.unwrap();
t.into()
}
#[test]
fn it_works_for_optional_value() {
new_test_ext().execute_with(|| {
// Check that GenesisBuilder works properly.
let val1 = 42;
let val2 = 27;
assert_eq!(Dummy::<Test>::get(), Some(val1));
// Check that accumulate works when we have Some value in Dummy already.
assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val2));
assert_eq!(Dummy::<Test>::get(), Some(val1 + val2));
// Check that accumulate works when we Dummy has None in it.
<Example as OnInitialize<u64>>::on_initialize(2);
assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val1));
assert_eq!(Dummy::<Test>::get(), Some(val1 + val2 + val1));
});
}
#[test]
fn it_works_for_default_value() {
new_test_ext().execute_with(|| {
assert_eq!(Foo::<Test>::get(), 24);
assert_ok!(Example::accumulate_foo(RuntimeOrigin::signed(1), 1));
assert_eq!(Foo::<Test>::get(), 25);
});
}
#[test]
fn set_dummy_works() {
new_test_ext().execute_with(|| {
let test_val = 133;
assert_ok!(Example::set_dummy(RuntimeOrigin::root(), test_val.into()));
assert_eq!(Dummy::<Test>::get(), Some(test_val));
});
}
#[test]
fn signed_ext_watch_dummy_works() {
new_test_ext().execute_with(|| {
let call = pallet_example_basic::Call::set_dummy { new_value: 10 }.into();
let info = DispatchInfo::default();
assert_eq!(
WatchDummy::<Test>(PhantomData)
.validate_only(Some(1).into(), &call, &info, 150, External, 0)
.unwrap()
.0
.priority,
u64::MAX,
);
assert_eq!(
WatchDummy::<Test>(PhantomData)
.validate_only(Some(1).into(), &call, &info, 250, External, 0)
.unwrap_err(),
InvalidTransaction::ExhaustsResources.into(),
);
})
}
#[test]
fn counted_map_works() {
new_test_ext().execute_with(|| {
assert_eq!(CountedMap::<Test>::count(), 0);
CountedMap::<Test>::insert(3, 3);
assert_eq!(CountedMap::<Test>::count(), 1);
})
}
#[test]
fn weights_work() {
// must have a defined weight.
let default_call = pallet_example_basic::Call::<Test>::accumulate_dummy { increase_by: 10 };
let info1 = default_call.get_dispatch_info();
// aka. `let info = <Call<Test> as GetDispatchInfo>::get_dispatch_info(&default_call);`
// TODO: account for proof size weight
assert!(info1.call_weight.ref_time() > 0);
assert_eq!(info1.call_weight, <Test as Config>::WeightInfo::accumulate_dummy());
// `set_dummy` is simpler than `accumulate_dummy`, and the weight
// should be less.
let custom_call = pallet_example_basic::Call::<Test>::set_dummy { new_value: 20 };
let info2 = custom_call.get_dispatch_info();
// TODO: account for proof size weight
assert!(info1.call_weight.ref_time() > info2.call_weight.ref_time());
}
+100
View File
@@ -0,0 +1,100 @@
// This file is part of Substrate.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Autogenerated weights for pallet_example_basic
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-10-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! HOSTNAME: `Shawns-MacBook-Pro.local`, CPU: `<UNKNOWN>`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// ./target/release/substrate
// benchmark
// pallet
// --chain=dev
// --execution=wasm
// --wasm-execution=compiled
// --pallet=pallet_example_basic
// --extrinsic=*
// --steps=50
// --repeat=20
// --output=./
// --template
// ./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for pallet_example_basic.
pub trait WeightInfo {
fn set_dummy_benchmark() -> Weight;
fn accumulate_dummy() -> Weight;
fn sort_vector(x: u32, ) -> Weight;
}
/// Weights for pallet_example_basic using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: BasicExample Dummy (r:0 w:1)
fn set_dummy_benchmark() -> Weight {
Weight::from_parts(19_000_000 as u64, 0)
.saturating_add(T::DbWeight::get().writes(1 as u64))
}
// Storage: BasicExample Dummy (r:1 w:1)
fn accumulate_dummy() -> Weight {
Weight::from_parts(18_000_000 as u64, 0)
.saturating_add(T::DbWeight::get().reads(1 as u64))
.saturating_add(T::DbWeight::get().writes(1 as u64))
}
/// The range of component `x` is `[0, 10000]`.
fn sort_vector(x: u32, ) -> Weight {
Weight::from_parts(0 as u64, 0)
// Standard Error: 2
.saturating_add(Weight::from_parts(520 as u64, 0).saturating_mul(x as u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
// Storage: BasicExample Dummy (r:0 w:1)
fn set_dummy_benchmark() -> Weight {
Weight::from_parts(19_000_000 as u64, 0)
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
// Storage: BasicExample Dummy (r:1 w:1)
fn accumulate_dummy() -> Weight {
Weight::from_parts(18_000_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(1 as u64))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
/// The range of component `x` is `[0, 10000]`.
fn sort_vector(x: u32, ) -> Weight {
Weight::from_parts(0 as u64, 0)
// Standard Error: 2
.saturating_add(Weight::from_parts(520 as u64, 0).saturating_mul(x as u64))
}
}
@@ -0,0 +1,50 @@
[package]
name = "pallet-default-config-example"
version = "10.0.0"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example pallet demonstrating derive_impl / default_config in action"
readme = "README.md"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
@@ -0,0 +1,8 @@
# Default Config Example Pallet
An example pallet demonstrating the ability to derive default testing configs via
`#[derive_impl]` and `#[pallet::config(with_default)]`.
Run `cargo doc --package pallet-default-config-example --open` to view this pallet's documentation.
License: MIT-0
@@ -0,0 +1,237 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Default Config Pallet Example
//!
//! A simple example of a FRAME pallet that utilizes [`frame_support::derive_impl`] to demonstrate
//! the simpler way to implement `Config` trait of pallets. This example only showcases this in a
//! `mock.rs` environment, but the same applies to a real runtime as well.
//!
//! See the source code of [`tests`] for a real examples.
//!
//! Study the following types:
//!
//! - [`pallet::DefaultConfig`], and how it differs from [`pallet::Config`].
//! - [`struct@pallet::config_preludes::TestDefaultConfig`] and how it implements
//! [`pallet::DefaultConfig`].
//! - Notice how [`pallet::DefaultConfig`] is independent of [`frame_system::Config`].
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
/// This pallet is annotated to have a default config. This will auto-generate
/// [`DefaultConfig`].
///
/// It will be an identical, but won't have anything that is `#[pallet::no_default]`.
#[pallet::config(with_default)]
pub trait Config: frame_system::Config {
/// The overarching task type. This is coming from the runtime, and cannot have a default.
/// In general, `Runtime*`-oriented types cannot have a sensible default.
#[pallet::no_default] // optional. `RuntimeEvent` is automatically excluded as well.
type RuntimeTask: Task;
/// An input parameter to this pallet. This value can have a default, because it is not
/// reliant on `frame_system::Config` or the overarching runtime in any way.
type WithDefaultValue: Get<u32>;
/// Same as [`Config::WithDefaultValue`], but we don't intend to define a default for this
/// in our tests below.
type OverwrittenDefaultValue: Get<u32>;
/// An input parameter that relies on `<Self as frame_system::Config>::AccountId`. This can
/// too have a default, as long as it is present in `frame_system::DefaultConfig`.
type CanDeriveDefaultFromSystem: Get<Self::AccountId>;
/// We might choose to declare as one that doesn't have a default, for whatever semantical
/// reason.
#[pallet::no_default]
type HasNoDefault: Get<u32>;
/// Some types can technically have no default, such as those the rely on
/// `frame_system::Config` but are not present in `frame_system::DefaultConfig`. For
/// example, a `RuntimeCall` cannot reasonably have a default.
#[pallet::no_default] // if we skip this, there will be a compiler error.
type CannotHaveDefault: Get<Self::RuntimeCall>;
/// Something that is a normal type, with default.
type WithDefaultType;
/// Same as [`Config::WithDefaultType`], but we don't intend to define a default for this
/// in our tests below.
type OverwrittenDefaultType;
}
/// Container for different types that implement [`DefaultConfig`]` of this pallet.
pub mod config_preludes {
// This will help use not need to disambiguate anything when using `derive_impl`.
use super::*;
use frame_support::derive_impl;
/// A type providing default configurations for this pallet in testing environment.
pub struct TestDefaultConfig;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
impl frame_system::DefaultConfig for TestDefaultConfig {}
#[frame_support::register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
type WithDefaultValue = frame_support::traits::ConstU32<42>;
type OverwrittenDefaultValue = frame_support::traits::ConstU32<42>;
// `frame_system::config_preludes::TestDefaultConfig` declares account-id as u64.
type CanDeriveDefaultFromSystem = frame_support::traits::ConstU64<42>;
type WithDefaultType = u32;
type OverwrittenDefaultType = u32;
}
/// A type providing default configurations for this pallet in another environment. Examples
/// could be a teyrchain, or a solochain.
///
/// Appropriate derive for `frame_system::DefaultConfig` needs to be provided. In this
/// example, we simple derive `frame_system::config_preludes::TestDefaultConfig` again.
pub struct OtherDefaultConfig;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
impl frame_system::DefaultConfig for OtherDefaultConfig {}
#[frame_support::register_default_impl(OtherDefaultConfig)]
impl DefaultConfig for OtherDefaultConfig {
type WithDefaultValue = frame_support::traits::ConstU32<66>;
type OverwrittenDefaultValue = frame_support::traits::ConstU32<66>;
type CanDeriveDefaultFromSystem = frame_support::traits::ConstU64<42>;
type WithDefaultType = u32;
type OverwrittenDefaultType = u32;
}
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::event]
pub enum Event<T: Config> {}
}
#[cfg(any(test, doc))]
pub mod tests {
use super::*;
use frame_support::{derive_impl, parameter_types};
use pallet::{self as pallet_default_config_example, config_preludes::*};
type Block = frame_system::mocking::MockBlock<Runtime>;
frame_support::construct_runtime!(
pub enum Runtime {
System: frame_system,
DefaultPallet: pallet_default_config_example,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
// these items are defined by frame-system as `no_default`, so we must specify them here.
type Block = Block;
// all of this is coming from `frame_system::config_preludes::TestDefaultConfig`.
// type Nonce = u32;
// type BlockNumber = u32;
// type Hash = sp_core::hash::H256;
// type Hashing = sp_runtime::traits::BlakeTwo256;
// type AccountId = u64;
// type Lookup = sp_runtime::traits::IdentityLookup<u64>;
// type BlockHashCount = frame_support::traits::ConstU32<10>;
// type MaxConsumers = frame_support::traits::ConstU32<16>;
// type AccountData = ();
// type OnNewAccount = ();
// type OnKilledAccount = ();
// type SystemWeightInfo = ();
// type SS58Prefix = ();
// type Version = ();
// type BlockWeights = ();
// type BlockLength = ();
// type DbWeight = ();
// type BaseCallFilter = frame_support::traits::Everything;
// type BlockHashCount = frame_support::traits::ConstU64<10>;
// type OnSetCode = ();
// These are marked as `#[inject_runtime_type]`. Hence, they are being injected as
// types generated by `construct_runtime`.
// type RuntimeOrigin = RuntimeOrigin;
// type RuntimeCall = RuntimeCall;
// type RuntimeEvent = RuntimeEvent;
// type PalletInfo = PalletInfo;
// you could still overwrite any of them if desired.
type SS58Prefix = frame_support::traits::ConstU16<456>;
}
parameter_types! {
pub const SomeCall: RuntimeCall = RuntimeCall::System(frame_system::Call::<Runtime>::remark { remark: alloc::vec![] });
}
#[derive_impl(TestDefaultConfig as pallet::DefaultConfig)]
impl pallet_default_config_example::Config for Runtime {
// This cannot have default.
type RuntimeTask = RuntimeTask;
type HasNoDefault = frame_support::traits::ConstU32<1>;
type CannotHaveDefault = SomeCall;
type OverwrittenDefaultValue = frame_support::traits::ConstU32<678>;
type OverwrittenDefaultType = u128;
}
#[test]
fn it_works() {
use frame_support::traits::Get;
use pallet::{Config, DefaultConfig};
// assert one of the value types that is not overwritten.
assert_eq!(
<<Runtime as Config>::WithDefaultValue as Get<u32>>::get(),
<<TestDefaultConfig as DefaultConfig>::WithDefaultValue as Get<u32>>::get()
);
// assert one of the value types that is overwritten.
assert_eq!(<<Runtime as Config>::OverwrittenDefaultValue as Get<u32>>::get(), 678u32);
// assert one of the types that is not overwritten.
assert_eq!(
std::any::TypeId::of::<<Runtime as Config>::WithDefaultType>(),
std::any::TypeId::of::<<TestDefaultConfig as DefaultConfig>::WithDefaultType>()
);
// assert one of the types that is overwritten.
assert_eq!(
std::any::TypeId::of::<<Runtime as Config>::OverwrittenDefaultType>(),
std::any::TypeId::of::<u128>()
)
}
}
@@ -0,0 +1,56 @@
[package]
name = "pallet-dev-mode"
version = "10.0.0"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example pallet"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
pallet-balances = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-core = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-balances/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
@@ -0,0 +1,11 @@
<!-- markdown-link-check-disable -->
# Dev Mode Example Pallet
A simple example of a FRAME pallet demonstrating
the ease of requirements for a pallet in dev mode.
Run `cargo doc --package pallet-dev-mode --open` to view this pallet's documentation.
**Dev mode is not meant to be used in production.**
License: MIT-0
@@ -0,0 +1,124 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! <!-- markdown-link-check-disable -->
//! # Dev Mode Example Pallet
//!
//! A simple example of a FRAME pallet demonstrating
//! the ease of requirements for a pallet in dev mode.
//!
//! Run `cargo doc --package pallet-dev-mode --open` to view this pallet's documentation.
//!
//! **Dev mode is not meant to be used in production.**
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{vec, vec::Vec};
use frame_support::dispatch::DispatchResult;
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;
/// Enable `dev_mode` for this pallet.
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: pallet_balances::Config + frame_system::Config {}
// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
// method.
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::call]
impl<T: Config> Pallet<T> {
// No need to define a `call_index` attribute here because of `dev_mode`.
// No need to define a `weight` attribute here because of `dev_mode`.
pub fn add_dummy(origin: OriginFor<T>, id: T::AccountId) -> DispatchResult {
ensure_root(origin)?;
if let Some(mut dummies) = Dummy::<T>::get() {
dummies.push(id.clone());
Dummy::<T>::set(Some(dummies));
} else {
Dummy::<T>::set(Some(vec![id.clone()]));
}
// Let's deposit an event to let the outside world know this happened.
Self::deposit_event(Event::AddDummy { account: id });
Ok(())
}
// No need to define a `call_index` attribute here because of `dev_mode`.
// No need to define a `weight` attribute here because of `dev_mode`.
pub fn set_bar(
origin: OriginFor<T>,
#[pallet::compact] new_value: T::Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
// Put the new value into storage.
<Bar<T>>::insert(&sender, new_value);
Self::deposit_event(Event::SetBar { account: sender, balance: new_value });
Ok(())
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
AddDummy { account: T::AccountId },
SetBar { account: T::AccountId, balance: BalanceOf<T> },
}
/// The MEL requirement for bounded pallets is skipped by `dev_mode`.
/// This means that all storages are marked as unbounded.
/// This is equivalent to specifying `#[pallet::unbounded]` on this type definitions.
/// When the dev_mode is removed, we would need to implement implement `MaxEncodedLen`.
#[pallet::storage]
pub type Dummy<T: Config> = StorageValue<_, Vec<T::AccountId>>;
/// The Hasher requirement is skipped by `dev_mode`. So, second parameter can be `_`
/// and `Blake2_128Concat` is used as a default.
/// When the dev_mode is removed, we would need to specify the hasher like so:
/// `pub type Bar<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance>;`.
#[pallet::storage]
pub type Bar<T: Config> = StorageMap<_, _, T::AccountId, T::Balance>;
}
@@ -0,0 +1,117 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Tests for pallet-dev-mode.
use crate::*;
use frame_support::{assert_ok, derive_impl};
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
// Reexport crate as its pallet name for construct_runtime.
use crate as pallet_dev_mode;
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Example: pallet_dev_mode,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Nonce = u64;
type Hash = H256;
type RuntimeCall = RuntimeCall;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
}
impl Config for Test {}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = RuntimeGenesisConfig {
// We use default for brevity, but you can configure as desired if needed.
system: Default::default(),
balances: Default::default(),
}
.build_storage()
.unwrap();
t.into()
}
#[test]
fn it_works_for_optional_value() {
new_test_ext().execute_with(|| {
assert_eq!(Dummy::<Test>::get(), None);
let val1 = 42;
assert_ok!(Example::add_dummy(RuntimeOrigin::root(), val1));
assert_eq!(Dummy::<Test>::get(), Some(vec![val1]));
// Check that accumulate works when we have Some value in Dummy already.
let val2 = 27;
assert_ok!(Example::add_dummy(RuntimeOrigin::root(), val2));
assert_eq!(Dummy::<Test>::get(), Some(vec![val1, val2]));
});
}
#[test]
fn set_dummy_works() {
new_test_ext().execute_with(|| {
let test_val = 133;
assert_ok!(Example::set_bar(RuntimeOrigin::signed(1), test_val.into()));
assert_eq!(Bar::<Test>::get(1), Some(test_val));
});
}
@@ -0,0 +1,30 @@
[package]
name = "pallet-example-frame-crate"
version = "0.0.1"
authors = [
"Kurdistan Tech Institute <info@pezkuwichain.io>",
"Parity Technologies <admin@parity.io>",
]
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example pallet with umbrella crate"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame = { features = ["runtime"], workspace = true }
[features]
default = ["std"]
std = ["codec/std", "frame/std", "scale-info/std"]
runtime-benchmarks = ["frame/runtime-benchmarks"]
@@ -0,0 +1,68 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use frame::prelude::*;
#[frame::pallet(dev_mode)]
pub mod pallet {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::event]
pub enum Event<T: Config> {}
#[pallet::storage]
pub type Value<T> = StorageValue<Value = u32>;
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn some_dispatchable(_origin: OriginFor<T>) -> DispatchResult {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use crate::pallet as my_pallet;
use frame::testing_prelude::*;
construct_runtime!(
pub enum Runtime {
System: frame_system,
MyPallet: my_pallet,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
impl my_pallet::Config for Runtime {}
}
@@ -0,0 +1,63 @@
[package]
name = "pallet-example-kitchensink"
version = "4.0.0-dev"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example kitchensink pallet"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame-support = { features = ["experimental"], workspace = true }
frame-system = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
frame-benchmarking = { optional = true, workspace = true }
pallet-balances = { workspace = true }
[dev-dependencies]
sp-core = { workspace = true }
[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",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,110 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Benchmarking for `pallet-example-kitchensink`.
// Only enable this module for benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
#[allow(unused)]
use crate::Pallet as Kitchensink;
use frame_benchmarking::v2::*;
use frame_support::pallet_prelude::TransactionSource;
use frame_system::RawOrigin;
// To actually run this benchmark on pallet-example-kitchensink, we need to put this pallet into the
// runtime and compile it with `runtime-benchmarks` feature. The detail procedures are
// documented at:
// https://docs.pezkuwichain.io/reference/how-to-guides/weights/add-benchmarks/
//
// The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file.
// The exact command of how the estimate generated is printed at the top of the file.
// Details on using the benchmarks macro can be seen at:
// https://docs.pezkuwichain.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks
#[benchmarks]
mod benchmarks {
use super::*;
// This will measure the execution time of `set_foo`.
#[benchmark]
fn set_foo_benchmark() {
// This is the benchmark setup phase.
// `set_foo` is a constant time function, hence we hard-code some random value here.
let value = 1000u32.into();
#[extrinsic_call]
set_foo(RawOrigin::Root, value, 10u128); // The execution phase is just running `set_foo` extrinsic call
// This is the optional benchmark verification phase, asserting certain states.
assert_eq!(Foo::<T>::get(), Some(value))
}
// This will measure the execution time of `set_foo_using_authorize`.
#[benchmark]
fn set_foo_using_authorize() {
// This is the benchmark setup phase.
// `set_foo_using_authorize` is only authorized when value is 42 so we will use it.
let value = 42u32;
// We dispatch with authorized origin, it is the origin resulting from authorization.
let origin = RawOrigin::Authorized;
#[extrinsic_call]
_(origin, value); // The execution phase is just running `set_foo_using_authorize` extrinsic call
// This is the optional benchmark verification phase, asserting certain states.
assert_eq!(Foo::<T>::get(), Some(42))
}
// This will measure the weight for the closure in `[pallet::authorize(...)]`.
#[benchmark]
fn authorize_set_foo_using_authorize() {
// This is the benchmark setup phase.
let call = Call::<T>::set_foo_using_authorize { new_foo: 42 };
let source = TransactionSource::External;
Foo::<T>::kill();
// We use a block with specific code to benchmark the closure.
#[block]
{
use frame_support::traits::Authorize;
call.authorize(source)
.expect("Call give some authorization")
.expect("Authorization is successful");
}
}
// This line generates test cases for benchmarking, and could be run by:
// `cargo test -p pallet-example-kitchensink --all-features`, you will see one line per case:
// `test benchmarking::bench_set_foo_benchmark ... ok`
// `test benchmarking::bench_set_foo_using_authorize_benchmark ... ok` in the result.
// `test benchmarking::bench_authorize_set_foo_using_authorize_benchmark ... ok` in the
// result.
//
// The line generates three steps per benchmark, with repeat=1 and the three steps are
// [low, mid, high] of the range.
impl_benchmark_test_suite!(Kitchensink, crate::tests::new_test_ext(), crate::tests::Test);
}
@@ -0,0 +1,354 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Kitchensink Example Pallet
//!
//! **This pallet serves as an example and is not meant to be used in production.**
//!
//! The kitchen-sink catalog of the the FRAME macros and their various syntax options.
//!
//! This example does not focus on pallet instancing, `dev_mode`, and does nto include any 'where'
//! clauses on `T`. These will both incur additional complexity to the syntax, but are not discussed
//! here.
#![cfg_attr(not(feature = "std"), no_std)]
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(feature = "try-runtime")]
use sp_runtime::TryRuntimeError;
pub mod weights;
pub use weights::*;
extern crate alloc;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// The config trait of the pallet. You can basically do anything with the config trait that you
/// can do with a normal rust trait: import items consisting of types, constants and functions.
///
/// A very common pattern is for a pallet to import implementations of traits such as
/// [`frame_support::traits::Currency`], [`frame_support::traits::fungibles::Inspect`] and
/// [`frame_support::traits::Get`]. These are all types that the pallet is delegating to the top
/// level runtime to provide to it.
///
/// The `FRAME`-specific syntax are:
///
/// * the use of `#[pallet::constant]`([`frame_support::procedural`]), which places a `Get`
/// implementation in the metadata.
/// * `type RuntimeEvent`, which is mandatory if your pallet has events. See TODO.
/// * Needless to say, because [`Config`] is bounded by [`frame_system::Config`], you can use
/// all the items from [`frame_system::Config`] as well, such as `AccountId`.
/// * `#[pallet::disable_frame_system_supertrait_check]` would remove the need for
/// `frame_system::Config` to exist, which you should almost never need.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
/// This is a normal Rust type, nothing specific to FRAME here.
type Currency: frame_support::traits::fungible::Inspect<Self::AccountId>;
/// Similarly, let the runtime decide this.
fn some_function() -> u32;
/// And this
const FOO: u32;
/// This is a FRAME-specific item. It will be placed in the metadata of the pallet, and
/// therefore can be queried by offchain applications.
#[pallet::constant]
type InMetadata: Get<u32>;
}
/// Allows you to define some extra constants to be added into constant metadata.
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
#[allow(non_snake_case)]
fn SomeValue() -> u32 {
unimplemented!()
}
#[pallet::constant_name(OtherValue)]
fn arbitrary_name() -> u32 {
unimplemented!()
}
}
const STORAGE_VERSION: frame_support::traits::StorageVersion = StorageVersion::new(1);
/// The pallet struct. There's nothing special to FRAME about this; it can implement functions
/// in an impl blocks, traits and so on.
#[pallet::pallet]
#[pallet::without_storage_info]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
/// Allows you to define some origin for the pallet.
#[pallet::origin]
pub type Origin<T> = frame_system::RawOrigin<<T as frame_system::Config>::AccountId>;
// first, we showcase all the possible storage types, with most of their details.
/// A storage value. We mark this as unbounded, alter its prefix, and define a custom storage
/// getter for it.
///
/// The value is stored a single trie node, and therefore can be retrieved with a single
/// database access.
#[pallet::storage]
#[pallet::unbounded] // optional
#[pallet::storage_prefix = "OtherFoo"] // optional
pub type Foo<T> = StorageValue<Value = u32>;
#[pallet::type_value]
pub fn DefaultForFoo() -> u32 {
1
}
#[pallet::storage]
pub type FooWithDefault<T> =
StorageValue<Value = u32, QueryKind = ValueQuery, OnEmpty = DefaultForFoo>;
/// A storage map. This creates a mapping from keys of type `u32` to values of type `u32`.
///
/// Keys and values can be iterated, albeit each value is stored under a unique trie key,
/// meaning that an iteration consists of many database accesses.
#[pallet::storage]
pub type Bar<T> = StorageMap<Hasher = Blake2_128Concat, Key = u32, Value = u32>;
/// Conceptually same as `StorageMap<>` where the key is a tuple of `(u32, u32)`. On top, it
/// provides some functions to iterate or remove items based on only the first key.
#[pallet::storage]
pub type Qux<T> = StorageDoubleMap<
Hasher1 = Blake2_128Concat,
Key1 = u32,
Hasher2 = Blake2_128Concat,
Key2 = u32,
Value = u32,
>;
/// Same as `StorageDoubleMap`, but with arbitrary number of keys.
#[pallet::storage]
pub type Quux<T> = StorageNMap<
Key = (
NMapKey<Blake2_128Concat, u8>,
NMapKey<Blake2_128Concat, u16>,
NMapKey<Blake2_128Concat, u32>,
),
Value = u64,
>;
/// In all of these examples, we chose a syntax where the storage item is defined using the
/// explicit generic syntax (`X = Y`). Alternatively:
#[pallet::storage]
pub type AlternativeSyntax<T> = StorageMap<_, Blake2_128Concat, u32, u32>;
/// Lastly, all storage items, as you saw, had to be generic over `T`. If they want to use an
/// item from `Config`, `<T: Config>` should be used.
#[pallet::storage]
pub type AlternativeSyntax2<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u32>;
/// The genesis config type. This allows the pallet to define how it should initialized upon
/// genesis.
///
/// It can be generic over `T` or not, depending on whether it is or not.
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub foo: u32,
pub bar: BlockNumberFor<T>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { foo: 0, bar: Default::default() }
}
}
/// Allows you to define how `genesis_configuration is built.
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
Foo::<T>::put(self.foo);
}
}
/// The call declaration. This states the entry points that we handle. The
/// macro takes care of the marshalling of arguments and dispatch.
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::set_foo_benchmark())]
/// Marks this call as feeless if `new_foo` is zero.
#[pallet::feeless_if(|_origin: &OriginFor<T>, new_foo: &u32, _other_compact: &u128| -> bool {
*new_foo == 0
})]
pub fn set_foo(
_: OriginFor<T>,
new_foo: u32,
#[pallet::compact] _other_compact: u128,
) -> DispatchResult {
Foo::<T>::set(Some(new_foo));
Ok(())
}
/// A call that is specially authorized.
/// Authorized call can be dispatched by anybody without requiring any signature or fee.
#[pallet::call_index(1)]
#[pallet::authorize(|
_source: TransactionSource,
new_foo: &u32,
| -> TransactionValidityWithRefund {
if *new_foo == 42 && Foo::<T>::get().is_none() {
// This is the amount to refund, here we refund nothing.
let refund = Weight::zero();
// The transaction needs to give a provided tag.
// See `ValidTransaction` documentation.
let validity = ValidTransaction::with_tag_prefix("pallet-kitchen-sink")
.and_provides("set_foo_using_authorize")
.into();
Ok((validity, refund))
} else {
Err(InvalidTransaction::Call.into())
}
})]
#[pallet::weight(T::WeightInfo::set_foo_using_authorize())]
#[pallet::weight_of_authorize(T::WeightInfo::authorize_set_foo_using_authorize())]
pub fn set_foo_using_authorize(origin: OriginFor<T>, new_foo: u32) -> DispatchResult {
// We only dispatch if it comes from the authorized origin. Meaning that the closure
// passed in `pallet::authorize` has successfully authorized the call.
ensure_authorized(origin)?;
Foo::<T>::set(Some(new_foo));
Ok(())
}
}
/// The event type. This exactly like a normal Rust enum.
///
/// It can or cannot be generic over `<T: Config>`. Note that unlike a normal enum, if none of
/// the variants actually use `<T: Config>`, the macro will generate a hidden `PhantomData`
/// variant.
///
/// The `generate_deposit` macro generates a function on `Pallet` called `deposit_event` which
/// will properly convert the error type of your pallet into `RuntimeEvent` (recall `type
/// RuntimeEvent: From<Event<Self>>`, so it can be converted) and deposit it via
/// `frame_system::Pallet::deposit_event`.
#[pallet::event]
#[pallet::generate_deposit(pub fn deposit_event)]
pub enum Event<T: Config> {
/// A simple tuple style variant.
SomethingHappened(u32),
/// A simple struct-style variant. Note that we use `AccountId` from `T` because `T:
/// Config`, which by extension implies `T: frame_system::Config`.
SomethingDetailedHappened { at: u32, to: T::AccountId },
/// Another variant.
SomeoneJoined(T::AccountId),
}
/// The error enum. Must always be generic over `<T>`, which is expanded to `<T: Config>`.
#[pallet::error]
pub enum Error<T> {
SomethingWentWrong,
SomethingBroke,
}
/// All the possible hooks that a pallet can have. See [`frame_support::traits::Hooks`] for more
/// info.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {}
fn offchain_worker(_n: BlockNumberFor<T>) {
unimplemented!()
}
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
unimplemented!()
}
fn on_finalize(_n: BlockNumberFor<T>) {
unimplemented!()
}
fn on_idle(_n: BlockNumberFor<T>, _remaining_weight: Weight) -> Weight {
unimplemented!()
}
fn on_runtime_upgrade() -> Weight {
unimplemented!()
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
unimplemented!()
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
unimplemented!()
}
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
unimplemented!()
}
}
/// Allows you to define an enum on the pallet which will then instruct `construct_runtime` to
/// amalgamate all similarly-named enums from other pallets into an aggregate enum.
#[pallet::composite_enum]
pub enum HoldReason {
Staking,
}
/// Allows the pallet to provide some inherent. See [`frame_support::inherent::ProvideInherent`]
/// for more info.
#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = MakeFatalError<()>;
const INHERENT_IDENTIFIER: [u8; 8] = *b"test1234";
fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
unimplemented!();
}
fn is_inherent(_call: &Self::Call) -> bool {
unimplemented!()
}
}
}
@@ -0,0 +1,102 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Tests for pallet-example-kitchensink.
use crate::*;
use frame_support::{assert_ok, derive_impl, parameter_types, traits::VariantCountOf};
use sp_runtime::BuildStorage;
// Reexport crate as its pallet name for construct_runtime.
use crate as pallet_example_kitchensink;
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Kitchensink: pallet_example_kitchensink::{Pallet, Call, Storage, Config<T>, Event<T>},
}
);
/// Using a default config for [`frame_system`] in tests. See `default-config` example for more
/// details.
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
type AccountData = pallet_balances::AccountData<u64>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
type WeightInfo = ();
type FreezeIdentifier = RuntimeFreezeReason;
type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
type RuntimeHoldReason = RuntimeHoldReason;
type RuntimeFreezeReason = RuntimeFreezeReason;
}
parameter_types! {
pub const InMetadata: u32 = 30;
}
impl Config for Test {
type WeightInfo = ();
type Currency = Balances;
type InMetadata = InMetadata;
const FOO: u32 = 100;
fn some_function() -> u32 {
5u32
}
}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = RuntimeGenesisConfig {
// We use default for brevity, but you can configure as desired if needed.
system: Default::default(),
balances: Default::default(),
kitchensink: pallet_example_kitchensink::GenesisConfig { bar: 32, foo: 24 },
}
.build_storage()
.unwrap();
t.into()
}
#[test]
fn set_foo_works() {
new_test_ext().execute_with(|| {
assert_eq!(Foo::<Test>::get(), Some(24)); // From genesis config.
let val1 = 42;
assert_ok!(Kitchensink::set_foo(RuntimeOrigin::root(), val1, 2));
assert_eq!(Foo::<Test>::get(), Some(val1));
});
}
+104
View File
@@ -0,0 +1,104 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Autogenerated weights for `pallet_example_kitchensink`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-06-02, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `MacBook.local`, CPU: `<UNKNOWN>`
//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// ./target/release/node-template
// benchmark
// pallet
// --chain
// dev
// --pallet
// pallet_example_kitchensink
// --extrinsic
// *
// --steps
// 20
// --repeat
// 10
// --output
// frame/examples/kitchensink/src/weights.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for pallet_template.
pub trait WeightInfo {
fn set_foo_benchmark() -> Weight;
fn set_foo_using_authorize() -> Weight;
fn authorize_set_foo_using_authorize() -> Weight;
}
/// Weight functions for `pallet_example_kitchensink`.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: Kitchensink OtherFoo (r:0 w:1)
/// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured)
fn set_foo_benchmark() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 1_000_000 picoseconds.
Weight::from_parts(1_000_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
fn set_foo_using_authorize() -> Weight {
Weight::zero()
}
fn authorize_set_foo_using_authorize() -> Weight {
Weight::zero()
}
}
impl WeightInfo for () {
/// Storage: Kitchensink OtherFoo (r:0 w:1)
/// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured)
fn set_foo_benchmark() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 1_000_000 picoseconds.
Weight::from_parts(1_000_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn set_foo_using_authorize() -> Weight {
Weight::zero()
}
fn authorize_set_foo_using_authorize() -> Weight {
Weight::zero()
}
}
@@ -0,0 +1,48 @@
[package]
name = "pallet-example-mbm"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Example FRAME pallet for multi-block migrations"
publish = false
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
pallet-migrations = { workspace = true }
scale-info = { workspace = true }
sp-io = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-migrations/std",
"scale-info/std",
"sp-io/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-migrations/runtime-benchmarks",
"sp-io/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-migrations/try-runtime",
]
@@ -0,0 +1,87 @@
// 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.
#![cfg_attr(not(feature = "std"), no_std)]
//! # Multi-Block Migrations Example Pallet
//!
//! This pallet serves as a minimal example of a pallet that uses the [Multi-Block Migrations
//! Framework](frame_support::migrations). You can observe how to configure it in a runtime in the
//! `kitchensink-runtime` crate.
//!
//! ## Introduction and Purpose
//!
//! The primary purpose of this pallet is to demonstrate the concept of Multi-Block Migrations in
//! Substrate. It showcases the migration of values from in the
//! [`MyMap`](`pallet::MyMap`) storage map a `u32` to a `u64` data type using the
//! [`SteppedMigration`](`frame_support::migrations::SteppedMigration`) implementation from the
//! [`migrations::v1`] module.
//!
//! The [`MyMap`](`pallet::MyMap`) storage item is defined in this `pallet`, and is
//! aliased to [`v0::MyMap`](`migrations::v1::v0::MyMap`) in the [`migrations::v1`]
//! module.
//!
//! ## How to Read the Documentation
//!
//! To access and navigate this documentation in your browser, use the following command:
//!
//! - `cargo doc --package pallet-example-mbm --open`
//!
//! This documentation is organized to help you understand the pallet's components, features, and
//! migration process.
//!
//! ## Example Usage
//!
//! To use this pallet and understand multi-block migrations, you can refer to the
//! [`migrations::v1`] module, which contains a step-by-step migration example.
//!
//! ## Pallet Structure
//!
//! The pallet is structured as follows:
//!
//! - [`migrations`]: Contains migration-related modules and migration logic.
//! - [`v1`](`migrations::v1`): Demonstrates the migration process for changing the data type in
//! the storage map.
//! - [`pallet`]: Defines the pallet configuration and storage items.
//!
//! ## Migration Safety
//!
//! When working with migrations, it's crucial to ensure the safety of your migrations. The
//! preferred tool to test migrations is
//! [`try-runtime-cli`](https://github.com/paritytech/try-runtime-cli). Support will be added to
//! dry-run MBMs once they are stable
//! (tracked: <https://github.com/paritytech/try-runtime-cli/issues/17>).
pub mod migrations;
mod mock;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::{pallet_prelude::StorageMap, Blake2_128Concat};
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {}
/// Define a storage item to illustrate multi-block migrations.
#[pallet::storage]
pub type MyMap<T: Config> = StorageMap<_, Blake2_128Concat, u32, u64>;
}
@@ -0,0 +1,29 @@
// 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.
/// # Multi-Block Migrations Module
///
/// This module showcases a simple use of the multi-block migrations framework.
pub mod v1;
/// A unique identifier across all pallets.
///
/// This constant represents a unique identifier for the migrations of this pallet.
/// It helps differentiate migrations for this pallet from those of others. Note that we don't
/// directly pull the crate name from the environment, since that would change if the crate were
/// ever to be renamed and could cause historic migrations to run again.
pub const PALLET_MIGRATIONS_ID: &[u8; 18] = b"pallet-example-mbm";
@@ -0,0 +1,54 @@
// 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.
//! Benchmark the multi-block-migration.
#![cfg(feature = "runtime-benchmarks")]
use crate::{
migrations::{
v1,
v1::{weights, weights::WeightInfo},
},
Config, Pallet,
};
use frame_benchmarking::v2::*;
use frame_support::{migrations::SteppedMigration, weights::WeightMeter};
#[benchmarks]
mod benches {
use super::*;
/// Benchmark a single step of the `v1::LazyMigrationV1` migration.
#[benchmark]
fn step() {
v1::v0::MyMap::<T>::insert(0, 0);
let mut meter = WeightMeter::new();
#[block]
{
v1::LazyMigrationV1::<T, weights::SubstrateWeight<T>>::step(None, &mut meter).unwrap();
}
// Check that the new storage is decodable:
assert_eq!(crate::MyMap::<T>::get(0), Some(0));
// uses twice the weight once for migration and then for checking if there is another key.
assert_eq!(meter.consumed(), weights::SubstrateWeight::<T>::step() * 2);
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Runtime);
}
@@ -0,0 +1,161 @@
// 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.
//! # Multi-Block Migration v1
//!
//! This module showcases a simple migration that iterates over the values in the
//! [`v0::MyMap`](`crate::migrations::v1::v0::MyMap`) storage map, transforms them,
//! and inserts them into the [`MyMap`](`crate::pallet::MyMap`) storage map.
extern crate alloc;
use super::PALLET_MIGRATIONS_ID;
use crate::pallet::{Config, MyMap};
use frame_support::{
migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
pallet_prelude::PhantomData,
weights::WeightMeter,
};
#[cfg(feature = "try-runtime")]
use alloc::collections::btree_map::BTreeMap;
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
mod benchmarks;
mod tests;
pub mod weights;
/// Module containing the OLD (v0) storage items.
///
/// Before running this migration, the storage alias defined here represents the
/// `on_chain` storage.
// This module is public only for the purposes of linking it in the documentation. It is not
// intended to be used by any other code.
pub mod v0 {
use super::Config;
use crate::pallet::Pallet;
use frame_support::{storage_alias, Blake2_128Concat};
#[storage_alias]
/// The storage item that is being migrated from.
pub type MyMap<T: Config> = StorageMap<Pallet<T>, Blake2_128Concat, u32, u32>;
}
/// Migrates the items of the [`crate::MyMap`] map from `u32` to `u64`.
///
/// The `step` function will be called once per block. It is very important that this function
/// *never* panics and never uses more weight than it got in its meter. The migrations should also
/// try to make maximal progress per step, so that the total time it takes to migrate stays low.
pub struct LazyMigrationV1<T: Config, W: weights::WeightInfo>(PhantomData<(T, W)>);
impl<T: Config, W: weights::WeightInfo> SteppedMigration for LazyMigrationV1<T, W> {
type Cursor = u32;
// Without the explicit length here the construction of the ID would not be infallible.
type Identifier = MigrationId<18>;
/// The identifier of this migration. Which should be globally unique.
fn id() -> Self::Identifier {
MigrationId { pallet_id: *PALLET_MIGRATIONS_ID, version_from: 0, version_to: 1 }
}
/// The actual logic of the migration.
///
/// This function is called repeatedly until it returns `Ok(None)`, indicating that the
/// migration is complete. Ideally, the migration should be designed in such a way that each
/// step consumes as much weight as possible. However, this is simplified to perform one stored
/// value mutation per block.
fn step(
mut cursor: Option<Self::Cursor>,
meter: &mut WeightMeter,
) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
let required = W::step();
// If there is not enough weight for a single step, return an error. This case can be
// problematic if it is the first migration that ran in this block. But there is nothing
// that we can do about it here.
if meter.remaining().any_lt(required) {
return Err(SteppedMigrationError::InsufficientWeight { required });
}
// We loop here to do as much progress as possible per step.
loop {
if meter.try_consume(required).is_err() {
break;
}
let mut iter = if let Some(last_key) = cursor {
// If a cursor is provided, start iterating from the stored value
// corresponding to the last key processed in the previous step.
// Note that this only works if the old and the new map use the same way to hash
// storage keys.
v0::MyMap::<T>::iter_from(v0::MyMap::<T>::hashed_key_for(last_key))
} else {
// If no cursor is provided, start iterating from the beginning.
v0::MyMap::<T>::iter()
};
// If there's a next item in the iterator, perform the migration.
if let Some((last_key, value)) = iter.next() {
// Migrate the inner value: u32 -> u64.
let value = value as u64;
// We can just insert here since the old and the new map share the same key-space.
// Otherwise it would have to invert the concat hash function and re-hash it.
MyMap::<T>::insert(last_key, value);
cursor = Some(last_key) // Return the processed key as the new cursor.
} else {
cursor = None; // Signal that the migration is complete (no more items to process).
break;
}
}
Ok(cursor)
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, frame_support::sp_runtime::TryRuntimeError> {
use codec::Encode;
// Return the state of the storage before the migration.
Ok(v0::MyMap::<T>::iter().collect::<BTreeMap<_, _>>().encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(prev: Vec<u8>) -> Result<(), frame_support::sp_runtime::TryRuntimeError> {
use codec::Decode;
// Check the state of the storage after the migration.
let prev_map = BTreeMap::<u32, u32>::decode(&mut &prev[..])
.expect("Failed to decode the previous storage state");
// Check the len of prev and post are the same.
assert_eq!(
MyMap::<T>::iter().count(),
prev_map.len(),
"Migration failed: the number of items in the storage after the migration is not the same as before"
);
for (key, value) in prev_map {
let new_value =
MyMap::<T>::get(key).expect("Failed to get the value after the migration");
assert_eq!(
value as u64, new_value,
"Migration failed: the value after the migration is not the same as before"
);
}
Ok(())
}
}
@@ -0,0 +1,79 @@
// 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.
#![cfg(all(test, not(feature = "runtime-benchmarks")))]
use crate::{
migrations::{
v1,
v1::{weights, weights::WeightInfo as _},
},
mock::{
new_test_ext, run_to_block, AllPalletsWithSystem, MigratorServiceWeight, Runtime as T,
System,
},
};
use codec::Decode;
use frame_support::traits::OnRuntimeUpgrade;
use pallet_migrations::WeightInfo as _;
#[test]
fn lazy_migration_works() {
new_test_ext().execute_with(|| {
frame_support::__private::sp_tracing::try_init_simple();
// Insert some values into the old storage map.
for i in 0..1024 {
v1::v0::MyMap::<T>::insert(i, i);
}
// Give it enough weight do do exactly 16 iterations:
let limit = <T as pallet_migrations::Config>::WeightInfo::progress_mbms_none() +
pallet_migrations::Pallet::<T>::exec_migration_max_weight() +
weights::SubstrateWeight::<T>::step() * 16;
MigratorServiceWeight::set(&limit);
System::set_block_number(1);
AllPalletsWithSystem::on_runtime_upgrade(); // onboard MBMs
let mut last_decodable = 0;
for block in 2..=65 {
run_to_block(block);
let mut decodable = 0;
for i in 0..1024 {
let key = crate::MyMap::<T>::hashed_key_for(i);
let value =
frame_support::storage::unhashed::get_raw(&key[..]).expect("value exists");
if let Ok(value) = u64::decode(&mut &value[..]) {
assert_eq!(value, i as u64);
decodable += 1;
} else {
assert_eq!(u32::decode(&mut &value[..]).expect("not migrated yet"), i);
}
}
assert_eq!(decodable, last_decodable + 16);
last_decodable = decodable;
}
// Check that everything is decodable now:
for i in 0..1024 {
assert_eq!(crate::MyMap::<T>::get(i), Some(i as u64));
}
});
}
@@ -0,0 +1,84 @@
// 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.
//! Autogenerated weights for `pallet_example_mbm`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2024-03-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `Olivers-MBP`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// pezkuwi-omni-bencher
// v1
// benchmark
// pallet
// --runtime
// target/release/wbuild/kitchensink-runtime/kitchensink_runtime.compact.compressed.wasm
// --pallet
// pallet_example_mbm
// --extrinsic
//
// --template
// substrate/.maintain/frame-weight-template.hbs
// --output
// substrate/frame/examples/multi-block-migrations/src/migrations/weights.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pallet_example_mbm`.
pub trait WeightInfo {
fn step() -> Weight;
}
/// Weights for `pallet_example_mbm` using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `PalletExampleMbms::MyMap` (r:2 w:1)
/// Proof: `PalletExampleMbms::MyMap` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
fn step() -> Weight {
// Proof Size summary in bytes:
// Measured: `28`
// Estimated: `5996`
// Minimum execution time: 6_000_000 picoseconds.
Weight::from_parts(8_000_000, 5996)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `PalletExampleMbms::MyMap` (r:2 w:1)
/// Proof: `PalletExampleMbms::MyMap` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
fn step() -> Weight {
// Proof Size summary in bytes:
// Measured: `28`
// Estimated: `5996`
// Minimum execution time: 6_000_000 picoseconds.
Weight::from_parts(8_000_000, 5996)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}
@@ -0,0 +1,88 @@
// 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.
#![cfg(test)]
//! # Mock runtime for testing Multi-Block-Migrations
//!
//! This runtime is for testing only and should *never* be used in production. Please see the
//! comments on the specific config items. The core part of this runtime is the
//! [`pallet_migrations::Config`] implementation, where you define the migrations you want to run
//! using the [`Migrations`] type.
use frame_support::{
construct_runtime, derive_impl, migrations::MultiStepMigrator, pallet_prelude::Weight,
};
type Block = frame_system::mocking::MockBlock<Runtime>;
impl crate::Config for Runtime {}
frame_support::parameter_types! {
pub storage MigratorServiceWeight: Weight = Weight::from_parts(100, 100); // do not use in prod
}
#[derive_impl(pallet_migrations::config_preludes::TestDefaultConfig)]
impl pallet_migrations::Config for Runtime {
// Here we inject the actual MBMs. Currently there is just one, but it accepts a tuple.
//
// # Example
// ```ignore
// type Migrations = (v1::Migration<Runtime>, v2::Migration<Runtime>, v3::Migration<Runtime>);
// ```
#[cfg(not(feature = "runtime-benchmarks"))]
type Migrations = (
crate::migrations::v1::LazyMigrationV1<
Runtime,
crate::migrations::v1::weights::SubstrateWeight<Runtime>,
>,
);
#[cfg(feature = "runtime-benchmarks")]
type Migrations = pallet_migrations::mock_helpers::MockedMigrations;
type MaxServiceWeight = MigratorServiceWeight;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
type MultiBlockMigrator = Migrator;
}
// Construct the runtime using the `construct_runtime` macro, specifying the pallet_migrations.
construct_runtime! {
pub struct Runtime
{
System: frame_system,
Pallet: crate,
Migrator: pallet_migrations,
}
}
pub fn new_test_ext() -> sp_io::TestExternalities {
sp_io::TestExternalities::new(Default::default())
}
#[allow(dead_code)]
pub fn run_to_block(n: u64) {
System::run_to_block_with::<AllPalletsWithSystem>(
n,
frame_system::RunToBlockHooks::default().after_initialize(|_| {
// Done by Executive:
<Runtime as frame_system::Config>::MultiBlockMigrator::step();
}),
);
}
@@ -0,0 +1,107 @@
// 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.
// 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.
//! Autogenerated weights for `pallet_example_mbm`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// frame-omni-bencher
// v1
// benchmark
// pallet
// --extrinsic=*
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
// --pallet=pallet_example_mbm
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/substrate/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/substrate/frame/examples/multi-block-migrations/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=substrate/.maintain/frame-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --genesis-builder-policy=none
// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage,pallet_election_provider_multi_block,pallet_election_provider_multi_block::signed,pallet_election_provider_multi_block::unsigned,pallet_election_provider_multi_block::verifier
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pallet_example_mbm`.
pub trait WeightInfo {
fn step() -> Weight;
}
/// Weights for `pallet_example_mbm` using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `PalletExampleMbms::MyMap` (r:2 w:1)
/// Proof: `PalletExampleMbms::MyMap` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
fn step() -> Weight {
// Proof Size summary in bytes:
// Measured: `28`
// Estimated: `5996`
// Minimum execution time: 6_832_000 picoseconds.
Weight::from_parts(7_201_000, 5996)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `PalletExampleMbms::MyMap` (r:2 w:1)
/// Proof: `PalletExampleMbms::MyMap` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
fn step() -> Weight {
// Proof Size summary in bytes:
// Measured: `28`
// Estimated: `5996`
// Minimum execution time: 6_832_000 picoseconds.
Weight::from_parts(7_201_000, 5996)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}
@@ -0,0 +1,55 @@
[package]
name = "pallet-example-offchain-worker"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example pallet for offchain worker"
readme = "README.md"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
lite-json = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-keystore = { optional = true, workspace = true }
sp-runtime = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"lite-json/std",
"log/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-keystore/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
@@ -0,0 +1,29 @@
<!-- markdown-link-check-disable -->
# Offchain Worker Example Pallet
The Offchain Worker Example: A simple pallet demonstrating
concepts, APIs and structures common to most offchain workers.
Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's
documentation.
- [`pallet_example_offchain_worker::Trait`](./trait.Trait.html)
- [`Call`](./enum.Call.html)
- [`Module`](./struct.Module.html)
**This pallet serves as an example showcasing Substrate off-chain worker and is not meant to be
used in production.**
## Overview
In this example we are going to build a very simplistic, naive and definitely NOT
production-ready oracle for BTC/USD price.
Offchain Worker (OCW) will be triggered after every block, fetch the current price
and prepare either signed or unsigned transaction to feed the result back on chain.
The on-chain logic will simply aggregate the results and store last `64` values to compute
the average price.
Additional logic in OCW is put in place to prevent spamming the network with both signed
and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
one unsigned transaction floating in the network.
License: MIT-0
@@ -0,0 +1,734 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! <!-- markdown-link-check-disable -->
//! # Offchain Worker Example Pallet
//!
//! The Offchain Worker Example: A simple pallet demonstrating
//! concepts, APIs and structures common to most offchain workers.
//!
//! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's
//! documentation.
//!
//! - [`Config`]
//! - [`Call`]
//! - [`Pallet`]
//!
//! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to
//! be used in production.**
//!
//! ## Overview
//!
//! In this example we are going to build a very simplistic, naive and definitely NOT
//! production-ready oracle for BTC/USD price.
//! Offchain Worker (OCW) will be triggered after every block, fetch the current price
//! and prepare either signed or unsigned transaction to feed the result back on chain.
//! The on-chain logic will simply aggregate the results and store last `64` values to compute
//! the average price.
//! Additional logic in OCW is put in place to prevent spamming the network with both signed
//! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
//! one unsigned transaction floating in the network.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::vec::Vec;
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::traits::Get;
use frame_system::{
self as system,
offchain::{
AppCrypto, CreateBare, CreateSignedTransaction, SendSignedTransaction,
SendUnsignedTransaction, SignedPayload, Signer, SigningTypes, SubmitTransaction,
},
pallet_prelude::BlockNumberFor,
};
use lite_json::json::JsonValue;
use sp_core::crypto::KeyTypeId;
use sp_runtime::{
offchain::{
http,
storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
Duration,
},
traits::Zero,
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
RuntimeDebug,
};
#[cfg(test)]
mod tests;
/// Defines application identifier for crypto keys of this module.
///
/// Every module that deals with signatures needs to declare its unique identifier for
/// its crypto keys.
/// When offchain worker is signing transactions it's going to request keys of type
/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction.
/// The keys can be inserted manually via RPC (see `author_insertKey`).
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!");
/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers.
/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment
/// the types with this pallet-specific identifier.
pub mod crypto {
use super::KEY_TYPE;
use sp_core::sr25519::Signature as Sr25519Signature;
use sp_runtime::{
app_crypto::{app_crypto, sr25519},
traits::Verify,
MultiSignature, MultiSigner,
};
app_crypto!(sr25519, KEY_TYPE);
pub struct TestAuthId;
impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for TestAuthId {
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}
// implemented for mock runtime in test
impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature>
for TestAuthId
{
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}
}
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// This pallet's configuration trait
#[pallet::config]
pub trait Config:
CreateSignedTransaction<Call<Self>> + CreateBare<Call<Self>> + frame_system::Config
{
/// The identifier type for an offchain worker.
type AuthorityId: AppCrypto<Self::Public, Self::Signature>;
// Configuration parameters
/// A grace period after we send transaction.
///
/// To avoid sending too many transactions, we only attempt to send one
/// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate
/// sending between distinct runs of this offchain worker.
#[pallet::constant]
type GracePeriod: Get<BlockNumberFor<Self>>;
/// Number of blocks of cooldown after unsigned transaction is included.
///
/// This ensures that we only accept unsigned transactions once, every `UnsignedInterval`
/// blocks.
#[pallet::constant]
type UnsignedInterval: Get<BlockNumberFor<Self>>;
/// A configuration for base priority of unsigned transactions.
///
/// This is exposed so that it can be tuned for particular runtime, when
/// multiple pallets send unsigned transactions.
#[pallet::constant]
type UnsignedPriority: Get<TransactionPriority>;
/// Maximum number of prices.
#[pallet::constant]
type MaxPrices: Get<u32>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
/// Offchain Worker entry point.
///
/// By implementing `fn offchain_worker` you declare a new offchain worker.
/// This function will be called when the node is fully synced and a new best block is
/// successfully imported.
/// Note that it's not guaranteed for offchain workers to run on EVERY block, there might
/// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs),
/// so the code should be able to handle that.
/// You can use `Local Storage` API to coordinate runs of the worker.
fn offchain_worker(block_number: BlockNumberFor<T>) {
// Note that having logs compiled to WASM may cause the size of the blob to increase
// significantly. You can use `RuntimeDebug` custom derive to hide details of the types
// in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable
// all logging and thus, remove any logging from the WASM.
log::info!("Hello World from offchain workers!");
// Since off-chain workers are just part of the runtime code, they have direct access
// to the storage and other included pallets.
//
// We can easily import `frame_system` and retrieve a block hash of the parent block.
let parent_hash = <system::Pallet<T>>::block_hash(block_number - 1u32.into());
log::debug!("Current block: {:?} (parent hash: {:?})", block_number, parent_hash);
// It's a good practice to keep `fn offchain_worker()` function minimal, and move most
// of the code to separate `impl` block.
// Here we call a helper function to calculate current average price.
// This function reads storage entries of the current state.
let average: Option<u32> = Self::average_price();
log::debug!("Current price: {:?}", average);
// For this example we are going to send both signed and unsigned transactions
// depending on the block number.
// Usually it's enough to choose one or the other.
let should_send = Self::choose_transaction_type(block_number);
let res = match should_send {
TransactionType::Signed => Self::fetch_price_and_send_signed(),
TransactionType::UnsignedForAny =>
Self::fetch_price_and_send_unsigned_for_any_account(block_number),
TransactionType::UnsignedForAll =>
Self::fetch_price_and_send_unsigned_for_all_accounts(block_number),
TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number),
TransactionType::None => Ok(()),
};
if let Err(e) = res {
log::error!("Error: {}", e);
}
}
}
/// A public part of the pallet.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Submit new price to the list.
///
/// This method is a public function of the module and can be called from within
/// a transaction. It appends given `price` to current list of prices.
/// In our example the `offchain worker` will create, sign & submit a transaction that
/// calls this function passing the price.
///
/// The transaction needs to be signed (see `ensure_signed`) check, so that the caller
/// pays a fee to execute it.
/// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting
/// excessive transactions, but note that it doesn't ensure the price oracle is actually
/// working and receives (and provides) meaningful data.
/// This example is not focused on correctness of the oracle itself, but rather its
/// purpose is to showcase offchain worker capabilities.
#[pallet::call_index(0)]
#[pallet::weight({0})]
pub fn submit_price(origin: OriginFor<T>, price: u32) -> DispatchResultWithPostInfo {
// Retrieve sender of the transaction.
let who = ensure_signed(origin)?;
// Add the price to the on-chain list.
Self::add_price(Some(who), price);
Ok(().into())
}
/// Submit new price to the list via unsigned transaction.
///
/// Works exactly like the `submit_price` function, but since we allow sending the
/// transaction without a signature, and hence without paying any fees,
/// we need a way to make sure that only some transactions are accepted.
/// This function can be called only once every `T::UnsignedInterval` blocks.
/// Transactions that call that function are de-duplicated on the pool level
/// via `validate_unsigned` implementation and also are rendered invalid if
/// the function has already been called in current "session".
///
/// It's important to specify `weight` for unsigned calls as well, because even though
/// they don't charge fees, we still don't want a single block to contain unlimited
/// number of such transactions.
///
/// This example is not focused on correctness of the oracle itself, but rather its
/// purpose is to showcase offchain worker capabilities.
#[pallet::call_index(1)]
#[pallet::weight({0})]
pub fn submit_price_unsigned(
origin: OriginFor<T>,
_block_number: BlockNumberFor<T>,
price: u32,
) -> DispatchResultWithPostInfo {
// This ensures that the function can only be called via unsigned transaction.
ensure_none(origin)?;
// Add the price to the on-chain list, but mark it as coming from an empty address.
Self::add_price(None, price);
// now increment the block number at which we expect next unsigned transaction.
let current_block = <system::Pallet<T>>::block_number();
<NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
Ok(().into())
}
#[pallet::call_index(2)]
#[pallet::weight({0})]
pub fn submit_price_unsigned_with_signed_payload(
origin: OriginFor<T>,
price_payload: PricePayload<T::Public, BlockNumberFor<T>>,
_signature: T::Signature,
) -> DispatchResultWithPostInfo {
// This ensures that the function can only be called via unsigned transaction.
ensure_none(origin)?;
// Add the price to the on-chain list, but mark it as coming from an empty address.
Self::add_price(None, price_payload.price);
// now increment the block number at which we expect next unsigned transaction.
let current_block = <system::Pallet<T>>::block_number();
<NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
Ok(().into())
}
}
/// Events for the pallet.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event generated when new price is accepted to contribute to the average.
NewPrice { price: u32, maybe_who: Option<T::AccountId> },
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
/// Validate unsigned call to this module.
///
/// By default unsigned transactions are disallowed, but implementing the validator
/// here we make sure that some particular calls (the ones produced by offchain worker)
/// are being whitelisted and marked as valid.
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
// Firstly let's check that we call the right function.
if let Call::submit_price_unsigned_with_signed_payload {
price_payload: ref payload,
ref signature,
} = call
{
let signature_valid =
SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone());
if !signature_valid {
return InvalidTransaction::BadProof.into();
}
Self::validate_transaction_parameters(&payload.block_number, &payload.price)
} else if let Call::submit_price_unsigned { block_number, price: new_price } = call {
Self::validate_transaction_parameters(block_number, new_price)
} else {
InvalidTransaction::Call.into()
}
}
}
/// A vector of recently submitted prices.
///
/// This is used to calculate average price, should have bounded size.
#[pallet::storage]
pub(super) type Prices<T: Config> = StorageValue<_, BoundedVec<u32, T::MaxPrices>, ValueQuery>;
/// Defines the block when next unsigned transaction will be accepted.
///
/// To prevent spam of unsigned (and unpaid!) transactions on the network,
/// we only allow one transaction every `T::UnsignedInterval` blocks.
/// This storage entry defines when new transaction is going to be accepted.
#[pallet::storage]
pub(super) type NextUnsignedAt<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
}
/// Payload used by this example crate to hold price
/// data required to submit a transaction.
#[derive(
Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo,
)]
pub struct PricePayload<Public, BlockNumber> {
block_number: BlockNumber,
price: u32,
public: Public,
}
impl<T: SigningTypes> SignedPayload<T> for PricePayload<T::Public, BlockNumberFor<T>> {
fn public(&self) -> T::Public {
self.public.clone()
}
}
enum TransactionType {
Signed,
UnsignedForAny,
UnsignedForAll,
Raw,
None,
}
impl<T: Config> Pallet<T> {
/// Chooses which transaction type to send.
///
/// This function serves mostly to showcase `StorageValue` helper
/// and local storage usage.
///
/// Returns a type of transaction that should be produced in current run.
fn choose_transaction_type(block_number: BlockNumberFor<T>) -> TransactionType {
/// A friendlier name for the error that is going to be returned in case we are in the grace
/// period.
const RECENTLY_SENT: () = ();
// Start off by creating a reference to Local Storage value.
// Since the local storage is common for all offchain workers, it's a good practice
// to prepend your entry with the module name.
let val = StorageValueRef::persistent(b"example_ocw::last_send");
// The Local Storage is persisted and shared between runs of the offchain workers,
// and offchain workers may run concurrently. We can use the `mutate` function, to
// write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set`
// low-level method of local storage API, which means that only one worker
// will be able to "acquire a lock" and send a transaction if multiple workers
// happen to be executed concurrently.
let res =
val.mutate(|last_send: Result<Option<BlockNumberFor<T>>, StorageRetrievalError>| {
match last_send {
// If we already have a value in storage and the block number is recent enough
// we avoid sending another transaction at this time.
Ok(Some(block)) if block_number < block + T::GracePeriod::get() =>
Err(RECENTLY_SENT),
// In every other case we attempt to acquire the lock and send a transaction.
_ => Ok(block_number),
}
});
// The result of `mutate` call will give us a nested `Result` type.
// The first one matches the return of the closure passed to `mutate`, i.e.
// if we return `Err` from the closure, we get an `Err` here.
// In case we return `Ok`, here we will have another (inner) `Result` that indicates
// if the value has been set to the storage correctly - i.e. if it wasn't
// written to in the meantime.
match res {
// The value has been set correctly, which means we can safely send a transaction now.
Ok(block_number) => {
// We will send different transactions based on a random number.
// Note that this logic doesn't really guarantee that the transactions will be sent
// in an alternating fashion (i.e. fairly distributed). Depending on the execution
// order and lock acquisition, we may end up for instance sending two `Signed`
// transactions in a row. If a strict order is desired, it's better to use
// the storage entry for that. (for instance store both block number and a flag
// indicating the type of next transaction to send).
let transaction_type = block_number % 4u32.into();
if transaction_type == Zero::zero() {
TransactionType::Signed
} else if transaction_type == BlockNumberFor::<T>::from(1u32) {
TransactionType::UnsignedForAny
} else if transaction_type == BlockNumberFor::<T>::from(2u32) {
TransactionType::UnsignedForAll
} else {
TransactionType::Raw
}
},
// We are in the grace period, we should not send a transaction this time.
Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None,
// We wanted to send a transaction, but failed to write the block number (acquire a
// lock). This indicates that another offchain worker that was running concurrently
// most likely executed the same logic and succeeded at writing to storage.
// Thus we don't really want to send the transaction, knowing that the other run
// already did.
Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None,
}
}
/// A helper function to fetch the price and send signed transaction.
fn fetch_price_and_send_signed() -> Result<(), &'static str> {
let signer = Signer::<T, T::AuthorityId>::all_accounts();
if !signer.can_sign() {
return Err(
"No local accounts available. Consider adding one via `author_insertKey` RPC.",
);
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// Using `send_signed_transaction` associated type we create and submit a transaction
// representing the call, we've just created.
// Submit signed will return a vector of results for all accounts that were found in the
// local keystore with expected `KEY_TYPE`.
let results = signer.send_signed_transaction(|_account| {
// Received price is wrapped into a call to `submit_price` public function of this
// pallet. This means that the transaction, when executed, will simply call that
// function passing `price` as an argument.
Call::submit_price { price }
});
for (acc, res) in &results {
match res {
Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price),
Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e),
}
}
Ok(())
}
/// A helper function to fetch the price and send a raw unsigned transaction.
fn fetch_price_and_send_raw_unsigned(
block_number: BlockNumberFor<T>,
) -> Result<(), &'static str> {
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = NextUnsignedAt::<T>::get();
if next_unsigned_at > block_number {
return Err("Too early to send unsigned transaction");
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// Received price is wrapped into a call to `submit_price_unsigned` public function of this
// pallet. This means that the transaction, when executed, will simply call that function
// passing `price` as an argument.
let call = Call::submit_price_unsigned { block_number, price };
// Now let's create a transaction out of this call and submit it to the pool.
// Here we showcase two ways to send an unsigned transaction / unsigned payload (raw)
//
// By default unsigned transactions are disallowed, so we need to whitelist this case
// by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully
// implement unsigned validation logic, as any mistakes can lead to opening DoS or spam
// attack vectors. See validation logic docs for more details.
//
let xt = T::create_bare(call.into());
SubmitTransaction::<T, Call<T>>::submit_transaction(xt)
.map_err(|()| "Unable to submit unsigned transaction.")?;
Ok(())
}
/// A helper function to fetch the price, sign payload and send an unsigned transaction
fn fetch_price_and_send_unsigned_for_any_account(
block_number: BlockNumberFor<T>,
) -> Result<(), &'static str> {
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = NextUnsignedAt::<T>::get();
if next_unsigned_at > block_number {
return Err("Too early to send unsigned transaction");
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// -- Sign using any account
let (_, result) = Signer::<T, T::AuthorityId>::any_account()
.send_unsigned_transaction(
|account| PricePayload { price, block_number, public: account.public.clone() },
|payload, signature| Call::submit_price_unsigned_with_signed_payload {
price_payload: payload,
signature,
},
)
.ok_or("No local accounts accounts available.")?;
result.map_err(|()| "Unable to submit transaction")?;
Ok(())
}
/// A helper function to fetch the price, sign payload and send an unsigned transaction
fn fetch_price_and_send_unsigned_for_all_accounts(
block_number: BlockNumberFor<T>,
) -> Result<(), &'static str> {
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = NextUnsignedAt::<T>::get();
if next_unsigned_at > block_number {
return Err("Too early to send unsigned transaction");
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// -- Sign using all accounts
let transaction_results = Signer::<T, T::AuthorityId>::all_accounts()
.send_unsigned_transaction(
|account| PricePayload { price, block_number, public: account.public.clone() },
|payload, signature| Call::submit_price_unsigned_with_signed_payload {
price_payload: payload,
signature,
},
);
for (_account_id, result) in transaction_results.into_iter() {
if result.is_err() {
return Err("Unable to submit transaction");
}
}
Ok(())
}
/// Fetch current price and return the result in cents.
fn fetch_price() -> Result<u32, http::Error> {
// We want to keep the offchain worker execution time reasonable, so we set a hard-coded
// deadline to 2s to complete the external call.
// You can also wait indefinitely for the response, however you may still get a timeout
// coming from the host machine.
let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000));
// Initiate an external HTTP GET request.
// This is using high-level wrappers from `sp_runtime`, for the low-level calls that
// you can find in `sp_io`. The API is trying to be similar to `request`, but
// since we are running in a custom WASM execution environment we can't simply
// import the library here.
let request =
http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD");
// We set the deadline for sending of the request, note that awaiting response can
// have a separate deadline. Next we send the request, before that it's also possible
// to alter request headers or stream body content in case of non-GET requests.
let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?;
// The request is already being processed by the host, we are free to do anything
// else in the worker (we can send multiple concurrent requests too).
// At some point however we probably want to check the response though,
// so we can block current thread and wait for it to finish.
// Note that since the request is being driven by the host, we don't have to wait
// for the request to have it complete, we will just not read the response.
let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??;
// Let's check the status code before we proceed to reading the response.
if response.code != 200 {
log::warn!("Unexpected status code: {}", response.code);
return Err(http::Error::Unknown);
}
// Next we want to fully read the response body and collect it to a vector of bytes.
// Note that the return object allows you to read the body in chunks as well
// with a way to control the deadline.
let body = response.body().collect::<Vec<u8>>();
// Create a str slice from the body.
let body_str = alloc::str::from_utf8(&body).map_err(|_| {
log::warn!("No UTF8 body");
http::Error::Unknown
})?;
let price = match Self::parse_price(body_str) {
Some(price) => Ok(price),
None => {
log::warn!("Unable to extract price from the response: {:?}", body_str);
Err(http::Error::Unknown)
},
}?;
log::warn!("Got price: {} cents", price);
Ok(price)
}
/// Parse the price from the given JSON string using `lite-json`.
///
/// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful.
fn parse_price(price_str: &str) -> Option<u32> {
let val = lite_json::parse_json(price_str);
let price = match val.ok()? {
JsonValue::Object(obj) => {
let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
match v {
JsonValue::Number(number) => number,
_ => return None,
}
},
_ => return None,
};
let exp = price.fraction_length.saturating_sub(2);
Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
}
/// Add new price to the list.
fn add_price(maybe_who: Option<T::AccountId>, price: u32) {
log::info!("Adding to the average: {}", price);
<Prices<T>>::mutate(|prices| {
if prices.try_push(price).is_err() {
prices[(price % T::MaxPrices::get()) as usize] = price;
}
});
let average = Self::average_price()
.expect("The average is not empty, because it was just mutated; qed");
log::info!("Current average price is: {}", average);
// here we are raising the NewPrice event
Self::deposit_event(Event::NewPrice { price, maybe_who });
}
/// Calculate current average price.
fn average_price() -> Option<u32> {
let prices = Prices::<T>::get();
if prices.is_empty() {
None
} else {
Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32)
}
}
fn validate_transaction_parameters(
block_number: &BlockNumberFor<T>,
new_price: &u32,
) -> TransactionValidity {
// Now let's check if the transaction has any chance to succeed.
let next_unsigned_at = NextUnsignedAt::<T>::get();
if &next_unsigned_at > block_number {
return InvalidTransaction::Stale.into();
}
// Let's make sure to reject transactions from the future.
let current_block = <system::Pallet<T>>::block_number();
if &current_block < block_number {
return InvalidTransaction::Future.into();
}
// We prioritize transactions that are more far away from current average.
//
// Note this doesn't make much sense when building an actual oracle, but this example
// is here mostly to show off offchain workers capabilities, not about building an
// oracle.
let avg_price = Self::average_price()
.map(|price| if &price > new_price { price - new_price } else { new_price - price })
.unwrap_or(0);
ValidTransaction::with_tag_prefix("ExampleOffchainWorker")
// We set base priority to 2**20 and hope it's included before any other
// transactions in the pool. Next we tweak the priority depending on how much
// it differs from the current average. (the more it differs the more priority it
// has).
.priority(T::UnsignedPriority::get().saturating_add(avg_price as _))
// This transaction does not require anything else to go before into the pool.
// In theory we could require `previous_unsigned_at` transaction to go first,
// but it's not necessary in our case.
//.and_requires()
// We set the `provides` tag to be the same as `next_unsigned_at`. This makes
// sure only one transaction produced after `next_unsigned_at` will ever
// get to the transaction pool and will end up in the block.
// We can still have multiple transactions compete for the same "spot",
// and the one with higher priority will replace other one in the pool.
.and_provides(next_unsigned_at)
// The transaction is only valid for next 5 blocks. After that it's
// going to be revalidated by the pool.
.longevity(5)
// It's fine to propagate that transaction to other peers, which means it can be
// created even by nodes that don't produce blocks.
// Note that sometimes it's better to keep it for yourself (if you are the block
// producer), since for instance in some schemes others may copy your solution and
// claim a reward.
.propagate(true)
.build()
}
}
@@ -0,0 +1,419 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate as example_offchain_worker;
use crate::*;
use codec::Decode;
use frame_support::{
assert_ok, derive_impl, parameter_types,
traits::{ConstU32, ConstU64},
};
use sp_core::{
offchain::{testing, OffchainWorkerExt, TransactionPoolExt},
sr25519::Signature,
H256,
};
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt};
use sp_runtime::{
testing::TestXt,
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
RuntimeAppPublic,
};
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the module, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Example: example_offchain_worker,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = sp_core::sr25519::Public;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
type Extrinsic = TestXt<RuntimeCall, ()>;
type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
impl frame_system::offchain::SigningTypes for Test {
type Public = <Signature as Verify>::Signer;
type Signature = Signature;
}
impl<LocalCall> frame_system::offchain::CreateTransactionBase<LocalCall> for Test
where
RuntimeCall: From<LocalCall>,
{
type RuntimeCall = RuntimeCall;
type Extrinsic = Extrinsic;
}
impl<LocalCall> frame_system::offchain::CreateTransaction<LocalCall> for Test
where
RuntimeCall: From<LocalCall>,
{
type Extension = ();
fn create_transaction(call: RuntimeCall, _extension: Self::Extension) -> Extrinsic {
Extrinsic::new_transaction(call, ())
}
}
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Test
where
RuntimeCall: From<LocalCall>,
{
fn create_signed_transaction<
C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>,
>(
call: RuntimeCall,
_public: <Signature as Verify>::Signer,
_account: AccountId,
nonce: u64,
) -> Option<Extrinsic> {
Some(Extrinsic::new_signed(call, nonce, (), ()))
}
}
impl<LocalCall> frame_system::offchain::CreateBare<LocalCall> for Test
where
RuntimeCall: From<LocalCall>,
{
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
Extrinsic::new_bare(call)
}
}
parameter_types! {
pub const UnsignedPriority: u64 = 1 << 20;
}
impl Config for Test {
type AuthorityId = crypto::TestAuthId;
type GracePeriod = ConstU64<5>;
type UnsignedInterval = ConstU64<128>;
type UnsignedPriority = UnsignedPriority;
type MaxPrices = ConstU32<64>;
}
fn test_pub() -> sp_core::sr25519::Public {
sp_core::sr25519::Public::from_raw([1u8; 32])
}
#[test]
fn it_aggregates_the_price() {
sp_io::TestExternalities::default().execute_with(|| {
assert_eq!(Example::average_price(), None);
assert_ok!(Example::submit_price(RuntimeOrigin::signed(test_pub()), 27));
assert_eq!(Example::average_price(), Some(27));
assert_ok!(Example::submit_price(RuntimeOrigin::signed(test_pub()), 43));
assert_eq!(Example::average_price(), Some(35));
});
}
#[test]
fn should_make_http_call_and_parse_result() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
price_oracle_response(&mut state.write());
t.execute_with(|| {
// when
let price = Example::fetch_price().unwrap();
// then
assert_eq!(price, 15523);
});
}
#[test]
fn knows_how_to_mock_several_http_calls() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
{
let mut state = state.write();
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 1}"#.to_vec()),
sent: true,
..Default::default()
});
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 2}"#.to_vec()),
sent: true,
..Default::default()
});
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 3}"#.to_vec()),
sent: true,
..Default::default()
});
}
t.execute_with(|| {
let price1 = Example::fetch_price().unwrap();
let price2 = Example::fetch_price().unwrap();
let price3 = Example::fetch_price().unwrap();
assert_eq!(price1, 100);
assert_eq!(price2, 200);
assert_eq!(price3, 300);
})
}
#[test]
fn should_submit_signed_transaction_on_chain() {
const PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(crate::crypto::Public::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt::new(keystore));
price_oracle_response(&mut offchain_state.write());
t.execute_with(|| {
// when
Example::fetch_price_and_send_signed().unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert!(matches!(tx.preamble, sp_runtime::generic::Preamble::Signed(0, (), (),)));
assert_eq!(tx.function, RuntimeCall::Example(crate::Call::submit_price { price: 15523 }));
});
}
#[test]
fn should_submit_unsigned_transaction_on_chain_for_any_account() {
const PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(crate::crypto::Public::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
let public_key = *keystore.sr25519_public_keys(crate::crypto::Public::ID).get(0).unwrap();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt::new(keystore));
price_oracle_response(&mut offchain_state.write());
let price_payload = PricePayload {
block_number: 1,
price: 15523,
public: <Test as SigningTypes>::Public::from(public_key),
};
// let signature = price_payload.sign::<crypto::TestAuthId>().unwrap();
t.execute_with(|| {
// when
Example::fetch_price_and_send_unsigned_for_any_account(1).unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert!(tx.is_inherent());
if let RuntimeCall::Example(crate::Call::submit_price_unsigned_with_signed_payload {
price_payload: body,
signature,
}) = tx.function
{
assert_eq!(body, price_payload);
let signature_valid = <PricePayload<
<Test as SigningTypes>::Public,
frame_system::pallet_prelude::BlockNumberFor<Test>,
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(
&price_payload, signature
);
assert!(signature_valid);
}
});
}
#[test]
fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
const PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(crate::crypto::Public::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
let public_key = *keystore.sr25519_public_keys(crate::crypto::Public::ID).get(0).unwrap();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt::new(keystore));
price_oracle_response(&mut offchain_state.write());
let price_payload = PricePayload {
block_number: 1,
price: 15523,
public: <Test as SigningTypes>::Public::from(public_key),
};
// let signature = price_payload.sign::<crypto::TestAuthId>().unwrap();
t.execute_with(|| {
// when
Example::fetch_price_and_send_unsigned_for_all_accounts(1).unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert!(tx.is_inherent());
if let RuntimeCall::Example(crate::Call::submit_price_unsigned_with_signed_payload {
price_payload: body,
signature,
}) = tx.function
{
assert_eq!(body, price_payload);
let signature_valid = <PricePayload<
<Test as SigningTypes>::Public,
frame_system::pallet_prelude::BlockNumberFor<Test>,
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(
&price_payload, signature
);
assert!(signature_valid);
}
});
}
#[test]
fn should_submit_raw_unsigned_transaction_on_chain() {
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = MemoryKeystore::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt::new(keystore));
price_oracle_response(&mut offchain_state.write());
t.execute_with(|| {
// when
Example::fetch_price_and_send_raw_unsigned(1).unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert!(tx.is_inherent());
assert_eq!(
tx.function,
RuntimeCall::Example(crate::Call::submit_price_unsigned {
block_number: 1,
price: 15523
})
);
});
}
fn price_oracle_response(state: &mut testing::OffchainState) {
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 155.23}"#.to_vec()),
sent: true,
..Default::default()
});
}
#[test]
fn parse_price_works() {
let test_data = alloc::vec![
("{\"USD\":6536.92}", Some(653692)),
("{\"USD\":65.92}", Some(6592)),
("{\"USD\":6536.924565}", Some(653692)),
("{\"USD\":6536}", Some(653600)),
("{\"USD2\":6536}", None),
("{\"USD\":\"6432\"}", None),
];
for (json, expected) in test_data {
assert_eq!(expected, Example::parse_price(json));
}
}
@@ -0,0 +1,55 @@
[package]
name = "pallet-example-single-block-migrations"
version = "0.0.1"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example pallet demonstrating best-practices for writing storage migrations."
publish = false
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
docify = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
pallet-balances = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-version = { workspace = true }
[features]
default = ["std"]
std = [
"codec/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-version/std",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-version/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,222 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Single Block Migration Example Pallet
//!
//! An example pallet demonstrating best-practices for writing single-block migrations in the
//! context of upgrading pallet storage.
//!
//! ## Forewarning
//!
//! Single block migrations **MUST** execute in a single block, therefore when executed on a
//! teyrchain are only appropriate when guaranteed to not exceed block weight limits. If a
//! teyrchain submits a block that exceeds the block weight limit it will **brick the chain**!
//!
//! If weight is a concern or you are not sure which type of migration to use, you should probably
//! use a multi-block migration.
//!
//! TODO: Link above to multi-block migration example.
//!
//! ## Pallet Overview
//!
//! This example pallet contains a single storage item [`Value`](pallet::Value), which may be set by
//! any signed origin by calling the [`set_value`](crate::Call::set_value) extrinsic.
//!
//! For the purposes of this exercise, we imagine that in [`StorageVersion`] V0 of this pallet
//! [`Value`](pallet::Value) is a `u32`, and this what is currently stored on-chain.
//!
//! ```ignore
//! // (Old) Storage Version V0 representation of `Value`
//! #[pallet::storage]
//! pub type Value<T: Config> = StorageValue<_, u32>;
//! ```
//!
//! In [`StorageVersion`] V1 of the pallet a new struct [`CurrentAndPreviousValue`] is introduced:
#![doc = docify::embed!("src/lib.rs", CurrentAndPreviousValue)]
//! and [`Value`](pallet::Value) is updated to store this new struct instead of a `u32`:
#![doc = docify::embed!("src/lib.rs", Value)]
//!
//! In StorageVersion V1 of the pallet when [`set_value`](crate::Call::set_value) is called, the
//! new value is stored in the `current` field of [`CurrentAndPreviousValue`], and the previous
//! value (if it exists) is stored in the `previous` field.
#![doc = docify::embed!("src/lib.rs", pallet_calls)]
//!
//! ## Why a migration is necessary
//!
//! Without a migration, there will be a discrepancy between the on-chain storage for [`Value`] (in
//! V0 it is a `u32`) and the current storage for [`Value`] (in V1 it was changed to a
//! [`CurrentAndPreviousValue`] struct).
//!
//! The on-chain storage for [`Value`] would be a `u32` but the runtime would try to read it as a
//! [`CurrentAndPreviousValue`]. This would result in unacceptable undefined behavior.
//!
//! ## Adding a migration module
//!
//! Writing a pallets migrations in a separate module is strongly recommended.
//!
//! Here's how the migration module is defined for this pallet:
//!
//! ```text
//! substrate/frame/examples/single-block-migrations/src/
//! ├── lib.rs <-- pallet definition
//! ├── Cargo.toml <-- pallet manifest
//! └── migrations/
//! ├── mod.rs <-- migrations module definition
//! └── v1.rs <-- migration logic for the V0 to V1 transition
//! ```
//!
//! This structure allows keeping migration logic separate from the pallet logic and
//! easily adding new migrations in the future.
//!
//! ## Writing the Migration
//!
//! All code related to the migration can be found under
//! [`v1.rs`](migrations::v1).
//!
//! See the migration source code for detailed comments.
//!
//! Here's a brief overview of modules and types defined in `v1.rs`:
//!
//! ### `mod v0`
//!
//! Here we define a [`storage_alias`](frame_support::storage_alias) for the old v0 [`Value`]
//! format.
//!
//! This allows reading the old v0 value from storage during the migration.
//!
//! ### `InnerMigrateV0ToV1`
//!
//! Here we define our raw migration logic,
//! `InnerMigrateV0ToV1` which implements the [`UncheckedOnRuntimeUpgrade`] trait.
//!
//! #### Why [`UncheckedOnRuntimeUpgrade`]?
//!
//! Otherwise, we would have two implementations of [`OnRuntimeUpgrade`] which could be confusing,
//! and may lead to accidentally using the wrong one.
//!
//! #### Standalone Struct or Pallet Hook?
//!
//! Note that the storage migration logic is attached to a standalone struct implementing
//! [`UncheckedOnRuntimeUpgrade`], rather than implementing the
//! [`Hooks::on_runtime_upgrade`](frame_support::traits::Hooks::on_runtime_upgrade) hook directly on
//! the pallet. The pallet hook is better suited for special types of logic that need to execute on
//! every runtime upgrade, but not so much for one-off storage migrations.
//!
//! ### `MigrateV0ToV1`
//!
//! Here, `InnerMigrateV0ToV1` is wrapped in a
//! [`VersionedMigration`] to define
//! [`MigrateV0ToV1`](crate::migrations::v1::MigrateV0ToV1), which may be used
//! in runtimes.
//!
//! Using [`VersionedMigration`] ensures that
//! - The migration only runs once when the on-chain storage version is `0`
//! - The on-chain storage version is updated to `1` after the migration executes
//! - Reads and writes from checking and setting the on-chain storage version are accounted for in
//! the final [`Weight`](frame_support::weights::Weight)
//!
//! ### `mod test`
//!
//! Here basic unit tests are defined for the migration.
//!
//! When writing migration tests, don't forget to check:
//! - `on_runtime_upgrade` returns the expected weight
//! - `post_upgrade` succeeds when given the bytes returned by `pre_upgrade`
//! - Pallet storage is in the expected state after the migration
//!
//! [`VersionedMigration`]: frame_support::migrations::VersionedMigration
//! [`GetStorageVersion`]: frame_support::traits::GetStorageVersion
//! [`OnRuntimeUpgrade`]: frame_support::traits::OnRuntimeUpgrade
//! [`UncheckedOnRuntimeUpgrade`]: frame_support::traits::UncheckedOnRuntimeUpgrade
//! [`MigrateV0ToV1`]: crate::migrations::v1::MigrateV0ToV1
// We make sure this pallet uses `no_std` for compiling to Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
// allow non-camel-case names for storage version V0 value
#![allow(non_camel_case_types)]
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
// Export migrations so they may be used in the runtime.
pub mod migrations;
#[doc(hidden)]
mod mock;
extern crate alloc;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::traits::StorageVersion;
use sp_runtime::RuntimeDebug;
/// Example struct holding the most recently set [`u32`] and the
/// second most recently set [`u32`] (if one existed).
#[docify::export]
#[derive(
Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
)]
pub struct CurrentAndPreviousValue {
/// The most recently set value.
pub current: u32,
/// The previous value, if one existed.
pub previous: Option<u32>,
}
// Pallet for demonstrating storage migrations.
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// Define the current [`StorageVersion`] of the pallet.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {}
/// [`StorageVersion`] V1 of [`Value`].
///
/// Currently used.
#[docify::export]
#[pallet::storage]
pub type Value<T: Config> = StorageValue<_, CurrentAndPreviousValue>;
#[docify::export(pallet_calls)]
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn set_value(origin: OriginFor<T>, value: u32) -> DispatchResult {
ensure_signed(origin)?;
let previous = Value::<T>::get().map(|v| v.current);
let new_struct = CurrentAndPreviousValue { current: value, previous };
<Value<T>>::put(new_struct);
Ok(())
}
}
}
@@ -0,0 +1,26 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/// Module containing all logic associated with the example migration from
/// [`StorageVersion`](frame_support::traits::StorageVersion) V0 to V1.
pub mod v1;
@@ -0,0 +1,202 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use frame_support::{
storage_alias,
traits::{Get, UncheckedOnRuntimeUpgrade},
};
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
/// Collection of storage item formats from the previous storage version.
///
/// Required so we can read values in the v0 storage format during the migration.
mod v0 {
use super::*;
/// V0 type for [`crate::Value`].
#[storage_alias]
pub type Value<T: crate::Config> = StorageValue<crate::Pallet<T>, u32>;
}
/// Implements [`UncheckedOnRuntimeUpgrade`], migrating the state of this pallet from V0 to V1.
///
/// In V0 of the template [`crate::Value`] is just a `u32`. In V1, it has been upgraded to
/// contain the struct [`crate::CurrentAndPreviousValue`].
///
/// In this migration, update the on-chain storage for the pallet to reflect the new storage
/// layout.
pub struct InnerMigrateV0ToV1<T: crate::Config>(core::marker::PhantomData<T>);
impl<T: crate::Config> UncheckedOnRuntimeUpgrade for InnerMigrateV0ToV1<T> {
/// Return the existing [`crate::Value`] so we can check that it was correctly set in
/// `InnerMigrateV0ToV1::post_upgrade`.
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
use codec::Encode;
// Access the old value using the `storage_alias` type
let old_value = v0::Value::<T>::get();
// Return it as an encoded `Vec<u8>`
Ok(old_value.encode())
}
/// Migrate the storage from V0 to V1.
///
/// - If the value doesn't exist, there is nothing to do.
/// - If the value exists, it is read and then written back to storage inside a
/// [`crate::CurrentAndPreviousValue`].
fn on_runtime_upgrade() -> frame_support::weights::Weight {
// Read the old value from storage
if let Some(old_value) = v0::Value::<T>::take() {
// Write the new value to storage
let new = crate::CurrentAndPreviousValue { current: old_value, previous: None };
crate::Value::<T>::put(new);
// One read + write for taking the old value, and one write for setting the new value
T::DbWeight::get().reads_writes(1, 2)
} else {
// No writes since there was no old value, just one read for checking
T::DbWeight::get().reads(1)
}
}
/// Verifies the storage was migrated correctly.
///
/// - If there was no old value, the new value should not be set.
/// - If there was an old value, the new value should be a [`crate::CurrentAndPreviousValue`].
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
use codec::Decode;
use frame_support::ensure;
let maybe_old_value = Option::<u32>::decode(&mut &state[..]).map_err(|_| {
sp_runtime::TryRuntimeError::Other("Failed to decode old value from storage")
})?;
match maybe_old_value {
Some(old_value) => {
let expected_new_value =
crate::CurrentAndPreviousValue { current: old_value, previous: None };
let actual_new_value = crate::Value::<T>::get();
ensure!(actual_new_value.is_some(), "New value not set");
ensure!(
actual_new_value == Some(expected_new_value),
"New value not set correctly"
);
},
None => {
ensure!(crate::Value::<T>::get().is_none(), "New value unexpectedly set");
},
};
Ok(())
}
}
/// [`UncheckedOnRuntimeUpgrade`] implementation [`InnerMigrateV0ToV1`] wrapped in a
/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), which ensures that:
/// - The migration only runs once when the on-chain storage version is 0
/// - The on-chain storage version is updated to `1` after the migration executes
/// - Reads/Writes from checking/settings the on-chain storage version are accounted for
pub type MigrateV0ToV1<T> = frame_support::migrations::VersionedMigration<
0, // The migration will only execute when the on-chain storage version is 0
1, // The on-chain storage version will be set to 1 after the migration is complete
InnerMigrateV0ToV1<T>,
crate::pallet::Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
/// Tests for our migration.
///
/// When writing migration tests, it is important to check:
/// 1. `on_runtime_upgrade` returns the expected weight
/// 2. `post_upgrade` succeeds when given the bytes returned by `pre_upgrade`
/// 3. The storage is in the expected state after the migration
#[cfg(any(all(feature = "try-runtime", test), doc))]
mod test {
use self::InnerMigrateV0ToV1;
use super::*;
use crate::mock::{new_test_ext, MockRuntime};
use frame_support::assert_ok;
#[test]
fn handles_no_existing_value() {
new_test_ext().execute_with(|| {
// By default, no value should be set. Verify this assumption.
assert!(crate::Value::<MockRuntime>::get().is_none());
assert!(v0::Value::<MockRuntime>::get().is_none());
// Get the pre_upgrade bytes
let bytes = match InnerMigrateV0ToV1::<MockRuntime>::pre_upgrade() {
Ok(bytes) => bytes,
Err(e) => panic!("pre_upgrade failed: {e:?}"),
};
// Execute the migration
let weight = InnerMigrateV0ToV1::<MockRuntime>::on_runtime_upgrade();
// Verify post_upgrade succeeds
assert_ok!(InnerMigrateV0ToV1::<MockRuntime>::post_upgrade(bytes));
// The weight should be just 1 read for trying to access the old value.
assert_eq!(weight, <MockRuntime as frame_system::Config>::DbWeight::get().reads(1));
// After the migration, no value should have been set.
assert!(crate::Value::<MockRuntime>::get().is_none());
})
}
#[test]
fn handles_existing_value() {
new_test_ext().execute_with(|| {
// Set up an initial value
let initial_value = 42;
v0::Value::<MockRuntime>::put(initial_value);
// Get the pre_upgrade bytes
let bytes = match InnerMigrateV0ToV1::<MockRuntime>::pre_upgrade() {
Ok(bytes) => bytes,
Err(e) => panic!("pre_upgrade failed: {e:?}"),
};
// Execute the migration
let weight = InnerMigrateV0ToV1::<MockRuntime>::on_runtime_upgrade();
// Verify post_upgrade succeeds
assert_ok!(InnerMigrateV0ToV1::<MockRuntime>::post_upgrade(bytes));
// The weight used should be 1 read for the old value, and 1 write for the new
// value.
assert_eq!(
weight,
<MockRuntime as frame_system::Config>::DbWeight::get().reads_writes(1, 2)
);
// After the migration, the new value should be set as the `current` value.
let expected_new_value =
crate::CurrentAndPreviousValue { current: initial_value, previous: None };
assert_eq!(crate::Value::<MockRuntime>::get(), Some(expected_new_value));
})
}
}
@@ -0,0 +1,64 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#![cfg(any(all(feature = "try-runtime", test), doc))]
use crate::*;
use frame_support::{derive_impl, weights::constants::ParityDbWeight};
// Re-export crate as its pallet name for construct_runtime.
use crate as pallet_example_storage_migration;
type Block = frame_system::mocking::MockBlock<MockRuntime>;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub struct MockRuntime {
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Example: pallet_example_storage_migration::{Pallet, Call, Storage},
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for MockRuntime {
type Block = Block;
type AccountData = pallet_balances::AccountData<u64>;
type DbWeight = ParityDbWeight;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for MockRuntime {
type AccountStore = System;
}
impl Config for MockRuntime {}
pub fn new_test_ext() -> sp_io::TestExternalities {
use sp_runtime::BuildStorage;
let t = RuntimeGenesisConfig { system: Default::default(), balances: Default::default() }
.build_storage()
.unwrap();
t.into()
}
+52
View File
@@ -0,0 +1,52 @@
[package]
name = "pallet-example-split"
version = "10.0.0"
authors.workspace = true
edition.workspace = true
license = "MIT-0"
homepage.workspace = true
repository.workspace = true
description = "FRAME example split pallet"
readme = "README.md"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-io = { workspace = true }
frame-benchmarking = { optional = true, workspace = true }
[dev-dependencies]
sp-core = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-io/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"]
+10
View File
@@ -0,0 +1,10 @@
<!-- markdown-link-check-disable -->
# Basic Example For Splitting A Pallet
A simple example of a FRAME pallet demonstrating the ability to split sections across multiple
files.
Note that this is purely experimental at this point.
Run `cargo doc --package pallet-example-split --open` to view this pallet's documentation.
License: MIT-0
@@ -0,0 +1,60 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Benchmarking setup for pallet-example-split
// Only enable this module for benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
#[allow(unused)]
use crate::Pallet as Template;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn do_something() {
let value = 100u32.into();
let caller: T::AccountId = whitelisted_caller();
#[extrinsic_call]
do_something(RawOrigin::Signed(caller), value);
assert_eq!(Something::<T>::get(), Some(value));
}
#[benchmark]
fn cause_error() {
Something::<T>::put(100u32);
let caller: T::AccountId = whitelisted_caller();
#[extrinsic_call]
cause_error(RawOrigin::Signed(caller));
assert_eq!(Something::<T>::get(), Some(101u32));
}
impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test);
}
@@ -0,0 +1,37 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use frame_support::pallet_macros::*;
/// A [`pallet_section`] that defines the events for a pallet.
/// This can later be imported into the pallet using [`import_section`].
#[pallet_section]
mod events {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event documentation should end with an array that provides descriptive names for event
/// parameters. [something, who]
SomethingStored { something: u32, who: T::AccountId },
}
}
+127
View File
@@ -0,0 +1,127 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Split Example Pallet
//!
//! **This pallet serves as an example and is not meant to be used in production.**
//!
//! A FRAME pallet demonstrating the ability to split sections across multiple files.
//!
//! Note that this is purely experimental at this point.
#![cfg_attr(not(feature = "std"), no_std)]
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod events;
pub mod weights;
pub use weights::*;
use frame_support::pallet_macros::*;
/// Imports a [`pallet_section`] defined at [`events::events`].
/// This brings the events defined in that section into the pallet's namespace.
#[import_section(events::events)]
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
}
// The pallet's runtime storage items.
#[pallet::storage]
pub type Something<T> = StorageValue<_, u32>;
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// Error names should be descriptive.
NoneValue,
/// Errors should have helpful documentation associated with them.
StorageOverflow,
}
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// An example dispatchable that takes a singles value as a parameter, writes the value to
/// storage and emits an event. This function must be dispatched by a signed extrinsic.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::do_something())]
pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult {
// Check that the extrinsic was signed and get the signer.
// This function will return an error if the extrinsic is not signed.
let who = ensure_signed(origin)?;
// Update storage.
<Something<T>>::put(something);
// Emit an event.
Self::deposit_event(Event::SomethingStored { something, who });
// Return a successful DispatchResultWithPostInfo
Ok(())
}
/// An example dispatchable that may throw a custom error.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::cause_error())]
pub fn cause_error(origin: OriginFor<T>) -> DispatchResult {
let _who = ensure_signed(origin)?;
// Read a value from storage.
match Something::<T>::get() {
// Return an error if the value has not been set.
None => return Err(Error::<T>::NoneValue.into()),
Some(old) => {
// Increment the value read from storage; will error in the event of overflow.
let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
// Update the value in storage with the incremented result.
<Something<T>>::put(new);
Ok(())
},
}
}
}
}
@@ -0,0 +1,52 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate as pallet_template;
use frame_support::{derive_impl, sp_runtime::BuildStorage};
type Block = frame_system::mocking::MockBlock<Test>;
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
TemplatePallet: pallet_template,
}
);
/// Using a default config for [`frame_system`] in tests. See `default-config` example for more
/// details.
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
}
impl pallet_template::Config for Test {
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into()
}
@@ -0,0 +1,50 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::{mock::*, Error, Event, Something};
use frame_support::{assert_noop, assert_ok};
#[test]
fn it_works_for_default_value() {
new_test_ext().execute_with(|| {
// Go past genesis block so events get deposited
System::set_block_number(1);
// Dispatch a signed extrinsic.
assert_ok!(TemplatePallet::do_something(RuntimeOrigin::signed(1), 42));
// Read pallet storage and assert an expected result.
assert_eq!(Something::<Test>::get(), Some(42));
// Assert that the correct event was deposited
System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into());
});
}
#[test]
fn correct_error_for_none_value() {
new_test_ext().execute_with(|| {
// Ensure the expected error is thrown when no value is present.
assert_noop!(
TemplatePallet::cause_error(RuntimeOrigin::signed(1)),
Error::<Test>::NoneValue
);
});
}
+113
View File
@@ -0,0 +1,113 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: MIT-0
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Autogenerated weights for pallet_template
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-04-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `Alexs-MacBook-Pro-2.local`, CPU: `<UNKNOWN>`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// ../../target/release/node-template
// benchmark
// pallet
// --chain
// dev
// --pallet
// pallet_template
// --extrinsic
// *
// --steps=50
// --repeat=20
// --execution=wasm
// --wasm-execution=compiled
// --output
// pallets/template/src/weights.rs
// --template
// ../../.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for pallet_template.
pub trait WeightInfo {
fn do_something() -> Weight;
fn cause_error() -> Weight;
}
/// Weights for pallet_template using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: TemplatePallet Something (r:0 w:1)
/// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
fn do_something() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 8_000_000 picoseconds.
Weight::from_parts(9_000_000, 0)
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: TemplatePallet Something (r:1 w:1)
/// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
fn cause_error() -> Weight {
// Proof Size summary in bytes:
// Measured: `32`
// Estimated: `1489`
// Minimum execution time: 6_000_000 picoseconds.
Weight::from_parts(6_000_000, 1489)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: TemplatePallet Something (r:0 w:1)
/// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
fn do_something() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 8_000_000 picoseconds.
Weight::from_parts(9_000_000, 0)
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: TemplatePallet Something (r:1 w:1)
/// Proof: TemplatePallet Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
fn cause_error() -> Weight {
// Proof Size summary in bytes:
// Measured: `32`
// Estimated: `1489`
// Minimum execution time: 6_000_000 picoseconds.
Weight::from_parts(6_000_000, 1489)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}
+58
View File
@@ -0,0 +1,58 @@
// 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.
//! # FRAME Pallet Examples
//!
//! This crate contains a collection of simple examples of FRAME pallets, demonstrating useful
//! features in action. It is not intended to be used in production.
//!
//! ## Pallets
//!
//! - [`pallet_example_basic`]: This pallet demonstrates concepts, APIs and structures common to
//! most FRAME runtimes.
//!
//! - [`pallet_example_offchain_worker`]: This pallet demonstrates concepts, APIs and structures
//! common to most offchain workers.
//!
//! - [`pallet_default_config_example`]: This pallet demonstrates different ways to implement the
//! `Config` trait of pallets.
//!
//! - [`pallet_dev_mode`]: This pallet demonstrates the ease of requirements for a pallet in "dev
//! mode".
//!
//! - [`pallet_example_kitchensink`]: This pallet demonstrates a catalog of all FRAME macros in use
//! and their various syntax options.
//!
//! - [`pallet_example_split`]: A simple example of a FRAME pallet demonstrating the ability to
//! split sections across multiple files.
//!
//! - [`pallet_example_frame_crate`]: Example pallet showcasing how one can be built using only the
//! `frame` umbrella crate.
//!
//! - [`pallet_example_single_block_migrations`]: An example pallet demonstrating best-practices for
//! writing storage migrations.
//!
//! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work.
//!
//! - [`pallet_example_view_functions`]: This pallet demonstrates the use of view functions to query
//! pallet state.
//!
//! - [`pallet_example_authorization_tx_extension`]: An example `TransactionExtension` that
//! authorizes a custom origin through signature validation, along with two support pallets to
//! showcase the usage.
//!
//! **Tip**: Use `cargo doc --package <pallet-name> --open` to view each pallet's documentation.
+56
View File
@@ -0,0 +1,56 @@
[package]
name = "pallet-example-tasks"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Pallet to demonstrate the usage of Tasks to recognize and execute service work"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
frame-benchmarking = { optional = true, workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
experimental = ["frame-support/experimental", "frame-system/experimental"]
@@ -0,0 +1,42 @@
// 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.
//! Benchmarking for `pallet-example-tasks`.
#![cfg(feature = "runtime-benchmarks")]
use crate::*;
use frame_benchmarking::v2::*;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn add_number_into_total() {
Numbers::<T>::insert(0, 1);
#[block]
{
Task::<T>::add_number_into_total(0).unwrap();
}
assert_eq!(Numbers::<T>::get(0), None);
}
impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::mock::Runtime);
}
+111
View File
@@ -0,0 +1,111 @@
// 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.
//! This pallet demonstrates the use of the `pallet::task` api for service work.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::dispatch::DispatchResult;
use frame_system::offchain::CreateBare;
#[cfg(feature = "experimental")]
use frame_system::offchain::SubmitTransaction;
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
pub mod mock;
pub mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::*;
#[cfg(feature = "experimental")]
const LOG_TARGET: &str = "pallet-example-tasks";
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::error]
pub enum Error<T> {
/// The referenced task was not found.
NotFound,
}
#[pallet::tasks_experimental]
impl<T: Config> Pallet<T> {
/// Add a pair of numbers into the totals and remove them.
#[pallet::task_list(Numbers::<T>::iter_keys())]
#[pallet::task_condition(|i| Numbers::<T>::contains_key(i))]
#[pallet::task_weight(T::WeightInfo::add_number_into_total())]
#[pallet::task_index(0)]
pub fn add_number_into_total(i: u32) -> DispatchResult {
let v = Numbers::<T>::take(i).ok_or(Error::<T>::NotFound)?;
Total::<T>::mutate(|(total_keys, total_values)| {
*total_keys += i;
*total_values += v;
});
Ok(())
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
#[cfg(feature = "experimental")]
fn offchain_worker(_block_number: BlockNumberFor<T>) {
if let Some(key) = Numbers::<T>::iter_keys().next() {
// Create a valid task
let task = Task::<T>::AddNumberIntoTotal { i: key };
let runtime_task = <T as Config>::RuntimeTask::from(task);
let call = frame_system::Call::<T>::do_task { task: runtime_task.into() };
// Submit the task as an unsigned transaction
let xt = <T as CreateBare<frame_system::Call<T>>>::create_bare(call.into());
let res = SubmitTransaction::<T, frame_system::Call<T>>::submit_transaction(xt);
match res {
Ok(_) => log::info!(target: LOG_TARGET, "Submitted the task."),
Err(e) => log::error!(target: LOG_TARGET, "Error submitting task: {:?}", e),
}
}
}
#[cfg(not(feature = "experimental"))]
fn offchain_worker(_block_number: BlockNumberFor<T>) {}
}
#[pallet::config]
pub trait Config: CreateBare<frame_system::Call<Self>> + frame_system::Config {
type RuntimeTask: frame_support::traits::Task
+ IsType<<Self as frame_system::Config>::RuntimeTask>
+ From<Task<Self>>;
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
/// Some running total.
#[pallet::storage]
pub type Total<T: Config> = StorageValue<_, (u32, u32), ValueQuery>;
/// Numbers to be added into the total.
#[pallet::storage]
pub type Numbers<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
}
@@ -0,0 +1,73 @@
// 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.
//! Mock runtime for `tasks-example` tests.
#![cfg(test)]
use crate::{self as pallet_example_tasks};
use frame_support::derive_impl;
use sp_runtime::testing::TestXt;
pub type AccountId = u32;
pub type Balance = u32;
type Block = frame_system::mocking::MockBlock<Runtime>;
frame_support::construct_runtime!(
pub enum Runtime {
System: frame_system,
TasksExample: pallet_example_tasks,
}
);
pub type Extrinsic = TestXt<RuntimeCall, ()>;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
}
impl<LocalCall> frame_system::offchain::CreateTransactionBase<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
type RuntimeCall = RuntimeCall;
type Extrinsic = Extrinsic;
}
impl<LocalCall> frame_system::offchain::CreateBare<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
Extrinsic::new_bare(call)
}
}
impl pallet_example_tasks::Config for Runtime {
type RuntimeTask = RuntimeTask;
type WeightInfo = ();
}
pub fn advance_to(b: u64) {
#[cfg(feature = "experimental")]
use frame_support::traits::Hooks;
while System::block_number() < b {
System::set_block_number(System::block_number() + 1);
#[cfg(feature = "experimental")]
TasksExample::offchain_worker(System::block_number());
}
}
+163
View File
@@ -0,0 +1,163 @@
// 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.
//! Tests for `pallet-example-tasks`.
#![cfg(test)]
use crate::{mock::*, Numbers};
#[cfg(feature = "experimental")]
use codec::Decode;
use frame_support::traits::Task;
#[cfg(feature = "experimental")]
use sp_core::offchain::{testing, OffchainWorkerExt, TransactionPoolExt};
use sp_runtime::BuildStorage;
#[cfg(feature = "experimental")]
use frame_support::{assert_noop, assert_ok};
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = RuntimeGenesisConfig {
// We use default for brevity, but you can configure as desired if needed.
system: Default::default(),
}
.build_storage()
.unwrap();
t.into()
}
#[test]
fn task_enumerate_works() {
new_test_ext().execute_with(|| {
Numbers::<Runtime>::insert(0, 1);
assert_eq!(crate::pallet::Task::<Runtime>::iter().collect::<Vec<_>>().len(), 1);
});
}
#[test]
fn runtime_task_enumerate_works_via_frame_system_config() {
new_test_ext().execute_with(|| {
Numbers::<Runtime>::insert(0, 1);
Numbers::<Runtime>::insert(1, 4);
assert_eq!(
<Runtime as frame_system::Config>::RuntimeTask::iter().collect::<Vec<_>>().len(),
2
);
});
}
#[test]
fn runtime_task_enumerate_works_via_pallet_config() {
new_test_ext().execute_with(|| {
Numbers::<Runtime>::insert(1, 4);
assert_eq!(
<Runtime as crate::pallet::Config>::RuntimeTask::iter()
.collect::<Vec<_>>()
.len(),
1
);
});
}
#[test]
fn task_index_works_at_pallet_level() {
new_test_ext().execute_with(|| {
assert_eq!(crate::pallet::Task::<Runtime>::AddNumberIntoTotal { i: 2u32 }.task_index(), 0);
});
}
#[test]
fn task_index_works_at_runtime_level() {
new_test_ext().execute_with(|| {
assert_eq!(
<Runtime as frame_system::Config>::RuntimeTask::TasksExample(crate::pallet::Task::<
Runtime,
>::AddNumberIntoTotal {
i: 1u32
})
.task_index(),
0
);
});
}
#[cfg(feature = "experimental")]
#[test]
fn task_execution_works() {
new_test_ext().execute_with(|| {
System::set_block_number(1);
Numbers::<Runtime>::insert(0, 1);
Numbers::<Runtime>::insert(1, 4);
let task =
<Runtime as frame_system::Config>::RuntimeTask::TasksExample(crate::pallet::Task::<
Runtime,
>::AddNumberIntoTotal {
i: 1u32,
});
assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),));
assert_eq!(Numbers::<Runtime>::get(0), Some(1));
assert_eq!(Numbers::<Runtime>::get(1), None);
assert_eq!(crate::Total::<Runtime>::get(), (1, 4));
System::assert_last_event(frame_system::Event::<Runtime>::TaskCompleted { task }.into());
});
}
#[cfg(feature = "experimental")]
#[test]
fn task_execution_fails_for_invalid_task() {
new_test_ext().execute_with(|| {
Numbers::<Runtime>::insert(1, 4);
assert_noop!(
System::do_task(
RuntimeOrigin::signed(1),
<Runtime as frame_system::Config>::RuntimeTask::TasksExample(
crate::pallet::Task::<Runtime>::AddNumberIntoTotal { i: 0u32 }
),
),
frame_system::Error::<Runtime>::InvalidTask
);
});
}
#[cfg(feature = "experimental")]
#[test]
fn task_with_offchain_worker() {
let (offchain, _offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.execute_with(|| {
advance_to(1);
assert!(pool_state.read().transactions.is_empty());
Numbers::<Runtime>::insert(0, 10);
assert_eq!(crate::Total::<Runtime>::get(), (0, 0));
advance_to(2);
let tx = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
use sp_runtime::traits::ExtrinsicLike;
assert!(tx.is_bare());
});
}
+111
View File
@@ -0,0 +1,111 @@
// 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.
// 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.
//! Autogenerated weights for `pallet_example_tasks`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// frame-omni-bencher
// v1
// benchmark
// pallet
// --extrinsic=*
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
// --pallet=pallet_example_tasks
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/substrate/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/substrate/frame/examples/tasks/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=substrate/.maintain/frame-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --genesis-builder-policy=none
// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage,pallet_election_provider_multi_block,pallet_election_provider_multi_block::signed,pallet_election_provider_multi_block::unsigned,pallet_election_provider_multi_block::verifier
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pallet_example_tasks`.
pub trait WeightInfo {
fn add_number_into_total() -> Weight;
}
/// Weights for `pallet_example_tasks` using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `TasksExample::Numbers` (r:1 w:1)
/// Proof: `TasksExample::Numbers` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `TasksExample::Total` (r:1 w:1)
/// Proof: `TasksExample::Total` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn add_number_into_total() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3465`
// Minimum execution time: 2_861_000 picoseconds.
Weight::from_parts(2_984_000, 3465)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `TasksExample::Numbers` (r:1 w:1)
/// Proof: `TasksExample::Numbers` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `TasksExample::Total` (r:1 w:1)
/// Proof: `TasksExample::Total` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn add_number_into_total() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3465`
// Minimum execution time: 2_861_000 picoseconds.
Weight::from_parts(2_984_000, 3465)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
}
@@ -0,0 +1,60 @@
[package]
name = "pallet-example-view-functions"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Pallet to demonstrate the usage of view functions to query pallet state"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { default-features = false, workspace = true }
frame-metadata = { features = ["current"], workspace = true }
log = { workspace = true }
scale-info = { default-features = false, features = [
"derive",
], workspace = true }
frame-support = { default-features = false, workspace = true }
frame-system = { default-features = false, workspace = true }
sp-core = { default-features = false, workspace = true }
sp-io = { default-features = false, workspace = true }
sp-metadata-ir = { default-features = false, workspace = true }
sp-runtime = { default-features = false, workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-metadata/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-metadata-ir/std",
"sp-runtime/std",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,114 @@
// 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.
//! This pallet demonstrates the use of the `pallet::view_functions` api for service
//! work.
#![cfg_attr(not(feature = "std"), no_std)]
pub mod tests;
use frame_support::Parameter;
use scale_info::TypeInfo;
pub struct SomeType1;
impl From<SomeType1> for u64 {
fn from(_t: SomeType1) -> Self {
0u64
}
}
pub trait SomeAssociation1 {
type _1: Parameter + codec::MaxEncodedLen + TypeInfo;
}
impl SomeAssociation1 for u64 {
type _1 = u64;
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
#[pallet::error]
pub enum Error<T> {}
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type SomeValue<T: Config> = StorageValue<_, u32>;
#[pallet::storage]
pub type SomeMap<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
#[pallet::view_functions]
impl<T: Config> Pallet<T>
where
T::AccountId: From<SomeType1> + SomeAssociation1,
{
/// Query value with no input args.
pub fn get_value() -> Option<u32> {
SomeValue::<T>::get()
}
/// Query value with input args.
pub fn get_value_with_arg(key: u32) -> Option<u32> {
SomeMap::<T>::get(key)
}
}
}
#[frame_support::pallet]
pub mod pallet2 {
use super::*;
use frame_support::pallet_prelude::*;
#[pallet::error]
pub enum Error<T, I = ()> {}
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::storage]
pub type SomeValue<T: Config<I>, I: 'static = ()> = StorageValue<_, u32>;
#[pallet::storage]
pub type SomeMap<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, u32, u32, OptionQuery>;
#[pallet::view_functions]
impl<T: Config<I>, I: 'static> Pallet<T, I>
where
T::AccountId: From<SomeType1> + SomeAssociation1,
{
/// Query value with no input args.
pub fn get_value() -> Option<u32> {
SomeValue::<T, I>::get()
}
/// Query value with input args.
pub fn get_value_with_arg(key: u32) -> Option<u32> {
SomeMap::<T, I>::get(key)
}
}
}
@@ -0,0 +1,175 @@
// 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.
//! Tests for `pallet-example-view-functions`.
#![cfg(test)]
use crate::{
pallet::{self, Pallet},
pallet2,
};
use codec::{Decode, Encode};
use scale_info::meta_type;
use frame_support::{derive_impl, pallet_prelude::PalletInfoAccess, view_functions::ViewFunction};
use sp_io::hashing::twox_128;
use sp_metadata_ir::{
ItemDeprecationInfoIR, PalletViewFunctionMetadataIR, PalletViewFunctionParamMetadataIR,
};
use sp_runtime::testing::TestXt;
pub type AccountId = u32;
pub type Balance = u32;
type Block = frame_system::mocking::MockBlock<Runtime>;
frame_support::construct_runtime!(
pub enum Runtime {
System: frame_system,
ViewFunctionsExample: pallet,
ViewFunctionsInstance: pallet2,
ViewFunctionsInstance1: pallet2::<Instance1>,
}
);
pub type Extrinsic = TestXt<RuntimeCall, ()>;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
}
impl pallet::Config for Runtime {}
impl pallet2::Config<pallet2::Instance1> for Runtime {}
impl pallet2::Config for Runtime {}
pub fn new_test_ext() -> sp_io::TestExternalities {
use sp_runtime::BuildStorage;
let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap();
t.into()
}
#[test]
fn pallet_get_value_query() {
new_test_ext().execute_with(|| {
let some_value = Some(99);
pallet::SomeValue::<Runtime>::set(some_value);
assert_eq!(some_value, Pallet::<Runtime>::get_value());
let query = pallet::GetValueViewFunction::<Runtime>::new();
test_dispatch_view_function(&query, some_value);
});
}
#[test]
fn pallet_get_value_with_arg_query() {
new_test_ext().execute_with(|| {
let some_key = 1u32;
let some_value = Some(123);
pallet::SomeMap::<Runtime>::set(some_key, some_value);
assert_eq!(some_value, Pallet::<Runtime>::get_value_with_arg(some_key));
let query = pallet::GetValueWithArgViewFunction::<Runtime>::new(some_key);
test_dispatch_view_function(&query, some_value);
});
}
#[test]
fn pallet_multiple_instances() {
use pallet2::Instance1;
new_test_ext().execute_with(|| {
let instance_value = Some(123);
let instance1_value = Some(456);
pallet2::SomeValue::<Runtime>::set(instance_value);
pallet2::SomeValue::<Runtime, Instance1>::set(instance1_value);
let query = pallet2::GetValueViewFunction::<Runtime>::new();
test_dispatch_view_function(&query, instance_value);
let query_instance1 = pallet2::GetValueViewFunction::<Runtime, Instance1>::new();
test_dispatch_view_function(&query_instance1, instance1_value);
});
}
#[test]
fn metadata_ir_definitions() {
new_test_ext().execute_with(|| {
let metadata_ir = Runtime::metadata_ir();
let pallet1 = metadata_ir
.pallets
.iter()
.find(|pallet| pallet.name == "ViewFunctionsExample")
.unwrap();
fn view_fn_id(preifx_hash: [u8; 16], view_fn_signature: &str) -> [u8; 32] {
let mut id = [0u8; 32];
id[..16].copy_from_slice(&preifx_hash);
id[16..].copy_from_slice(&twox_128(view_fn_signature.as_bytes()));
id
}
let get_value_id = view_fn_id(
<ViewFunctionsExample as PalletInfoAccess>::name_hash(),
"get_value() -> Option<u32>",
);
let get_value_with_arg_id = view_fn_id(
<ViewFunctionsExample as PalletInfoAccess>::name_hash(),
"get_value_with_arg(u32) -> Option<u32>",
);
pretty_assertions::assert_eq!(
pallet1.view_functions,
vec![
PalletViewFunctionMetadataIR {
name: "get_value",
id: get_value_id,
inputs: vec![],
output: meta_type::<Option<u32>>(),
docs: vec![" Query value with no input args."],
deprecation_info: ItemDeprecationInfoIR::NotDeprecated,
},
PalletViewFunctionMetadataIR {
name: "get_value_with_arg",
id: get_value_with_arg_id,
inputs: vec![PalletViewFunctionParamMetadataIR {
name: "key",
ty: meta_type::<u32>()
},],
output: meta_type::<Option<u32>>(),
docs: vec![" Query value with input args."],
deprecation_info: ItemDeprecationInfoIR::NotDeprecated,
},
]
);
});
}
fn test_dispatch_view_function<Q, V>(query: &Q, expected: V)
where
Q: ViewFunction + Encode,
V: Decode + Eq + PartialEq + std::fmt::Debug,
{
let input = query.encode();
let output = Runtime::execute_view_function(Q::id(), input).unwrap();
let query_result = V::decode(&mut &output[..]).unwrap();
assert_eq!(expected, query_result,);
}