├── staging ├── ink │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── pallets │ ├── src │ │ ├── lib.rs │ │ ├── currency.rs │ │ └── staking.rs │ └── Cargo.toml ├── node │ ├── build.rs │ ├── src │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── cli.rs │ │ ├── chain_spec.rs │ │ ├── rpc.rs │ │ ├── command.rs │ │ └── service.rs │ └── Cargo.toml └── runtime │ ├── build.rs │ ├── Cargo.toml │ └── src │ └── lib.rs ├── .gitignore ├── tutorial ├── step-0 │ ├── pallets │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── currency.rs │ │ └── Cargo.toml │ └── README.md ├── step-1 │ ├── pallets │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── currency.rs │ │ └── Cargo.toml │ └── README.md ├── step-2 │ ├── pallets │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── currency.rs │ │ └── Cargo.toml │ └── README.md ├── step-3 │ ├── pallets │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── currency.rs │ │ └── Cargo.toml │ ├── runtime │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── README.md ├── step-4 │ ├── pallets │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── currency.rs │ │ └── Cargo.toml │ ├── runtime │ │ ├── build.rs │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── README.md ├── step-5 │ ├── pallets │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── currency.rs │ │ └── Cargo.toml │ ├── runtime │ │ ├── build.rs │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── README.md ├── node │ ├── src │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── chain_spec.rs │ │ ├── cli.rs │ │ ├── rpc.rs │ │ ├── command.rs │ │ └── service.rs │ ├── build.rs │ └── Cargo.toml └── step-6 │ ├── pallets │ ├── src │ │ ├── lib.rs │ │ ├── currency.rs │ │ └── staking.rs │ └── Cargo.toml │ ├── runtime │ ├── build.rs │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── README.md ├── inspect_step.sh ├── rustfmt.toml ├── ensure_node_compatible.sh ├── README.md └── SCRIPT.md /staging/ink/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | .vscode 4 | .cargo 5 | -------------------------------------------------------------------------------- /tutorial/step-0/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod currency; 2 | -------------------------------------------------------------------------------- /tutorial/step-1/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod currency; 2 | -------------------------------------------------------------------------------- /tutorial/step-2/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod currency; 2 | -------------------------------------------------------------------------------- /tutorial/step-3/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod currency; 2 | -------------------------------------------------------------------------------- /inspect_step.sh: -------------------------------------------------------------------------------- 1 | NOW=$1 2 | PREV=$[NOW - 1] 3 | delta tutorial/step-$PREV tutorial/step-$NOW 4 | -------------------------------------------------------------------------------- /tutorial/step-4/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod currency; 4 | -------------------------------------------------------------------------------- /tutorial/step-5/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod currency; 4 | -------------------------------------------------------------------------------- /tutorial/node/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod chain_spec; 2 | pub(crate) mod cli; 3 | pub mod rpc; 4 | pub mod service; 5 | -------------------------------------------------------------------------------- /staging/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod currency; 4 | pub mod staking; 5 | -------------------------------------------------------------------------------- /tutorial/step-6/pallets/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod currency; 4 | pub mod staking; 5 | -------------------------------------------------------------------------------- /staging/node/build.rs: -------------------------------------------------------------------------------- 1 | use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; 2 | 3 | fn main() { 4 | generate_cargo_keys(); 5 | rerun_if_git_head_changed(); 6 | } 7 | -------------------------------------------------------------------------------- /tutorial/node/build.rs: -------------------------------------------------------------------------------- 1 | use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; 2 | 3 | fn main() { 4 | generate_cargo_keys(); 5 | rerun_if_git_head_changed(); 6 | } 7 | -------------------------------------------------------------------------------- /staging/ink/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "staging-ink" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /staging/runtime/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(feature = "std")] 3 | { 4 | substrate_wasm_builder::WasmBuilder::new() 5 | .with_current_project() 6 | .export_heap_base() 7 | .import_memory() 8 | .build(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tutorial/step-4/runtime/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(feature = "std")] 3 | { 4 | substrate_wasm_builder::WasmBuilder::new() 5 | .with_current_project() 6 | .export_heap_base() 7 | .import_memory() 8 | .build(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tutorial/step-5/runtime/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(feature = "std")] 3 | { 4 | substrate_wasm_builder::WasmBuilder::new() 5 | .with_current_project() 6 | .export_heap_base() 7 | .import_memory() 8 | .build(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tutorial/step-6/runtime/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(feature = "std")] 3 | { 4 | substrate_wasm_builder::WasmBuilder::new() 5 | .with_current_project() 6 | .export_heap_base() 7 | .import_memory() 8 | .build(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tutorial/node/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Substrate Node Template CLI library. 2 | #![warn(missing_docs)] 3 | 4 | mod chain_spec; 5 | #[macro_use] 6 | mod service; 7 | mod cli; 8 | mod command; 9 | mod rpc; 10 | 11 | fn main() -> sc_cli::Result<()> { 12 | command::run() 13 | } 14 | -------------------------------------------------------------------------------- /tutorial/step-0/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | #[pallet::config] 8 | pub trait Config: frame_system::Config {} 9 | 10 | #[pallet::pallet] 11 | pub struct Pallet(_); 12 | } 13 | -------------------------------------------------------------------------------- /tutorial/step-0/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-0-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0" } 11 | scale-info = { version = "2.6.0" } 12 | -------------------------------------------------------------------------------- /tutorial/step-1/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-1-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0" } 11 | scale-info = { version = "2.6.0" } 12 | -------------------------------------------------------------------------------- /tutorial/step-2/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-2-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0" } 11 | scale-info = { version = "2.6.0" } 12 | -------------------------------------------------------------------------------- /tutorial/step-3/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-3-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0" } 11 | scale-info = { version = "2.6.0" } 12 | -------------------------------------------------------------------------------- /staging/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "staging-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | parity-scale-codec = { version = "3.0.0", default-features = false } 9 | scale-info = { version = "2.6.0", default-features = false } 10 | 11 | frame = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false, features = ["runtime"] } 12 | 13 | [features] 14 | default = ["std"] 15 | std = [ 16 | "parity-scale-codec/std", 17 | "scale-info/std", 18 | 19 | "frame/std", 20 | ] 21 | -------------------------------------------------------------------------------- /tutorial/step-4/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-4-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0", default-features = false } 11 | scale-info = { version = "2.6.0", default-features = false } 12 | 13 | [features] 14 | default = ["std"] 15 | std = [ 16 | "frame/std", 17 | 18 | "parity-scale-codec/std", 19 | "scale-info/std", 20 | ] 21 | -------------------------------------------------------------------------------- /tutorial/step-5/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-5-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0", default-features = false } 11 | scale-info = { version = "2.6.0", default-features = false } 12 | 13 | [features] 14 | default = ["std"] 15 | std = [ 16 | "frame/std", 17 | 18 | "parity-scale-codec/std", 19 | "scale-info/std", 20 | ] 21 | -------------------------------------------------------------------------------- /tutorial/step-6/pallets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-6-pallets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 8 | 9 | # TODO https://github.com/paritytech/substrate/issues/14127 10 | parity-scale-codec = { version = "3.0.0", default-features = false } 11 | scale-info = { version = "2.6.0", default-features = false } 12 | 13 | [features] 14 | default = ["std"] 15 | std = [ 16 | "frame/std", 17 | 18 | "parity-scale-codec/std", 19 | "scale-info/std", 20 | ] 21 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Basic 2 | hard_tabs = true 3 | max_width = 100 4 | use_small_heuristics = "Max" 5 | # Imports 6 | imports_granularity = "Crate" 7 | reorder_imports = true 8 | # Consistency 9 | newline_style = "Unix" 10 | # Format comments 11 | comment_width = 100 12 | wrap_comments = true 13 | # Misc 14 | chain_width = 80 15 | spaces_around_ranges = false 16 | binop_separator = "Back" 17 | reorder_impl_items = false 18 | match_arm_leading_pipes = "Preserve" 19 | match_arm_blocks = false 20 | match_block_trailing_comma = true 21 | trailing_comma = "Vertical" 22 | trailing_semicolon = false 23 | use_field_init_shorthand = true 24 | edition = "2021" 25 | -------------------------------------------------------------------------------- /tutorial/step-3/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-3-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", features = ["runtime"] } 8 | pallets = { package = "step-3-pallets", path = "../pallets" } 9 | 10 | # TODO: https://github.com/paritytech/substrate/issues/14145 11 | sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 12 | 13 | # TODO: https://github.com/paritytech/substrate/issues/14127 14 | parity-scale-codec = { version = "3.0.0" } 15 | scale-info = { version = "2.6.0" } 16 | 17 | [features] 18 | default = ["std"] 19 | std = [ 20 | "sp-api/std" 21 | ] 22 | -------------------------------------------------------------------------------- /staging/node/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | pub mod chain_spec; 19 | pub(crate) mod cli; 20 | pub mod rpc; 21 | pub mod service; 22 | -------------------------------------------------------------------------------- /staging/node/src/main.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! Substrate Node Template CLI library. 19 | #![warn(missing_docs)] 20 | 21 | mod chain_spec; 22 | #[macro_use] 23 | mod service; 24 | mod cli; 25 | mod command; 26 | mod rpc; 27 | 28 | fn main() -> sc_cli::Result<()> { 29 | command::run() 30 | } 31 | -------------------------------------------------------------------------------- /tutorial/node/src/chain_spec.rs: -------------------------------------------------------------------------------- 1 | use runtime::{RuntimeGenesisConfig, SystemConfig, WASM_BINARY}; 2 | use sc_service::{ChainType, Properties}; 3 | 4 | pub type ChainSpec = sc_service::GenericChainSpec; 5 | 6 | fn props() -> Properties { 7 | let mut properties = Properties::new(); 8 | properties.insert("tokenDecimals".to_string(), 0.into()); 9 | properties.insert("tokenSymbol".to_string(), "UNIT".into()); 10 | properties 11 | } 12 | 13 | pub fn development_config() -> Result { 14 | let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; 15 | Ok(ChainSpec::from_genesis( 16 | "Polkadot-SDK-Tutorial-Development", 17 | "pst-dev", 18 | ChainType::Development, 19 | move || testnet_genesis(wasm_binary), 20 | vec![], 21 | None, 22 | None, 23 | None, 24 | Some(props()), 25 | None, 26 | )) 27 | } 28 | 29 | /// Configure initial storage state for FRAME modules. 30 | fn testnet_genesis(wasm_binary: &[u8]) -> RuntimeGenesisConfig { 31 | RuntimeGenesisConfig { 32 | system: SystemConfig { code: wasm_binary.to_vec() }, 33 | ..Default::default() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/step-4/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-4-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false, features = ["runtime"] } 8 | pallets = { package = "step-4-pallets", path = "../pallets", default-features = false } 9 | 10 | # TODO: https://github.com/paritytech/substrate/issues/14145 11 | sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 12 | 13 | # TODO: https://github.com/paritytech/substrate/issues/14127 14 | parity-scale-codec = { version = "3.0.0", default-features = false } 15 | scale-info = { version = "2.6.0", default-features = false } 16 | 17 | [build-dependencies] 18 | substrate-wasm-builder = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", optional = true } 19 | 20 | [features] 21 | default = ["std"] 22 | std = [ 23 | "frame/std", 24 | "pallets/std", 25 | 26 | "sp-api/std", 27 | 28 | "parity-scale-codec/std", 29 | "scale-info/std", 30 | 31 | "substrate-wasm-builder", 32 | ] 33 | 34 | 35 | -------------------------------------------------------------------------------- /tutorial/step-6/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-6-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false, features = ["runtime"] } 8 | pallets = { package = "step-6-pallets", path = "../pallets", default-features = false } 9 | 10 | # TODO: https://github.com/paritytech/substrate/issues/14145 11 | sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 12 | 13 | # TODO: https://github.com/paritytech/substrate/issues/14127 14 | parity-scale-codec = { version = "3.0.0", default-features = false } 15 | scale-info = { version = "2.6.0", default-features = false } 16 | 17 | [build-dependencies] 18 | substrate-wasm-builder = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", optional = true } 19 | 20 | [features] 21 | default = ["std"] 22 | std = [ 23 | "frame/std", 24 | "pallets/std", 25 | 26 | "sp-api/std", 27 | 28 | "parity-scale-codec/std", 29 | "scale-info/std", 30 | 31 | "substrate-wasm-builder", 32 | ] 33 | 34 | 35 | -------------------------------------------------------------------------------- /tutorial/step-1/README.md: -------------------------------------------------------------------------------- 1 | # Step 1 2 | 3 | In this step we will build a very very simple currency pallet, that can mint and trasnfer tokens. 4 | 5 | - [Step 1](#step-1) 6 | - [State Transition, Background Knowledge](#state-transition-background-knowledge) 7 | - [In the code](#in-the-code) 8 | 9 | 10 | ## State Transition, Background Knowledge 11 | 12 | * Brief intro about what total issuance is, although it is pretty self-explanatory. 13 | * Technically you want `mint` to be permissioned, but for now we don't have the tools, so anyone can mint anything. 14 | 15 | 16 | ## In the code 17 | 18 | * `frame::prelude`, followed by `use super::*`. A very common pattern. 19 | * `#[pallet::storage]` declares a type as *state*. For now, we only know 20 | * Mapping. 21 | * Single value. 22 | * `ValueQuery` vs `OptionQuery`. (TODO: imporve RustDocs). 23 | * How to navigate the storage APIs. No need to explain every single one, just point them to the right rust-docs. 24 | * eg. `get`, `put`, `insert` and `mutate` has been used. 25 | 26 | * Where is `T::AccountId` coming from? better spend some time on this now and explain it well. 27 | * As noted in step0, `Err("AnyString".into())` is acceptable for `DispatchResult`. 28 | -------------------------------------------------------------------------------- /tutorial/step-5/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "step-5-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false, features = ["runtime"] } 8 | pallets = { package = "step-5-pallets", path = "../pallets", default-features = false } 9 | 10 | # TODO: https://github.com/paritytech/substrate/issues/14145 11 | sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 12 | 13 | # TODO: https://github.com/paritytech/substrate/issues/14127 14 | frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", default-features = false } 15 | parity-scale-codec = { version = "3.0.0", default-features = false } 16 | scale-info = { version = "2.6.0", default-features = false } 17 | 18 | [build-dependencies] 19 | substrate-wasm-builder = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api", optional = true } 20 | 21 | [features] 22 | default = ["std"] 23 | std = [ 24 | "pallets/std", 25 | "frame/std", 26 | 27 | "sp-api/std", 28 | 29 | "parity-scale-codec/std", 30 | "scale-info/std", 31 | 32 | "substrate-wasm-builder", 33 | ] 34 | 35 | 36 | -------------------------------------------------------------------------------- /tutorial/step-1/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | pub type Balance = u128; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config {} 11 | 12 | /// Mapping from account ID to balance. 13 | #[pallet::storage] 14 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 15 | 16 | /// Sum of all the tokens in existence. 17 | #[pallet::storage] 18 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 19 | 20 | #[pallet::pallet] 21 | pub struct Pallet(_); 22 | 23 | #[pallet::call] 24 | impl Pallet { 25 | /// Mint `amount` new tokens for `to`. 26 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 27 | let _anyone = ensure_signed(origin)?; 28 | 29 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 30 | TotalIssuance::::mutate(|t| *t += amount); 31 | 32 | Ok(()) 33 | } 34 | 35 | /// Transfer exactly `amount` from `origin` to `to`. `origin` must exist, and `to` may not. 36 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 37 | let sender = ensure_signed(origin)?; 38 | 39 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 40 | if sender_balance < amount { 41 | return Err("notEnoughBalance".into()) 42 | } 43 | let reminder = sender_balance - amount; 44 | 45 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 46 | Balances::::insert(&sender, reminder); 47 | 48 | Ok(()) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ensure_node_compatible.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the list of step numbers to ignore 4 | ignore_steps=("3") 5 | 6 | # Iterate over all folders in the 'tutorial' directory 7 | for dir in tutorial/*/ 8 | do 9 | dir=${dir%/} # Remove the trailing slash from the directory name 10 | dir=${dir#tutorial/} # Remove the 'tutorial/' prefix from the directory name 11 | 12 | # If the folder name starts with 'step-' and contains a subfolder named 'runtime' 13 | if [[ $dir == step-* && -d "tutorial/${dir}/runtime" ]] 14 | then 15 | # Extract the step number 16 | step_number=${dir#step-} 17 | 18 | # Check if the step number is in ignore_steps 19 | if ! printf '%s\n' "${ignore_steps[@]}" | grep -q -w "$step_number" 20 | then 21 | # Print a string 22 | echo "Checking $dir" 23 | 24 | # Create the new runtime value 25 | new_runtime_value="runtime = { package = \"${dir}-runtime\", path = \"../${dir}/runtime\" }" 26 | 27 | # Replace the value of 'runtime' in the Cargo.toml file 28 | sed -i '' "s#^runtime = .*#$new_runtime_value#" tutorial/node/Cargo.toml 29 | 30 | if cargo test -p tutorial-node >/dev/null 2>&1; then 31 | echo "✅ cargo test for ${dir} completed successfully" 32 | cargo run -p tutorial-node -- build-spec --dev > tutorial/${dir}/dev.json 2>/dev/null 33 | echo "✅ written chain spec to tutorial/${dir}/dev.json" 34 | else 35 | echo "❌ cargo test for ${dir} completed with errors" 36 | cargo test -p tutorial-node >/dev/null 37 | fi 38 | 39 | fi 40 | fi 41 | done 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED polkadot-sdk-docs 2 | 3 | > This reposityory was moslty used as an issue tracker and playground for what has come to fuitition in `polkadot-sdk` repository as `polkadot-sdk-docs` crate: https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/index.html. 4 | 5 | --- 6 | 7 | A holistic, minimal documentation portal for the Polkadot developers. 8 | 9 | ## Master Tutorial 10 | 11 | The very, very rough plan that I have so far is written in [SCRIPT.md](./SCRIPT.md). Note that this 12 | topic is chosen such that it complements the imagine drawn in `mini_substrate` exercise in the PBA 13 | Rust entrance exam. 14 | 15 | ## Tutorial Structure 16 | 17 | This tutorial is being structured in different folders. That is, each distinct step is one folder in 18 | the [tutorial](./tutorial/) folder. 19 | 20 | The [staging](./staging/) folder is only for the author, and serves as a playground. 21 | 22 | Each step of the tutorial, in its current state, is merely: 23 | 24 | 1. The code required to achieve that step. 25 | 2. A README.md file containing very high level instructions about how to achieve those. 26 | 27 | These markdown files are not the final version of this tutorial and are meant to be raw material 28 | that can be later used to craft: 29 | 30 | 1. a written book 31 | 2. a slide deck 32 | 3. an, indeed, a somewhat interactive tutorial. 33 | 34 | 35 | The best way to check and study each part is to `diff` the two folders. For that, we suggest a 36 | better toll like `delta`: 37 | 38 | ``` 39 | delta tutorial/step-3 tutorial/step-4 40 | ``` 41 | 42 | The `README` file of step `n` should justify all changes that were made in the code of step `n-1` 43 | against step `n`. 44 | -------------------------------------------------------------------------------- /tutorial/node/src/cli.rs: -------------------------------------------------------------------------------- 1 | use sc_cli::RunCmd; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Consensus { 5 | ManualSeal(u64), 6 | InstantSeal, 7 | } 8 | 9 | impl std::str::FromStr for Consensus { 10 | type Err = String; 11 | 12 | fn from_str(s: &str) -> Result { 13 | Ok(if s == "instant-seal" { 14 | Consensus::InstantSeal 15 | } else if let Some(block_time) = s.strip_prefix("manual-seal-") { 16 | Consensus::ManualSeal(block_time.parse().map_err(|_| "invalid block time")?) 17 | } else { 18 | return Err("incorrect consensus identifier".into()) 19 | }) 20 | } 21 | } 22 | 23 | #[derive(Debug, clap::Parser)] 24 | pub struct Cli { 25 | #[command(subcommand)] 26 | pub subcommand: Option, 27 | 28 | #[clap(long, default_value = "manual-seal-3000")] 29 | pub consensus: Consensus, 30 | 31 | #[clap(flatten)] 32 | pub run: RunCmd, 33 | } 34 | 35 | #[derive(Debug, clap::Subcommand)] 36 | pub enum Subcommand { 37 | /// Key management cli utilities 38 | #[command(subcommand)] 39 | Key(sc_cli::KeySubcommand), 40 | 41 | /// Build a chain specification. 42 | BuildSpec(sc_cli::BuildSpecCmd), 43 | 44 | /// Validate blocks. 45 | CheckBlock(sc_cli::CheckBlockCmd), 46 | 47 | /// Export blocks. 48 | ExportBlocks(sc_cli::ExportBlocksCmd), 49 | 50 | /// Export the state of a given block into a chain spec. 51 | ExportState(sc_cli::ExportStateCmd), 52 | 53 | /// Import blocks. 54 | ImportBlocks(sc_cli::ImportBlocksCmd), 55 | 56 | /// Remove the whole chain. 57 | PurgeChain(sc_cli::PurgeChainCmd), 58 | 59 | /// Revert the chain to a previous state. 60 | Revert(sc_cli::RevertCmd), 61 | 62 | /// Db meta columns information. 63 | ChainInfo(sc_cli::ChainInfoCmd), 64 | } 65 | -------------------------------------------------------------------------------- /staging/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "staging-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | parity-scale-codec = { version = "3.0.0", default-features = false } 8 | scale-info = { version = "2.6.0", default-features = false } 9 | 10 | frame = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false, features = ["runtime"] } 11 | staging-pallets = { path = "../pallets", default-features = false } 12 | 13 | # Pallets that we want to use 14 | pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false, features = ["insecure_zero_ed"] } 15 | pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false } 16 | pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false } 17 | pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false } 18 | pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", default-features = false } 19 | 20 | [build-dependencies] 21 | substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api", optional = true } 22 | 23 | [features] 24 | default = ["std"] 25 | std = [ 26 | "parity-scale-codec/std", 27 | "scale-info/std", 28 | 29 | "frame/std", 30 | "staging-pallets/std", 31 | 32 | "pallet-balances/std", 33 | "pallet-sudo/std", 34 | "pallet-timestamp/std", 35 | "pallet-transaction-payment/std", 36 | "pallet-transaction-payment-rpc-runtime-api/std", 37 | 38 | "substrate-wasm-builder", 39 | ] 40 | 41 | 42 | -------------------------------------------------------------------------------- /tutorial/step-3/README.md: -------------------------------------------------------------------------------- 1 | # Step 3 2 | 3 | In this step, we will expand our pallet to live in a basic runtime. We will do this one step at a 4 | time, and initially our runtime is not compiling to wasm. 5 | 6 | - [Step 3](#step-3) 7 | - [State Transition, Background Knowledge](#state-transition-background-knowledge) 8 | - [In the code](#in-the-code) 9 | 10 | ### State Transition, Background Knowledge 11 | 12 | * The main realization of this section is that a runtime is really just what we built in the 13 | previous section for tests. The main differences are: 14 | 15 | * We use some of the types generated by `construct_runtime!` macro, to create `Executive`. 16 | * We use this `Executive` to fulfill a a set of trait implementations, wrapped in 17 | `impl_runtime_apis`. 18 | * This macro will essentially mean that these trait functions are "exported functions" from our wasm 19 | function, such that the client can call into them. 20 | 21 | ### In the code 22 | 23 | - the implementation of `frame_system::Config` for `Runtime` should be simple: 24 | - Most items are the same as before. 25 | - We use a real `RuntimeVersion`. See its docs for more info. 26 | - we use a real account id type given to us by `runtime_types_common`. 27 | - we bring in our sibling `frame-pallets` crate that contain the currency pallet we wrote. 28 | - implement its config as well for runtime. 29 | - to create an `Executive`, we need primitive types like `Block` and such to be defined. Again, 30 | `runtime_types_common` gives us one examples set of these that are reasonable be use. You would be 31 | free to to build custom versions of this if need be. 32 | - Finally, you will see that a number of traits are implemented at the bottom of the file, all 33 | wrapped in `impl_runtime_apis!`. See the reference docs section on client/runtime communication, 34 | and runtime APIs for more info. 35 | -------------------------------------------------------------------------------- /tutorial/step-4/README.md: -------------------------------------------------------------------------------- 1 | # Step 4 2 | 3 | In this step, we will only tweak the existing runtime such that it will compile to WASM as well. We 4 | also combine it with a substrate node template. 5 | 6 | - [Step 4](#step-4) 7 | - [information Points](#information-points) 8 | - [In the code](#in-the-code) 9 | - [Interaction](#interaction) 10 | 11 | ## information Points 12 | 13 | This section is about establishing: 14 | 15 | - what is `feature = std` 16 | - what is `no_std` 17 | - how we make substrate crates, including the `runtime` build in wasm. 18 | 19 | TODO: link to a reference doc page about this. The "FRAME Tips and Tricks in PBA" also covers this. 20 | 21 | ## In the code 22 | 23 | - In the pallets, we now have to update each dependency to have `default-features = false`. This 24 | disables the `std` feature, and enables it only in the newly declared `[feature] std = [...]`. 25 | - We add the `#![cfg_attr(not(feature = "std"), no_std)]` to the entire crate, which reads: "if 26 | feature 'std' is not enabled, build this in a no_std env". 27 | - We apply a similar change in the `runtime` section with deps 28 | - We add a `build.rs` that will build the WASM blob. (no need to know the details of 29 | `substrate_wasm_builder`). 30 | 31 | Next, we will add new features that will allow our pallets to have some initial state, aka genesis 32 | state. Then, we will use this in both our tests, and in the node that we run. 33 | 34 | ## Interaction 35 | 36 | At this point, the learner can add this pallet to the node-template provided that only works with 37 | manual-seal, and run it. 38 | 39 | TODO: the node template needs to be packed nicely for them to use. See: 40 | https://github.com/paritytech/substrate/issues/13951. 41 | 42 | Using 1 node per chapter might be way too hard. 43 | But using just one node is also not feasible. Need something like https://github.com/paritytech/substrate/issues/14268 44 | 45 | -------------------------------------------------------------------------------- /tutorial/node/src/rpc.rs: -------------------------------------------------------------------------------- 1 | //! A collection of node-specific RPC methods. 2 | //! Substrate provides the `sc-rpc` crate, which defines the core RPC layer 3 | //! used by Substrate nodes. This file extends those RPC definitions with 4 | //! capabilities that are specific to this project's runtime configuration. 5 | 6 | #![warn(missing_docs)] 7 | 8 | use jsonrpsee::RpcModule; 9 | use runtime::interface::{AccountId, Index, OpaqueBlock}; 10 | use sc_transaction_pool_api::TransactionPool; 11 | use sp_block_builder::BlockBuilder; 12 | use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; 13 | use std::sync::Arc; 14 | 15 | pub use sc_rpc_api::DenyUnsafe; 16 | 17 | /// Full client dependencies. 18 | pub struct FullDeps { 19 | /// The client instance to use. 20 | pub client: Arc, 21 | /// Transaction pool instance. 22 | pub pool: Arc

, 23 | /// Whether to deny unsafe calls 24 | pub deny_unsafe: DenyUnsafe, 25 | } 26 | 27 | /// Instantiate all full RPC extensions. 28 | pub fn create_full( 29 | deps: FullDeps, 30 | ) -> Result, Box> 31 | where 32 | C: sp_api::ProvideRuntimeApi< 33 | // TODO: bloody hell.. 34 | frame::runtime::runtime_types_generic::Block< 35 | frame::runtime::runtime_types_generic::Header, 36 | frame::runtime::runtime_types_generic::OpaqueExtrinsic, 37 | >, 38 | // OpaqueBlock, 39 | >, 40 | C: HeaderBackend + HeaderMetadata + 'static, 41 | C: Send + Sync + 'static, 42 | C::Api: BlockBuilder, 43 | P: TransactionPool + 'static, 44 | C::Api: substrate_frame_rpc_system::AccountNonceApi, 45 | { 46 | use substrate_frame_rpc_system::{System, SystemApiServer}; 47 | 48 | let mut module = RpcModule::new(()); 49 | let FullDeps { client, pool, deny_unsafe } = deps; 50 | 51 | module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; 52 | 53 | Ok(module) 54 | } 55 | -------------------------------------------------------------------------------- /staging/node/src/cli.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use sc_cli::RunCmd; 19 | 20 | #[derive(Debug, Clone)] 21 | pub enum Consensus { 22 | ManualSeal(u64), 23 | InstantSeal, 24 | } 25 | 26 | impl std::str::FromStr for Consensus { 27 | type Err = String; 28 | 29 | fn from_str(s: &str) -> Result { 30 | Ok(if s == "instant-seal" { 31 | Consensus::InstantSeal 32 | } else if let Some(block_time) = s.strip_prefix("manual-seal-") { 33 | Consensus::ManualSeal(block_time.parse().map_err(|_| "invalid block time")?) 34 | } else { 35 | return Err("incorrect consensus identifier".into()) 36 | }) 37 | } 38 | } 39 | 40 | #[derive(Debug, clap::Parser)] 41 | pub struct Cli { 42 | #[command(subcommand)] 43 | pub subcommand: Option, 44 | 45 | #[clap(long, default_value = "manual-seal-3000")] 46 | pub consensus: Consensus, 47 | 48 | #[clap(flatten)] 49 | pub run: RunCmd, 50 | } 51 | 52 | #[derive(Debug, clap::Subcommand)] 53 | pub enum Subcommand { 54 | /// Key management cli utilities 55 | #[command(subcommand)] 56 | Key(sc_cli::KeySubcommand), 57 | 58 | /// Build a chain specification. 59 | BuildSpec(sc_cli::BuildSpecCmd), 60 | 61 | /// Validate blocks. 62 | CheckBlock(sc_cli::CheckBlockCmd), 63 | 64 | /// Export blocks. 65 | ExportBlocks(sc_cli::ExportBlocksCmd), 66 | 67 | /// Export the state of a given block into a chain spec. 68 | ExportState(sc_cli::ExportStateCmd), 69 | 70 | /// Import blocks. 71 | ImportBlocks(sc_cli::ImportBlocksCmd), 72 | 73 | /// Remove the whole chain. 74 | PurgeChain(sc_cli::PurgeChainCmd), 75 | 76 | /// Revert the chain to a previous state. 77 | Revert(sc_cli::RevertCmd), 78 | 79 | /// Db meta columns information. 80 | ChainInfo(sc_cli::ChainInfoCmd), 81 | } 82 | -------------------------------------------------------------------------------- /staging/node/src/chain_spec.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use runtime::{BalancesConfig, RuntimeGenesisConfig, SudoConfig, SystemConfig, WASM_BINARY}; 19 | use sc_service::{ChainType, Properties}; 20 | use sp_keyring::AccountKeyring; 21 | 22 | /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. 23 | pub type ChainSpec = sc_service::GenericChainSpec; 24 | 25 | fn props() -> Properties { 26 | let mut properties = Properties::new(); 27 | properties.insert("tokenDecimals".to_string(), 0.into()); 28 | properties.insert("tokenSymbol".to_string(), "MINI".into()); 29 | properties 30 | } 31 | 32 | pub fn development_config() -> Result { 33 | let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; 34 | Ok(ChainSpec::from_genesis( 35 | "Development", 36 | "dev", 37 | ChainType::Development, 38 | move || testnet_genesis(wasm_binary), 39 | vec![], 40 | None, 41 | None, 42 | None, 43 | Some(props()), 44 | None, 45 | )) 46 | } 47 | 48 | /// Configure initial storage state for FRAME modules. 49 | fn testnet_genesis(wasm_binary: &[u8]) -> RuntimeGenesisConfig { 50 | use frame::traits::Get; 51 | use runtime::interface::{Balance, MinimumBalance}; 52 | let endowment = >::get().max(1) * 1000; 53 | let balances = AccountKeyring::iter() 54 | .map(|a| (a.to_account_id(), endowment)) 55 | .collect::>(); 56 | RuntimeGenesisConfig { 57 | system: SystemConfig { 58 | // Add Wasm runtime to storage. 59 | code: wasm_binary.to_vec(), 60 | _config: Default::default(), 61 | }, 62 | balances: BalancesConfig { balances }, 63 | sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, 64 | ..Default::default() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /staging/node/src/rpc.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! A collection of node-specific RPC methods. 19 | //! Substrate provides the `sc-rpc` crate, which defines the core RPC layer 20 | //! used by Substrate nodes. This file extends those RPC definitions with 21 | //! capabilities that are specific to this project's runtime configuration. 22 | 23 | #![warn(missing_docs)] 24 | 25 | use jsonrpsee::RpcModule; 26 | use runtime::interface::{AccountId, Nonce, OpaqueBlock}; 27 | use sc_transaction_pool_api::TransactionPool; 28 | use sp_block_builder::BlockBuilder; 29 | use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; 30 | use std::sync::Arc; 31 | 32 | pub use sc_rpc_api::DenyUnsafe; 33 | 34 | /// Full client dependencies. 35 | pub struct FullDeps { 36 | /// The client instance to use. 37 | pub client: Arc, 38 | /// Transaction pool instance. 39 | pub pool: Arc

, 40 | /// Whether to deny unsafe calls 41 | pub deny_unsafe: DenyUnsafe, 42 | } 43 | 44 | /// Instantiate all full RPC extensions. 45 | pub fn create_full( 46 | deps: FullDeps, 47 | ) -> Result, Box> 48 | where 49 | C: sp_api::ProvideRuntimeApi< 50 | // TODO: bloody hell.. 51 | frame::deps::sp_runtime::generic::Block< 52 | frame::deps::sp_runtime::generic::Header, 53 | frame::deps::sp_runtime::OpaqueExtrinsic, 54 | >, 55 | // OpaqueBlock, 56 | >, 57 | C: HeaderBackend + HeaderMetadata + 'static, 58 | C: Send + Sync + 'static, 59 | P: TransactionPool + 'static, 60 | C::Api: BlockBuilder, 61 | C::Api: substrate_frame_rpc_system::AccountNonceApi, 62 | { 63 | use substrate_frame_rpc_system::{System, SystemApiServer}; 64 | 65 | let mut module = RpcModule::new(()); 66 | let FullDeps { client, pool, deny_unsafe } = deps; 67 | 68 | module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; 69 | // NOTE: we have intentionally ignored adding tx-payments's custom RPC here. 70 | 71 | Ok(module) 72 | } 73 | -------------------------------------------------------------------------------- /tutorial/step-5/README.md: -------------------------------------------------------------------------------- 1 | # Step 5 2 | 3 | In this step, we will start using `#[pallet::genesis]` features. 4 | 5 | - [Step 5](#step-5) 6 | - [information Points](#information-points) 7 | - [In the code](#in-the-code) 8 | - [Interaction](#interaction) 9 | 10 | ## information Points 11 | 12 | 13 | ## In the code 14 | 15 | - Let's look at our `test_state()`. Not so pretty. Also, when we launched a node, it had no initial 16 | balance when we ran a node. Can we do better? yes. 17 | - start by adding `#[pallet::genesis_config]`. The fields of this struct are the pieces of 18 | information that your pallet expects to receive from someone (eg. a genesis chain specification). 19 | For our case, we expect to get a list of accounts and balances. 20 | - Then, `#[pallet::genesis_build]` defines what your pallet should do with that. We will insert into 21 | `Balances`, and update `TotalIssuance`. 22 | - This function is called before your chain even launches. So if there is anything wrong, we simply 23 | panic and no need for nicer error handling. 24 | 25 | > Later on you will learn about avoiding panics in the runtime. In general, you should not panic. 26 | 27 | - `GenesisConfig` is required to have a `Default`. We are simply happy with an empty vector for 28 | that, so we derive it. 29 | 30 | > Note that `frame::derive::DefaultNoBound` is simply preventing clashes with ``. If the 31 | > struct hadn't had ``, `derive(Default)` would have been just enough. 32 | 33 | - Then, to using this in our test setup. 34 | - First, we explore the approach where we create the `GenesisConfig` of each pallet, then amalgamate 35 | them in `RuntimeGenesisConfig` (this is generated by `construct_runtime`) and use that as our 36 | initial state. This is demonstrate it `test_state_new`. 37 | - we demonstrate an alternative syntax for that in `test_state_new_2` which is semantically the 38 | same. We leave it up to you to figure out why! 39 | - Finally, we create a builder pattern to do the same. This is a very common practice in Rust and is 40 | well-explained externally. 41 | - Other than being more ergonomic, the benefit of our builder pattern is that it will flexibly 42 | support adding more accounts at genesis. 43 | - We add a new test `ext_builder_works` to demonstrate this. 44 | - Moreover, it allows some additional code to be executed *after* each tests. We add some code that 45 | ensures total issuance is always checked, now we get even more coverage. 46 | - We add a new test `duplicate_genesis_fails` to demonstrate the check we put into place. 47 | - We Migrate all existing tests to use the `ExtBuilder`. 48 | 49 | 50 | ## Interaction 51 | 52 | TODO: Finally, we can add a few initial balances in the cli's chain spec as well. given the unclear 53 | status of the node for now I won't cover this, but the code in staging is demonstrating what needs 54 | to be done. 55 | -------------------------------------------------------------------------------- /tutorial/node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tutorial-node" 3 | version = "4.0.0-dev" 4 | description = "A fresh FRAME-based Substrate node, ready for hacking." 5 | authors = ["Substrate DevHub "] 6 | homepage = "https://substrate.io/" 7 | edition = "2021" 8 | license = "MIT-0" 9 | publish = false 10 | repository = "https://github.com/substrate-developer-hub/substrate-node-template/" 11 | build = "build.rs" 12 | 13 | [package.metadata.docs.rs] 14 | targets = ["x86_64-unknown-linux-gnu"] 15 | 16 | [[bin]] 17 | name = "tutorial-node" 18 | 19 | [dependencies] 20 | clap = { version = "4.0.9", features = ["derive"] } 21 | futures = { version = "0.3.21", features = ["thread-pool"] } 22 | futures-timer = "3.0.1" 23 | jsonrpsee = { version = "0.16.2", features = ["server"] } 24 | 25 | 26 | sc-cli = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 27 | sc-executor = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 28 | sc-network = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 29 | sc-service = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 30 | sc-telemetry = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 31 | sc-transaction-pool = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 32 | sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 33 | sc-consensus = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 34 | sc-consensus-manual-seal = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 35 | sc-rpc-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 36 | sc-basic-authorship = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 37 | 38 | sp-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 39 | sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 40 | sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 41 | sp-blockchain = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 42 | sp-block-builder = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 43 | 44 | substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 45 | 46 | # Local Dependencies 47 | runtime = { package = "step-6-runtime", path = "../step-6/runtime" } 48 | 49 | frame = { git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 50 | 51 | [build-dependencies] 52 | substrate-build-script-utils = { version = "3.0.0", git = "https://github.com/paritytech/substrate.git", branch = "kiz-frame-api" } 53 | 54 | [features] 55 | default = [] 56 | -------------------------------------------------------------------------------- /tutorial/step-6/README.md: -------------------------------------------------------------------------------- 1 | # Step 6 2 | 3 | In this step, we will introduce a dead simple staking pallet, write some basic tests for it, and 4 | integrate it into our runtime. Interactions at the end as per usual. 5 | 6 | - [Step 6](#step-6) 7 | - [information Points](#information-points) 8 | - [Staking System](#staking-system) 9 | - [Pallet Hooks](#pallet-hooks) 10 | - [In the code](#in-the-code) 11 | - [WHAT HAVE WE DONE?](#what-have-we-done) 12 | 13 | ## information Points 14 | 15 | 16 | ### Staking System 17 | 18 | The staking system's spec: 19 | - anyone can call `register(amount)`, which registers them as "wanna-be validator' with `amount` as 20 | their approval-stake. - There's no means to unregister yourself for now. 21 | - anyone can call `delegate(who, amount)`, which increases the approval-stake of `who`. 22 | - every x blocks, we want to get the top `x` wanna-be validators based on approval-stake, to the 23 | best of our abilities. We do this `on_initialize`. 24 | - We want to make sure both validator and delegators actually hold the tokens they have, so we 25 | tightly couple ourselves with the currency pallet. 26 | 27 | ### Pallet Hooks 28 | 29 | - demonstrated as one of the top notch features of a pallet against a contract. 30 | - Look into the enhanced documentation of `Hooks` to learn more. 31 | - Some basic diagram would be good. 32 | - `on_initialize` should be explained as the most powerful of the hooks, but also one that requires 33 | the most responsibility. 34 | 35 | > TODO: given `fn poll()` on the horizon already, we should quickly retract talking about `fn 36 | > on_initialize`. 37 | 38 | ## In the code 39 | 40 | - The logic of the pallet calls and intended storage items should be pretty self-explanatory given 41 | the above. 42 | - Notice how `trait Config: system::Config + pallet_currency::Config`. This allows us to directly 43 | tap into the `Pallet` and storage items of the `pallet_currency`. 44 | - We build a `GenesisConfig` very similar to what we just learned in mod 5. 45 | - Look into `ensure!` and how it is syntactic sugar from FRAME. 46 | - In the tests: 47 | - we build a similar `ExtBuilder`. 48 | 49 | 50 | TODO: in the future, we can easily come back to this, change the order of Staking and Currency in 51 | the staking tests and see failures. 52 | 53 | 54 | ## WHAT HAVE WE DONE? 55 | 56 | We have made a fairly complex pallet/runtime artefact thus far. But truth be told, we have cut 57 | corners. Too many of them. Let's talk about them, and go fix them one by one: 58 | 59 | 1. Our pallet design is 100% not scalable. Let us be clear: the type of code you write in this step 60 | is horrible, and you should not attempt to write such garbage in production. Context: 61 | https://forum.polkadot.network/t/tellling-the-story-of-the-game-theory-behind-frame-pallets/2282/12?u=kianenigma 62 | 63 | 2. The currency pallet does not prevent transfer of tokens that are locked. We should add reserves 64 | and. 65 | 66 | And many more, but for now we focus on these two, as they are the most critical ones. 67 | -------------------------------------------------------------------------------- /tutorial/step-0/README.md: -------------------------------------------------------------------------------- 1 | # Step 0 2 | 3 | This is the shell pallet, that will act as the starting code. 4 | 5 | - [Step 0](#step-0) 6 | - [Project setup.](#project-setup) 7 | - [State Transition, Background Knowledge](#state-transition-background-knowledge) 8 | - [In the code](#in-the-code) 9 | 10 | 11 | ## Project setup. 12 | 13 | * in `Cargo.toml`, we are only bringing in `frame` package. This is an all-in-one crate to get your 14 | started with FRAME. The rust docs of this frame should be TRIPLE A. 15 | * At this stage, because you are not building any 'runtimes', your pallet is just like a normal rust 16 | caret that only compiles to native. We will cover this in a later chapter. 17 | * `rustfmt` file is included. Use it. 18 | * Explain broadly speaking that we want to build a currency and staking system, therefore this file 19 | name was chosen. 20 | 21 | ## State Transition, Background Knowledge 22 | 23 | * Some general information about state transition, pallets and runtime should be covered beforehand. 24 | Use this diagram. 25 | 26 | https://mermaid.live/edit#pako:eNqNU1FPwjAQ_itNE8LLJB1sTPZggs4HEsUEDBE7HspWoLHrltKJOPbfbTdBUEy4h7X97rtr79tdAaM0ptCHoWg0CiaY8kHRVCua0KbfFDRXkvBmWTYaoTAUsODpJloRqcBzUAHG7nDEGRVqBq6ubsAIy1woltCZiQnFacg6ny8lyVZgiId0M1ZEGR74toN3rFJJlnRCeE5tXC1HNGMTG9vuEUZFDP5P9Egy-zQ-ZpJGiqUCPIxOPX27quNFX4B-XXpbu6Y2bqOzd-udqdls9-vhKQEO2DojKlodhd4Rzn9OT5ItmTik-ptjgAf6JzHCL1IOnVcOmcLqJ16sHsL6M7tYQoT7nEW07ogXdEZJhG_Tee2fogvkHFTUoNhrWNZwUMG7p7cdGB6Q1h66f9dtuT5h3ku5A4NQQAsmVCaExbr9C0MJYdX4IfT1NqYLknMV6skoNZXkKh1vRQR9JXNqwTyLtf4BI1qnBPoLwtcazYh4TdNkT9JH6BfwA_ptz255bde-Rt220-1dexbcarTntJDnuR2343Qcx_U6pQU_qwSo5VqQxkzr_1hPaDWo5ReGog5x 27 | 28 | Terminology that must be covered: 29 | 30 | * dispatch ~ call. Has arguments that are like the *payload*. 31 | * origin: *sender* of a dispatch. 32 | * state ~ storage ~ storage value ~ storage map 33 | * event, akin to return type of pallet 34 | * `DispatchResult`, and `DispatchError`. The only thing we care about `DispatchError` is that it is 35 | `From<&'static str>`. 36 | 37 | 38 | * Explain broadly that FRAME achieves giving you this set of abstractions via a set of macros. More 39 | documentation, plus the full list of macros should be in the docs of the `frame` crate. 40 | 41 | ## In the code 42 | 43 | * Each pallet is coded as a module ie `mod` in Rust. It actually can be broken down into smaller 44 | pieces (https://github.com/paritytech/substrate/pull/13950), but for the sake of simplicity, assume 45 | each pallet must be a rust module for now. 46 | * `dev_mode` will reduce the complation requirements. Will be covered later. 47 | * A shell pallet only needs a config `trait` and a pallet `struct`. 48 | * The former is, as the name suggest, a way to configure the pallet. We won't need it for 49 | now. 50 | * The latter is the main struct that is mandatory to exist in the pallet, and will implement 51 | all the important functions of the pallet, among dispatchable function. 52 | 53 | ## Setup 54 | 55 | - One should have and use Rust analyzer properly in the remainder of this tutorial. 56 | - One should properly use the rust-docs of the hinted types of FRAME for the remainder of this 57 | tutorial. 58 | -------------------------------------------------------------------------------- /staging/node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "staging-node" 3 | version = "4.0.0-dev" 4 | description = "A fresh FRAME-based Substrate node, ready for hacking." 5 | authors = ["Substrate DevHub "] 6 | homepage = "https://substrate.io/" 7 | edition = "2021" 8 | license = "MIT-0" 9 | publish = false 10 | repository = "https://github.com/substrate-developer-hub/substrate-node-template/" 11 | build = "build.rs" 12 | 13 | [package.metadata.docs.rs] 14 | targets = ["x86_64-unknown-linux-gnu"] 15 | 16 | [[bin]] 17 | name = "staging-node" 18 | 19 | [dependencies] 20 | clap = { version = "4.0.9", features = ["derive"] } 21 | futures = { version = "0.3.21", features = ["thread-pool"] } 22 | futures-timer = "3.0.1" 23 | jsonrpsee = { version = "0.16.2", features = ["server"] } 24 | 25 | sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 26 | sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 27 | sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 28 | sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 29 | sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 30 | sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 31 | sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 32 | sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 33 | sc-consensus-manual-seal = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 34 | sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 35 | sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 36 | sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 37 | sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 38 | sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 39 | sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 40 | sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 41 | sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 42 | sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 43 | sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 44 | 45 | substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 46 | 47 | # Local Dependencies 48 | runtime = { package = "staging-runtime", path = "../runtime" } 49 | frame = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 50 | 51 | [build-dependencies] 52 | substrate-build-script-utils = { version = "3.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", branch = "kiz-frame-api" } 53 | 54 | [features] 55 | default = [] 56 | 57 | -------------------------------------------------------------------------------- /tutorial/step-2/README.md: -------------------------------------------------------------------------------- 1 | # Step 2 2 | 3 | In this step, we will build a very simple test setup for the existing code we have. 4 | 5 | - [Step 2](#step-2) - [information Points](#information-points) 6 | - [State Transition, Background Knowledge](#state-transition-background-knowledge) 7 | - [In the code](#in-the-code) 8 | 9 | 10 | ## State Transition, Background Knowledge 11 | 12 | * at this point, we have to compose a runtime out of our pallet, and the system pallet. This 13 | requires us to understand the relation between the two. 14 | 15 | In written form 16 | 17 | * A runtime is a `struct Runtime` that implements `Config` of *all pallets*. 18 | * `struct Runtime` is in itself generated by `construct_runtime!()` macro, that also needs the list 19 | of all pallets. Details of this macro will be explained later. 20 | 21 | * As an idiom, while importing, re-exporting, and later on listing your pallets in 22 | `construct_runtime`, always use a pattern like this: 23 | 24 | ``` 25 | // point to `mod pallet` and import it as `_pallet`; 26 | use crate::currency::pallet as pallet_currency; 27 | ``` 28 | 29 | 30 | 31 | ## In the code 32 | 33 | * First, we implement `frame_system::Config for Runtime`. Most of the implementation is being 34 | automatically derived from `derive_impl`, which brings in a sensible set of configs for testing. 35 | Some of the types are dependent on the runtime, so they cannot have defaults, such as `Runtime*`. 36 | We will learn more about this later. 37 | 38 | * `frame_system::config_preludes::TestDefaultConfig` defined `type AccountId = u64`. So this is our 39 | account id type. This can be represented as `::AccountId`. 40 | 41 | > This is the power that generics give us in substrate. Imagine how hard testing would have been, if 42 | > you had to use a real account id for testing. 43 | 44 | * impl `pallet::Config` for `Runtime` is empty, because our pallet has no configurations. 45 | 46 | * Any code in substrate accessing storage must be wrapped in `TestState`'s `execute_with` method. 47 | So, we make a helper function to create a `TestState` with some initial data. 48 | 49 | * Notice how what used to be `TotalIssuance::::get()` in the actual pallet code is now 50 | `TotalIssuance::::get()`? Anywhere that we used to say `` within the pallet, 51 | we can now replace it with ``. Recall: 52 | 53 | > A runtime is a `struct Runtime` that implements `Config` of *all pallets*. 54 | 55 | * Tests should be self-explanatory. We cover a success-path of transfer and mint, and check that the 56 | storages have been updated properly. 57 | 58 | * some suggested guide lines about writing tests, presented as extra detail: 59 | * We suggest writing one test for each success-path of a dispatchable, and one for each 60 | failure-path. In this step, we are not fully covering that yet. 61 | * For each tests, try and maintain a "given, when, then" mental model. 62 | * In that spirit, it helps to have a helper method (or a Builder pattern, if you feel fancy) to 63 | create the initial state, and have a test called `basic_setup` or similar that ensures the 64 | initial state of the pallet is correct. 65 | 66 | As a next step, you should write a test for the case of sending more balance that one has (eg. Alice 67 | transferring 200) yourself. 68 | 69 | In general, this was our first baby-step in "interacting" with the pallet that we have built. Next 70 | we will continue on the same path, and before writing any further logic: 71 | 72 | 1. integrate this pallet into a real runtime ("which will look fairly similar to the test setup we 73 | had here"). 74 | 75 | 2. interact with it using: 76 | * PJS-Apps 77 | * PJS-API/CAPI 78 | * SubXT 79 | -------------------------------------------------------------------------------- /tutorial/node/src/command.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | chain_spec, 3 | cli::{Cli, Subcommand}, 4 | service, 5 | }; 6 | use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; 7 | use sc_service::PartialComponents; 8 | 9 | #[cfg(feature = "try-runtime")] 10 | use try_runtime_cli::block_building_info::timestamp_with_aura_info; 11 | 12 | impl SubstrateCli for Cli { 13 | fn impl_name() -> String { 14 | "Substrate Node".into() 15 | } 16 | 17 | fn impl_version() -> String { 18 | env!("SUBSTRATE_CLI_IMPL_VERSION").into() 19 | } 20 | 21 | fn description() -> String { 22 | env!("CARGO_PKG_DESCRIPTION").into() 23 | } 24 | 25 | fn author() -> String { 26 | env!("CARGO_PKG_AUTHORS").into() 27 | } 28 | 29 | fn support_url() -> String { 30 | "support.anonymous.an".into() 31 | } 32 | 33 | fn copyright_start_year() -> i32 { 34 | 2017 35 | } 36 | 37 | fn load_spec(&self, id: &str) -> Result, String> { 38 | Ok(match id { 39 | "dev" | "" => Box::new(chain_spec::development_config()?), 40 | path => 41 | Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), 42 | }) 43 | } 44 | 45 | fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { 46 | &runtime::VERSION 47 | } 48 | } 49 | 50 | /// Parse and run command line arguments 51 | pub fn run() -> sc_cli::Result<()> { 52 | let cli = Cli::from_args(); 53 | 54 | match &cli.subcommand { 55 | Some(Subcommand::Key(cmd)) => cmd.run(&cli), 56 | Some(Subcommand::BuildSpec(cmd)) => { 57 | let runner = cli.create_runner(cmd)?; 58 | runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) 59 | }, 60 | Some(Subcommand::CheckBlock(cmd)) => { 61 | let runner = cli.create_runner(cmd)?; 62 | runner.async_run(|config| { 63 | let PartialComponents { client, task_manager, import_queue, .. } = 64 | service::new_partial(&config)?; 65 | Ok((cmd.run(client, import_queue), task_manager)) 66 | }) 67 | }, 68 | Some(Subcommand::ExportBlocks(cmd)) => { 69 | let runner = cli.create_runner(cmd)?; 70 | runner.async_run(|config| { 71 | let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; 72 | Ok((cmd.run(client, config.database), task_manager)) 73 | }) 74 | }, 75 | Some(Subcommand::ExportState(cmd)) => { 76 | let runner = cli.create_runner(cmd)?; 77 | runner.async_run(|config| { 78 | let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; 79 | Ok((cmd.run(client, config.chain_spec), task_manager)) 80 | }) 81 | }, 82 | Some(Subcommand::ImportBlocks(cmd)) => { 83 | let runner = cli.create_runner(cmd)?; 84 | runner.async_run(|config| { 85 | let PartialComponents { client, task_manager, import_queue, .. } = 86 | service::new_partial(&config)?; 87 | Ok((cmd.run(client, import_queue), task_manager)) 88 | }) 89 | }, 90 | Some(Subcommand::PurgeChain(cmd)) => { 91 | let runner = cli.create_runner(cmd)?; 92 | runner.sync_run(|config| cmd.run(config.database)) 93 | }, 94 | Some(Subcommand::Revert(cmd)) => { 95 | let runner = cli.create_runner(cmd)?; 96 | runner.async_run(|config| { 97 | let PartialComponents { client, task_manager, backend, .. } = 98 | service::new_partial(&config)?; 99 | Ok((cmd.run(client, backend, None), task_manager)) 100 | }) 101 | }, 102 | Some(Subcommand::ChainInfo(cmd)) => { 103 | let runner = cli.create_runner(cmd)?; 104 | runner.sync_run(|config| cmd.run::(&config)) 105 | }, 106 | None => { 107 | let runner = cli.create_runner(&cli.run)?; 108 | runner.run_node_until_exit(|config| async move { 109 | service::new_full(config, cli.consensus).map_err(sc_cli::Error::Service) 110 | }) 111 | }, 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /staging/node/src/command.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use crate::{ 19 | chain_spec, 20 | cli::{Cli, Subcommand}, 21 | service, 22 | }; 23 | use sc_cli::SubstrateCli; 24 | use sc_service::PartialComponents; 25 | 26 | #[cfg(feature = "try-runtime")] 27 | use try_runtime_cli::block_building_info::timestamp_with_aura_info; 28 | 29 | impl SubstrateCli for Cli { 30 | fn impl_name() -> String { 31 | "Substrate Node".into() 32 | } 33 | 34 | fn impl_version() -> String { 35 | env!("SUBSTRATE_CLI_IMPL_VERSION").into() 36 | } 37 | 38 | fn description() -> String { 39 | env!("CARGO_PKG_DESCRIPTION").into() 40 | } 41 | 42 | fn author() -> String { 43 | env!("CARGO_PKG_AUTHORS").into() 44 | } 45 | 46 | fn support_url() -> String { 47 | "support.anonymous.an".into() 48 | } 49 | 50 | fn copyright_start_year() -> i32 { 51 | 2017 52 | } 53 | 54 | fn load_spec(&self, id: &str) -> Result, String> { 55 | Ok(match id { 56 | "dev" => Box::new(chain_spec::development_config()?), 57 | path => 58 | Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), 59 | }) 60 | } 61 | } 62 | 63 | /// Parse and run command line arguments 64 | pub fn run() -> sc_cli::Result<()> { 65 | let cli = Cli::from_args(); 66 | 67 | match &cli.subcommand { 68 | Some(Subcommand::Key(cmd)) => cmd.run(&cli), 69 | Some(Subcommand::BuildSpec(cmd)) => { 70 | let runner = cli.create_runner(cmd)?; 71 | runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) 72 | }, 73 | Some(Subcommand::CheckBlock(cmd)) => { 74 | let runner = cli.create_runner(cmd)?; 75 | runner.async_run(|config| { 76 | let PartialComponents { client, task_manager, import_queue, .. } = 77 | service::new_partial(&config)?; 78 | Ok((cmd.run(client, import_queue), task_manager)) 79 | }) 80 | }, 81 | Some(Subcommand::ExportBlocks(cmd)) => { 82 | let runner = cli.create_runner(cmd)?; 83 | runner.async_run(|config| { 84 | let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; 85 | Ok((cmd.run(client, config.database), task_manager)) 86 | }) 87 | }, 88 | Some(Subcommand::ExportState(cmd)) => { 89 | let runner = cli.create_runner(cmd)?; 90 | runner.async_run(|config| { 91 | let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; 92 | Ok((cmd.run(client, config.chain_spec), task_manager)) 93 | }) 94 | }, 95 | Some(Subcommand::ImportBlocks(cmd)) => { 96 | let runner = cli.create_runner(cmd)?; 97 | runner.async_run(|config| { 98 | let PartialComponents { client, task_manager, import_queue, .. } = 99 | service::new_partial(&config)?; 100 | Ok((cmd.run(client, import_queue), task_manager)) 101 | }) 102 | }, 103 | Some(Subcommand::PurgeChain(cmd)) => { 104 | let runner = cli.create_runner(cmd)?; 105 | runner.sync_run(|config| cmd.run(config.database)) 106 | }, 107 | Some(Subcommand::Revert(cmd)) => { 108 | let runner = cli.create_runner(cmd)?; 109 | runner.async_run(|config| { 110 | let PartialComponents { client, task_manager, backend, .. } = 111 | service::new_partial(&config)?; 112 | Ok((cmd.run(client, backend, None), task_manager)) 113 | }) 114 | }, 115 | Some(Subcommand::ChainInfo(cmd)) => { 116 | let runner = cli.create_runner(cmd)?; 117 | runner.sync_run(|config| cmd.run::(&config)) 118 | }, 119 | None => { 120 | let runner = cli.create_runner(&cli.run)?; 121 | runner.run_node_until_exit(|config| async move { 122 | service::new_full(config, cli.consensus).map_err(sc_cli::Error::Service) 123 | }) 124 | }, 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tutorial/step-3/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | use frame::{ 2 | prelude::*, 3 | runtime::{prelude::*, runtime_apis}, 4 | }; 5 | use pallets::currency::pallet as pallet_currency; 6 | 7 | #[runtime_version] 8 | pub const VERSION: RuntimeVersion = RuntimeVersion { 9 | spec_name: create_runtime_str!("tutorial-node"), 10 | impl_name: create_runtime_str!(""), 11 | authoring_version: 1, 12 | spec_version: 3, 13 | impl_version: 1, 14 | apis: RUNTIME_API_VERSIONS, 15 | transaction_version: 1, 16 | state_version: 1, 17 | }; 18 | 19 | #[cfg(feature = "std")] 20 | pub fn native_version() -> NativeVersion { 21 | NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } 22 | } 23 | 24 | type SignedExtra = ( 25 | frame_system::CheckNonZeroSender, 26 | frame_system::CheckSpecVersion, 27 | frame_system::CheckTxVersion, 28 | frame_system::CheckGenesis, 29 | frame_system::CheckEra, 30 | frame_system::CheckNonce, 31 | frame_system::CheckWeight, 32 | ); 33 | 34 | construct_runtime!( 35 | pub struct Runtime 36 | where 37 | Block = Block, 38 | NodeBlock = Block, 39 | UncheckedExtrinsic = Extrinsic, 40 | { 41 | System: frame_system, 42 | Currency: pallet_currency, 43 | } 44 | ); 45 | 46 | parameter_types! { 47 | pub const Version: RuntimeVersion = VERSION; 48 | } 49 | 50 | #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] 51 | impl frame_system::Config for Runtime { 52 | type BaseCallFilter = frame_support::traits::Everything; 53 | type RuntimeOrigin = RuntimeOrigin; 54 | type RuntimeCall = RuntimeCall; 55 | type RuntimeEvent = RuntimeEvent; 56 | type PalletInfo = PalletInfo; 57 | type OnSetCode = (); 58 | 59 | type Version = Version; 60 | } 61 | 62 | impl pallet_currency::Config for Runtime {} 63 | 64 | use frame::runtime::runtime_types_common::{self, ExtrinsicOf, HeaderOf}; 65 | 66 | type Block = runtime_types_common::BlockOf; 67 | type Header = runtime_types_common::HeaderOf; 68 | type Extrinsic = runtime_types_common::ExtrinsicOf; 69 | 70 | type RuntimeExecutive = 71 | Executive, Runtime, AllPalletsWithSystem>; 72 | 73 | impl_runtime_apis! { 74 | impl runtime_apis::Core for Runtime { 75 | fn version() -> RuntimeVersion { 76 | VERSION 77 | } 78 | 79 | fn execute_block(block: Block) { 80 | RuntimeExecutive::execute_block(block) 81 | } 82 | 83 | fn initialize_block(header: &Header) { 84 | RuntimeExecutive::initialize_block(header) 85 | } 86 | } 87 | impl runtime_apis::Metadata for Runtime { 88 | fn metadata() -> OpaqueMetadata { 89 | OpaqueMetadata::new(Runtime::metadata().into()) 90 | } 91 | 92 | fn metadata_at_version(version: u32) -> Option { 93 | Runtime::metadata_at_version(version) 94 | } 95 | 96 | fn metadata_versions() -> Vec { 97 | Runtime::metadata_versions() 98 | } 99 | } 100 | 101 | impl runtime_apis::BlockBuilder for Runtime { 102 | fn apply_extrinsic(extrinsic: ExtrinsicOf) -> ApplyExtrinsicResult { 103 | RuntimeExecutive::apply_extrinsic(extrinsic) 104 | } 105 | 106 | fn finalize_block() -> HeaderOf { 107 | RuntimeExecutive::finalize_block() 108 | } 109 | 110 | fn inherent_extrinsics(data: InherentData) -> Vec> { 111 | data.create_extrinsics() 112 | } 113 | 114 | fn check_inherents( 115 | block: Block, 116 | data: InherentData, 117 | ) -> CheckInherentsResult { 118 | data.check_extrinsics(&block) 119 | } 120 | } 121 | 122 | impl runtime_apis::TaggedTransactionQueue for Runtime { 123 | fn validate_transaction( 124 | source: TransactionSource, 125 | tx: ExtrinsicOf, 126 | block_hash: ::Hash, 127 | ) -> TransactionValidity { 128 | RuntimeExecutive::validate_transaction(source, tx, block_hash) 129 | } 130 | } 131 | 132 | impl runtime_apis::OffchainWorkerApi for Runtime { 133 | fn offchain_worker(header: &HeaderOf) { 134 | RuntimeExecutive::offchain_worker(header) 135 | } 136 | } 137 | 138 | impl runtime_apis::SessionKeys for Runtime { 139 | fn generate_session_keys(_seed: Option>) -> Vec { 140 | Default::default() 141 | } 142 | 143 | fn decode_session_keys( 144 | _encoded: Vec, 145 | ) -> Option, runtime_apis::KeyTypeId)>> { 146 | Default::default() 147 | } 148 | } 149 | 150 | impl runtime_apis::AccountNonceApi< 151 | Block, 152 | ::AccountId, 153 | ::Index, 154 | > for Runtime { 155 | fn account_nonce(account: ::AccountId) -> ::Index { 156 | System::account_nonce(account) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /SCRIPT.md: -------------------------------------------------------------------------------- 1 | # Script for the Master Tutorial 2 | 3 | ## Part 0: What are we building? 4 | 5 | - What was a runtime and pallet again? 6 | - What is the anatomy of a pallet? - config, call, pallet, event, error, hook 7 | 8 | ## Part 1: Build a simple balance pallet 9 | 10 | ### Step 1: Simplest pallet 11 | - `type Balances = StorageMap` 12 | - `type TotalIssuance = StorageValue` 13 | - basic call to `transfer` from a to b. 14 | - basic call to mint if anyone is calling 15 | - 100% using `dev_mode`. 16 | 17 | ### Step 2 : Tests 18 | - Adding pallet to a mock runtime. 19 | - basic tests. 20 | - introduction to all interactions 21 | 22 | ### Step 3: Interactions 23 | 24 | #### Adding pallet to the node-template runtime + `#[pallet::genesis_config]` 25 | 26 | #### PJS-Apps 27 | 28 | #### subxt 29 | 30 | #### CAPI/PJS-API 31 | 32 | (Optional) 33 | 34 | > After this, all steps should have an section where we update the tests according to the new steps, 35 | > and interact with the chain using one or more of the tools 36 | 37 | ## Part 2: Build a BROKEN Validator Selection Pallet 38 | 39 | ### Step 1: Basic Staking; use of `on_initialize` 40 | 41 | - anyone can call `register(amount)`, which registers them as "wanna-be validator' with `amount` as 42 | their approval-stake. - There's no means to unregister yourself for now. 43 | - anyone can call `delegate(who, amount)`, which increases the approval-stake of `who`. 44 | - tightly coupled with currency, we check that any of the above two have enough funds - but not 45 | reserving happens. 46 | - every x blocks, we want to get the top `x` wanna-be validators based on approval-stake, to the 47 | best of our abilities. 48 | - write tests for this as well. 49 | 50 | But, before going any further, we should acknowledge that this is broken in different ways. 51 | 52 | * We need to add reserves, and make funds not transferrable. * A test can demonstrate this. 53 | * It is not Sybil resistent + Scalable. 54 | 55 | The full answer should be here: 56 | https://forum.polkadot.network/t/tellling-the-story-of-the-game-theory-behind-frame-pallets/2282/12?u=kianenigma 57 | 58 | ### Step 2 Adding Reserves 59 | 60 | - Adding reserve functionality on the balances side 61 | - Fixing staking to use the reserves. 62 | - Updating tests. 63 | - Adding a reserve high enough for `register`. 64 | - Potentially adding an absolute maximum as well, a good segway into `CountedMap`. 65 | 66 | > This will only fix this using the crypto-economic axis. Once we build a governance system, we will 67 | > also allow the onchain governance to whitelist a set of accounts. 68 | 69 | ### Step 3: Permissionless sort 70 | 71 | Allow anyone to submit a the top x for a good amount of deposit. Reward if success, slash if fail. 72 | 73 | ### Step 4: Blockspace scaling with `on_idle` 74 | 75 | TODO: I want to add another alternative here, where we gradually sort the list, to showcase the edge 76 | that FRAME has over ink! a bit more. 77 | 78 | This can be considered optional. 79 | 80 | ## Part 3: Facelift Session 81 | 82 | > I plan to have multiple "facelift" stop points in the tutorial, where we make progress fast, but 83 | > cut corners, and once we have something tangible, we come back to it. 84 | 85 | At this point, we have something that kinda kinda works. It is time to take a step back, and improve 86 | where we have cut corners. Ideas: 87 | - Start using safe math. 88 | - Start properly using pallet errors. 89 | - Start properly introducing events. 90 | - Update tests to reflect both. `assert_events` as a practice. 91 | - introduce `ensure_root`, us it for mint and other permissioned operations. 92 | - Loosely couple Staking and Currency. - First using a custom fake trait. - Then using 93 | `fungibles::*`. 94 | - Introduce the concept of `ExistentialDeposit` (good one, useful IRL). 95 | - For account storage, use system (mehhh). 96 | - Talk about the choice of `Get<_>` as opposed to storage items for configurations. 97 | 98 | ## Part 4: Benchmarking, Weight, PoV 99 | 100 | Before going much further, let's finish the existing code with benchmarks. 101 | 102 | ## Other Part Ideas: 103 | - build a simple governance system. 104 | - build a system where members of a collective are allowed free remarks onchain at a rate limited 105 | fashion. 106 | - build a sudo pallet. Or, build something that is like council but for validators, such that 3/4 of 107 | validators can dispatch anything. Anything that covers custom origins will be great. 108 | - build a liquid staking system where we issue representative tokens. If a governance pallet exists, 109 | making the liquid tokens usable for governance. 110 | - Still need a good reason to use a double/N map, and do a deep dive on storage. 111 | - At some point, really circle back to the fact that: This code is leveraging `transactional`, 112 | - Adding transaction-payment and configuring it to the runtime. 113 | 114 | ## Appendix: List of all FRAME macros 115 | 116 | Useful to think what is being covered where. 117 | 118 | - [ ] call 119 | - [ ] call_index(_) 120 | - [ ] compact 121 | - [ ] composite_enum 122 | - [ ] config 123 | - [ ] constant 124 | - [ ] disable_frame_system_supertrait_check 125 | - [ ] error 126 | - [ ] event 127 | - [ ] extra_constants 128 | - [ ] generate_deposit 129 | - [ ] generate_storage_info 130 | - [ ] genesis_build 131 | - [ ] genesis_config 132 | - [ ] hooks 133 | - [ ] inherent 134 | - [ ] origin 135 | - [ ] pallet 136 | - [ ] storage 137 | - [ ] storage_prefix 138 | - [ ] type_value 139 | - [ ] unbounded 140 | - [ ] validate_unsigned 141 | - [ ] weight 142 | - [ ] whitelist_storage 143 | - [ ] without_storage_info 144 | -------------------------------------------------------------------------------- /tutorial/step-4/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | // Make the WASM binary available. 4 | #[cfg(feature = "std")] 5 | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); 6 | 7 | use frame::{ 8 | prelude::*, 9 | runtime::{prelude::*, runtime_apis}, 10 | }; 11 | use pallets::currency::pallet as pallet_currency; 12 | 13 | #[runtime_version] 14 | pub const VERSION: RuntimeVersion = RuntimeVersion { 15 | spec_name: create_runtime_str!("tutorial-node"), 16 | impl_name: create_runtime_str!(""), 17 | authoring_version: 1, 18 | spec_version: 4, 19 | impl_version: 1, 20 | apis: RUNTIME_API_VERSIONS, 21 | transaction_version: 1, 22 | state_version: 1, 23 | }; 24 | 25 | #[cfg(feature = "std")] 26 | pub fn native_version() -> NativeVersion { 27 | NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } 28 | } 29 | 30 | type SignedExtra = ( 31 | frame_system::CheckNonZeroSender, 32 | frame_system::CheckSpecVersion, 33 | frame_system::CheckTxVersion, 34 | frame_system::CheckGenesis, 35 | frame_system::CheckEra, 36 | frame_system::CheckNonce, 37 | frame_system::CheckWeight, 38 | ); 39 | 40 | construct_runtime!( 41 | pub struct Runtime 42 | where 43 | Block = Block, 44 | NodeBlock = Block, 45 | UncheckedExtrinsic = Extrinsic, 46 | { 47 | System: frame_system, 48 | Currency: pallet_currency, 49 | } 50 | ); 51 | 52 | parameter_types! { 53 | pub const Version: RuntimeVersion = VERSION; 54 | } 55 | 56 | #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] 57 | impl frame_system::Config for Runtime { 58 | type BaseCallFilter = frame_support::traits::Everything; 59 | type RuntimeOrigin = RuntimeOrigin; 60 | type RuntimeCall = RuntimeCall; 61 | type RuntimeEvent = RuntimeEvent; 62 | type PalletInfo = PalletInfo; 63 | type OnSetCode = (); 64 | 65 | type Version = Version; 66 | } 67 | 68 | impl pallet_currency::Config for Runtime {} 69 | 70 | use frame::runtime::runtime_types_common::{self, ExtrinsicOf, HeaderOf}; 71 | 72 | type Block = runtime_types_common::BlockOf; 73 | type Header = runtime_types_common::HeaderOf; 74 | type Extrinsic = runtime_types_common::ExtrinsicOf; 75 | 76 | type RuntimeExecutive = 77 | Executive, Runtime, AllPalletsWithSystem>; 78 | 79 | impl_runtime_apis! { 80 | impl runtime_apis::Core for Runtime { 81 | fn version() -> RuntimeVersion { 82 | VERSION 83 | } 84 | 85 | fn execute_block(block: Block) { 86 | RuntimeExecutive::execute_block(block) 87 | } 88 | 89 | fn initialize_block(header: &Header) { 90 | RuntimeExecutive::initialize_block(header) 91 | } 92 | } 93 | impl runtime_apis::Metadata for Runtime { 94 | fn metadata() -> OpaqueMetadata { 95 | OpaqueMetadata::new(Runtime::metadata().into()) 96 | } 97 | 98 | fn metadata_at_version(version: u32) -> Option { 99 | Runtime::metadata_at_version(version) 100 | } 101 | 102 | fn metadata_versions() -> Vec { 103 | Runtime::metadata_versions() 104 | } 105 | } 106 | 107 | impl runtime_apis::BlockBuilder for Runtime { 108 | fn apply_extrinsic(extrinsic: ExtrinsicOf) -> ApplyExtrinsicResult { 109 | RuntimeExecutive::apply_extrinsic(extrinsic) 110 | } 111 | 112 | fn finalize_block() -> HeaderOf { 113 | RuntimeExecutive::finalize_block() 114 | } 115 | 116 | fn inherent_extrinsics(data: InherentData) -> Vec> { 117 | data.create_extrinsics() 118 | } 119 | 120 | fn check_inherents( 121 | block: Block, 122 | data: InherentData, 123 | ) -> CheckInherentsResult { 124 | data.check_extrinsics(&block) 125 | } 126 | } 127 | 128 | impl runtime_apis::TaggedTransactionQueue for Runtime { 129 | fn validate_transaction( 130 | source: TransactionSource, 131 | tx: ExtrinsicOf, 132 | block_hash: ::Hash, 133 | ) -> TransactionValidity { 134 | RuntimeExecutive::validate_transaction(source, tx, block_hash) 135 | } 136 | } 137 | 138 | impl runtime_apis::OffchainWorkerApi for Runtime { 139 | fn offchain_worker(header: &HeaderOf) { 140 | RuntimeExecutive::offchain_worker(header) 141 | } 142 | } 143 | 144 | impl runtime_apis::SessionKeys for Runtime { 145 | fn generate_session_keys(_seed: Option>) -> Vec { 146 | Default::default() 147 | } 148 | 149 | fn decode_session_keys( 150 | _encoded: Vec, 151 | ) -> Option, runtime_apis::KeyTypeId)>> { 152 | Default::default() 153 | } 154 | } 155 | 156 | impl runtime_apis::AccountNonceApi< 157 | Block, 158 | ::AccountId, 159 | ::Index, 160 | > for Runtime { 161 | fn account_nonce(account: interface::AccountId) -> interface::Index { 162 | System::account_nonce(account) 163 | } 164 | } 165 | } 166 | 167 | /// Some re-exports that the node side code needs to know. Some are useful in this context as well. 168 | /// 169 | /// Other types should preferably be private. 170 | // TODO: this should be standardized in some way, see: 171 | // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 172 | pub mod interface { 173 | use super::*; 174 | 175 | pub type OpaqueBlock = runtime_types_common::OpaqueBlockOf; 176 | pub type AccountId = ::AccountId; 177 | pub type Index = ::Index; 178 | pub type Hash = ::Hash; 179 | } 180 | -------------------------------------------------------------------------------- /tutorial/step-5/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | // Make the WASM binary available. 4 | #[cfg(feature = "std")] 5 | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); 6 | 7 | use frame::{ 8 | prelude::*, 9 | runtime::{ 10 | prelude::*, 11 | runtime_apis, 12 | runtime_types_common::{self, ExtrinsicOf, HeaderOf}, 13 | }, 14 | }; 15 | use pallets::currency::pallet as pallet_currency; 16 | 17 | #[runtime_version] 18 | pub const VERSION: RuntimeVersion = RuntimeVersion { 19 | spec_name: create_runtime_str!("tutorial-node"), 20 | impl_name: create_runtime_str!(""), 21 | authoring_version: 1, 22 | spec_version: 5, 23 | impl_version: 1, 24 | apis: RUNTIME_API_VERSIONS, 25 | transaction_version: 1, 26 | state_version: 1, 27 | }; 28 | 29 | #[cfg(feature = "std")] 30 | pub fn native_version() -> NativeVersion { 31 | NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } 32 | } 33 | 34 | type SignedExtra = ( 35 | frame_system::CheckNonZeroSender, 36 | frame_system::CheckSpecVersion, 37 | frame_system::CheckTxVersion, 38 | frame_system::CheckGenesis, 39 | frame_system::CheckEra, 40 | frame_system::CheckNonce, 41 | frame_system::CheckWeight, 42 | ); 43 | 44 | construct_runtime!( 45 | pub struct Runtime 46 | where 47 | Block = Block, 48 | NodeBlock = Block, 49 | UncheckedExtrinsic = Extrinsic, 50 | { 51 | System: frame_system, 52 | Currency: pallet_currency, 53 | } 54 | ); 55 | 56 | parameter_types! { 57 | pub const Version: RuntimeVersion = VERSION; 58 | } 59 | 60 | #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] 61 | impl frame_system::Config for Runtime { 62 | type BaseCallFilter = frame_support::traits::Everything; 63 | type RuntimeOrigin = RuntimeOrigin; 64 | type RuntimeCall = RuntimeCall; 65 | type RuntimeEvent = RuntimeEvent; 66 | type PalletInfo = PalletInfo; 67 | type OnSetCode = (); 68 | 69 | type Version = Version; 70 | } 71 | 72 | impl pallet_currency::Config for Runtime {} 73 | 74 | type Block = runtime_types_common::BlockOf; 75 | type Header = runtime_types_common::HeaderOf; 76 | type Extrinsic = runtime_types_common::ExtrinsicOf; 77 | 78 | type RuntimeExecutive = 79 | Executive, Runtime, AllPalletsWithSystem>; 80 | 81 | impl_runtime_apis! { 82 | impl runtime_apis::Core for Runtime { 83 | fn version() -> RuntimeVersion { 84 | VERSION 85 | } 86 | 87 | fn execute_block(block: Block) { 88 | RuntimeExecutive::execute_block(block) 89 | } 90 | 91 | fn initialize_block(header: &Header) { 92 | RuntimeExecutive::initialize_block(header) 93 | } 94 | } 95 | impl runtime_apis::Metadata for Runtime { 96 | fn metadata() -> OpaqueMetadata { 97 | OpaqueMetadata::new(Runtime::metadata().into()) 98 | } 99 | 100 | fn metadata_at_version(version: u32) -> Option { 101 | Runtime::metadata_at_version(version) 102 | } 103 | 104 | fn metadata_versions() -> Vec { 105 | Runtime::metadata_versions() 106 | } 107 | } 108 | 109 | impl runtime_apis::BlockBuilder for Runtime { 110 | fn apply_extrinsic(extrinsic: ExtrinsicOf) -> ApplyExtrinsicResult { 111 | RuntimeExecutive::apply_extrinsic(extrinsic) 112 | } 113 | 114 | fn finalize_block() -> HeaderOf { 115 | RuntimeExecutive::finalize_block() 116 | } 117 | 118 | fn inherent_extrinsics(data: InherentData) -> Vec> { 119 | data.create_extrinsics() 120 | } 121 | 122 | fn check_inherents( 123 | block: Block, 124 | data: InherentData, 125 | ) -> CheckInherentsResult { 126 | data.check_extrinsics(&block) 127 | } 128 | } 129 | 130 | impl runtime_apis::TaggedTransactionQueue for Runtime { 131 | fn validate_transaction( 132 | source: TransactionSource, 133 | tx: ExtrinsicOf, 134 | block_hash: ::Hash, 135 | ) -> TransactionValidity { 136 | RuntimeExecutive::validate_transaction(source, tx, block_hash) 137 | } 138 | } 139 | 140 | impl runtime_apis::OffchainWorkerApi for Runtime { 141 | fn offchain_worker(header: &HeaderOf) { 142 | RuntimeExecutive::offchain_worker(header) 143 | } 144 | } 145 | 146 | impl runtime_apis::SessionKeys for Runtime { 147 | fn generate_session_keys(_seed: Option>) -> Vec { 148 | Default::default() 149 | } 150 | 151 | fn decode_session_keys( 152 | _encoded: Vec, 153 | ) -> Option, runtime_apis::KeyTypeId)>> { 154 | Default::default() 155 | } 156 | } 157 | 158 | impl runtime_apis::AccountNonceApi< 159 | Block, 160 | ::AccountId, 161 | ::Index, 162 | > for Runtime { 163 | fn account_nonce(account: interface::AccountId) -> interface::Index { 164 | System::account_nonce(account) 165 | } 166 | } 167 | } 168 | 169 | /// Some re-exports that the node side code needs to know. Some are useful in this context as well. 170 | /// 171 | /// Other types should preferably be private. 172 | // TODO: this should be standardized in some way, see: 173 | // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 174 | pub mod interface { 175 | use super::*; 176 | 177 | pub type OpaqueBlock = runtime_types_common::OpaqueBlockOf; 178 | pub type AccountId = ::AccountId; 179 | pub type Index = ::Index; 180 | pub type Hash = ::Hash; 181 | } 182 | -------------------------------------------------------------------------------- /tutorial/step-2/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | pub type Balance = u128; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config {} 11 | 12 | #[pallet::storage] 13 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 14 | 15 | #[pallet::storage] 16 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 17 | 18 | #[pallet::pallet] 19 | pub struct Pallet(_); 20 | 21 | #[pallet::call] 22 | impl Pallet { 23 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 24 | let _anyone = ensure_signed(origin)?; 25 | 26 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 27 | TotalIssuance::::mutate(|t| *t += amount); 28 | 29 | Ok(()) 30 | } 31 | 32 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 33 | let sender = ensure_signed(origin)?; 34 | 35 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 36 | if sender_balance < amount { 37 | return Err("notEnoughBalance".into()) 38 | } 39 | let reminder = sender_balance - amount; 40 | 41 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 42 | Balances::::insert(&sender, reminder); 43 | 44 | Ok(()) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::{pallet as pallet_currency, *}; 51 | use frame::testing_prelude::*; 52 | 53 | type Extrinsic = MockUncheckedExtrinsic; 54 | type Block = MockBlock; 55 | 56 | construct_runtime!( 57 | pub struct Runtime 58 | where 59 | Block = Block, 60 | NodeBlock = Block, 61 | UncheckedExtrinsic = Extrinsic, 62 | { 63 | System: frame_system, 64 | Currency: pallet_currency, 65 | } 66 | ); 67 | 68 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 69 | impl frame_system::Config for Runtime { 70 | type RuntimeOrigin = RuntimeOrigin; 71 | type RuntimeCall = RuntimeCall; 72 | type RuntimeEvent = RuntimeEvent; 73 | type PalletInfo = PalletInfo; 74 | type BaseCallFilter = frame::traits::Everything; 75 | type OnSetCode = (); 76 | } 77 | 78 | impl pallet::Config for Runtime {} 79 | 80 | const ALICE: ::AccountId = 1; 81 | const BOB: ::AccountId = 2; 82 | const EVE: ::AccountId = 3; 83 | 84 | fn test_state() -> TestState { 85 | let mut state = TestState::new_empty(); 86 | state.execute_with(|| { 87 | pallet::Balances::::insert(&ALICE, 100); 88 | pallet::Balances::::insert(&BOB, 100); 89 | pallet::TotalIssuance::::put(200); 90 | }); 91 | 92 | state 93 | } 94 | 95 | #[test] 96 | fn initial_state_works() { 97 | test_state().execute_with(|| { 98 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 99 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 100 | assert_eq!(pallet::Balances::::get(&EVE), None); 101 | assert_eq!(pallet::TotalIssuance::::get(), 200); 102 | }); 103 | } 104 | #[test] 105 | fn test_mint() { 106 | test_state().execute_with(|| { 107 | // given the initial state, when: 108 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), BOB, 100)); 109 | 110 | // then: 111 | assert_eq!(pallet::Balances::::get(&BOB), Some(200)); 112 | assert_eq!(pallet::TotalIssuance::::get(), 300); 113 | 114 | // given: 115 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), EVE, 100)); 116 | 117 | // then: 118 | assert_eq!(pallet::Balances::::get(&EVE), Some(100)); 119 | assert_eq!(pallet::TotalIssuance::::get(), 400); 120 | }); 121 | } 122 | 123 | #[test] 124 | fn transfer_works() { 125 | test_state().execute_with(|| { 126 | // given the the initial state, when: 127 | assert_ok!(pallet::Pallet::::transfer( 128 | RuntimeOrigin::signed(ALICE), 129 | BOB, 130 | 50 131 | )); 132 | 133 | // then: 134 | assert_eq!(pallet::Balances::::get(&ALICE), Some(50)); 135 | assert_eq!(pallet::Balances::::get(&BOB), Some(150)); 136 | assert_eq!(pallet::TotalIssuance::::get(), 200); 137 | 138 | // when: 139 | assert_ok!(pallet::Pallet::::transfer( 140 | RuntimeOrigin::signed(BOB), 141 | ALICE, 142 | 50 143 | )); 144 | 145 | // then: 146 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 147 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 148 | assert_eq!(pallet::TotalIssuance::::get(), 200); 149 | }); 150 | } 151 | 152 | #[test] 153 | fn transfer_from_non_existent_fails() { 154 | test_state().execute_with(|| { 155 | // given the the initial state, when: 156 | assert_err!( 157 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 158 | "NonExistentAccount" 159 | ); 160 | 161 | // then nothing has changed. 162 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 163 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 164 | assert_eq!(pallet::Balances::::get(&EVE), None); 165 | assert_eq!(pallet::TotalIssuance::::get(), 200); 166 | 167 | // in fact, this frame-helper ensures that nothing in the state has been updated 168 | // prior and after execution: 169 | assert_noop!( 170 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 171 | "NonExistentAccount" 172 | ); 173 | }); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tutorial/step-3/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | pub type Balance = u128; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config {} 11 | 12 | #[pallet::storage] 13 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 14 | 15 | #[pallet::storage] 16 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 17 | 18 | #[pallet::pallet] 19 | pub struct Pallet(_); 20 | 21 | #[pallet::call] 22 | impl Pallet { 23 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 24 | let _anyone = ensure_signed(origin)?; 25 | 26 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 27 | TotalIssuance::::mutate(|t| *t += amount); 28 | 29 | Ok(()) 30 | } 31 | 32 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 33 | let sender = ensure_signed(origin)?; 34 | 35 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 36 | if sender_balance < amount { 37 | return Err("notEnoughBalance".into()) 38 | } 39 | let reminder = sender_balance - amount; 40 | 41 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 42 | Balances::::insert(&sender, reminder); 43 | 44 | Ok(()) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::{pallet as pallet_currency, *}; 51 | use frame::testing_prelude::*; 52 | 53 | type Extrinsic = MockUncheckedExtrinsic; 54 | type Block = MockBlock; 55 | 56 | construct_runtime!( 57 | pub struct Runtime 58 | where 59 | Block = Block, 60 | NodeBlock = Block, 61 | UncheckedExtrinsic = Extrinsic, 62 | { 63 | System: frame_system, 64 | Currency: pallet_currency, 65 | } 66 | ); 67 | 68 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 69 | impl frame_system::Config for Runtime { 70 | type RuntimeOrigin = RuntimeOrigin; 71 | type RuntimeCall = RuntimeCall; 72 | type RuntimeEvent = RuntimeEvent; 73 | type PalletInfo = PalletInfo; 74 | type BaseCallFilter = frame::traits::Everything; 75 | type OnSetCode = (); 76 | } 77 | 78 | impl pallet::Config for Runtime {} 79 | 80 | const ALICE: ::AccountId = 1; 81 | const BOB: ::AccountId = 2; 82 | const EVE: ::AccountId = 3; 83 | 84 | fn test_state() -> TestState { 85 | let mut state = TestState::new_empty(); 86 | state.execute_with(|| { 87 | pallet::Balances::::insert(&ALICE, 100); 88 | pallet::Balances::::insert(&BOB, 100); 89 | pallet::TotalIssuance::::put(200); 90 | }); 91 | 92 | state 93 | } 94 | 95 | #[test] 96 | fn initial_state_works() { 97 | test_state().execute_with(|| { 98 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 99 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 100 | assert_eq!(pallet::Balances::::get(&EVE), None); 101 | assert_eq!(pallet::TotalIssuance::::get(), 200); 102 | }); 103 | } 104 | #[test] 105 | fn test_mint() { 106 | test_state().execute_with(|| { 107 | // given the initial state, when: 108 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), BOB, 100)); 109 | 110 | // then: 111 | assert_eq!(pallet::Balances::::get(&BOB), Some(200)); 112 | assert_eq!(pallet::TotalIssuance::::get(), 300); 113 | 114 | // given: 115 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), EVE, 100)); 116 | 117 | // then: 118 | assert_eq!(pallet::Balances::::get(&EVE), Some(100)); 119 | assert_eq!(pallet::TotalIssuance::::get(), 400); 120 | }); 121 | } 122 | 123 | #[test] 124 | fn transfer_works() { 125 | test_state().execute_with(|| { 126 | // given the the initial state, when: 127 | assert_ok!(pallet::Pallet::::transfer( 128 | RuntimeOrigin::signed(ALICE), 129 | BOB, 130 | 50 131 | )); 132 | 133 | // then: 134 | assert_eq!(pallet::Balances::::get(&ALICE), Some(50)); 135 | assert_eq!(pallet::Balances::::get(&BOB), Some(150)); 136 | assert_eq!(pallet::TotalIssuance::::get(), 200); 137 | 138 | // when: 139 | assert_ok!(pallet::Pallet::::transfer( 140 | RuntimeOrigin::signed(BOB), 141 | ALICE, 142 | 50 143 | )); 144 | 145 | // then: 146 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 147 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 148 | assert_eq!(pallet::TotalIssuance::::get(), 200); 149 | }); 150 | } 151 | 152 | #[test] 153 | fn transfer_from_non_existent_fails() { 154 | test_state().execute_with(|| { 155 | // given the the initial state, when: 156 | assert_err!( 157 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 158 | "NonExistentAccount" 159 | ); 160 | 161 | // then nothing has changed. 162 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 163 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 164 | assert_eq!(pallet::Balances::::get(&EVE), None); 165 | assert_eq!(pallet::TotalIssuance::::get(), 200); 166 | 167 | // in fact, this frame-helper ensures that nothing in the state has been updated 168 | // prior and after execution: 169 | assert_noop!( 170 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 171 | "NonExistentAccount" 172 | ); 173 | }); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tutorial/step-4/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | pub type Balance = u128; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config {} 11 | 12 | #[pallet::storage] 13 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 14 | 15 | #[pallet::storage] 16 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 17 | 18 | #[pallet::pallet] 19 | pub struct Pallet(_); 20 | 21 | #[pallet::call] 22 | impl Pallet { 23 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 24 | let _anyone = ensure_signed(origin)?; 25 | 26 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 27 | TotalIssuance::::mutate(|t| *t += amount); 28 | 29 | Ok(()) 30 | } 31 | 32 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 33 | let sender = ensure_signed(origin)?; 34 | 35 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 36 | if sender_balance < amount { 37 | return Err("notEnoughBalance".into()) 38 | } 39 | let reminder = sender_balance - amount; 40 | 41 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 42 | Balances::::insert(&sender, reminder); 43 | 44 | Ok(()) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::{pallet as pallet_currency, *}; 51 | use frame::testing_prelude::*; 52 | 53 | type Extrinsic = MockUncheckedExtrinsic; 54 | type Block = MockBlock; 55 | 56 | construct_runtime!( 57 | pub struct Runtime 58 | where 59 | Block = Block, 60 | NodeBlock = Block, 61 | UncheckedExtrinsic = Extrinsic, 62 | { 63 | System: frame_system, 64 | Currency: pallet_currency, 65 | } 66 | ); 67 | 68 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 69 | impl frame_system::Config for Runtime { 70 | type RuntimeOrigin = RuntimeOrigin; 71 | type RuntimeCall = RuntimeCall; 72 | type RuntimeEvent = RuntimeEvent; 73 | type PalletInfo = PalletInfo; 74 | type BaseCallFilter = frame::traits::Everything; 75 | type OnSetCode = (); 76 | } 77 | 78 | impl pallet::Config for Runtime {} 79 | 80 | const ALICE: ::AccountId = 1; 81 | const BOB: ::AccountId = 2; 82 | const EVE: ::AccountId = 3; 83 | 84 | fn test_state() -> TestState { 85 | let mut state = TestState::new_empty(); 86 | state.execute_with(|| { 87 | pallet::Balances::::insert(&ALICE, 100); 88 | pallet::Balances::::insert(&BOB, 100); 89 | pallet::TotalIssuance::::put(200); 90 | }); 91 | 92 | state 93 | } 94 | 95 | #[test] 96 | fn initial_state_works() { 97 | test_state().execute_with(|| { 98 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 99 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 100 | assert_eq!(pallet::Balances::::get(&EVE), None); 101 | assert_eq!(pallet::TotalIssuance::::get(), 200); 102 | }); 103 | } 104 | #[test] 105 | fn test_mint() { 106 | test_state().execute_with(|| { 107 | // given the initial state, when: 108 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), BOB, 100)); 109 | 110 | // then: 111 | assert_eq!(pallet::Balances::::get(&BOB), Some(200)); 112 | assert_eq!(pallet::TotalIssuance::::get(), 300); 113 | 114 | // given: 115 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), EVE, 100)); 116 | 117 | // then: 118 | assert_eq!(pallet::Balances::::get(&EVE), Some(100)); 119 | assert_eq!(pallet::TotalIssuance::::get(), 400); 120 | }); 121 | } 122 | 123 | #[test] 124 | fn transfer_works() { 125 | test_state().execute_with(|| { 126 | // given the the initial state, when: 127 | assert_ok!(pallet::Pallet::::transfer( 128 | RuntimeOrigin::signed(ALICE), 129 | BOB, 130 | 50 131 | )); 132 | 133 | // them: 134 | assert_eq!(pallet::Balances::::get(&ALICE), Some(50)); 135 | assert_eq!(pallet::Balances::::get(&BOB), Some(150)); 136 | assert_eq!(pallet::TotalIssuance::::get(), 200); 137 | 138 | // when: 139 | assert_ok!(pallet::Pallet::::transfer( 140 | RuntimeOrigin::signed(BOB), 141 | ALICE, 142 | 50 143 | )); 144 | 145 | // then: 146 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 147 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 148 | assert_eq!(pallet::TotalIssuance::::get(), 200); 149 | }); 150 | } 151 | 152 | #[test] 153 | fn transfer_from_non_existent_fails() { 154 | test_state().execute_with(|| { 155 | // given the the initial state, when: 156 | assert_err!( 157 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 158 | "NonExistentAccount" 159 | ); 160 | 161 | // then nothing has changed. 162 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 163 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 164 | assert_eq!(pallet::Balances::::get(&EVE), None); 165 | assert_eq!(pallet::TotalIssuance::::get(), 200); 166 | 167 | // in fact, this frame-helper ensures that nothing in the state has been updated 168 | // prior and after execution: 169 | assert_noop!( 170 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 171 | "NonExistentAccount" 172 | ); 173 | }); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tutorial/step-6/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | // Make the WASM binary available. 4 | #[cfg(feature = "std")] 5 | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); 6 | 7 | use frame::{ 8 | prelude::*, 9 | runtime::{ 10 | prelude::*, 11 | runtime_apis, 12 | runtime_types_common::{self, ExtrinsicOf, HeaderOf}, 13 | }, 14 | }; 15 | use pallets::{currency::pallet as pallet_currency, staking::pallet as pallet_staking}; 16 | 17 | #[runtime_version] 18 | pub const VERSION: RuntimeVersion = RuntimeVersion { 19 | spec_name: create_runtime_str!("tutorial-node"), 20 | impl_name: create_runtime_str!(""), 21 | authoring_version: 1, 22 | spec_version: 6, 23 | impl_version: 1, 24 | apis: RUNTIME_API_VERSIONS, 25 | transaction_version: 1, 26 | state_version: 1, 27 | }; 28 | 29 | #[cfg(feature = "std")] 30 | pub fn native_version() -> NativeVersion { 31 | NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } 32 | } 33 | 34 | type SignedExtra = ( 35 | frame_system::CheckNonZeroSender, 36 | frame_system::CheckSpecVersion, 37 | frame_system::CheckTxVersion, 38 | frame_system::CheckGenesis, 39 | frame_system::CheckEra, 40 | frame_system::CheckNonce, 41 | frame_system::CheckWeight, 42 | ); 43 | 44 | construct_runtime!( 45 | pub struct Runtime 46 | where 47 | Block = Block, 48 | NodeBlock = Block, 49 | UncheckedExtrinsic = Extrinsic, 50 | { 51 | System: frame_system, 52 | Currency: pallet_currency, 53 | Staking: pallet_staking, 54 | } 55 | ); 56 | 57 | parameter_types! { 58 | pub const Version: RuntimeVersion = VERSION; 59 | } 60 | 61 | #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] 62 | impl frame_system::Config for Runtime { 63 | type BaseCallFilter = frame_support::traits::Everything; 64 | type RuntimeOrigin = RuntimeOrigin; 65 | type RuntimeCall = RuntimeCall; 66 | type RuntimeEvent = RuntimeEvent; 67 | type PalletInfo = PalletInfo; 68 | type OnSetCode = (); 69 | 70 | type Version = Version; 71 | } 72 | 73 | impl pallet_currency::Config for Runtime {} 74 | 75 | parameter_types! { 76 | pub const EraDuration: ::BlockNumber = 5; 77 | pub const ValidatorCount: u32 = 2; 78 | } 79 | 80 | impl pallet_staking::Config for Runtime { 81 | type EraDuration = EraDuration; 82 | type ValidatorCount = ValidatorCount; 83 | } 84 | 85 | type Block = runtime_types_common::BlockOf; 86 | type Header = runtime_types_common::HeaderOf; 87 | type Extrinsic = runtime_types_common::ExtrinsicOf; 88 | 89 | type RuntimeExecutive = 90 | Executive, Runtime, AllPalletsWithSystem>; 91 | 92 | impl_runtime_apis! { 93 | impl runtime_apis::Core for Runtime { 94 | fn version() -> RuntimeVersion { 95 | VERSION 96 | } 97 | 98 | fn execute_block(block: Block) { 99 | RuntimeExecutive::execute_block(block) 100 | } 101 | 102 | fn initialize_block(header: &Header) { 103 | RuntimeExecutive::initialize_block(header) 104 | } 105 | } 106 | impl runtime_apis::Metadata for Runtime { 107 | fn metadata() -> OpaqueMetadata { 108 | OpaqueMetadata::new(Runtime::metadata().into()) 109 | } 110 | 111 | fn metadata_at_version(version: u32) -> Option { 112 | Runtime::metadata_at_version(version) 113 | } 114 | 115 | fn metadata_versions() -> Vec { 116 | Runtime::metadata_versions() 117 | } 118 | } 119 | 120 | impl runtime_apis::BlockBuilder for Runtime { 121 | fn apply_extrinsic(extrinsic: ExtrinsicOf) -> ApplyExtrinsicResult { 122 | RuntimeExecutive::apply_extrinsic(extrinsic) 123 | } 124 | 125 | fn finalize_block() -> HeaderOf { 126 | RuntimeExecutive::finalize_block() 127 | } 128 | 129 | fn inherent_extrinsics(data: InherentData) -> Vec> { 130 | data.create_extrinsics() 131 | } 132 | 133 | fn check_inherents( 134 | block: Block, 135 | data: InherentData, 136 | ) -> CheckInherentsResult { 137 | data.check_extrinsics(&block) 138 | } 139 | } 140 | 141 | impl runtime_apis::TaggedTransactionQueue for Runtime { 142 | fn validate_transaction( 143 | source: TransactionSource, 144 | tx: ExtrinsicOf, 145 | block_hash: ::Hash, 146 | ) -> TransactionValidity { 147 | RuntimeExecutive::validate_transaction(source, tx, block_hash) 148 | } 149 | } 150 | 151 | impl runtime_apis::OffchainWorkerApi for Runtime { 152 | fn offchain_worker(header: &HeaderOf) { 153 | RuntimeExecutive::offchain_worker(header) 154 | } 155 | } 156 | 157 | impl runtime_apis::SessionKeys for Runtime { 158 | fn generate_session_keys(_seed: Option>) -> Vec { 159 | Default::default() 160 | } 161 | 162 | fn decode_session_keys( 163 | _encoded: Vec, 164 | ) -> Option, runtime_apis::KeyTypeId)>> { 165 | Default::default() 166 | } 167 | } 168 | 169 | impl runtime_apis::AccountNonceApi< 170 | Block, 171 | ::AccountId, 172 | ::Index, 173 | > for Runtime { 174 | fn account_nonce(account: interface::AccountId) -> interface::Index { 175 | System::account_nonce(account) 176 | } 177 | } 178 | } 179 | 180 | /// Some re-exports that the node side code needs to know. Some are useful in this context as well. 181 | /// 182 | /// Other types should preferably be private. 183 | // TODO: this should be standardized in some way, see: 184 | // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 185 | pub mod interface { 186 | use super::*; 187 | 188 | pub type OpaqueBlock = runtime_types_common::OpaqueBlockOf; 189 | pub type AccountId = ::AccountId; 190 | pub type Index = ::Index; 191 | pub type Hash = ::Hash; 192 | } 193 | -------------------------------------------------------------------------------- /staging/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | pub use pallet::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use frame::prelude::*; 6 | pub type Balance = u128; 7 | 8 | #[pallet::config] 9 | pub trait Config: frame_system::Config {} 10 | 11 | #[pallet::pallet] 12 | pub struct Pallet(_); 13 | 14 | #[pallet::storage] 15 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 16 | 17 | #[pallet::storage] 18 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 19 | 20 | #[derive(frame::derive::DefaultNoBound)] 21 | #[pallet::genesis_config] 22 | pub struct GenesisConfig { 23 | pub balances: Vec<(T::AccountId, Balance)>, 24 | } 25 | 26 | // TODO: 27 | // https://github.com/paritytech/polkadot-sdk/pull/1642/files#diff-1a8ad3ec3e24e92089201972e112619421ef6c31484f65d45d30da7a8fae69fbR41 28 | use frame::deps::sp_runtime; 29 | #[pallet::genesis_build] 30 | impl BuildGenesisConfig for GenesisConfig { 31 | fn build(&self) { 32 | for (who, amount) in &self.balances { 33 | assert!(!Balances::::contains_key(who), "duplicate balance in genesis"); 34 | Balances::::insert(who, amount); 35 | TotalIssuance::::mutate(|t| *t += amount); 36 | } 37 | } 38 | } 39 | 40 | #[pallet::call] 41 | impl Pallet { 42 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 43 | let _anyone = ensure_signed(origin)?; 44 | 45 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 46 | TotalIssuance::::mutate(|t| *t += amount); 47 | 48 | Ok(()) 49 | } 50 | 51 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 52 | let sender = ensure_signed(origin)?; 53 | 54 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 55 | if sender_balance < amount { 56 | return Err("notEnoughBalance".into()) 57 | } 58 | let reminder = sender_balance - amount; 59 | 60 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 61 | Balances::::insert(&sender, reminder); 62 | 63 | Ok(()) 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use crate::currency::pallet::{self as pallet_currency, *}; 70 | use frame::testing_prelude::*; 71 | 72 | construct_runtime!( 73 | pub struct Runtime { 74 | System: frame_system, 75 | Currency: pallet_currency, 76 | } 77 | ); 78 | 79 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 80 | impl frame_system::Config for Runtime { 81 | type Block = MockBlock; 82 | } 83 | 84 | impl pallet_currency::Config for Runtime {} 85 | 86 | const ALICE: ::AccountId = 1; 87 | const BOB: ::AccountId = 2; 88 | const EVE: ::AccountId = 3; 89 | 90 | #[allow(unused)] 91 | fn test_state_new() -> TestState { 92 | let system = frame_system::GenesisConfig::default(); 93 | let currency = 94 | pallet_currency::GenesisConfig { balances: vec![(ALICE, 100), (BOB, 100)] }; 95 | let runtime_genesis = RuntimeGenesisConfig { system, currency }; 96 | 97 | TestState::new(runtime_genesis.build_storage().unwrap()) 98 | } 99 | 100 | struct ExtBuilder { 101 | balances: Vec<(::AccountId, Balance)>, 102 | } 103 | 104 | impl Default for ExtBuilder { 105 | fn default() -> Self { 106 | Self { balances: vec![(ALICE, 100), (BOB, 100)] } 107 | } 108 | } 109 | 110 | impl ExtBuilder { 111 | fn add_balance( 112 | mut self, 113 | who: ::AccountId, 114 | amount: Balance, 115 | ) -> Self { 116 | self.balances.push((who, amount)); 117 | self 118 | } 119 | 120 | fn build_and_execute(self, test: impl FnOnce() -> ()) { 121 | let system = frame_system::GenesisConfig::default(); 122 | let currency = pallet_currency::GenesisConfig { balances: self.balances }; 123 | let runtime_genesis = RuntimeGenesisConfig { system, currency }; 124 | 125 | let mut ext = TestState::new(runtime_genesis.build_storage().unwrap()); 126 | ext.execute_with(test); 127 | 128 | ext.execute_with(|| { 129 | assert_eq!( 130 | Balances::::iter().map(|(_, x)| x).sum::(), 131 | TotalIssuance::::get() 132 | ); 133 | }) 134 | } 135 | } 136 | 137 | #[test] 138 | fn initial_state_works() { 139 | ExtBuilder::default().build_and_execute(|| { 140 | assert_eq!(Balances::::get(&ALICE), Some(100)); 141 | assert_eq!(Balances::::get(&BOB), Some(100)); 142 | assert_eq!(Balances::::get(&EVE), None); 143 | assert_eq!(TotalIssuance::::get(), 200); 144 | }); 145 | } 146 | 147 | #[test] 148 | fn ext_builder_works() { 149 | ExtBuilder::default().add_balance(EVE, 42).build_and_execute(|| { 150 | assert_eq!(Balances::::get(&EVE), Some(42)); 151 | assert_eq!(TotalIssuance::::get(), 242); 152 | }) 153 | } 154 | 155 | #[test] 156 | #[should_panic] 157 | fn duplicate_genesis_fails() { 158 | ExtBuilder::default() 159 | .add_balance(EVE, 42) 160 | .add_balance(EVE, 43) 161 | .build_and_execute(|| { 162 | assert_eq!(Balances::::get(&EVE), None); 163 | assert_eq!(TotalIssuance::::get(), 242); 164 | }) 165 | } 166 | 167 | #[test] 168 | fn test_mint() { 169 | ExtBuilder::default().build_and_execute(|| { 170 | // given the initial state, when: 171 | assert_ok!(Pallet::::mint(RuntimeOrigin::signed(ALICE), BOB, 100)); 172 | 173 | // then: 174 | assert_eq!(Balances::::get(&BOB), Some(200)); 175 | assert_eq!(TotalIssuance::::get(), 300); 176 | 177 | // given: 178 | assert_ok!(Pallet::::mint(RuntimeOrigin::signed(ALICE), EVE, 100)); 179 | 180 | // then: 181 | assert_eq!(Balances::::get(&EVE), Some(100)); 182 | assert_eq!(TotalIssuance::::get(), 400); 183 | }); 184 | } 185 | 186 | #[test] 187 | fn transfer_works() { 188 | ExtBuilder::default().build_and_execute(|| { 189 | // given the the initial state, when: 190 | assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); 191 | 192 | // then: 193 | assert_eq!(Balances::::get(&ALICE), Some(50)); 194 | assert_eq!(Balances::::get(&BOB), Some(150)); 195 | assert_eq!(TotalIssuance::::get(), 200); 196 | 197 | // when: 198 | assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(BOB), ALICE, 50)); 199 | 200 | // then: 201 | assert_eq!(Balances::::get(&ALICE), Some(100)); 202 | assert_eq!(Balances::::get(&BOB), Some(100)); 203 | assert_eq!(TotalIssuance::::get(), 200); 204 | }); 205 | } 206 | 207 | #[test] 208 | fn transfer_from_non_existent_fails() { 209 | ExtBuilder::default().build_and_execute(|| { 210 | // given the the initial state, when: 211 | assert_err!( 212 | Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 213 | "NonExistentAccount" 214 | ); 215 | 216 | // then nothing has changed. 217 | assert_eq!(Balances::::get(&ALICE), Some(100)); 218 | assert_eq!(Balances::::get(&BOB), Some(100)); 219 | assert_eq!(Balances::::get(&EVE), None); 220 | assert_eq!(TotalIssuance::::get(), 200); 221 | }); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tutorial/node/src/service.rs: -------------------------------------------------------------------------------- 1 | use runtime::{self, interface::OpaqueBlock as Block, RuntimeApi}; 2 | pub use sc_executor::NativeElseWasmExecutor; 3 | use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; 4 | use sc_telemetry::{Telemetry, TelemetryWorker}; 5 | use std::sync::Arc; 6 | 7 | use crate::cli::Consensus; 8 | 9 | // Our native executor instance. 10 | pub struct ExecutorDispatch; 11 | 12 | impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { 13 | /// Only enable the benchmarking host functions when we actually want to benchmark. 14 | #[cfg(feature = "runtime-benchmarks")] 15 | type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; 16 | /// Otherwise we only use the default Substrate host functions. 17 | #[cfg(not(feature = "runtime-benchmarks"))] 18 | type ExtendHostFunctions = (); 19 | 20 | fn dispatch(method: &str, data: &[u8]) -> Option> { 21 | runtime::api::dispatch(method, data) 22 | } 23 | 24 | fn native_version() -> sc_executor::NativeVersion { 25 | runtime::native_version() 26 | } 27 | } 28 | 29 | pub(crate) type FullClient = 30 | sc_service::TFullClient>; 31 | type FullBackend = sc_service::TFullBackend; 32 | type FullSelectChain = sc_consensus::LongestChain; 33 | 34 | pub fn new_partial( 35 | config: &Configuration, 36 | ) -> Result< 37 | sc_service::PartialComponents< 38 | FullClient, 39 | FullBackend, 40 | FullSelectChain, 41 | sc_consensus::DefaultImportQueue, 42 | sc_transaction_pool::FullPool, 43 | Option, 44 | >, 45 | ServiceError, 46 | > { 47 | let telemetry = config 48 | .telemetry_endpoints 49 | .clone() 50 | .filter(|x| !x.is_empty()) 51 | .map(|endpoints| -> Result<_, sc_telemetry::Error> { 52 | let worker = TelemetryWorker::new(16)?; 53 | let telemetry = worker.handle().new_telemetry(endpoints); 54 | Ok((worker, telemetry)) 55 | }) 56 | .transpose()?; 57 | 58 | let executor = sc_service::new_native_or_wasm_executor(&config); 59 | 60 | let (client, backend, keystore_container, task_manager) = 61 | sc_service::new_full_parts::( 62 | config, 63 | telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), 64 | executor, 65 | )?; 66 | let client = Arc::new(client); 67 | 68 | let telemetry = telemetry.map(|(worker, telemetry)| { 69 | task_manager.spawn_handle().spawn("telemetry", None, worker.run()); 70 | telemetry 71 | }); 72 | 73 | let select_chain = sc_consensus::LongestChain::new(backend.clone()); 74 | 75 | let transaction_pool = sc_transaction_pool::BasicPool::new_full( 76 | config.transaction_pool.clone(), 77 | config.role.is_authority().into(), 78 | config.prometheus_registry(), 79 | task_manager.spawn_essential_handle(), 80 | client.clone(), 81 | ); 82 | 83 | let import_queue = sc_consensus_manual_seal::import_queue( 84 | Box::new(client.clone()), 85 | &task_manager.spawn_essential_handle(), 86 | config.prometheus_registry(), 87 | ); 88 | 89 | Ok(sc_service::PartialComponents { 90 | client, 91 | backend, 92 | task_manager, 93 | import_queue, 94 | keystore_container, 95 | select_chain, 96 | transaction_pool, 97 | other: (telemetry), 98 | }) 99 | } 100 | 101 | /// Builds a new service for a full client. 102 | pub fn new_full(config: Configuration, consensus: Consensus) -> Result { 103 | let sc_service::PartialComponents { 104 | client, 105 | backend, 106 | mut task_manager, 107 | import_queue, 108 | keystore_container, 109 | select_chain, 110 | transaction_pool, 111 | other: mut telemetry, 112 | } = new_partial(&config)?; 113 | 114 | let net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); 115 | 116 | let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = 117 | sc_service::build_network(sc_service::BuildNetworkParams { 118 | config: &config, 119 | client: client.clone(), 120 | transaction_pool: transaction_pool.clone(), 121 | spawn_handle: task_manager.spawn_handle(), 122 | import_queue, 123 | net_config, 124 | block_announce_validator_builder: None, 125 | warp_sync_params: None, 126 | })?; 127 | 128 | if config.offchain_worker.enabled { 129 | sc_service::build_offchain_workers( 130 | &config, 131 | task_manager.spawn_handle(), 132 | client.clone(), 133 | network.clone(), 134 | ); 135 | } 136 | 137 | let rpc_extensions_builder = { 138 | let client = client.clone(); 139 | let pool = transaction_pool.clone(); 140 | 141 | Box::new(move |deny_unsafe, _| { 142 | let deps = 143 | crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; 144 | crate::rpc::create_full(deps).map_err(Into::into) 145 | }) 146 | }; 147 | 148 | let prometheus_registry = config.prometheus_registry().cloned(); 149 | 150 | let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { 151 | network, 152 | client: client.clone(), 153 | keystore: keystore_container.keystore(), 154 | task_manager: &mut task_manager, 155 | transaction_pool: transaction_pool.clone(), 156 | rpc_builder: rpc_extensions_builder, 157 | backend, 158 | system_rpc_tx, 159 | tx_handler_controller, 160 | sync_service, 161 | config, 162 | telemetry: telemetry.as_mut(), 163 | })?; 164 | 165 | let proposer = sc_basic_authorship::ProposerFactory::new( 166 | task_manager.spawn_handle(), 167 | client.clone(), 168 | transaction_pool.clone(), 169 | prometheus_registry.as_ref(), 170 | telemetry.as_ref().map(|x| x.handle()), 171 | ); 172 | 173 | match consensus { 174 | Consensus::InstantSeal => { 175 | let params = sc_consensus_manual_seal::InstantSealParams { 176 | block_import: client.clone(), 177 | env: proposer, 178 | client, 179 | pool: transaction_pool, 180 | select_chain, 181 | consensus_data_provider: None, 182 | create_inherent_data_providers: move |_, ()| async move { 183 | Ok(sp_timestamp::InherentDataProvider::from_system_time()) 184 | }, 185 | }; 186 | 187 | let authorship_future = sc_consensus_manual_seal::run_instant_seal(params); 188 | 189 | task_manager.spawn_essential_handle().spawn_blocking( 190 | "instant-seal", 191 | None, 192 | authorship_future, 193 | ); 194 | }, 195 | Consensus::ManualSeal(block_time) => { 196 | let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); 197 | task_manager.spawn_handle().spawn("block_authoring", None, async move { 198 | loop { 199 | futures_timer::Delay::new(std::time::Duration::from_millis(block_time)).await; 200 | sink.try_send(sc_consensus_manual_seal::EngineCommand::SealNewBlock { 201 | create_empty: true, 202 | finalize: true, 203 | parent_hash: None, 204 | sender: None, 205 | }) 206 | .unwrap(); 207 | } 208 | }); 209 | 210 | let params = sc_consensus_manual_seal::ManualSealParams { 211 | block_import: client.clone(), 212 | env: proposer, 213 | client, 214 | pool: transaction_pool, 215 | select_chain, 216 | commands_stream: Box::pin(commands_stream), 217 | consensus_data_provider: None, 218 | create_inherent_data_providers: move |_, ()| async move { 219 | Ok(sp_timestamp::InherentDataProvider::from_system_time()) 220 | }, 221 | }; 222 | let authorship_future = sc_consensus_manual_seal::run_manual_seal(params); 223 | 224 | task_manager.spawn_essential_handle().spawn_blocking( 225 | "manual-seal", 226 | None, 227 | authorship_future, 228 | ); 229 | }, 230 | } 231 | 232 | network_starter.start_network(); 233 | Ok(task_manager) 234 | } 235 | -------------------------------------------------------------------------------- /tutorial/step-6/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | pub type Balance = u128; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config {} 11 | 12 | #[pallet::storage] 13 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 14 | 15 | #[pallet::storage] 16 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 17 | 18 | #[pallet::pallet] 19 | pub struct Pallet(_); 20 | 21 | #[pallet::call] 22 | impl Pallet { 23 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 24 | let _anyone = ensure_signed(origin)?; 25 | 26 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 27 | TotalIssuance::::mutate(|t| *t += amount); 28 | 29 | Ok(()) 30 | } 31 | 32 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 33 | let sender = ensure_signed(origin)?; 34 | 35 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 36 | if sender_balance < amount { 37 | return Err("notEnoughBalance".into()) 38 | } 39 | let reminder = sender_balance - amount; 40 | 41 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 42 | Balances::::insert(&sender, reminder); 43 | 44 | Ok(()) 45 | } 46 | } 47 | 48 | #[derive(frame::derive::DefaultNoBound)] 49 | #[pallet::genesis_config] 50 | pub struct GenesisConfig { 51 | pub balances: Vec<(T::AccountId, Balance)>, 52 | } 53 | 54 | #[pallet::genesis_build] 55 | impl GenesisBuild for GenesisConfig { 56 | fn build(&self) { 57 | for (who, amount) in &self.balances { 58 | assert!(!Balances::::contains_key(who), "duplicate balance in genesis"); 59 | Balances::::insert(who, amount); 60 | TotalIssuance::::mutate(|t| *t += amount); 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::{pallet as pallet_currency, *}; 68 | use frame::testing_prelude::*; 69 | 70 | type Extrinsic = MockUncheckedExtrinsic; 71 | type Block = MockBlock; 72 | 73 | construct_runtime!( 74 | pub struct Runtime 75 | where 76 | Block = Block, 77 | NodeBlock = Block, 78 | UncheckedExtrinsic = Extrinsic, 79 | { 80 | System: frame_system, 81 | Currency: pallet_currency, 82 | } 83 | ); 84 | 85 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 86 | impl frame_system::Config for Runtime { 87 | type RuntimeOrigin = RuntimeOrigin; 88 | type RuntimeCall = RuntimeCall; 89 | type RuntimeEvent = RuntimeEvent; 90 | type PalletInfo = PalletInfo; 91 | type BaseCallFilter = frame::traits::Everything; 92 | type OnSetCode = (); 93 | } 94 | 95 | impl pallet::Config for Runtime {} 96 | 97 | const ALICE: ::AccountId = 1; 98 | const BOB: ::AccountId = 2; 99 | const EVE: ::AccountId = 3; 100 | 101 | struct ExtBuilder { 102 | balances: Vec<(::AccountId, Balance)>, 103 | } 104 | 105 | impl Default for ExtBuilder { 106 | fn default() -> Self { 107 | Self { balances: vec![(ALICE, 100), (BOB, 100)] } 108 | } 109 | } 110 | 111 | impl ExtBuilder { 112 | fn add_balance( 113 | mut self, 114 | who: ::AccountId, 115 | amount: Balance, 116 | ) -> Self { 117 | self.balances.push((who, amount)); 118 | self 119 | } 120 | 121 | fn build_and_execute(self, test: impl FnOnce() -> ()) { 122 | let system = frame_system::GenesisConfig::default(); 123 | let currency = pallet_currency::GenesisConfig { balances: self.balances }; 124 | let runtime_genesis = RuntimeGenesisConfig { system, currency }; 125 | 126 | let mut ext = TestState::new(runtime_genesis.build_storage().unwrap()); 127 | ext.execute_with(test); 128 | // possibly do more here. 129 | } 130 | } 131 | 132 | #[test] 133 | fn initial_state_works() { 134 | ExtBuilder::default().build_and_execute(|| { 135 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 136 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 137 | assert_eq!(pallet::Balances::::get(&EVE), None); 138 | assert_eq!(pallet::TotalIssuance::::get(), 200); 139 | }); 140 | } 141 | 142 | #[test] 143 | fn ext_builder_works() { 144 | ExtBuilder::default().add_balance(EVE, 42).build_and_execute(|| { 145 | assert_eq!(pallet::Balances::::get(&EVE), Some(42)); 146 | assert_eq!(pallet::TotalIssuance::::get(), 242); 147 | }) 148 | } 149 | 150 | #[test] 151 | #[should_panic] 152 | fn duplicate_genesis_fails() { 153 | ExtBuilder::default() 154 | .add_balance(EVE, 42) 155 | .add_balance(EVE, 43) 156 | .build_and_execute(|| { 157 | assert_eq!(pallet::Balances::::get(&EVE), None); 158 | assert_eq!(pallet::TotalIssuance::::get(), 242); 159 | }) 160 | } 161 | 162 | #[test] 163 | fn test_mint() { 164 | ExtBuilder::default().build_and_execute(|| { 165 | // given the initial state, when: 166 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), BOB, 100)); 167 | 168 | // then: 169 | assert_eq!(pallet::Balances::::get(&BOB), Some(200)); 170 | assert_eq!(pallet::TotalIssuance::::get(), 300); 171 | 172 | // given: 173 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), EVE, 100)); 174 | 175 | // then: 176 | assert_eq!(pallet::Balances::::get(&EVE), Some(100)); 177 | assert_eq!(pallet::TotalIssuance::::get(), 400); 178 | }); 179 | } 180 | 181 | #[test] 182 | fn transfer_works() { 183 | ExtBuilder::default().build_and_execute(|| { 184 | // given the the initial state, when: 185 | assert_ok!(pallet::Pallet::::transfer( 186 | RuntimeOrigin::signed(ALICE), 187 | BOB, 188 | 50 189 | )); 190 | 191 | // them: 192 | assert_eq!(pallet::Balances::::get(&ALICE), Some(50)); 193 | assert_eq!(pallet::Balances::::get(&BOB), Some(150)); 194 | assert_eq!(pallet::TotalIssuance::::get(), 200); 195 | 196 | // when: 197 | assert_ok!(pallet::Pallet::::transfer( 198 | RuntimeOrigin::signed(BOB), 199 | ALICE, 200 | 50 201 | )); 202 | 203 | // then: 204 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 205 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 206 | assert_eq!(pallet::TotalIssuance::::get(), 200); 207 | }); 208 | } 209 | 210 | #[test] 211 | fn transfer_from_non_existent_fails() { 212 | ExtBuilder::default().build_and_execute(|| { 213 | // given the the initial state, when: 214 | assert_err!( 215 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 216 | "NonExistentAccount" 217 | ); 218 | 219 | // then nothing has changed. 220 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 221 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 222 | assert_eq!(pallet::Balances::::get(&EVE), None); 223 | assert_eq!(pallet::TotalIssuance::::get(), 200); 224 | 225 | // in fact, this frame-helper ensures that nothing in the state has been updated 226 | // prior and after execution: 227 | assert_noop!( 228 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 229 | "NonExistentAccount" 230 | ); 231 | }); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /tutorial/step-5/pallets/src/currency.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | 7 | pub type Balance = u128; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config {} 11 | 12 | #[pallet::storage] 13 | pub type Balances = StorageMap<_, _, T::AccountId, Balance>; 14 | 15 | #[pallet::storage] 16 | pub type TotalIssuance = StorageValue<_, Balance, ValueQuery>; 17 | 18 | #[pallet::pallet] 19 | pub struct Pallet(_); 20 | 21 | #[pallet::call] 22 | impl Pallet { 23 | pub fn mint(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 24 | let _anyone = ensure_signed(origin)?; 25 | 26 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 27 | TotalIssuance::::mutate(|t| *t += amount); 28 | 29 | Ok(()) 30 | } 31 | 32 | pub fn transfer(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 33 | let sender = ensure_signed(origin)?; 34 | 35 | let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; 36 | if sender_balance < amount { 37 | return Err("notEnoughBalance".into()) 38 | } 39 | let reminder = sender_balance - amount; 40 | 41 | Balances::::mutate(to, |b| *b = Some(b.unwrap_or(0) + amount)); 42 | Balances::::insert(&sender, reminder); 43 | 44 | Ok(()) 45 | } 46 | } 47 | 48 | #[derive(frame::derive::DefaultNoBound)] 49 | #[pallet::genesis_config] 50 | pub struct GenesisConfig { 51 | pub balances: Vec<(T::AccountId, Balance)>, 52 | } 53 | 54 | #[pallet::genesis_build] 55 | impl GenesisBuild for GenesisConfig { 56 | fn build(&self) { 57 | for (who, amount) in &self.balances { 58 | assert!(!Balances::::contains_key(who), "duplicate balance in genesis"); 59 | Balances::::insert(who, amount); 60 | TotalIssuance::::mutate(|t| *t += amount); 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::{pallet as pallet_currency, *}; 68 | use frame::testing_prelude::*; 69 | 70 | type Extrinsic = MockUncheckedExtrinsic; 71 | type Block = MockBlock; 72 | 73 | construct_runtime!( 74 | pub struct Runtime 75 | where 76 | Block = Block, 77 | NodeBlock = Block, 78 | UncheckedExtrinsic = Extrinsic, 79 | { 80 | System: frame_system, 81 | Currency: pallet_currency, 82 | } 83 | ); 84 | 85 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 86 | impl frame_system::Config for Runtime { 87 | type RuntimeOrigin = RuntimeOrigin; 88 | type RuntimeCall = RuntimeCall; 89 | type RuntimeEvent = RuntimeEvent; 90 | type PalletInfo = PalletInfo; 91 | type BaseCallFilter = frame::traits::Everything; 92 | type OnSetCode = (); 93 | } 94 | 95 | impl pallet::Config for Runtime {} 96 | 97 | const ALICE: ::AccountId = 1; 98 | const BOB: ::AccountId = 2; 99 | const EVE: ::AccountId = 3; 100 | 101 | #[allow(unused)] 102 | fn test_state_new() -> TestState { 103 | let system = frame_system::GenesisConfig::default(); 104 | let currency = 105 | pallet_currency::GenesisConfig { balances: vec![(ALICE, 100), (BOB, 100)] }; 106 | let runtime_genesis = RuntimeGenesisConfig { system, currency }; 107 | 108 | TestState::new(runtime_genesis.build_storage().unwrap()) 109 | } 110 | 111 | #[allow(unused)] 112 | fn test_state_new_2() -> TestState { 113 | let mut storage: Storage = Default::default(); 114 | >::assimilate_storage( 115 | &Default::default(), 116 | &mut storage, 117 | ); 118 | pallet_currency::GenesisConfig:: { balances: vec![(ALICE, 100), (BOB, 100)] } 119 | .assimilate_storage(&mut storage); 120 | TestState::new(storage) 121 | } 122 | 123 | struct ExtBuilder { 124 | balances: Vec<(::AccountId, Balance)>, 125 | } 126 | 127 | impl Default for ExtBuilder { 128 | fn default() -> Self { 129 | Self { balances: vec![(ALICE, 100), (BOB, 100)] } 130 | } 131 | } 132 | 133 | impl ExtBuilder { 134 | fn add_balance( 135 | mut self, 136 | who: ::AccountId, 137 | amount: Balance, 138 | ) -> Self { 139 | self.balances.push((who, amount)); 140 | self 141 | } 142 | 143 | fn build_and_execute(self, test: impl FnOnce() -> ()) { 144 | let system = frame_system::GenesisConfig::default(); 145 | let currency = pallet_currency::GenesisConfig { balances: self.balances }; 146 | let runtime_genesis = RuntimeGenesisConfig { system, currency }; 147 | 148 | let mut ext = TestState::new(runtime_genesis.build_storage().unwrap()); 149 | ext.execute_with(test); 150 | // possibly do more here. 151 | } 152 | } 153 | 154 | #[test] 155 | fn initial_state_works() { 156 | ExtBuilder::default().build_and_execute(|| { 157 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 158 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 159 | assert_eq!(pallet::Balances::::get(&EVE), None); 160 | assert_eq!(pallet::TotalIssuance::::get(), 200); 161 | }); 162 | } 163 | 164 | #[test] 165 | fn ext_builder_works() { 166 | ExtBuilder::default().add_balance(EVE, 42).build_and_execute(|| { 167 | assert_eq!(pallet::Balances::::get(&EVE), Some(42)); 168 | assert_eq!(pallet::TotalIssuance::::get(), 242); 169 | }) 170 | } 171 | 172 | #[test] 173 | #[should_panic] 174 | fn duplicate_genesis_fails() { 175 | ExtBuilder::default() 176 | .add_balance(EVE, 42) 177 | .add_balance(EVE, 43) 178 | .build_and_execute(|| { 179 | assert_eq!(pallet::Balances::::get(&EVE), None); 180 | assert_eq!(pallet::TotalIssuance::::get(), 242); 181 | }) 182 | } 183 | 184 | #[test] 185 | fn test_mint() { 186 | ExtBuilder::default().build_and_execute(|| { 187 | // given the initial state, when: 188 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), BOB, 100)); 189 | 190 | // then: 191 | assert_eq!(pallet::Balances::::get(&BOB), Some(200)); 192 | assert_eq!(pallet::TotalIssuance::::get(), 300); 193 | 194 | // given: 195 | assert_ok!(pallet::Pallet::::mint(RuntimeOrigin::signed(ALICE), EVE, 100)); 196 | 197 | // then: 198 | assert_eq!(pallet::Balances::::get(&EVE), Some(100)); 199 | assert_eq!(pallet::TotalIssuance::::get(), 400); 200 | }); 201 | } 202 | 203 | #[test] 204 | fn transfer_works() { 205 | ExtBuilder::default().build_and_execute(|| { 206 | // given the the initial state, when: 207 | assert_ok!(pallet::Pallet::::transfer( 208 | RuntimeOrigin::signed(ALICE), 209 | BOB, 210 | 50 211 | )); 212 | 213 | // them: 214 | assert_eq!(pallet::Balances::::get(&ALICE), Some(50)); 215 | assert_eq!(pallet::Balances::::get(&BOB), Some(150)); 216 | assert_eq!(pallet::TotalIssuance::::get(), 200); 217 | 218 | // when: 219 | assert_ok!(pallet::Pallet::::transfer( 220 | RuntimeOrigin::signed(BOB), 221 | ALICE, 222 | 50 223 | )); 224 | 225 | // then: 226 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 227 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 228 | assert_eq!(pallet::TotalIssuance::::get(), 200); 229 | }); 230 | } 231 | 232 | #[test] 233 | fn transfer_from_non_existent_fails() { 234 | ExtBuilder::default().build_and_execute(|| { 235 | // given the the initial state, when: 236 | assert_err!( 237 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 238 | "NonExistentAccount" 239 | ); 240 | 241 | // then nothing has changed. 242 | assert_eq!(pallet::Balances::::get(&ALICE), Some(100)); 243 | assert_eq!(pallet::Balances::::get(&BOB), Some(100)); 244 | assert_eq!(pallet::Balances::::get(&EVE), None); 245 | assert_eq!(pallet::TotalIssuance::::get(), 200); 246 | 247 | // in fact, this frame-helper ensures that nothing in the state has been updated 248 | // prior and after execution: 249 | assert_noop!( 250 | pallet::Pallet::::transfer(RuntimeOrigin::signed(EVE), ALICE, 10), 251 | "NonExistentAccount" 252 | ); 253 | }); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /staging/node/src/service.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use futures::FutureExt; 19 | use runtime::{self, interface::OpaqueBlock as Block, RuntimeApi}; 20 | use sc_client_api::backend::Backend; 21 | use sc_executor::WasmExecutor; 22 | use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; 23 | use sc_telemetry::{Telemetry, TelemetryWorker}; 24 | use sc_transaction_pool_api::OffchainTransactionPoolFactory; 25 | use std::sync::Arc; 26 | 27 | use crate::cli::Consensus; 28 | 29 | #[cfg(feature = "runtime-benchmarks")] 30 | type HostFunctions = 31 | (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions); 32 | 33 | #[cfg(not(feature = "runtime-benchmarks"))] 34 | type HostFunctions = sp_io::SubstrateHostFunctions; 35 | 36 | pub(crate) type FullClient = 37 | sc_service::TFullClient>; 38 | type FullBackend = sc_service::TFullBackend; 39 | type FullSelectChain = sc_consensus::LongestChain; 40 | 41 | pub fn new_partial( 42 | config: &Configuration, 43 | ) -> Result< 44 | sc_service::PartialComponents< 45 | FullClient, 46 | FullBackend, 47 | FullSelectChain, 48 | sc_consensus::DefaultImportQueue, 49 | sc_transaction_pool::FullPool, 50 | Option, 51 | >, 52 | ServiceError, 53 | > { 54 | let telemetry = config 55 | .telemetry_endpoints 56 | .clone() 57 | .filter(|x| !x.is_empty()) 58 | .map(|endpoints| -> Result<_, sc_telemetry::Error> { 59 | let worker = TelemetryWorker::new(16)?; 60 | let telemetry = worker.handle().new_telemetry(endpoints); 61 | Ok((worker, telemetry)) 62 | }) 63 | .transpose()?; 64 | 65 | let executor = sc_service::new_wasm_executor(&config); 66 | 67 | let (client, backend, keystore_container, task_manager) = 68 | sc_service::new_full_parts::( 69 | config, 70 | telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), 71 | executor, 72 | )?; 73 | let client = Arc::new(client); 74 | 75 | let telemetry = telemetry.map(|(worker, telemetry)| { 76 | task_manager.spawn_handle().spawn("telemetry", None, worker.run()); 77 | telemetry 78 | }); 79 | 80 | let select_chain = sc_consensus::LongestChain::new(backend.clone()); 81 | 82 | let transaction_pool = sc_transaction_pool::BasicPool::new_full( 83 | config.transaction_pool.clone(), 84 | config.role.is_authority().into(), 85 | config.prometheus_registry(), 86 | task_manager.spawn_essential_handle(), 87 | client.clone(), 88 | ); 89 | 90 | let import_queue = sc_consensus_manual_seal::import_queue( 91 | Box::new(client.clone()), 92 | &task_manager.spawn_essential_handle(), 93 | config.prometheus_registry(), 94 | ); 95 | 96 | Ok(sc_service::PartialComponents { 97 | client, 98 | backend, 99 | task_manager, 100 | import_queue, 101 | keystore_container, 102 | select_chain, 103 | transaction_pool, 104 | other: (telemetry), 105 | }) 106 | } 107 | 108 | /// Builds a new service for a full client. 109 | pub fn new_full(config: Configuration, consensus: Consensus) -> Result { 110 | let sc_service::PartialComponents { 111 | client, 112 | backend, 113 | mut task_manager, 114 | import_queue, 115 | keystore_container, 116 | select_chain, 117 | transaction_pool, 118 | other: mut telemetry, 119 | } = new_partial(&config)?; 120 | 121 | let net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); 122 | 123 | let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = 124 | sc_service::build_network(sc_service::BuildNetworkParams { 125 | config: &config, 126 | client: client.clone(), 127 | transaction_pool: transaction_pool.clone(), 128 | spawn_handle: task_manager.spawn_handle(), 129 | import_queue, 130 | net_config, 131 | block_announce_validator_builder: None, 132 | warp_sync_params: None, 133 | block_relay: None, 134 | })?; 135 | 136 | if config.offchain_worker.enabled { 137 | task_manager.spawn_handle().spawn( 138 | "offchain-workers-runner", 139 | "offchain-worker", 140 | sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { 141 | runtime_api_provider: client.clone(), 142 | is_validator: config.role.is_authority(), 143 | keystore: Some(keystore_container.keystore()), 144 | offchain_db: backend.offchain_storage(), 145 | transaction_pool: Some(OffchainTransactionPoolFactory::new( 146 | transaction_pool.clone(), 147 | )), 148 | network_provider: network.clone(), 149 | enable_http_requests: true, 150 | custom_extensions: |_| vec![], 151 | }) 152 | .run(client.clone(), task_manager.spawn_handle()) 153 | .boxed(), 154 | ); 155 | } 156 | 157 | let rpc_extensions_builder = { 158 | let client = client.clone(); 159 | let pool = transaction_pool.clone(); 160 | 161 | Box::new(move |deny_unsafe, _| { 162 | let deps = 163 | crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; 164 | crate::rpc::create_full(deps).map_err(Into::into) 165 | }) 166 | }; 167 | 168 | let prometheus_registry = config.prometheus_registry().cloned(); 169 | 170 | let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { 171 | network, 172 | client: client.clone(), 173 | keystore: keystore_container.keystore(), 174 | task_manager: &mut task_manager, 175 | transaction_pool: transaction_pool.clone(), 176 | rpc_builder: rpc_extensions_builder, 177 | backend, 178 | system_rpc_tx, 179 | tx_handler_controller, 180 | sync_service, 181 | config, 182 | telemetry: telemetry.as_mut(), 183 | })?; 184 | 185 | let proposer = sc_basic_authorship::ProposerFactory::new( 186 | task_manager.spawn_handle(), 187 | client.clone(), 188 | transaction_pool.clone(), 189 | prometheus_registry.as_ref(), 190 | telemetry.as_ref().map(|x| x.handle()), 191 | ); 192 | 193 | match consensus { 194 | Consensus::InstantSeal => { 195 | let params = sc_consensus_manual_seal::InstantSealParams { 196 | block_import: client.clone(), 197 | env: proposer, 198 | client, 199 | pool: transaction_pool, 200 | select_chain, 201 | consensus_data_provider: None, 202 | create_inherent_data_providers: move |_, ()| async move { 203 | Ok(sp_timestamp::InherentDataProvider::from_system_time()) 204 | }, 205 | }; 206 | 207 | let authorship_future = sc_consensus_manual_seal::run_instant_seal(params); 208 | 209 | task_manager.spawn_essential_handle().spawn_blocking( 210 | "instant-seal", 211 | None, 212 | authorship_future, 213 | ); 214 | }, 215 | Consensus::ManualSeal(block_time) => { 216 | let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); 217 | task_manager.spawn_handle().spawn("block_authoring", None, async move { 218 | loop { 219 | futures_timer::Delay::new(std::time::Duration::from_millis(block_time)).await; 220 | sink.try_send(sc_consensus_manual_seal::EngineCommand::SealNewBlock { 221 | create_empty: true, 222 | finalize: true, 223 | parent_hash: None, 224 | sender: None, 225 | }) 226 | .unwrap(); 227 | } 228 | }); 229 | 230 | let params = sc_consensus_manual_seal::ManualSealParams { 231 | block_import: client.clone(), 232 | env: proposer, 233 | client, 234 | pool: transaction_pool, 235 | select_chain, 236 | commands_stream: Box::pin(commands_stream), 237 | consensus_data_provider: None, 238 | create_inherent_data_providers: move |_, ()| async move { 239 | Ok(sp_timestamp::InherentDataProvider::from_system_time()) 240 | }, 241 | }; 242 | let authorship_future = sc_consensus_manual_seal::run_manual_seal(params); 243 | 244 | task_manager.spawn_essential_handle().spawn_blocking( 245 | "manual-seal", 246 | None, 247 | authorship_future, 248 | ); 249 | }, 250 | } 251 | 252 | network_starter.start_network(); 253 | Ok(task_manager) 254 | } 255 | -------------------------------------------------------------------------------- /staging/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #![cfg_attr(not(feature = "std"), no_std)] 19 | 20 | // Make the WASM binary available. 21 | #[cfg(feature = "std")] 22 | include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); 23 | 24 | use frame::{ 25 | deps::frame_support::weights::FixedFee, // TODO: needs to come from somewhere reasonable. 26 | prelude::*, 27 | runtime::{ 28 | apis::{ 29 | // TODO: cleaner imports. 30 | self, 31 | impl_runtime_apis, 32 | ApplyExtrinsicResult, 33 | CheckInherentsResult, 34 | OpaqueMetadata, 35 | }, 36 | prelude::*, 37 | }, 38 | }; 39 | 40 | #[runtime_version] 41 | pub const VERSION: RuntimeVersion = RuntimeVersion { 42 | spec_name: create_runtime_str!("minimal-runtime"), 43 | impl_name: create_runtime_str!("minimal-runtime"), 44 | authoring_version: 1, 45 | spec_version: 0, 46 | impl_version: 1, 47 | apis: RUNTIME_API_VERSIONS, 48 | transaction_version: 1, 49 | state_version: 1, 50 | }; 51 | 52 | /// The version information used to identify this runtime when compiled natively. 53 | #[cfg(feature = "std")] 54 | pub fn native_version() -> NativeVersion { 55 | NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } 56 | } 57 | 58 | type SignedExtra = ( 59 | frame_system::CheckNonZeroSender, 60 | frame_system::CheckSpecVersion, 61 | frame_system::CheckTxVersion, 62 | frame_system::CheckGenesis, 63 | frame_system::CheckEra, 64 | frame_system::CheckNonce, 65 | frame_system::CheckWeight, 66 | pallet_transaction_payment::ChargeTransactionPayment, 67 | ); 68 | 69 | construct_runtime!( 70 | pub struct Runtime { 71 | System: frame_system, 72 | Timestamp: pallet_timestamp, 73 | 74 | Balances: pallet_balances, 75 | Sudo: pallet_sudo, 76 | TransactionPayment: pallet_transaction_payment, 77 | 78 | TutorialCurrency: staging_pallets::currency, 79 | TutorialStaking: staging_pallets::staking, 80 | } 81 | ); 82 | 83 | parameter_types! { 84 | pub const Version: RuntimeVersion = VERSION; 85 | } 86 | 87 | #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig as frame_system::DefaultConfig)] 88 | impl frame_system::Config for Runtime { 89 | type Block = Block; 90 | type Version = Version; 91 | type BlockHashCount = ConstU32<1024>; 92 | type AccountData = pallet_balances::AccountData<::Balance>; 93 | } 94 | 95 | #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] 96 | impl pallet_balances::Config for Runtime { 97 | type AccountStore = System; 98 | } 99 | 100 | #[derive_impl(pallet_sudo::config_preludes::TestDefaultConfig as pallet_sudo::DefaultConfig)] 101 | impl pallet_sudo::Config for Runtime {} 102 | 103 | #[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig as pallet_timestamp::DefaultConfig)] 104 | impl pallet_timestamp::Config for Runtime {} 105 | 106 | #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig as pallet_transaction_payment::DefaultConfig)] 107 | impl pallet_transaction_payment::Config for Runtime { 108 | // TODO: this a hack to make all transactions have a fixed amount of fee. We declare length to 109 | // fee function as a constant of 1, and no weight to fee. 110 | type WeightToFee = FixedFee<1, ::Balance>; 111 | type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; 112 | } 113 | 114 | impl staging_pallets::currency::Config for Runtime {} 115 | impl staging_pallets::staking::Config for Runtime { 116 | type EraDuration = ConstU32<200>; 117 | type ValidatorCount = ConstU32<4>; 118 | } 119 | 120 | type Block = frame::runtime::types_common::BlockOf; 121 | type Header = HeaderFor; 122 | 123 | type RuntimeExecutive = 124 | Executive, Runtime, AllPalletsWithSystem>; 125 | 126 | use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; 127 | 128 | impl_runtime_apis! { 129 | impl apis::Core for Runtime { 130 | fn version() -> RuntimeVersion { 131 | VERSION 132 | } 133 | 134 | fn execute_block(block: Block) { 135 | RuntimeExecutive::execute_block(block) 136 | } 137 | 138 | fn initialize_block(header: &Header) { 139 | RuntimeExecutive::initialize_block(header) 140 | } 141 | } 142 | impl apis::Metadata for Runtime { 143 | fn metadata() -> OpaqueMetadata { 144 | OpaqueMetadata::new(Runtime::metadata().into()) 145 | } 146 | 147 | fn metadata_at_version(version: u32) -> Option { 148 | Runtime::metadata_at_version(version) 149 | } 150 | 151 | fn metadata_versions() -> Vec { 152 | Runtime::metadata_versions() 153 | } 154 | } 155 | 156 | impl apis::BlockBuilder for Runtime { 157 | fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { 158 | RuntimeExecutive::apply_extrinsic(extrinsic) 159 | } 160 | 161 | fn finalize_block() -> HeaderFor { 162 | RuntimeExecutive::finalize_block() 163 | } 164 | 165 | fn inherent_extrinsics(data: InherentData) -> Vec> { 166 | data.create_extrinsics() 167 | } 168 | 169 | fn check_inherents( 170 | block: Block, 171 | data: InherentData, 172 | ) -> CheckInherentsResult { 173 | data.check_extrinsics(&block) 174 | } 175 | } 176 | 177 | impl apis::TaggedTransactionQueue for Runtime { 178 | fn validate_transaction( 179 | source: TransactionSource, 180 | tx: ExtrinsicFor, 181 | block_hash: ::Hash, 182 | ) -> TransactionValidity { 183 | RuntimeExecutive::validate_transaction(source, tx, block_hash) 184 | } 185 | } 186 | 187 | impl apis::OffchainWorkerApi for Runtime { 188 | // TODO: in `HeaderFor` you should think that using `HeaderFor` would be 189 | // equivalent, but it is not. A simple patch to `impl_runtime_apis` should simply prevent 190 | // the use of `Self` in this block of code. 191 | fn offchain_worker(header: &HeaderFor) { 192 | RuntimeExecutive::offchain_worker(header) 193 | } 194 | } 195 | 196 | impl apis::SessionKeys for Runtime { 197 | fn generate_session_keys(_seed: Option>) -> Vec { 198 | Default::default() 199 | } 200 | 201 | fn decode_session_keys( 202 | _encoded: Vec, 203 | ) -> Option, apis::KeyTypeId)>> { 204 | Default::default() 205 | } 206 | } 207 | 208 | impl apis::AccountNonceApi for Runtime { 209 | fn account_nonce(account: interface::AccountId) -> interface::Nonce { 210 | System::account_nonce(account) 211 | } 212 | } 213 | 214 | impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< 215 | Block, 216 | interface::Balance, 217 | > for Runtime { 218 | fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { 219 | TransactionPayment::query_info(uxt, len) 220 | } 221 | fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { 222 | TransactionPayment::query_fee_details(uxt, len) 223 | } 224 | fn query_weight_to_fee(weight: Weight) -> interface::Balance { 225 | TransactionPayment::weight_to_fee(weight) 226 | } 227 | fn query_length_to_fee(length: u32) -> interface::Balance { 228 | TransactionPayment::length_to_fee(length) 229 | } 230 | } 231 | } 232 | 233 | /// Some re-exports that the node side code needs to know. Some are useful in this context as well. 234 | /// 235 | /// Other types should preferably be private. 236 | // TODO: this should be standardized in some way, see: 237 | // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 238 | pub mod interface { 239 | use super::*; 240 | 241 | pub type OpaqueBlock = frame::runtime::types_common::OpaqueBlockOf; 242 | pub type AccountId = ::AccountId; 243 | pub type Nonce = ::Nonce; 244 | pub type Hash = ::Hash; 245 | pub type Balance = ::Balance; 246 | 247 | pub type MinimumBalance = ::ExistentialDeposit; 248 | } 249 | -------------------------------------------------------------------------------- /tutorial/step-6/pallets/src/staking.rs: -------------------------------------------------------------------------------- 1 | use frame::prelude::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use super::*; 6 | use crate::currency::pallet::{self as pallet_currency, Balance}; 7 | use frame::derive::{Decode, DefaultNoBound, Encode, TypeInfo}; 8 | 9 | #[pallet::config] 10 | pub trait Config: frame_system::Config + pallet_currency::Config { 11 | type ValidatorCount: Get; 12 | type EraDuration: Get; 13 | } 14 | 15 | #[pallet::pallet] 16 | pub struct Pallet(_); 17 | 18 | #[derive(Encode, Decode, TypeInfo, Eq, PartialEq, Clone, Debug)] 19 | pub struct ValidatorStake { 20 | pub(crate) own: Balance, 21 | pub(crate) delegated: Balance, 22 | } 23 | 24 | #[pallet::storage] 25 | pub type Validators = StorageMap<_, _, T::AccountId, ValidatorStake>; 26 | 27 | #[pallet::storage] 28 | pub type Delegators = StorageMap<_, _, T::AccountId, Balance>; 29 | 30 | #[pallet::storage] 31 | pub type ActiveValidators = StorageValue<_, Vec, ValueQuery>; 32 | 33 | #[derive(DefaultNoBound)] 34 | #[pallet::genesis_config] 35 | pub struct GenesisConfig { 36 | validators: Vec<(T::AccountId, Balance)>, 37 | delegators: Vec<(T::AccountId, T::AccountId, Balance)>, 38 | } 39 | 40 | #[pallet::genesis_build] 41 | impl GenesisBuild for GenesisConfig { 42 | fn build(&self) { 43 | use frame::deps::frame_support::assert_ok; 44 | use frame_system::RawOrigin; 45 | for (validator, self_stake) in &self.validators { 46 | let raw_origin = RawOrigin::Signed(validator.clone()); 47 | 48 | assert_ok!(Pallet::::register(raw_origin.into(), *self_stake)); 49 | } 50 | 51 | for (delegator, delegatee, stake) in &self.delegators { 52 | let raw_origin = RawOrigin::Signed(delegator.clone()); 53 | assert_ok!(Pallet::::delegate(raw_origin.into(), delegatee.clone(), *stake)); 54 | } 55 | } 56 | } 57 | 58 | #[pallet::call] 59 | impl Pallet { 60 | pub fn register(origin: OriginFor, amount: Balance) -> DispatchResult { 61 | let who = ensure_signed(origin)?; 62 | 63 | ensure!(!Validators::::contains_key(&who), "AlreadyRegistered"); 64 | ensure!( 65 | pallet_currency::Balances::::get(&who).map_or(false, |b| b >= amount), 66 | "InsufficientFunds" 67 | ); 68 | 69 | Validators::::insert(&who, ValidatorStake { own: amount, delegated: 0 }); 70 | 71 | Ok(()) 72 | } 73 | 74 | pub fn delegate(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 75 | let who = ensure_signed(origin)?; 76 | 77 | ensure!(!Delegators::::contains_key(&who), "AlreadyDelegator"); 78 | ensure!( 79 | pallet_currency::Balances::::get(&who).map_or(false, |b| b >= amount), 80 | "InsufficientFunds" 81 | ); 82 | ensure!(Validators::::contains_key(&to), "NotRegistered"); 83 | 84 | Delegators::::insert(&who, amount); 85 | Validators::::mutate(&to, |maybe_stake| { 86 | maybe_stake.as_mut().map(|mut stake| stake.delegated += amount) 87 | }); 88 | 89 | Ok(()) 90 | } 91 | } 92 | 93 | #[pallet::hooks] 94 | impl Hooks for Pallet { 95 | fn on_initialize(now: T::BlockNumber) -> Weight { 96 | use frame::traits::Zero; 97 | 98 | if (now % T::EraDuration::get()).is_zero() && !now.is_zero() { 99 | let mut all_validators = Validators::::iter().collect::>(); 100 | all_validators.sort_by_key(|(_, stake)| stake.own + stake.delegated); 101 | all_validators.reverse(); 102 | ActiveValidators::::put( 103 | all_validators 104 | .into_iter() 105 | .take(T::ValidatorCount::get() as usize) 106 | .map(|(acc, _)| acc) 107 | .collect::>(), 108 | ); 109 | } 110 | 111 | Default::default() 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::{pallet as pallet_staking, *}; 118 | use crate::currency::pallet::{self as pallet_currency, Balance}; 119 | use frame::testing_prelude::*; 120 | use pallet_staking::{ActiveValidators, ValidatorStake, Validators}; 121 | 122 | type Extrinsic = MockUncheckedExtrinsic; 123 | type Block = MockBlock; 124 | 125 | construct_runtime!( 126 | pub struct Runtime 127 | where 128 | Block = Block, 129 | NodeBlock = Block, 130 | UncheckedExtrinsic = Extrinsic, 131 | { 132 | System: frame_system, 133 | Currency: pallet_currency, 134 | Staking: pallet_staking, 135 | } 136 | ); 137 | 138 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 139 | impl frame_system::Config for Runtime { 140 | type BaseCallFilter = frame_support::traits::Everything; 141 | type RuntimeOrigin = RuntimeOrigin; 142 | type RuntimeCall = RuntimeCall; 143 | type RuntimeEvent = RuntimeEvent; 144 | type PalletInfo = PalletInfo; 145 | type OnSetCode = (); 146 | } 147 | 148 | parameter_types! { 149 | pub static ValidatorCount: u32 = 2; 150 | pub const EraDuration: ::BlockNumber = 3; 151 | } 152 | 153 | impl pallet_staking::Config for Runtime { 154 | type ValidatorCount = ValidatorCount; 155 | type EraDuration = EraDuration; 156 | } 157 | 158 | impl pallet_currency::Config for Runtime {} 159 | 160 | struct ExtBuilder { 161 | validators: Vec<(::AccountId, Balance)>, 162 | delegators: Vec<( 163 | ::AccountId, 164 | ::AccountId, 165 | Balance, 166 | )>, 167 | balances: Vec<(::AccountId, Balance)>, 168 | } 169 | 170 | impl Default for ExtBuilder { 171 | fn default() -> Self { 172 | let instance = Self { 173 | validators: Default::default(), 174 | delegators: Default::default(), 175 | balances: Default::default(), 176 | }; 177 | instance.add_validator(1, 10).add_validator(2, 20).add_validator(3, 30) 178 | } 179 | } 180 | 181 | impl ExtBuilder { 182 | fn add_validator( 183 | mut self, 184 | validator: ::AccountId, 185 | self_stake: Balance, 186 | ) -> Self { 187 | self.balances.push((validator, self_stake)); 188 | self.validators.push((validator, self_stake)); 189 | self 190 | } 191 | 192 | fn add_delegator( 193 | mut self, 194 | delegator: ::AccountId, 195 | delegatee: ::AccountId, 196 | stake: Balance, 197 | ) -> Self { 198 | self.balances.push((delegator, stake)); 199 | self.delegators.push((delegator, delegatee, stake)); 200 | self 201 | } 202 | 203 | fn build_and_execute(self, test: impl FnOnce() -> ()) { 204 | let system = frame_system::GenesisConfig::default(); 205 | let currency = pallet_currency::GenesisConfig { balances: self.balances }; 206 | let staking = pallet_staking::GenesisConfig { 207 | validators: self.validators, 208 | delegators: self.delegators, 209 | }; 210 | let runtime_genesis = RuntimeGenesisConfig { system, currency, staking }; 211 | let mut ext = TestState::new(runtime_genesis.build_storage().unwrap()); 212 | 213 | // process block 0 to simulate a proper genesis. Not mandatory to be done this way. 214 | // This sets the current block number (to be executed) to 1. 215 | ext.execute_with(next_block); 216 | ext.execute_with(test); 217 | } 218 | } 219 | 220 | fn next_block() { 221 | let now = frame_system::Pallet::::block_number(); 222 | pallet_staking::Pallet::::on_initialize(now); 223 | frame_system::Pallet::::set_block_number(now + 1); 224 | } 225 | 226 | #[test] 227 | fn basic_setup_works() { 228 | ExtBuilder::default().build_and_execute(|| { 229 | assert_eq!(frame_system::Pallet::::block_number(), 1); 230 | assert_eq!( 231 | Validators::::get(1).unwrap(), 232 | ValidatorStake { own: 10, delegated: 0 } 233 | ); 234 | assert_eq!( 235 | Validators::::get(2).unwrap(), 236 | ValidatorStake { own: 20, delegated: 0 } 237 | ); 238 | assert_eq!( 239 | Validators::::get(3).unwrap(), 240 | ValidatorStake { own: 30, delegated: 0 } 241 | ); 242 | assert_eq!(Validators::::iter().count(), 3); 243 | assert!(ActiveValidators::::get().is_empty()); 244 | }) 245 | } 246 | 247 | #[test] 248 | fn selects_validators() { 249 | ExtBuilder::default().build_and_execute(|| { 250 | // given initial state, 251 | 252 | // when processing block 1, nothing will happen. 253 | next_block(); 254 | assert!(ActiveValidators::::get().is_empty()); 255 | 256 | // when processing block 2, nothing will happen. 257 | next_block(); 258 | assert!(ActiveValidators::::get().is_empty()); 259 | 260 | // when processing block 3, new validators will be selected. 261 | next_block(); 262 | assert_eq!(ActiveValidators::::get(), vec![3, 2]); 263 | }) 264 | } 265 | 266 | #[test] 267 | fn considers_delegators() { 268 | // typically 2 and 3 win, and 1 and 3 269 | ExtBuilder::default().add_delegator(42, 1, 30).build_and_execute(|| { 270 | // given initial state, 271 | assert!(pallet_staking::Delegators::::get(42).is_some()); 272 | 273 | // when processing block 1 and 2, nothing will happen. 274 | next_block(); 275 | next_block(); 276 | assert!(ActiveValidators::::get().is_empty()); 277 | 278 | // when processing block 3, new validators will be selected. 279 | next_block(); 280 | assert_eq!(ActiveValidators::::get(), vec![1, 3]); 281 | }) 282 | } 283 | 284 | #[test] 285 | fn selects_right_number_of_validators() { 286 | ExtBuilder::default().build_and_execute(|| { 287 | // when processing block 1 and 2, nothing will happen. 288 | next_block(); 289 | next_block(); 290 | assert!(ActiveValidators::::get().is_empty()); 291 | 292 | // set the `Get` implementor static test variable to 3. 293 | ValidatorCount::set(3); 294 | 295 | next_block(); 296 | assert_eq!(ActiveValidators::::get(), vec![3, 2, 1]); 297 | 298 | // this time, set to 1. 299 | next_block(); 300 | next_block(); 301 | assert_eq!(ActiveValidators::::get(), vec![3, 2, 1]); 302 | 303 | ValidatorCount::set(1); 304 | next_block(); 305 | assert_eq!(ActiveValidators::::get(), vec![3]); 306 | }) 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /staging/pallets/src/staking.rs: -------------------------------------------------------------------------------- 1 | pub use pallet::*; 2 | 3 | #[frame::pallet(dev_mode)] 4 | pub mod pallet { 5 | use crate::currency::pallet::{self as pallet_currency, Balance}; 6 | use frame::{ 7 | derive::{Decode, DefaultNoBound, Encode, TypeInfo}, 8 | prelude::*, 9 | }; 10 | 11 | #[pallet::config] 12 | pub trait Config: frame_system::Config + pallet_currency::Config { 13 | type ValidatorCount: Get; 14 | type EraDuration: Get>; 15 | } 16 | 17 | #[pallet::pallet] 18 | pub struct Pallet(_); 19 | 20 | #[derive(Encode, Decode, TypeInfo, Eq, PartialEq, Clone, Debug)] 21 | pub struct ValidatorStake { 22 | pub(crate) own: Balance, 23 | pub(crate) delegated: Balance, 24 | } 25 | 26 | #[pallet::storage] 27 | pub type Validators = StorageMap<_, _, T::AccountId, ValidatorStake>; 28 | 29 | #[pallet::storage] 30 | pub type Delegators = StorageMap<_, _, T::AccountId, Balance>; 31 | 32 | #[pallet::storage] 33 | pub type ActiveValidators = StorageValue<_, Vec, ValueQuery>; 34 | 35 | #[derive(DefaultNoBound)] 36 | #[pallet::genesis_config] 37 | pub struct GenesisConfig { 38 | validators: Vec<(T::AccountId, Balance)>, 39 | delegators: Vec<(T::AccountId, T::AccountId, Balance)>, 40 | } 41 | 42 | // TODO: 43 | // https://github.com/paritytech/polkadot-sdk/pull/1642/files#diff-1a8ad3ec3e24e92089201972e112619421ef6c31484f65d45d30da7a8fae69fbR41 44 | use frame::deps::sp_runtime; 45 | #[pallet::genesis_build] 46 | impl BuildGenesisConfig for GenesisConfig { 47 | fn build(&self) { 48 | use frame::deps::frame_support::assert_ok; 49 | use frame_system::RawOrigin; 50 | for (validator, self_stake) in &self.validators { 51 | let raw_origin = RawOrigin::Signed(validator.clone()); 52 | 53 | assert_ok!(Pallet::::register(raw_origin.into(), *self_stake)); 54 | } 55 | 56 | for (delegator, delegatee, stake) in &self.delegators { 57 | let raw_origin = RawOrigin::Signed(delegator.clone()); 58 | assert_ok!(Pallet::::delegate(raw_origin.into(), delegatee.clone(), *stake)); 59 | } 60 | } 61 | } 62 | 63 | #[pallet::call] 64 | impl Pallet { 65 | pub fn register(origin: OriginFor, amount: Balance) -> DispatchResult { 66 | let who = ensure_signed(origin)?; 67 | 68 | ensure!(!Validators::::contains_key(&who), "AlreadyRegistered"); 69 | ensure!( 70 | pallet_currency::Balances::::get(&who).map_or(false, |b| b >= amount), 71 | "InsufficientFunds" 72 | ); 73 | 74 | Validators::::insert(&who, ValidatorStake { own: amount, delegated: 0 }); 75 | 76 | Ok(()) 77 | } 78 | 79 | pub fn delegate(origin: OriginFor, to: T::AccountId, amount: Balance) -> DispatchResult { 80 | let who = ensure_signed(origin)?; 81 | 82 | ensure!(!Delegators::::contains_key(&who), "AlreadyDelegator"); 83 | ensure!( 84 | pallet_currency::Balances::::get(&who).map_or(false, |b| b >= amount), 85 | "InsufficientFunds" 86 | ); 87 | 88 | // TODO: we can basically remove this because we have transactional. 89 | ensure!(Validators::::contains_key(&to), "NotRegistered"); 90 | 91 | Delegators::::insert(&who, amount); 92 | Validators::::mutate(&to, |maybe_stake| { 93 | maybe_stake.as_mut().map(|stake| stake.delegated += amount) 94 | }); 95 | 96 | Ok(()) 97 | } 98 | } 99 | 100 | #[pallet::hooks] 101 | impl Hooks> for Pallet { 102 | fn on_initialize(now: BlockNumberFor) -> Weight { 103 | use frame::traits::Zero; 104 | 105 | if (now % T::EraDuration::get()).is_zero() && !now.is_zero() { 106 | let mut all_validators = Validators::::iter().collect::>(); 107 | all_validators.sort_by_key(|(_, stake)| stake.own + stake.delegated); 108 | all_validators.reverse(); 109 | ActiveValidators::::put( 110 | all_validators 111 | .into_iter() 112 | .take(T::ValidatorCount::get() as usize) 113 | .map(|(acc, _)| acc) 114 | .collect::>(), 115 | ); 116 | } 117 | 118 | Default::default() 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod tests { 124 | use crate::{ 125 | currency::pallet::{self as pallet_currency, Balance}, 126 | staking::pallet::{self as pallet_staking, *}, 127 | }; 128 | use frame::testing_prelude::*; 129 | use pallet_staking::{ActiveValidators, ValidatorStake, Validators}; 130 | 131 | type AccountId = ::AccountId; 132 | 133 | construct_runtime!( 134 | pub struct Runtime { 135 | System: frame_system, 136 | Currency: pallet_currency, 137 | Staking: pallet_staking, 138 | } 139 | ); 140 | 141 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] 142 | impl frame_system::Config for Runtime { 143 | type Block = MockBlock; 144 | } 145 | 146 | // TODO: if we were to have private `struct` runtime, then these would also not need to be 147 | // pub. 148 | parameter_types! { 149 | pub static ValidatorCount: u32 = 2; 150 | pub const EraDuration: BlockNumberFor = 3; 151 | } 152 | 153 | impl pallet_staking::Config for Runtime { 154 | type ValidatorCount = ValidatorCount; 155 | type EraDuration = EraDuration; 156 | } 157 | 158 | impl pallet_currency::Config for Runtime {} 159 | 160 | struct ExtBuilder { 161 | validators: Vec<(AccountId, Balance)>, 162 | delegators: Vec<(AccountId, AccountId, Balance)>, 163 | balances: Vec<(AccountId, Balance)>, 164 | } 165 | 166 | impl Default for ExtBuilder { 167 | fn default() -> Self { 168 | let instance = Self { 169 | validators: Default::default(), 170 | delegators: Default::default(), 171 | balances: Default::default(), 172 | }; 173 | instance.add_validator(1, 10).add_validator(2, 20).add_validator(3, 30) 174 | } 175 | } 176 | 177 | impl ExtBuilder { 178 | fn add_validator(mut self, validator: AccountId, self_stake: Balance) -> Self { 179 | self.balances.push((validator, self_stake)); 180 | self.validators.push((validator, self_stake)); 181 | self 182 | } 183 | 184 | fn add_delegator( 185 | mut self, 186 | delegator: AccountId, 187 | delegatee: AccountId, 188 | stake: Balance, 189 | ) -> Self { 190 | self.balances.push((delegator, stake)); 191 | self.delegators.push((delegator, delegatee, stake)); 192 | self 193 | } 194 | 195 | fn build_and_execute(self, test: impl FnOnce() -> ()) { 196 | // In this example, we care about the order of genesis-initialization, so we use the 197 | // alternative syntax. 198 | // let mut storage: Storage = Default::default(); 199 | // frame_system::GenesisConfig::default() 200 | // .assimilate_storage::(&mut storage) 201 | // .unwrap(); 202 | // pallet_currency::GenesisConfig:: { balances: self.balances } 203 | // .assimilate_storage(&mut storage) 204 | // .unwrap(); 205 | // pallet_staking::GenesisConfig:: { 206 | // validators: self.validators, 207 | // delegators: self.delegators, 208 | // } 209 | // .assimilate_storage(&mut storage) 210 | // .unwrap(); 211 | // let mut ext = TestState::new(storage); 212 | 213 | let system = frame_system::GenesisConfig::default(); 214 | let currency = pallet_currency::GenesisConfig { balances: self.balances }; 215 | let staking = pallet_staking::GenesisConfig { 216 | validators: self.validators, 217 | delegators: self.delegators, 218 | }; 219 | let runtime_genesis = RuntimeGenesisConfig { system, currency, staking }; 220 | let mut ext = TestState::new(runtime_genesis.build_storage().unwrap()); 221 | 222 | // process block 0 to simulate a proper genesis. Not mandatory to be done this way. 223 | // This sets the current block number (to be executed) to 1. 224 | ext.execute_with(next_block); 225 | ext.execute_with(test); 226 | } 227 | } 228 | 229 | fn next_block() { 230 | let now = frame_system::Pallet::::block_number(); 231 | pallet_staking::Pallet::::on_initialize(now); 232 | frame_system::Pallet::::set_block_number(now + 1); 233 | } 234 | 235 | #[test] 236 | fn basic_setup_works() { 237 | ExtBuilder::default().build_and_execute(|| { 238 | assert_eq!(frame_system::Pallet::::block_number(), 1); 239 | assert_eq!( 240 | Validators::::get(1).unwrap(), 241 | ValidatorStake { own: 10, delegated: 0 } 242 | ); 243 | assert_eq!( 244 | Validators::::get(2).unwrap(), 245 | ValidatorStake { own: 20, delegated: 0 } 246 | ); 247 | assert_eq!( 248 | Validators::::get(3).unwrap(), 249 | ValidatorStake { own: 30, delegated: 0 } 250 | ); 251 | assert_eq!(Validators::::iter().count(), 3); 252 | assert!(ActiveValidators::::get().is_empty()); 253 | }) 254 | } 255 | 256 | #[test] 257 | fn selects_validators() { 258 | ExtBuilder::default().build_and_execute(|| { 259 | // given initial state, 260 | 261 | // when processing block 1, nothing will happen. 262 | next_block(); 263 | assert!(ActiveValidators::::get().is_empty()); 264 | 265 | // when processing block 2, nothing will happen. 266 | next_block(); 267 | assert!(ActiveValidators::::get().is_empty()); 268 | 269 | // when processing block 3, new validators will be selected. 270 | next_block(); 271 | assert_eq!(ActiveValidators::::get(), vec![3, 2]); 272 | }) 273 | } 274 | 275 | #[test] 276 | fn considers_delegators() { 277 | // typically 2 and 3 win, and 1 and 3 278 | ExtBuilder::default().add_delegator(42, 1, 30).build_and_execute(|| { 279 | // given initial state, 280 | assert!(pallet_staking::Delegators::::get(42).is_some()); 281 | 282 | // when processing block 1 and 2, nothing will happen. 283 | next_block(); 284 | next_block(); 285 | assert!(ActiveValidators::::get().is_empty()); 286 | 287 | // when processing block 3, new validators will be selected. 288 | next_block(); 289 | assert_eq!(ActiveValidators::::get(), vec![1, 3]); 290 | }) 291 | } 292 | 293 | #[test] 294 | fn selects_right_number_of_validators() { 295 | ExtBuilder::default().build_and_execute(|| { 296 | // when processing block 1 and 2, nothing will happen. 297 | next_block(); 298 | next_block(); 299 | assert!(ActiveValidators::::get().is_empty()); 300 | 301 | // set the `Get` implementor static test variable to 3. 302 | ValidatorCount::set(3); 303 | 304 | next_block(); 305 | assert_eq!(ActiveValidators::::get(), vec![3, 2, 1]); 306 | 307 | // this time, set to 1. 308 | next_block(); 309 | next_block(); 310 | assert_eq!(ActiveValidators::::get(), vec![3, 2, 1]); 311 | 312 | ValidatorCount::set(1); 313 | next_block(); 314 | assert_eq!(ActiveValidators::::get(), vec![3]); 315 | }) 316 | } 317 | } 318 | } 319 | --------------------------------------------------------------------------------