mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 20:17:57 +00:00
Move all example pallets under examples folder. (#10215)
* Put all examples under one folder Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com> * Updated Cargo.toml Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com> * updated for ci script Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com> * update Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com> * Added notes that example pallets are not meant to be used in production. Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com> * updated Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "pallet-example-basic"
|
||||
version = "4.0.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Unlicense"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME example pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
log = { version = "0.4.14", default-features = false }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
|
||||
pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
|
||||
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }
|
||||
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { version = "4.0.0-dev", path = "../../../primitives/core", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"pallet-balances/std",
|
||||
"scale-info/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std"
|
||||
]
|
||||
runtime-benchmarks = ["frame-benchmarking"]
|
||||
try-runtime = ["frame-support/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: Unlicense
|
||||
@@ -0,0 +1,78 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2021 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-basic.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::*;
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||
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.substrate.io/v3/runtime/benchmarking#how-to-benchmark
|
||||
//
|
||||
// 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://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks
|
||||
benchmarks! {
|
||||
// This will measure the execution time of `set_dummy` for b in [1..1000] range.
|
||||
set_dummy_benchmark {
|
||||
// This is the benchmark setup phase
|
||||
let b in 1 .. 1000;
|
||||
}: set_dummy(RawOrigin::Root, b.into()) // The execution phase is just running `set_dummy` extrinsic call
|
||||
verify {
|
||||
// This is the optional benchmark verification phase, asserting certain states.
|
||||
assert_eq!(Pallet::<T>::dummy(), Some(b.into()))
|
||||
}
|
||||
|
||||
// This will measure the execution time of `accumulate_dummy` for b in [1..1000] range.
|
||||
// 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.
|
||||
accumulate_dummy {
|
||||
let b in 1 .. 1000;
|
||||
// The caller account is whitelisted for DB reads/write by the benchmarking macro.
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
}: _(RawOrigin::Signed(caller), b.into())
|
||||
|
||||
// This will measure the execution time of sorting a vector.
|
||||
sort_vector {
|
||||
let x in 0 .. 10000;
|
||||
let mut m = Vec::<u32>::new();
|
||||
for i in (0..x).rev() {
|
||||
m.push(i);
|
||||
}
|
||||
}: {
|
||||
// The benchmark execution phase could also be a closure with custom code
|
||||
m.sort();
|
||||
}
|
||||
|
||||
// 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,751 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2017-2021 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.
|
||||
|
||||
//! <!-- 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>
|
||||
//!
|
||||
//! <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.
|
||||
//!
|
||||
//! - \[`Config`]
|
||||
//! - \[`Call`]
|
||||
//! - \[`Pallet`]
|
||||
//!
|
||||
//! \## 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>
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
dispatch::DispatchResult,
|
||||
traits::IsSubType,
|
||||
weights::{ClassifyDispatch, DispatchClass, Pays, PaysFee, WeighData, Weight},
|
||||
};
|
||||
use frame_system::ensure_signed;
|
||||
use log::info;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, DispatchInfoOf, SaturatedConversion, Saturating, SignedExtension},
|
||||
transaction_validity::{
|
||||
InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
|
||||
},
|
||||
};
|
||||
use sp_std::{marker::PhantomData, prelude::*};
|
||||
|
||||
// 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.substrate.io/v3/runtime/weights-and-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);
|
||||
(cents * multiplier).saturated_into::<Weight>()
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
|
||||
|
||||
/// 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]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
// Pallet implements [`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: T::BlockNumber) -> Weight {
|
||||
// Anything that needs to be done at the start of the block.
|
||||
// We don't do anything here.
|
||||
0
|
||||
}
|
||||
|
||||
// `on_finalize` is executed at the end of block after all extrinsic are dispatched.
|
||||
fn on_finalize(_n: T::BlockNumber) {
|
||||
// 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: T::BlockNumber) {
|
||||
// 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]
|
||||
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::weight(
|
||||
<T as pallet::Config>::WeightInfo::accumulate_dummy((*increase_by).saturated_into())
|
||||
)]
|
||||
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 = Self::dummy();
|
||||
// Will also work using the `::get` on the storage item type itself:
|
||||
// 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(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::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://paritytech.github.io/substrate/master/sp_io/logging/fn.log.html
|
||||
// https://paritytech.github.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(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(BalanceOf<T>),
|
||||
SetDummy(BalanceOf<T>),
|
||||
SetBar(T::AccountId, 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]
|
||||
// The getter attribute generate a function on `Pallet` placeholder:
|
||||
// `fn getter_name() -> Type` for basic value items or
|
||||
// `fn getter_name(key: KeyType) -> ValueType` for map items.
|
||||
#[pallet::getter(fn dummy)]
|
||||
pub(super) type Dummy<T: Config> = StorageValue<_, T::Balance>;
|
||||
|
||||
// A map that has enumerable entries.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn bar)]
|
||||
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]
|
||||
#[pallet::getter(fn foo)]
|
||||
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]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub dummy: T::Balance,
|
||||
pub bar: Vec<(T::AccountId, T::Balance)>,
|
||||
pub foo: T::Balance,
|
||||
}
|
||||
|
||||
// The default value for the genesis config type.
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
Self { dummy: Default::default(), bar: Default::default(), foo: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
// The build of genesis for the pallet.
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> GenesisBuild<T> 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::Origin, 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 signed extension and perform some
|
||||
// checks and [pre/post]processing [before/after] the transaction. A signed extension can be any
|
||||
// decodable type that implements `SignedExtension`. 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 signed extension can also indicate that a particular data must be present in the
|
||||
// _signing payload_ of a transaction by providing an implementation for the `additional_signed`
|
||||
// method. This example will not cover this type of extension. See `CheckSpecVersion` in
|
||||
// [FRAME System](https://github.com/paritytech/substrate/tree/master/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 `SignedExtension`. 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 signed extension can be found
|
||||
// [here](https://crates.parity.io/sp_runtime/traits/trait.SignedExtension.html).
|
||||
//
|
||||
// The signed 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 SignedExtra = (...)` in `node/runtime` and
|
||||
// `node-template` for an example of this.
|
||||
|
||||
/// A simple signed 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 signed extensions.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct WatchDummy<T: Config + Send + Sync>(PhantomData<T>);
|
||||
|
||||
impl<T: Config + Send + Sync> sp_std::fmt::Debug for WatchDummy<T> {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
||||
write!(f, "WatchDummy")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config + Send + Sync> SignedExtension for WatchDummy<T>
|
||||
where
|
||||
<T as frame_system::Config>::Call: IsSubType<Call<T>>,
|
||||
{
|
||||
const IDENTIFIER: &'static str = "WatchDummy";
|
||||
type AccountId = T::AccountId;
|
||||
type Call = <T as frame_system::Config>::Call;
|
||||
type AdditionalSigned = ();
|
||||
type Pre = ();
|
||||
|
||||
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
_who: &Self::AccountId,
|
||||
call: &Self::Call,
|
||||
_info: &DispatchInfoOf<Self::Call>,
|
||||
len: usize,
|
||||
) -> TransactionValidity {
|
||||
// if the transaction is too big, just drop it.
|
||||
if len > 200 {
|
||||
return InvalidTransaction::ExhaustsResources.into()
|
||||
}
|
||||
|
||||
// check for `set_dummy`
|
||||
match call.is_sub_type() {
|
||||
Some(Call::set_dummy { .. }) => {
|
||||
sp_runtime::print("set_dummy was received.");
|
||||
|
||||
let mut valid_tx = ValidTransaction::default();
|
||||
valid_tx.priority = Bounded::max_value();
|
||||
Ok(valid_tx)
|
||||
},
|
||||
_ => Ok(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2021 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-basic.
|
||||
|
||||
use crate::*;
|
||||
use frame_support::{
|
||||
assert_ok, parameter_types,
|
||||
traits::OnInitialize,
|
||||
weights::{DispatchInfo, GetDispatchInfo},
|
||||
};
|
||||
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::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
// Reexport crate as its pallet name for construct_runtime.
|
||||
use crate as pallet_example_basic;
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
// For testing the pallet, we construct a mock runtime.
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
Example: pallet_example_basic::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(1024);
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = Call;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
}
|
||||
impl pallet_balances::Config for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = u64;
|
||||
type DustRemoval = ();
|
||||
type Event = Event;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MagicNumber: u64 = 1_000_000_000;
|
||||
}
|
||||
impl Config for Test {
|
||||
type MagicNumber = MagicNumber;
|
||||
type Event = Event;
|
||||
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 = GenesisConfig {
|
||||
// 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: 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!(Example::dummy(), Some(val1));
|
||||
|
||||
// Check that accumulate works when we have Some value in Dummy already.
|
||||
assert_ok!(Example::accumulate_dummy(Origin::signed(1), val2));
|
||||
assert_eq!(Example::dummy(), 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(Origin::signed(1), val1));
|
||||
assert_eq!(Example::dummy(), Some(val1 + val2 + val1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works_for_default_value() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Example::foo(), 24);
|
||||
assert_ok!(Example::accumulate_foo(Origin::signed(1), 1));
|
||||
assert_eq!(Example::foo(), 25);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_dummy_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let test_val = 133;
|
||||
assert_ok!(Example::set_dummy(Origin::root(), test_val.into()));
|
||||
assert_eq!(Example::dummy(), 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(&1, &call, &info, 150)
|
||||
.unwrap()
|
||||
.priority,
|
||||
u64::MAX,
|
||||
);
|
||||
assert_eq!(
|
||||
WatchDummy::<Test>(PhantomData).validate(&1, &call, &info, 250),
|
||||
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);`
|
||||
assert!(info1.weight > 0);
|
||||
|
||||
// `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();
|
||||
assert!(info1.weight > info2.weight);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2021 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_basic
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
|
||||
//! DATE: 2021-03-15, STEPS: `[100, ]`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
|
||||
|
||||
// Executed Command:
|
||||
// ./target/release/substrate
|
||||
// benchmark
|
||||
// --chain
|
||||
// dev
|
||||
// --execution
|
||||
// wasm
|
||||
// --wasm-execution
|
||||
// compiled
|
||||
// --pallet
|
||||
// pallet_example_basic
|
||||
// --extrinsic
|
||||
// *
|
||||
// --steps
|
||||
// 100
|
||||
// --repeat
|
||||
// 10
|
||||
// --raw
|
||||
// --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 sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_example_basic.
|
||||
pub trait WeightInfo {
|
||||
fn set_dummy_benchmark(b: u32, ) -> Weight;
|
||||
fn accumulate_dummy(b: u32, ) -> 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> {
|
||||
fn set_dummy_benchmark(b: u32, ) -> Weight {
|
||||
(5_834_000 as Weight)
|
||||
.saturating_add((24_000 as Weight).saturating_mul(b as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn accumulate_dummy(b: u32, ) -> Weight {
|
||||
(51_353_000 as Weight)
|
||||
.saturating_add((14_000 as Weight).saturating_mul(b as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(T::DbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn sort_vector(x: u32, ) -> Weight {
|
||||
(2_569_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((4_000 as Weight).saturating_mul(x as Weight))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
fn set_dummy_benchmark(b: u32, ) -> Weight {
|
||||
(5_834_000 as Weight)
|
||||
.saturating_add((24_000 as Weight).saturating_mul(b as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn accumulate_dummy(b: u32, ) -> Weight {
|
||||
(51_353_000 as Weight)
|
||||
.saturating_add((14_000 as Weight).saturating_mul(b as Weight))
|
||||
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
|
||||
}
|
||||
fn sort_vector(x: u32, ) -> Weight {
|
||||
(2_569_000 as Weight)
|
||||
// Standard Error: 0
|
||||
.saturating_add((4_000 as Weight).saturating_mul(x as Weight))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
[package]
|
||||
name = "pallet-example-offchain-worker"
|
||||
version = "4.0.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Unlicense"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME example pallet for offchain worker"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
lite-json = { version = "0.1", default-features = false }
|
||||
log = { version = "0.4.14", default-features = false }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
|
||||
sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
|
||||
sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore", optional = true }
|
||||
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }
|
||||
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"lite-json/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-keystore",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"log/std",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
@@ -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: Unlicense
|
||||
@@ -0,0 +1,725 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 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.
|
||||
|
||||
//! <!-- 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)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::traits::Get;
|
||||
use frame_system::{
|
||||
self as system,
|
||||
offchain::{
|
||||
AppCrypto, CreateSignedTransaction, SendSignedTransaction, SendUnsignedTransaction,
|
||||
SignedPayload, Signer, SigningTypes, SubmitTransaction,
|
||||
},
|
||||
};
|
||||
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,
|
||||
};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
#[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>> + frame_system::Config {
|
||||
/// The identifier type for an offchain worker.
|
||||
type AuthorityId: AppCrypto<Self::Public, Self::Signature>;
|
||||
|
||||
/// The overarching event type.
|
||||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
|
||||
|
||||
/// The overarching dispatch call type.
|
||||
type Call: From<Call<Self>>;
|
||||
|
||||
// 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<Self::BlockNumber>;
|
||||
|
||||
/// 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<Self::BlockNumber>;
|
||||
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
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
|
||||
/// succesfuly 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: T::BlockNumber) {
|
||||
// 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
|
||||
/// excesive 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::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(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::weight(0)]
|
||||
pub fn submit_price_unsigned(
|
||||
origin: OriginFor<T>,
|
||||
_block_number: T::BlockNumber,
|
||||
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(Default::default(), 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::weight(0)]
|
||||
pub fn submit_price_unsigned_with_signed_payload(
|
||||
origin: OriginFor<T>,
|
||||
price_payload: PricePayload<T::Public, T::BlockNumber>,
|
||||
_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(Default::default(), 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.
|
||||
/// \[price, who\]
|
||||
NewPrice(u32, 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]
|
||||
#[pallet::getter(fn prices)]
|
||||
pub(super) type Prices<T: Config> = StorageValue<_, Vec<u32>, ValueQuery>;
|
||||
|
||||
/// Defines the block when next unsigned transaction will be accepted.
|
||||
///
|
||||
/// To prevent spam of unsigned (and unpayed!) 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]
|
||||
#[pallet::getter(fn next_unsigned_at)]
|
||||
pub(super) type NextUnsignedAt<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
|
||||
}
|
||||
|
||||
/// Payload used by this example crate to hold price
|
||||
/// data required to submit a transaction.
|
||||
#[derive(Encode, Decode, 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, T::BlockNumber> {
|
||||
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: T::BlockNumber) -> 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<T::BlockNumber>, 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) => {
|
||||
// Depending if the block is even or odd we will send a `Signed` or `Unsigned`
|
||||
// transaction.
|
||||
// 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 % 3u32.into();
|
||||
if transaction_type == Zero::zero() {
|
||||
TransactionType::Signed
|
||||
} else if transaction_type == T::BlockNumber::from(1u32) {
|
||||
TransactionType::UnsignedForAny
|
||||
} else if transaction_type == T::BlockNumber::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: T::BlockNumber) -> 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 carefuly
|
||||
// implement unsigned validation logic, as any mistakes can lead to opening DoS or spam
|
||||
// attack vectors. See validation logic docs for more details.
|
||||
//
|
||||
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
||||
.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: T::BlockNumber,
|
||||
) -> 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: T::BlockNumber,
|
||||
) -> 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 idefinitely 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 `reqwest`, 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 = sp_std::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.checked_sub(2).unwrap_or(0);
|
||||
Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
|
||||
}
|
||||
|
||||
/// Add new price to the list.
|
||||
fn add_price(who: T::AccountId, price: u32) {
|
||||
log::info!("Adding to the average: {}", price);
|
||||
<Prices<T>>::mutate(|prices| {
|
||||
const MAX_LEN: usize = 64;
|
||||
|
||||
if prices.len() < MAX_LEN {
|
||||
prices.push(price);
|
||||
} else {
|
||||
prices[price as usize % MAX_LEN] = 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, 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: &T::BlockNumber,
|
||||
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,407 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 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.
|
||||
|
||||
use crate as example_offchain_worker;
|
||||
use crate::*;
|
||||
use codec::Decode;
|
||||
use frame_support::{assert_ok, parameter_types};
|
||||
use sp_core::{
|
||||
offchain::{testing, OffchainWorkerExt, TransactionPoolExt},
|
||||
sr25519::Signature,
|
||||
H256,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore};
|
||||
use sp_runtime::{
|
||||
testing::{Header, TestXt},
|
||||
traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify},
|
||||
RuntimeAppPublic,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
// For testing the module, we construct a mock runtime.
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Example: example_offchain_worker::{Pallet, Call, Storage, Event<T>, ValidateUnsigned},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(1024);
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = sp_core::sr25519::Public;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
}
|
||||
|
||||
type Extrinsic = TestXt<Call, ()>;
|
||||
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::SendTransactionTypes<LocalCall> for Test
|
||||
where
|
||||
Call: From<LocalCall>,
|
||||
{
|
||||
type OverarchingCall = Call;
|
||||
type Extrinsic = Extrinsic;
|
||||
}
|
||||
|
||||
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Test
|
||||
where
|
||||
Call: From<LocalCall>,
|
||||
{
|
||||
fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(
|
||||
call: Call,
|
||||
_public: <Signature as Verify>::Signer,
|
||||
_account: AccountId,
|
||||
nonce: u64,
|
||||
) -> Option<(Call, <Extrinsic as ExtrinsicT>::SignaturePayload)> {
|
||||
Some((call, (nonce, ())))
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const GracePeriod: u64 = 5;
|
||||
pub const UnsignedInterval: u64 = 128;
|
||||
pub const UnsignedPriority: u64 = 1 << 20;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Event = Event;
|
||||
type AuthorityId = crypto::TestAuthId;
|
||||
type Call = Call;
|
||||
type GracePeriod = GracePeriod;
|
||||
type UnsignedInterval = UnsignedInterval;
|
||||
type UnsignedPriority = UnsignedPriority;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_aggregates_the_price() {
|
||||
sp_io::TestExternalities::default().execute_with(|| {
|
||||
assert_eq!(Example::average_price(), None);
|
||||
|
||||
assert_ok!(Example::submit_price(Origin::signed(Default::default()), 27));
|
||||
assert_eq!(Example::average_price(), Some(27));
|
||||
|
||||
assert_ok!(Example::submit_price(Origin::signed(Default::default()), 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 = KeyStore::new();
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
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(Arc::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_eq!(tx.signature.unwrap().0, 0);
|
||||
assert_eq!(tx.call, Call::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 = KeyStore::new();
|
||||
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
crate::crypto::Public::ID,
|
||||
Some(&format!("{}/hunter1", PHRASE)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let mut t = sp_io::TestExternalities::default();
|
||||
t.register_extension(OffchainWorkerExt::new(offchain));
|
||||
t.register_extension(TransactionPoolExt::new(pool));
|
||||
t.register_extension(KeystoreExt(Arc::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_eq!(tx.signature, None);
|
||||
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload {
|
||||
price_payload: body,
|
||||
signature,
|
||||
}) = tx.call
|
||||
{
|
||||
assert_eq!(body, price_payload);
|
||||
|
||||
let signature_valid =
|
||||
<PricePayload<
|
||||
<Test as SigningTypes>::Public,
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
> 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 = KeyStore::new();
|
||||
|
||||
SyncCryptoStore::sr25519_generate_new(
|
||||
&keystore,
|
||||
crate::crypto::Public::ID,
|
||||
Some(&format!("{}/hunter1", PHRASE)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let mut t = sp_io::TestExternalities::default();
|
||||
t.register_extension(OffchainWorkerExt::new(offchain));
|
||||
t.register_extension(TransactionPoolExt::new(pool));
|
||||
t.register_extension(KeystoreExt(Arc::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_eq!(tx.signature, None);
|
||||
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload {
|
||||
price_payload: body,
|
||||
signature,
|
||||
}) = tx.call
|
||||
{
|
||||
assert_eq!(body, price_payload);
|
||||
|
||||
let signature_valid =
|
||||
<PricePayload<
|
||||
<Test as SigningTypes>::Public,
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
> 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 = KeyStore::new();
|
||||
|
||||
let mut t = sp_io::TestExternalities::default();
|
||||
t.register_extension(OffchainWorkerExt::new(offchain));
|
||||
t.register_extension(TransactionPoolExt::new(pool));
|
||||
t.register_extension(KeystoreExt(Arc::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_eq!(tx.signature, None);
|
||||
assert_eq!(
|
||||
tx.call,
|
||||
Call::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 = 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,39 @@
|
||||
[package]
|
||||
name = "pallet-example-parallel"
|
||||
version = "3.0.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Unlicense"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME example pallet using runtime worker threads"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
|
||||
sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" }
|
||||
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
|
||||
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }
|
||||
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
|
||||
sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/tasks" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"sp-tasks/std",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- markdown-link-check-disable -->
|
||||
# Parallel Tasks Example Pallet
|
||||
|
||||
This example pallet demonstrates parallelizing validation of the enlisted participants (see
|
||||
`enlist_participants` dispatch).
|
||||
|
||||
**This pallet serves as an example and is not meant to be used in production.**
|
||||
@@ -0,0 +1,150 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 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.
|
||||
|
||||
//! # Parallel Tasks Example Pallet
|
||||
//!
|
||||
//! This example pallet demonstrates parallelizing validation of the enlisted participants
|
||||
//! (see `enlist_participants` dispatch).
|
||||
//!
|
||||
//! **This pallet serves as an example and is not meant to be used in production.**
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
/// The overarching dispatch call type.
|
||||
type Call: From<Call<Self>>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// A public part of the pallet.
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Get the new event running.
|
||||
#[pallet::weight(0)]
|
||||
pub fn run_event(origin: OriginFor<T>, id: Vec<u8>) -> DispatchResultWithPostInfo {
|
||||
let _ = ensure_signed(origin)?;
|
||||
<Participants<T>>::kill();
|
||||
<CurrentEventId<T>>::mutate(move |event_id| *event_id = id);
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
/// Submit list of participants to the current event.
|
||||
///
|
||||
/// The example utilizes parallel execution by checking half of the
|
||||
/// signatures in spawned task.
|
||||
#[pallet::weight(0)]
|
||||
pub fn enlist_participants(
|
||||
origin: OriginFor<T>,
|
||||
participants: Vec<EnlistedParticipant>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let _ = ensure_signed(origin)?;
|
||||
|
||||
if validate_participants_parallel(&<CurrentEventId<T>>::get(), &participants[..]) {
|
||||
for participant in participants {
|
||||
<Participants<T>>::append(participant.account);
|
||||
}
|
||||
}
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A vector of current participants
|
||||
///
|
||||
/// To enlist someone to participate, signed payload should be
|
||||
/// sent to `enlist`.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn participants)]
|
||||
pub(super) type Participants<T: Config> = StorageValue<_, Vec<Vec<u8>>, ValueQuery>;
|
||||
|
||||
/// Current event id to enlist participants to.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn get_current_event_id)]
|
||||
pub(super) type CurrentEventId<T: Config> = StorageValue<_, Vec<u8>, ValueQuery>;
|
||||
}
|
||||
|
||||
/// Request to enlist participant.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)]
|
||||
pub struct EnlistedParticipant {
|
||||
pub account: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EnlistedParticipant {
|
||||
fn verify(&self, event_id: &[u8]) -> bool {
|
||||
use sp_core::Public;
|
||||
use sp_runtime::traits::Verify;
|
||||
|
||||
match sp_core::sr25519::Signature::try_from(&self.signature[..]) {
|
||||
Ok(signature) => {
|
||||
let public = sp_core::sr25519::Public::from_slice(self.account.as_ref());
|
||||
signature.verify(event_id, &public)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_participants_parallel(event_id: &[u8], participants: &[EnlistedParticipant]) -> bool {
|
||||
fn spawn_verify(data: Vec<u8>) -> Vec<u8> {
|
||||
let stream = &mut &data[..];
|
||||
let event_id = Vec::<u8>::decode(stream).expect("Failed to decode");
|
||||
let participants = Vec::<EnlistedParticipant>::decode(stream).expect("Failed to decode");
|
||||
|
||||
for participant in participants {
|
||||
if !participant.verify(&event_id) {
|
||||
return false.encode()
|
||||
}
|
||||
}
|
||||
true.encode()
|
||||
}
|
||||
|
||||
let mut async_payload = Vec::new();
|
||||
event_id.encode_to(&mut async_payload);
|
||||
participants[..participants.len() / 2].encode_to(&mut async_payload);
|
||||
|
||||
let handle = sp_tasks::spawn(spawn_verify, async_payload);
|
||||
let mut result = true;
|
||||
|
||||
for participant in &participants[participants.len() / 2 + 1..] {
|
||||
if !participant.verify(event_id) {
|
||||
result = false;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
bool::decode(&mut &handle.join()[..]).expect("Failed to decode result") && result
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2021 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.
|
||||
|
||||
use crate::{self as pallet_example_parallel, *};
|
||||
|
||||
use frame_support::parameter_types;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Example: pallet_example_parallel::{Pallet, Call, Storage},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
type PalletInfo = PalletInfo;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = sp_core::sr25519::Public;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type DbWeight = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type Version = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const GracePeriod: u64 = 5;
|
||||
pub const UnsignedInterval: u64 = 128;
|
||||
pub const UnsignedPriority: u64 = 1 << 20;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type Call = Call;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_enlist() {
|
||||
use sp_core::Pair;
|
||||
|
||||
sp_io::TestExternalities::default().execute_with(|| {
|
||||
let (pair1, _) = sp_core::sr25519::Pair::generate();
|
||||
let (pair2, _) = sp_core::sr25519::Pair::generate();
|
||||
|
||||
let event_name = b"test";
|
||||
|
||||
Example::run_event(Origin::signed(Default::default()), event_name.to_vec())
|
||||
.expect("Failed to enlist");
|
||||
|
||||
let participants = vec![
|
||||
EnlistedParticipant {
|
||||
account: pair1.public().to_vec(),
|
||||
signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(),
|
||||
},
|
||||
EnlistedParticipant {
|
||||
account: pair2.public().to_vec(),
|
||||
signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(),
|
||||
},
|
||||
];
|
||||
|
||||
Example::enlist_participants(Origin::signed(Default::default()), participants)
|
||||
.expect("Failed to enlist");
|
||||
|
||||
assert_eq!(Example::participants().len(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_wrong_will_not_enlist_anyone() {
|
||||
use sp_core::Pair;
|
||||
|
||||
sp_io::TestExternalities::default().execute_with(|| {
|
||||
let (pair1, _) = sp_core::sr25519::Pair::generate();
|
||||
let (pair2, _) = sp_core::sr25519::Pair::generate();
|
||||
let (pair3, _) = sp_core::sr25519::Pair::generate();
|
||||
|
||||
let event_name = b"test";
|
||||
|
||||
Example::run_event(Origin::signed(Default::default()), event_name.to_vec())
|
||||
.expect("Failed to enlist");
|
||||
|
||||
let participants = vec![
|
||||
EnlistedParticipant {
|
||||
account: pair1.public().to_vec(),
|
||||
signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(),
|
||||
},
|
||||
EnlistedParticipant {
|
||||
account: pair2.public().to_vec(),
|
||||
signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(),
|
||||
},
|
||||
// signing wrong event
|
||||
EnlistedParticipant {
|
||||
account: pair3.public().to_vec(),
|
||||
signature: AsRef::<[u8]>::as_ref(&pair3.sign(&[])).to_vec(),
|
||||
},
|
||||
];
|
||||
|
||||
Example::enlist_participants(Origin::signed(Default::default()), participants)
|
||||
.expect("Failed to enlist");
|
||||
|
||||
assert_eq!(Example::participants().len(), 0);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user