feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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))
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 ¤t_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()
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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 },
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,);
|
||||
}
|
||||
Reference in New Issue
Block a user