├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── runtimes ├── Cargo.lock ├── Cargo.toml ├── asset-hub-kusama │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── coretime-kusama │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── kusama │ ├── Cargo.toml │ └── src │ │ └── main.rs └── src │ └── lib.rs └── templates ├── Cargo.lock ├── Cargo.toml ├── kitchensink ├── Cargo.toml └── src │ └── main.rs ├── solochain ├── Cargo.toml └── src │ └── main.rs └── src └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | SKIP_WASM_BUILD: 1 14 | 15 | jobs: 16 | run-fmt-and-clippy: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - run: rustup default nightly 22 | - run: rustup component add rustfmt clippy rust-src 23 | - uses: Swatinem/rust-cache@v2 24 | with: 25 | workspaces: "runtimes -> runtimes/target\ntemplates -> templates/target" 26 | - name: Run runtimes fmt 27 | run: cargo fmt --check --manifest-path runtimes/Cargo.toml 28 | - name: Run templates fmt 29 | run: cargo fmt --check --manifest-path templates/Cargo.toml 30 | - name: Run runtimes clippy 31 | run: cargo clippy -q --workspace --no-deps --manifest-path runtimes/Cargo.toml -- -D clippy::pedantic 32 | - name: Run templates clippy 33 | run: cargo clippy -q --workspace --no-deps --manifest-path templates/Cargo.toml -- -D clippy::pedantic 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cargo 2 | .cargo 3 | target 4 | output 5 | cargo_home 6 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Substrate Runtime Fuzzer 2 | 3 | `substrate-runtime-fuzzer` is a [substrate](https://github.com/paritytech/polkadot-sdk/tree/master/substrate) runtime fuzzing harness continuously developed at [SRLabs](https://srlabs.de) since 2020. 4 | 5 | It has been used as a part of our continuous audit process for [Parity](https://parity.io) as well as many parachains in the Polkadot and Kusama ecosystem. 6 | 7 | As a part of the [Substrate Builders Program](https://substrate.io/ecosystem/substrate-builders-program/), this fuzzer has been used on over 30 different projects, finding dozens of critical vulnerabilities in the process. 8 | 9 | ## How do I use it? 10 | 11 | Here are the steps to launch `substrate-runtime-fuzzer` against the [solochain template runtime](https://github.com/paritytech/polkadot-sdk/tree/master/templates/solochain/runtime): 12 | 13 | ``` 14 | cargo install ziggy cargo-afl honggfuzz grcov 15 | rustup target add wasm32-unknown-unknown 16 | git clone https://github.com/srlabs/substrate-runtime-fuzzer 17 | cd substrate-runtime-fuzzer/templates/solochain/ 18 | SKIP_WASM_BUILD=1 cargo ziggy fuzz 19 | ``` 20 | 21 | You can use the the `-j n` flag to run $n$ jobs in parallel. 22 | We also recommend using `--no-honggfuzz` if running for a long time with many cores, as honggfuzz will have a tendency to slow things down and bloat the corpus in this situation. 23 | 24 | `SKIP_WASM_BUILD=1` will reduce compilation time and the binary's size. We are not fuzzing the wasm runtime. 25 | 26 | ### What is "ziggy"? 27 | 28 | [`ziggy`](https://github.com/srlabs/ziggy/) is a fuzzer management tool written by the team at SRLabs. 29 | 30 | It will spawn multiple different coverage-guided fuzzers with the right configuration and regularly minimize the corpus and share it between instances. 31 | 32 | ## How do I use this on my substrate-based runtime? 33 | 34 | The first step is creating a project and adding the same dependencies as [this project](./templates/solochain/Cargo.toml). 35 | Then, you can modify the `solochain-template-runtime` dependency to your own runtime (either as a local `path` or as a git URL). 36 | 37 | Finally you should add a genesis configuration to make sure the fuzzer can reach as much of the code as possible. 38 | You can take inspiration from the genesis configuration of the [kitchensink fuzzer](./templates/kitchensink/src/main.rs). 39 | 40 | ## License 41 | 42 | Substrate Runtime Fuzzer is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0). 43 | 44 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 45 | -------------------------------------------------------------------------------- /runtimes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "substrate-runtimes-fuzzers" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [workspace] 8 | resolver = "2" 9 | members = [ 10 | "kusama", 11 | "asset-hub-kusama", 12 | "coretime-kusama", 13 | ] -------------------------------------------------------------------------------- /runtimes/asset-hub-kusama/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asset-hub-kusama-fuzzer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | ziggy = { version = "1.3.2", default-features = false } 9 | 10 | asset-hub-kusama-runtime = { git = "https://github.com/polkadot-fellows/runtimes.git", tag = "v1.4.3", default-features = false } 11 | staging-xcm = { default-features = false , version = "14.2.0" } 12 | 13 | parachains-common = { default-features = false , version = "18.0.0" } 14 | 15 | codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive", "max-encoded-len"] } 16 | 17 | frame-support = { default-features = false, version = "38.2.0" } 18 | frame-system = { default-features = false, version = "38.0.0" } 19 | 20 | sp-runtime = { default-features = false, version = "39.0.5" } 21 | sp-state-machine = { default-features = false, version = "0.42.0" } 22 | sp-consensus-aura = { default-features = false, version = "0.40.0" } 23 | sp-trie = { default-features = false, version = "37.0.0" } 24 | sp-core = { default-features = false, version = "34.0.0" } 25 | 26 | pallet-timestamp = { default-features = false, version = "37.0.0" } 27 | pallet-balances = { default-features = false, version = "39.0.0" } 28 | pallet-xcm = { default-features = false, version = "17.0.1" } 29 | pallet-utility = { default-features = false, version = "38.0.0" } 30 | pallet-proxy = { default-features = false, version = "38.0.0" } 31 | pallet-multisig = { default-features = false, version = "38.0.0" } 32 | pallet-vesting = { default-features = false, version = "38.0.0" } 33 | 34 | cumulus-primitives-core = { default-features = false , version = "0.16.0" } 35 | cumulus-primitives-parachain-inherent = { default-features = false , version = "0.16.0" } 36 | cumulus-pallet-parachain-system = { default-features = false, version = "0.17.1" } 37 | cumulus-test-relay-sproof-builder = { default-features = false , version = "0.16.0" } 38 | 39 | polkadot-parachain-primitives = { default-features = false, version = "14.0.0" } 40 | polkadot-primitives = { default-features = false, version = "16.0.0" } 41 | 42 | [features] 43 | default = ["std", "try-runtime"] 44 | fuzzing = [] 45 | std = [ 46 | "asset-hub-kusama-runtime/std", 47 | "parachains-common/std", 48 | "codec/std", 49 | "frame-support/std", 50 | "frame-system/std", 51 | "sp-runtime/std", 52 | "sp-state-machine/std", 53 | "sp-consensus-aura/std", 54 | "pallet-timestamp/std", 55 | "pallet-balances/std", 56 | "cumulus-primitives-core/std", 57 | "cumulus-primitives-parachain-inherent/std", 58 | "cumulus-pallet-parachain-system/std", 59 | "cumulus-test-relay-sproof-builder/std", 60 | "polkadot-parachain-primitives/std", 61 | "sp-trie/std", 62 | ] 63 | try-runtime = [ 64 | "asset-hub-kusama-runtime/try-runtime", 65 | "frame-support/try-runtime", 66 | "frame-system/try-runtime", 67 | "sp-runtime/try-runtime", 68 | "pallet-timestamp/try-runtime", 69 | ] 70 | -------------------------------------------------------------------------------- /runtimes/asset-hub-kusama/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use asset_hub_kusama_runtime::{ 3 | AllPalletsWithSystem, Balances, Executive, ParachainSystem, Runtime, RuntimeCall, 4 | RuntimeOrigin, Timestamp, 5 | }; 6 | use codec::{DecodeLimit, Encode}; 7 | use cumulus_primitives_core::relay_chain::Header; 8 | use frame_support::{ 9 | dispatch::GetDispatchInfo, 10 | pallet_prelude::Weight, 11 | traits::{IntegrityTest, TryState, TryStateSelect}, 12 | weights::constants::WEIGHT_REF_TIME_PER_SECOND, 13 | }; 14 | use frame_system::Account; 15 | use pallet_balances::{Holds, TotalIssuance}; 16 | use parachains_common::{AccountId, Balance, SLOT_DURATION}; 17 | use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; 18 | use sp_runtime::{ 19 | testing::H256, 20 | traits::{Dispatchable, Header as _}, 21 | Digest, DigestItem, Storage, 22 | }; 23 | use sp_state_machine::BasicExternalities; 24 | use std::{ 25 | collections::BTreeMap, 26 | iter, 27 | time::{Duration, Instant}, 28 | }; 29 | 30 | fn main() { 31 | let accounts: Vec = (0..5).map(|i| [i; 32].into()).collect(); 32 | let genesis = generate_genesis(&accounts); 33 | 34 | ziggy::fuzz!(|data: &[u8]| { 35 | process_input(&accounts, &genesis, data); 36 | }); 37 | } 38 | 39 | fn generate_genesis(accounts: &[AccountId]) -> Storage { 40 | use asset_hub_kusama_runtime::{ 41 | AssetsConfig, AuraConfig, AuraExtConfig, BalancesConfig, CollatorSelectionConfig, 42 | ForeignAssetsConfig, ParachainInfoConfig, ParachainSystemConfig, PolkadotXcmConfig, 43 | PoolAssetsConfig, RuntimeGenesisConfig, SessionConfig, SessionKeys, SystemConfig, 44 | TransactionPaymentConfig, VestingConfig, 45 | }; 46 | use sp_consensus_aura::sr25519::AuthorityId as AuraId; 47 | use sp_runtime::app_crypto::ByteArray; 48 | use sp_runtime::BuildStorage; 49 | 50 | let initial_authorities: Vec<(AccountId, AuraId)> = 51 | vec![([0; 32].into(), AuraId::from_slice(&[0; 32]).unwrap())]; 52 | 53 | RuntimeGenesisConfig { 54 | system: SystemConfig::default(), 55 | balances: BalancesConfig { 56 | // Configure endowed accounts with initial balance of 1 << 60. 57 | balances: accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), 58 | }, 59 | aura: AuraConfig::default(), 60 | session: SessionConfig { 61 | keys: initial_authorities 62 | .iter() 63 | .map(|x| (x.0.clone(), x.0.clone(), SessionKeys { aura: x.1.clone() })) 64 | .collect::>(), 65 | non_authority_keys: vec![], 66 | }, 67 | collator_selection: CollatorSelectionConfig { 68 | invulnerables: initial_authorities.iter().map(|x| (x.0.clone())).collect(), 69 | candidacy_bond: 1 << 57, 70 | desired_candidates: 1, 71 | }, 72 | aura_ext: AuraExtConfig::default(), 73 | parachain_info: ParachainInfoConfig::default(), 74 | parachain_system: ParachainSystemConfig::default(), 75 | polkadot_xcm: PolkadotXcmConfig::default(), 76 | assets: AssetsConfig::default(), 77 | foreign_assets: ForeignAssetsConfig::default(), 78 | pool_assets: PoolAssetsConfig::default(), 79 | transaction_payment: TransactionPaymentConfig::default(), 80 | vesting: VestingConfig::default(), 81 | } 82 | .build_storage() 83 | .unwrap() 84 | } 85 | 86 | fn recursively_find_call(call: RuntimeCall, matches_on: fn(RuntimeCall) -> bool) -> bool { 87 | if let RuntimeCall::Utility( 88 | pallet_utility::Call::batch { calls } 89 | | pallet_utility::Call::force_batch { calls } 90 | | pallet_utility::Call::batch_all { calls }, 91 | ) = call 92 | { 93 | for call in calls { 94 | if recursively_find_call(call.clone(), matches_on) { 95 | return true; 96 | } 97 | } 98 | } else if let RuntimeCall::Multisig(pallet_multisig::Call::as_multi_threshold_1 { 99 | call, .. 100 | }) 101 | | RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) 102 | | RuntimeCall::Proxy(pallet_proxy::Call::proxy { call, .. }) = call 103 | { 104 | return recursively_find_call(*call.clone(), matches_on); 105 | } else if matches_on(call) { 106 | return true; 107 | } 108 | false 109 | } 110 | 111 | fn process_input(accounts: &[AccountId], genesis: &Storage, data: &[u8]) { 112 | // We build the list of extrinsics we will execute 113 | let mut extrinsic_data = data; 114 | // Vec<(lapse, origin, extrinsic)> 115 | #[allow(deprecated)] 116 | let extrinsics: Vec<(u8, u8, RuntimeCall)> = 117 | iter::from_fn(|| DecodeLimit::decode_with_depth_limit(64, &mut extrinsic_data).ok()) 118 | .filter(|(_, _, x): &(_, _, RuntimeCall)| { 119 | !recursively_find_call(x.clone(), |call| { 120 | // We filter out calls with Fungible(0) as they cause a debug crash 121 | matches!(call.clone(), RuntimeCall::PolkadotXcm(pallet_xcm::Call::execute { message, .. }) 122 | if matches!(message.as_ref(), staging_xcm::VersionedXcm::V2(staging_xcm::v2::Xcm(msg)) 123 | if msg.iter().any(|m| matches!(m, staging_xcm::opaque::v2::prelude::BuyExecution { fees: staging_xcm::v2::MultiAsset { fun, .. }, .. } 124 | if fun == &staging_xcm::v2::Fungibility::Fungible(0) 125 | )) 126 | ) 127 | ) || matches!(call.clone(), RuntimeCall::System(_)) 128 | || matches!(call.clone(), RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. })) 129 | }) 130 | }).collect(); 131 | 132 | if extrinsics.is_empty() { 133 | return; 134 | } 135 | 136 | let mut block: u32 = 1; 137 | let mut weight: Weight = Weight::zero(); 138 | let mut elapsed: Duration = Duration::ZERO; 139 | 140 | BasicExternalities::execute_with_storage(&mut genesis.clone(), || { 141 | let initial_total_issuance = pallet_balances::TotalIssuance::::get(); 142 | 143 | initialize_block(block, &None); 144 | 145 | for (lapse, origin, extrinsic) in extrinsics { 146 | if lapse > 0 { 147 | let prev_header = finalize_block(elapsed); 148 | 149 | // We update our state variables 150 | block += u32::from(lapse); 151 | weight = Weight::zero(); 152 | elapsed = Duration::ZERO; 153 | 154 | // We start the next block 155 | initialize_block(block, &Some(prev_header)); 156 | } 157 | 158 | weight.saturating_accrue(extrinsic.get_dispatch_info().weight); 159 | if weight.ref_time() >= 2 * WEIGHT_REF_TIME_PER_SECOND { 160 | #[cfg(not(feature = "fuzzing"))] 161 | println!("Skipping because of max weight {weight}"); 162 | continue; 163 | } 164 | 165 | let origin = accounts[origin as usize % accounts.len()].clone(); 166 | 167 | #[cfg(not(feature = "fuzzing"))] 168 | println!("\n origin: {origin:?}"); 169 | #[cfg(not(feature = "fuzzing"))] 170 | println!(" call: {extrinsic:?}"); 171 | 172 | let now = Instant::now(); // We get the current time for timing purposes. 173 | #[allow(unused_variables)] 174 | let res = extrinsic.dispatch(RuntimeOrigin::signed(origin)); 175 | elapsed += now.elapsed(); 176 | 177 | #[cfg(not(feature = "fuzzing"))] 178 | println!(" result: {res:?}"); 179 | } 180 | 181 | finalize_block(elapsed); 182 | 183 | check_invariants(block, initial_total_issuance); 184 | }); 185 | } 186 | 187 | fn initialize_block(block: u32, prev_header: &Option
) { 188 | #[cfg(not(feature = "fuzzing"))] 189 | println!("\ninitializing block {block}"); 190 | 191 | let pre_digest = Digest { 192 | logs: vec![DigestItem::PreRuntime( 193 | AURA_ENGINE_ID, 194 | Slot::from(u64::from(block)).encode(), 195 | )], 196 | }; 197 | let parent_header = &Header::new( 198 | block, 199 | H256::default(), 200 | H256::default(), 201 | prev_header.clone().map(|x| x.hash()).unwrap_or_default(), 202 | pre_digest, 203 | ); 204 | Executive::initialize_block(parent_header); 205 | 206 | #[cfg(not(feature = "fuzzing"))] 207 | println!(" setting timestamp"); 208 | Timestamp::set(RuntimeOrigin::none(), u64::from(block) * SLOT_DURATION).unwrap(); 209 | 210 | #[cfg(not(feature = "fuzzing"))] 211 | println!(" setting parachain validation data"); 212 | let parachain_validation_data = { 213 | use cumulus_primitives_core::{relay_chain::HeadData, PersistedValidationData}; 214 | use cumulus_primitives_parachain_inherent::ParachainInherentData; 215 | use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; 216 | 217 | let parent_head = HeadData( 218 | prev_header 219 | .clone() 220 | .unwrap_or(parent_header.clone()) 221 | .encode(), 222 | ); 223 | let sproof_builder = RelayStateSproofBuilder { 224 | para_id: 100.into(), 225 | current_slot: cumulus_primitives_core::relay_chain::Slot::from(2 * u64::from(block)), 226 | included_para_head: Some(parent_head.clone()), 227 | ..Default::default() 228 | }; 229 | 230 | let (relay_parent_storage_root, relay_chain_state) = 231 | sproof_builder.into_state_root_and_proof(); 232 | ParachainInherentData { 233 | validation_data: polkadot_primitives::PersistedValidationData { 234 | parent_head, 235 | relay_parent_number: block, 236 | relay_parent_storage_root, 237 | max_pov_size: 1000, 238 | }, 239 | relay_chain_state, 240 | downward_messages: Vec::default(), 241 | horizontal_messages: BTreeMap::default(), 242 | } 243 | }; 244 | ParachainSystem::set_validation_data(RuntimeOrigin::none(), parachain_validation_data).unwrap(); 245 | } 246 | 247 | fn finalize_block(elapsed: Duration) -> Header { 248 | #[cfg(not(feature = "fuzzing"))] 249 | println!("\n time spent: {elapsed:?}"); 250 | assert!(elapsed.as_secs() <= 2, "block execution took too much time"); 251 | 252 | #[cfg(not(feature = "fuzzing"))] 253 | println!("finalizing block"); 254 | Executive::finalize_block() 255 | } 256 | 257 | fn check_invariants(block: u32, initial_total_issuance: Balance) { 258 | let mut counted_free = 0; 259 | let mut counted_reserved = 0; 260 | for (account, info) in Account::::iter() { 261 | let consumers = info.consumers; 262 | let providers = info.providers; 263 | assert!(!(consumers > 0 && providers == 0), "Invalid c/p state"); 264 | counted_free += info.data.free; 265 | counted_reserved += info.data.reserved; 266 | let max_lock: Balance = Balances::locks(&account) 267 | .iter() 268 | .map(|l| l.amount) 269 | .max() 270 | .unwrap_or_default(); 271 | assert_eq!( 272 | max_lock, info.data.frozen, 273 | "Max lock should be equal to frozen balance" 274 | ); 275 | let sum_holds: Balance = Holds::::get(&account) 276 | .iter() 277 | .map(|l| l.amount) 278 | .sum(); 279 | assert!( 280 | sum_holds <= info.data.reserved, 281 | "Sum of all holds ({sum_holds}) should be less than or equal to reserved balance {}", 282 | info.data.reserved 283 | ); 284 | } 285 | let total_issuance = TotalIssuance::::get(); 286 | let counted_issuance = counted_free + counted_reserved; 287 | // The reason we do not simply use `!=` here is that some balance might be transfered to another chain via XCM. 288 | // If we find some kind of workaround for this, we could replace `<` by `!=` here and make the check stronger. 289 | assert!(total_issuance <= counted_issuance,); 290 | assert!(total_issuance <= initial_total_issuance,); 291 | // We run all developer-defined integrity tests 292 | AllPalletsWithSystem::integrity_test(); 293 | AllPalletsWithSystem::try_state(block, TryStateSelect::All).unwrap(); 294 | } 295 | -------------------------------------------------------------------------------- /runtimes/coretime-kusama/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coretime-kusama-fuzzer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | ziggy = { version = "1.3.2", default-features = false } 9 | 10 | coretime-kusama-runtime = { git = "https://github.com/polkadot-fellows/runtimes.git", tag = "v1.4.3", default-features = false } 11 | system-parachains-constants = { git = "https://github.com/polkadot-fellows/runtimes.git", tag = "v1.4.3", default-features = false } 12 | 13 | parachains-common = { default-features = false , version = "18.0.0" } 14 | 15 | codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive", "max-encoded-len"] } 16 | 17 | frame-support = { default-features = false, version = "38.2.0" } 18 | frame-system = { default-features = false, version = "38.0.0" } 19 | 20 | sp-runtime = { default-features = false, version = "39.0.5" } 21 | sp-state-machine = { default-features = false, version = "0.43.0" } 22 | sp-consensus-aura = { default-features = false, version = "0.40.0" } 23 | sp-application-crypto = { default-features = false , version = "38.0.0" } 24 | 25 | pallet-timestamp = { default-features = false, version = "37.0.0" } 26 | pallet-balances = { default-features = false, version = "39.0.0" } 27 | pallet-utility = { default-features = false, version = "38.0.0" } 28 | pallet-multisig = { default-features = false, version = "38.0.0" } 29 | pallet-proxy = { default-features = false, version = "38.0.0" } 30 | pallet-broker = { default-features = false, version = "0.17.2" } 31 | pallet-xcm = { default-features = false , version = "17.0.1" } 32 | 33 | cumulus-primitives-core = { default-features = false , version = "0.16.0" } 34 | cumulus-primitives-parachain-inherent = { default-features = false , version = "0.16.0" } 35 | cumulus-pallet-parachain-system = { default-features = false, version = "0.17.1" } 36 | cumulus-test-relay-sproof-builder = { default-features = false , version = "0.16.0" } 37 | 38 | [features] 39 | default = ["std", "try-runtime"] 40 | fuzzing = [] 41 | std = [ 42 | "coretime-kusama-runtime/std", 43 | "codec/std", 44 | "sp-runtime/std", 45 | "frame-support/std", 46 | "frame-system/std", 47 | "pallet-timestamp/std", 48 | "pallet-xcm/std", 49 | "sp-consensus-aura/std", 50 | "cumulus-primitives-core/std", 51 | "cumulus-primitives-parachain-inherent/std", 52 | "cumulus-pallet-parachain-system/std", 53 | "cumulus-test-relay-sproof-builder/std", 54 | ] 55 | try-runtime = [ 56 | "coretime-kusama-runtime/try-runtime", 57 | "frame-support/try-runtime", 58 | "frame-system/try-runtime", 59 | "sp-runtime/try-runtime", 60 | "pallet-timestamp/try-runtime", 61 | ] 62 | -------------------------------------------------------------------------------- /runtimes/coretime-kusama/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use codec::{DecodeLimit, Encode}; 3 | use coretime_kusama_runtime::{ 4 | AllPalletsWithSystem, Balances, Broker, Executive, ParachainSystem, Runtime, RuntimeCall, 5 | RuntimeOrigin, Timestamp, 6 | }; 7 | use cumulus_primitives_core::relay_chain::Header; 8 | use frame_support::{ 9 | dispatch::GetDispatchInfo, 10 | pallet_prelude::Weight, 11 | traits::{IntegrityTest, TryState, TryStateSelect}, 12 | weights::constants::WEIGHT_REF_TIME_PER_SECOND, 13 | }; 14 | use frame_system::Account; 15 | use pallet_balances::{Holds, TotalIssuance}; 16 | use pallet_broker::{ConfigRecord, ConfigRecordOf, CoreIndex, CoreMask, Timeslice}; 17 | use parachains_common::{AccountId, Balance, SLOT_DURATION}; 18 | use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; 19 | use sp_runtime::{ 20 | testing::H256, 21 | traits::{AccountIdConversion, Dispatchable, Header as _}, 22 | Digest, DigestItem, Perbill, Storage, 23 | }; 24 | use sp_state_machine::BasicExternalities; 25 | use std::{ 26 | collections::{BTreeMap, HashMap}, 27 | iter, 28 | time::{Duration, Instant}, 29 | }; 30 | use system_parachains_constants::kusama::currency::UNITS; 31 | 32 | fn main() { 33 | let accounts: Vec = (0..5).map(|i| [i; 32].into()).collect(); 34 | let genesis = generate_genesis(&accounts); 35 | 36 | ziggy::fuzz!(|data: &[u8]| { 37 | process_input(&accounts, &genesis, data); 38 | }); 39 | } 40 | 41 | fn generate_genesis(accounts: &[AccountId]) -> Storage { 42 | use coretime_kusama_runtime::BuildStorage; 43 | use coretime_kusama_runtime::{ 44 | AuraConfig, AuraExtConfig, BalancesConfig, BrokerConfig, CollatorSelectionConfig, 45 | ParachainInfoConfig, ParachainSystemConfig, PolkadotXcmConfig, RuntimeGenesisConfig, 46 | SessionConfig, SessionKeys, SystemConfig, TransactionPaymentConfig, 47 | }; 48 | use sp_application_crypto::ByteArray; 49 | use sp_consensus_aura::sr25519::AuthorityId as AuraId; 50 | 51 | let initial_authorities: Vec<(AccountId, AuraId)> = 52 | vec![([0; 32].into(), AuraId::from_slice(&[0; 32]).unwrap())]; 53 | 54 | let mut storage = RuntimeGenesisConfig { 55 | system: SystemConfig::default(), 56 | balances: BalancesConfig { 57 | // Configure endowed accounts with initial balance of 1 << 60. 58 | balances: accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), 59 | }, 60 | aura: AuraConfig::default(), 61 | broker: BrokerConfig::default(), 62 | session: SessionConfig { 63 | keys: initial_authorities 64 | .iter() 65 | .map(|x| (x.0.clone(), x.0.clone(), SessionKeys { aura: x.1.clone() })) 66 | .collect::>(), 67 | non_authority_keys: vec![], 68 | }, 69 | collator_selection: CollatorSelectionConfig { 70 | invulnerables: initial_authorities.iter().map(|x| (x.0.clone())).collect(), 71 | candidacy_bond: 1 << 57, 72 | desired_candidates: 1, 73 | }, 74 | aura_ext: AuraExtConfig::default(), 75 | parachain_info: ParachainInfoConfig::default(), 76 | parachain_system: ParachainSystemConfig::default(), 77 | polkadot_xcm: PolkadotXcmConfig::default(), 78 | transaction_payment: TransactionPaymentConfig::default(), 79 | } 80 | .build_storage() 81 | .unwrap(); 82 | 83 | BasicExternalities::execute_with_storage(&mut storage, || { 84 | initialize_block(1, &None); 85 | Broker::configure(RuntimeOrigin::root(), new_config()).unwrap(); 86 | Broker::start_sales(RuntimeOrigin::root(), 10 * UNITS, 1).unwrap(); 87 | 88 | initialize_block(2, &Some(finalize_block(Duration::ZERO))); 89 | }); 90 | 91 | storage 92 | } 93 | 94 | fn new_config() -> ConfigRecordOf { 95 | ConfigRecord { 96 | advance_notice: 1, 97 | interlude_length: 1, 98 | leadin_length: 2, 99 | ideal_bulk_proportion: Perbill::from_percent(100), 100 | limit_cores_offered: None, 101 | region_length: 3, 102 | renewal_bump: Perbill::from_percent(3), 103 | contribution_timeout: 1, 104 | } 105 | } 106 | 107 | fn recursively_find_call(call: RuntimeCall, matches_on: fn(RuntimeCall) -> bool) -> bool { 108 | if let RuntimeCall::Utility( 109 | pallet_utility::Call::batch { calls } 110 | | pallet_utility::Call::force_batch { calls } 111 | | pallet_utility::Call::batch_all { calls }, 112 | ) = call 113 | { 114 | for call in calls { 115 | if recursively_find_call(call.clone(), matches_on) { 116 | return true; 117 | } 118 | } 119 | } else if let RuntimeCall::Multisig(pallet_multisig::Call::as_multi_threshold_1 { 120 | call, .. 121 | }) 122 | | RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) 123 | | RuntimeCall::Proxy(pallet_proxy::Call::proxy { call, .. }) = call 124 | { 125 | return recursively_find_call(*call.clone(), matches_on); 126 | } else if matches_on(call) { 127 | return true; 128 | } 129 | false 130 | } 131 | 132 | fn process_input(accounts: &[AccountId], genesis: &Storage, data: &[u8]) { 133 | // We build the list of extrinsics we will execute 134 | let mut extrinsic_data = data; 135 | let extrinsics: Vec<(/* lapse */ u8, /* origin */ u8, RuntimeCall)> = 136 | iter::from_fn(|| DecodeLimit::decode_with_depth_limit(64, &mut extrinsic_data).ok()) 137 | .filter(|(_, _, x): &(_, _, RuntimeCall)| { 138 | !recursively_find_call(x.clone(), |call| { 139 | matches!(call.clone(), RuntimeCall::System(_)) 140 | || matches!( 141 | call.clone(), 142 | RuntimeCall::PolkadotXcm(pallet_xcm::Call::execute { .. }) 143 | ) 144 | }) 145 | }) 146 | .collect(); 147 | if extrinsics.is_empty() { 148 | return; 149 | } 150 | 151 | let mut block: u32 = 2; 152 | let mut weight: Weight = Weight::zero(); 153 | let mut elapsed: Duration = Duration::ZERO; 154 | 155 | BasicExternalities::execute_with_storage(&mut genesis.clone(), || { 156 | let initial_total_issuance = pallet_balances::TotalIssuance::::get(); 157 | 158 | for (lapse, origin, extrinsic) in extrinsics { 159 | if lapse > 0 { 160 | let prev_header = finalize_block(elapsed); 161 | 162 | // We update our state variables 163 | block += u32::from(lapse) * 393; 164 | weight = Weight::zero(); 165 | elapsed = Duration::ZERO; 166 | 167 | initialize_block(block, &Some(prev_header)); 168 | } 169 | 170 | weight.saturating_accrue(extrinsic.get_dispatch_info().weight); 171 | if weight.ref_time() >= 2 * WEIGHT_REF_TIME_PER_SECOND { 172 | #[cfg(not(feature = "fuzzing"))] 173 | println!("Extrinsic would exhaust block weight, skipping"); 174 | continue; 175 | } 176 | 177 | let origin = accounts[usize::from(origin) % accounts.len()].clone(); 178 | 179 | #[cfg(not(feature = "fuzzing"))] 180 | println!("\n origin: {origin:?}"); 181 | #[cfg(not(feature = "fuzzing"))] 182 | println!(" call: {extrinsic:?}"); 183 | 184 | let now = Instant::now(); // We get the current time for timing purposes. 185 | #[allow(unused_variables)] 186 | let res = extrinsic.dispatch(RuntimeOrigin::signed(origin)); 187 | elapsed += now.elapsed(); 188 | 189 | #[cfg(not(feature = "fuzzing"))] 190 | println!(" result: {res:?}"); 191 | } 192 | 193 | finalize_block(elapsed); 194 | 195 | check_invariants(block, initial_total_issuance); 196 | }); 197 | } 198 | 199 | fn initialize_block(block: u32, prev_header: &Option
) { 200 | #[cfg(not(feature = "fuzzing"))] 201 | println!("\ninitializing block {block}"); 202 | 203 | let pre_digest = Digest { 204 | logs: vec![DigestItem::PreRuntime( 205 | AURA_ENGINE_ID, 206 | Slot::from(u64::from(block)).encode(), 207 | )], 208 | }; 209 | let parent_header = Header::new( 210 | block, 211 | H256::default(), 212 | H256::default(), 213 | prev_header.clone().map(|h| h.hash()).unwrap_or_default(), 214 | pre_digest, 215 | ); 216 | 217 | Executive::initialize_block(&parent_header.clone()); 218 | Timestamp::set(RuntimeOrigin::none(), u64::from(block) * SLOT_DURATION).unwrap(); 219 | 220 | #[cfg(not(feature = "fuzzing"))] 221 | println!(" setting parachain validation data"); 222 | let parachain_validation_data = { 223 | use cumulus_primitives_core::{relay_chain::HeadData, PersistedValidationData}; 224 | use cumulus_primitives_parachain_inherent::ParachainInherentData; 225 | use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; 226 | 227 | let parent_head = HeadData( 228 | prev_header 229 | .clone() 230 | .unwrap_or(parent_header.clone()) 231 | .encode(), 232 | ); 233 | let sproof_builder = RelayStateSproofBuilder { 234 | para_id: 100.into(), 235 | current_slot: Slot::from(2 * u64::from(block)), 236 | included_para_head: Some(parent_head.clone()), 237 | ..Default::default() 238 | }; 239 | 240 | let (relay_parent_storage_root, relay_chain_state) = 241 | sproof_builder.into_state_root_and_proof(); 242 | ParachainInherentData { 243 | validation_data: PersistedValidationData { 244 | parent_head, 245 | relay_parent_number: block, 246 | relay_parent_storage_root, 247 | max_pov_size: 1000, 248 | }, 249 | relay_chain_state, 250 | downward_messages: Vec::default(), 251 | horizontal_messages: BTreeMap::default(), 252 | } 253 | }; 254 | ParachainSystem::set_validation_data(RuntimeOrigin::none(), parachain_validation_data).unwrap(); 255 | 256 | // We have to send 1 DOT to the coretime burn address because of a defensive assertion that cannot be 257 | // reached in a real-world environment. 258 | let coretime_burn_account: AccountId = 259 | frame_support::PalletId(*b"py/ctbrn").into_account_truncating(); 260 | let coretime_burn_address = coretime_burn_account.into(); 261 | // The transfer may result an error if there are insufficient funds in the account, 262 | // but in that case, we just don't transfer to coretime burn address 263 | let _ = Balances::transfer_keep_alive( 264 | RuntimeOrigin::signed([0; 32].into()), 265 | coretime_burn_address, 266 | UNITS, 267 | ); 268 | } 269 | 270 | fn finalize_block(elapsed: Duration) -> Header { 271 | #[cfg(not(feature = "fuzzing"))] 272 | println!("\n time spent: {elapsed:?}"); 273 | assert!(elapsed.as_secs() <= 2, "block execution took too much time"); 274 | 275 | #[cfg(not(feature = "fuzzing"))] 276 | println!("finalizing block"); 277 | Executive::finalize_block() 278 | } 279 | 280 | fn check_invariants(block: u32, initial_total_issuance: Balance) { 281 | let mut counted_free = 0; 282 | let mut counted_reserved = 0; 283 | for (account, info) in Account::::iter() { 284 | let consumers = info.consumers; 285 | let providers = info.providers; 286 | assert!(!(consumers > 0 && providers == 0), "Invalid c/p state"); 287 | counted_free += info.data.free; 288 | counted_reserved += info.data.reserved; 289 | let max_lock: Balance = Balances::locks(&account) 290 | .iter() 291 | .map(|l| l.amount) 292 | .max() 293 | .unwrap_or_default(); 294 | assert_eq!( 295 | max_lock, info.data.frozen, 296 | "Max lock should be equal to frozen balance" 297 | ); 298 | let sum_holds: Balance = Holds::::get(&account) 299 | .iter() 300 | .map(|l| l.amount) 301 | .sum(); 302 | assert!( 303 | sum_holds <= info.data.reserved, 304 | "Sum of all holds ({sum_holds}) should be less than or equal to reserved balance {}", 305 | info.data.reserved 306 | ); 307 | } 308 | let total_issuance = TotalIssuance::::get(); 309 | let counted_issuance = counted_free + counted_reserved; 310 | assert!( 311 | total_issuance <= counted_issuance, 312 | "Inconsistent total issuance: {total_issuance} but counted {counted_issuance}" 313 | ); 314 | assert!( 315 | total_issuance <= initial_total_issuance, 316 | "Total issuance {total_issuance} greater than initial issuance {initial_total_issuance}" 317 | ); 318 | // Broker invariants 319 | let status = 320 | pallet_broker::Status::::get().expect("Broker pallet should always have a status"); 321 | let sale_info = pallet_broker::SaleInfo::::get() 322 | .expect("Broker pallet should always have a sale info"); 323 | assert!( 324 | sale_info.first_core <= status.core_count, 325 | "Sale info first_core too large" 326 | ); 327 | assert!( 328 | sale_info.cores_sold <= sale_info.cores_offered, 329 | "Sale info cores mismatch" 330 | ); 331 | let regions: Vec = pallet_broker::Regions::::iter() 332 | .map(|n| n.0) 333 | .collect(); 334 | let mut masks: std::collections::HashMap<(Timeslice, CoreIndex), CoreMask> = HashMap::default(); 335 | for region in regions { 336 | let region_record = pallet_broker::Regions::::get(region) 337 | .expect("Region id should have a region record"); 338 | for region_timeslice in region.begin..region_record.end { 339 | let mut existing_mask = *masks 340 | .get(&(region_timeslice, region.core)) 341 | .unwrap_or(&CoreMask::void()); 342 | assert_eq!(existing_mask ^ region.mask, existing_mask | region.mask); 343 | existing_mask |= region.mask; 344 | masks.insert((region_timeslice, region.core), existing_mask); 345 | } 346 | } 347 | // We run all developer-defined integrity tests 348 | AllPalletsWithSystem::integrity_test(); 349 | AllPalletsWithSystem::try_state(block, TryStateSelect::All).unwrap(); 350 | } 351 | -------------------------------------------------------------------------------- /runtimes/kusama/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kusama-fuzzer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | ziggy = { version = "1.3.2", default-features = false } 9 | 10 | staging-kusama-runtime = { git = "https://github.com/polkadot-fellows/runtimes.git", tag = "v1.4.3", default-features = false } 11 | staging-xcm = { default-features = false , version = "14.2.0" } 12 | 13 | kusama-runtime-constants = { git = "https://github.com/polkadot-fellows/runtimes.git", tag = "v1.4.3", default-features = false } 14 | 15 | polkadot-primitives = { version = "16.0.0", default-features = false } 16 | polkadot-runtime-parachains = { version = "17.0.2", default-features = false } 17 | 18 | codec = { version = "3.6.12", features = ["derive", "max-encoded-len"], default-features = false, package = "parity-scale-codec" } 19 | 20 | frame-support = { default-features = false , version = "38.2.0" } 21 | frame-system = { default-features = false , version = "38.0.0" } 22 | 23 | sp-application-crypto = { default-features = false , version = "38.0.0" } 24 | sp-runtime = { default-features = false , version = "39.0.5" } 25 | sp-state-machine = { default-features = false , version = "0.43.0" } 26 | sp-consensus-babe = { default-features = false , version = "0.40.0" } 27 | sp-consensus-beefy = { default-features = false , version = "22.1.0" } 28 | sp-authority-discovery = { default-features = false , version = "34.0.0" } 29 | sp-core = { default-features = false, version = "34.0.0" } 30 | 31 | pallet-balances = { default-features = false , version = "39.0.0" } 32 | pallet-grandpa = { default-features = false , version = "38.0.0" } 33 | pallet-society = { default-features = false, version = "38.0.0" } 34 | pallet-utility = { default-features = false , version = "38.0.0" } 35 | pallet-multisig = { default-features = false , version = "38.0.0" } 36 | pallet-proxy = { default-features = false , version = "38.0.0" } 37 | pallet-identity = { default-features = false , version = "38.0.0" } 38 | pallet-bounties = { default-features = false , version = "37.0.2" } 39 | pallet-staking = { default-features = false , version = "38.0.0" } 40 | pallet-timestamp = { default-features = false , version = "37.0.0" } 41 | pallet-xcm = { default-features = false , version = "17.0.1" } 42 | pallet-referenda = { default-features = false , version = "38.0.0" } 43 | 44 | [features] 45 | default = ["std", "try-runtime"] 46 | fuzzing = [] 47 | std = [ 48 | "staging-kusama-runtime/std", 49 | "codec/std", 50 | "sp-runtime/std", 51 | "frame-support/std", 52 | "frame-system/std", 53 | "pallet-timestamp/std", 54 | "pallet-grandpa/std", 55 | "pallet-balances/std", 56 | "pallet-staking/std", 57 | "sp-consensus-babe/std", 58 | "polkadot-primitives/std", 59 | "polkadot-runtime-parachains/std", 60 | "sp-authority-discovery/std", 61 | ] 62 | try-runtime = [ 63 | "staging-kusama-runtime/try-runtime", 64 | "frame-support/try-runtime", 65 | "frame-system/try-runtime", 66 | "sp-runtime/try-runtime", 67 | "pallet-timestamp/try-runtime", 68 | ] 69 | -------------------------------------------------------------------------------- /runtimes/kusama/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use codec::{DecodeLimit, Encode}; 3 | use frame_support::{ 4 | dispatch::GetDispatchInfo, 5 | pallet_prelude::Weight, 6 | traits::{IntegrityTest, OriginTrait, TryState, TryStateSelect}, 7 | weights::constants::WEIGHT_REF_TIME_PER_SECOND, 8 | }; 9 | use frame_system::Account; 10 | use kusama_runtime_constants::{currency::UNITS, time::SLOT_DURATION}; 11 | use pallet_balances::{Holds, TotalIssuance}; 12 | use pallet_grandpa::AuthorityId as GrandpaId; 13 | use pallet_staking::StakerStatus; 14 | use polkadot_primitives::{AccountId, AssignmentId, Balance, Header, ValidatorId}; 15 | use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; 16 | use sp_consensus_babe::AuthorityId as BabeId; 17 | use sp_consensus_babe::{ 18 | digests::{PreDigest, SecondaryPlainPreDigest}, 19 | Slot, BABE_ENGINE_ID, 20 | }; 21 | use sp_core::crypto::ByteArray; 22 | use sp_runtime::{app_crypto::ByteArray as _, BuildStorage, Perbill}; 23 | use sp_runtime::{ 24 | testing::H256, 25 | traits::{Dispatchable, Header as _}, 26 | Digest, DigestItem, Storage, 27 | }; 28 | use sp_state_machine::BasicExternalities; 29 | use staging_kusama_runtime::{ 30 | AllPalletsWithSystem, Balances, Executive, ParaInherent, Runtime, RuntimeCall, RuntimeOrigin, 31 | Timestamp, 32 | }; 33 | use std::{ 34 | iter, 35 | time::{Duration, Instant}, 36 | }; 37 | 38 | fn main() { 39 | let accounts: Vec = (0..5).map(|i| [i; 32].into()).collect(); 40 | let genesis = generate_genesis(&accounts); 41 | 42 | ziggy::fuzz!(|data: &[u8]| { 43 | process_input(&accounts, &genesis, data); 44 | }); 45 | } 46 | 47 | fn generate_genesis(accounts: &[AccountId]) -> Storage { 48 | use staging_kusama_runtime as kusama; 49 | 50 | const ENDOWMENT: Balance = 10_000_000 * UNITS; 51 | const STASH: Balance = ENDOWMENT / 1000; 52 | 53 | let initial_authority = kusama::SessionKeys { 54 | grandpa: GrandpaId::from_slice(&[0; 32]).unwrap(), 55 | babe: BabeId::from_slice(&[0; 32]).unwrap(), 56 | beefy: sp_application_crypto::ecdsa::Public::from_raw([0u8; 33]).into(), 57 | para_validator: ValidatorId::from_slice(&[0; 32]).unwrap(), 58 | para_assignment: AssignmentId::from_slice(&[0; 32]).unwrap(), 59 | authority_discovery: AuthorityDiscoveryId::from_slice(&[0; 32]).unwrap(), 60 | }; 61 | 62 | let stakers = vec![( 63 | [0; 32].into(), 64 | [0; 32].into(), 65 | STASH, 66 | StakerStatus::Validator, 67 | )]; 68 | 69 | let mut storage = kusama::RuntimeGenesisConfig { 70 | system: kusama::SystemConfig::default(), 71 | balances: kusama::BalancesConfig { 72 | // Configure endowed accounts with initial balance of 1 << 60. 73 | balances: accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), 74 | }, 75 | indices: kusama::IndicesConfig { indices: vec![] }, 76 | session: kusama::SessionConfig { 77 | keys: vec![([0; 32].into(), [0; 32].into(), initial_authority)], 78 | non_authority_keys: vec![], 79 | }, 80 | beefy: kusama::BeefyConfig::default(), 81 | staking: kusama::StakingConfig { 82 | validator_count: 1, 83 | minimum_validator_count: 1, 84 | invulnerables: vec![[0; 32].into()], 85 | slash_reward_fraction: Perbill::from_percent(10), 86 | stakers, 87 | ..Default::default() 88 | }, 89 | babe: kusama::BabeConfig { 90 | epoch_config: kusama::BABE_GENESIS_EPOCH_CONFIG, 91 | ..Default::default() 92 | }, 93 | grandpa: kusama::GrandpaConfig::default(), 94 | authority_discovery: kusama::AuthorityDiscoveryConfig::default(), 95 | claims: kusama::ClaimsConfig { 96 | claims: vec![], 97 | vesting: vec![], 98 | }, 99 | vesting: kusama::VestingConfig { vesting: vec![] }, 100 | treasury: kusama::TreasuryConfig::default(), 101 | hrmp: kusama::HrmpConfig::default(), 102 | configuration: kusama::ConfigurationConfig::default(), 103 | paras: kusama::ParasConfig::default(), 104 | xcm_pallet: kusama::XcmPalletConfig::default(), 105 | nomination_pools: kusama::NominationPoolsConfig { 106 | min_create_bond: 1 << 43, 107 | min_join_bond: 1 << 42, 108 | ..Default::default() 109 | }, 110 | nis_counterpart_balances: kusama::NisCounterpartBalancesConfig::default(), 111 | registrar: kusama::RegistrarConfig::default(), 112 | society: kusama::SocietyConfig::default(), 113 | transaction_payment: kusama::TransactionPaymentConfig::default(), 114 | } 115 | .build_storage() 116 | .unwrap(); 117 | BasicExternalities::execute_with_storage(&mut storage, || { 118 | // Identity::add_registrar(RuntimeOrigin::root(), accounts[0].clone().into()).unwrap(); 119 | }); 120 | storage 121 | } 122 | 123 | fn recursively_find_call(call: RuntimeCall, matches_on: fn(RuntimeCall) -> bool) -> bool { 124 | if let RuntimeCall::Utility( 125 | pallet_utility::Call::batch { calls } 126 | | pallet_utility::Call::force_batch { calls } 127 | | pallet_utility::Call::batch_all { calls }, 128 | ) = call 129 | { 130 | for call in calls { 131 | if recursively_find_call(call.clone(), matches_on) { 132 | return true; 133 | } 134 | } 135 | } else if let RuntimeCall::Multisig(pallet_multisig::Call::as_multi_threshold_1 { 136 | call, .. 137 | }) 138 | | RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) 139 | | RuntimeCall::Proxy(pallet_proxy::Call::proxy { call, .. }) = call 140 | { 141 | return recursively_find_call(*call.clone(), matches_on); 142 | } else if matches_on(call) { 143 | return true; 144 | } 145 | false 146 | } 147 | 148 | fn process_input(accounts: &[AccountId], genesis: &Storage, data: &[u8]) { 149 | let mut extrinsic_data = data; 150 | // We build the list of extrinsics we will execute 151 | #[allow(deprecated)] 152 | let extrinsics: Vec<(/* lapse */ u8, /* origin */ u8, RuntimeCall)> = iter::from_fn(|| { 153 | DecodeLimit::decode_with_depth_limit(64, &mut extrinsic_data).ok() 154 | }) 155 | .filter(|(_, _, x): &(_, _, RuntimeCall)| { 156 | !recursively_find_call(x.clone(), |call| { 157 | // We filter out calls with Fungible(0) as they cause a debug crash 158 | matches!(call.clone(), RuntimeCall::XcmPallet(pallet_xcm::Call::execute { message, .. }) 159 | if matches!(message.as_ref(), staging_xcm::VersionedXcm::V2(staging_xcm::v2::Xcm(msg)) 160 | if msg.iter().any(|m| matches!(m, staging_xcm::opaque::v2::prelude::BuyExecution { fees: staging_xcm::v2::MultiAsset { fun, .. }, .. } 161 | if fun == &staging_xcm::v2::Fungibility::Fungible(0) 162 | ) 163 | )) || matches!(message.as_ref(), staging_xcm::VersionedXcm::V3(staging_xcm::v3::Xcm(msg)) 164 | if msg.iter().any(|m| matches!(m, staging_xcm::opaque::v3::prelude::BuyExecution { weight_limit: staging_xcm::opaque::v3::WeightLimit::Limited(weight), .. } 165 | if weight.ref_time() <= 1 166 | )) 167 | ) 168 | ) 169 | || matches!(call.clone(), RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets_using_type_and_then { assets, ..}) 170 | if staging_xcm::v2::MultiAssets::try_from(*assets.clone()) 171 | .map(|assets| assets.inner().iter().any(|a| matches!(a, staging_xcm::v2::MultiAsset { fun, .. } 172 | if fun == &staging_xcm::v2::Fungibility::Fungible(0) 173 | ))).unwrap_or(false) 174 | ) 175 | || matches!(call.clone(), RuntimeCall::System(_)) 176 | || matches!( 177 | &call, 178 | RuntimeCall::Referenda(pallet_referenda::Call::submit { 179 | proposal_origin: matching_origin, 180 | .. 181 | }) if RuntimeOrigin::from(*matching_origin.clone()).caller() == RuntimeOrigin::root().caller() 182 | ) 183 | }) 184 | }) 185 | .collect(); 186 | if extrinsics.is_empty() { 187 | return; 188 | } 189 | 190 | let mut block: u32 = 1; 191 | let mut weight: Weight = Weight::zero(); 192 | let mut elapsed: Duration = Duration::ZERO; 193 | 194 | BasicExternalities::execute_with_storage(&mut genesis.clone(), || { 195 | let initial_total_issuance = TotalIssuance::::get(); 196 | 197 | initialize_block(block); 198 | 199 | for (lapse, origin, extrinsic) in extrinsics { 200 | if lapse > 0 { 201 | finalize_block(elapsed); 202 | 203 | block += u32::from(lapse) * 393; // 393 * 256 = 100608 which nearly corresponds to a week 204 | weight = 0.into(); 205 | elapsed = Duration::ZERO; 206 | 207 | initialize_block(block); 208 | } 209 | 210 | weight.saturating_accrue(extrinsic.get_dispatch_info().weight); 211 | if weight.ref_time() >= 2 * WEIGHT_REF_TIME_PER_SECOND { 212 | #[cfg(not(feature = "fuzzing"))] 213 | println!("Extrinsic would exhaust block weight, skipping"); 214 | continue; 215 | } 216 | 217 | let origin = if matches!( 218 | extrinsic, 219 | RuntimeCall::Bounties( 220 | pallet_bounties::Call::approve_bounty { .. } 221 | | pallet_bounties::Call::propose_curator { .. } 222 | | pallet_bounties::Call::close_bounty { .. } 223 | ) 224 | ) { 225 | RuntimeOrigin::root() 226 | } else { 227 | RuntimeOrigin::signed(accounts[origin as usize % accounts.len()].clone()) 228 | }; 229 | 230 | #[cfg(not(feature = "fuzzing"))] 231 | println!("\n origin: {origin:?}"); 232 | #[cfg(not(feature = "fuzzing"))] 233 | println!(" call: {extrinsic:?}"); 234 | 235 | let now = Instant::now(); // We get the current time for timing purposes. 236 | #[allow(unused_variables)] 237 | let res = extrinsic.clone().dispatch(origin); 238 | elapsed += now.elapsed(); 239 | 240 | #[cfg(not(feature = "fuzzing"))] 241 | println!(" result: {res:?}"); 242 | } 243 | 244 | finalize_block(elapsed); 245 | 246 | check_invariants(block, initial_total_issuance); 247 | }); 248 | } 249 | 250 | fn initialize_block(block: u32) { 251 | #[cfg(not(feature = "fuzzing"))] 252 | println!("\ninitializing block {block}"); 253 | 254 | let pre_digest = Digest { 255 | logs: vec![DigestItem::PreRuntime( 256 | BABE_ENGINE_ID, 257 | PreDigest::SecondaryPlain(SecondaryPlainPreDigest { 258 | slot: Slot::from(u64::from(block)), 259 | authority_index: 0, 260 | }) 261 | .encode(), 262 | )], 263 | }; 264 | 265 | let grandparent_header = Header::new( 266 | block, 267 | H256::default(), 268 | H256::default(), 269 | >::parent_hash(), 270 | pre_digest.clone(), 271 | ); 272 | 273 | let parent_header = Header::new( 274 | block, 275 | H256::default(), 276 | H256::default(), 277 | grandparent_header.hash(), 278 | pre_digest, 279 | ); 280 | 281 | Executive::initialize_block(&parent_header); 282 | 283 | #[cfg(not(feature = "fuzzing"))] 284 | println!(" setting timestamp"); 285 | Timestamp::set(RuntimeOrigin::none(), u64::from(block) * SLOT_DURATION).unwrap(); 286 | 287 | #[cfg(not(feature = "fuzzing"))] 288 | println!(" setting bitfields"); 289 | ParaInherent::enter( 290 | RuntimeOrigin::none(), 291 | polkadot_primitives::InherentData { 292 | parent_header: grandparent_header, 293 | backed_candidates: Vec::default(), 294 | bitfields: Vec::default(), 295 | disputes: Vec::default(), 296 | }, 297 | ) 298 | .unwrap(); 299 | } 300 | 301 | fn finalize_block(elapsed: Duration) { 302 | #[cfg(not(feature = "fuzzing"))] 303 | println!("\n time spent: {elapsed:?}"); 304 | assert!(elapsed.as_secs() <= 2, "block execution took too much time"); 305 | 306 | #[cfg(not(feature = "fuzzing"))] 307 | println!(" finalizing block"); 308 | Executive::finalize_block(); 309 | } 310 | 311 | fn check_invariants(block: u32, initial_total_issuance: Balance) { 312 | // After execution of all blocks, we run invariants 313 | let mut counted_free = 0; 314 | let mut counted_reserved = 0; 315 | for (account, info) in Account::::iter() { 316 | let consumers = info.consumers; 317 | let providers = info.providers; 318 | assert!(!(consumers > 0 && providers == 0), "Invalid c/p state"); 319 | counted_free += info.data.free; 320 | counted_reserved += info.data.reserved; 321 | let max_lock: Balance = Balances::locks(&account) 322 | .iter() 323 | .map(|l| l.amount) 324 | .max() 325 | .unwrap_or_default(); 326 | assert!( 327 | max_lock <= info.data.frozen, 328 | "Max lock ({max_lock}) should be less than or equal to frozen balance ({})", 329 | info.data.frozen 330 | ); 331 | let sum_holds: Balance = Holds::::get(&account) 332 | .iter() 333 | .map(|l| l.amount) 334 | .sum(); 335 | assert!( 336 | sum_holds <= info.data.reserved, 337 | "Sum of all holds ({sum_holds}) should be less than or equal to reserved balance {}", 338 | info.data.reserved 339 | ); 340 | } 341 | let total_issuance = TotalIssuance::::get(); 342 | let counted_issuance = counted_free + counted_reserved; 343 | assert!( 344 | total_issuance == counted_issuance, 345 | "Inconsistent total issuance: {total_issuance} but counted {counted_issuance}" 346 | ); 347 | assert!( 348 | total_issuance <= initial_total_issuance, 349 | "Total issuance {total_issuance} greater than initial issuance {initial_total_issuance}" 350 | ); 351 | // We run all developer-defined integrity tests 352 | AllPalletsWithSystem::integrity_test(); 353 | AllPalletsWithSystem::try_state(block, TryStateSelect::All).unwrap(); 354 | } 355 | -------------------------------------------------------------------------------- /runtimes/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "substrate-templates-fuzzers" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [workspace] 8 | resolver = "2" 9 | members = [ 10 | "kitchensink", 11 | "solochain", 12 | ] 13 | -------------------------------------------------------------------------------- /templates/kitchensink/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kitchensink-fuzzer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | ziggy = { version = "1.3.2", default-features = false } 9 | 10 | kitchensink-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 11 | 12 | codec = { version = "3.7.4", features = ["derive"], default-features = false, package = "parity-scale-codec" } 13 | 14 | node-primitives = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 15 | 16 | frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 17 | frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 18 | 19 | sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 20 | sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 21 | sp-state-machine = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 22 | sp-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 23 | sp-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 24 | sp-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 25 | 26 | pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 27 | pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 28 | pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 29 | pallet-staking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 30 | pallet-im-online = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 31 | pallet-referenda = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 32 | pallet-contracts = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 33 | pallet-society = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 34 | pallet-lottery = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 35 | pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 36 | pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 37 | pallet-remark = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 38 | pallet-transaction-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 39 | pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 40 | pallet-treasury = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 41 | pallet-collective = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 42 | pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 43 | pallet-broker = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 44 | pallet-revive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 45 | 46 | [features] 47 | default = ["std", "try-runtime"] 48 | fuzzing = [] 49 | std = [ 50 | "kitchensink-runtime/std", 51 | "codec/std", 52 | "sp-runtime/std", 53 | "frame-support/std", 54 | "pallet-timestamp/std", 55 | ] 56 | try-runtime = [ 57 | "kitchensink-runtime/try-runtime", 58 | "frame-support/try-runtime", 59 | "pallet-timestamp/try-runtime", 60 | ] 61 | -------------------------------------------------------------------------------- /templates/kitchensink/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use codec::{DecodeLimit, Encode}; 3 | use frame_support::{ 4 | dispatch::GetDispatchInfo, 5 | pallet_prelude::Weight, 6 | traits::{IntegrityTest, OriginTrait, TryState, TryStateSelect}, 7 | weights::constants::WEIGHT_REF_TIME_PER_SECOND, 8 | }; 9 | use frame_system::Account; 10 | use kitchensink_runtime::{ 11 | constants::{currency::DOLLARS, time::SLOT_DURATION}, 12 | AccountId, AllPalletsWithSystem, Balances, Broker, Executive, Runtime, RuntimeCall, 13 | RuntimeOrigin, Timestamp, 14 | }; 15 | use node_primitives::Balance; 16 | use pallet_balances::{Holds, TotalIssuance}; 17 | use sp_consensus_babe::{ 18 | digests::{PreDigest, SecondaryPlainPreDigest}, 19 | Slot, BABE_ENGINE_ID, 20 | }; 21 | use sp_runtime::{ 22 | testing::H256, 23 | traits::{Dispatchable, Header}, 24 | Digest, DigestItem, FixedU64, Perbill, Storage, 25 | }; 26 | use sp_state_machine::BasicExternalities; 27 | use std::{ 28 | iter, 29 | time::{Duration, Instant}, 30 | }; 31 | 32 | fn main() { 33 | let accounts: Vec = (0..5).map(|i| [i; 32].into()).collect(); 34 | let genesis = generate_genesis(&accounts); 35 | 36 | ziggy::fuzz!(|data: &[u8]| { 37 | process_input(&accounts, &genesis, data); 38 | }); 39 | } 40 | #[allow(clippy::too_many_lines)] 41 | fn generate_genesis(accounts: &[AccountId]) -> Storage { 42 | use kitchensink_runtime::{ 43 | AllianceConfig, AllianceMotionConfig, AssetsConfig, AuthorityDiscoveryConfig, BabeConfig, 44 | BalancesConfig, BeefyConfig, BrokerConfig, CouncilConfig, DemocracyConfig, ElectionsConfig, 45 | GluttonConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, MixnetConfig, 46 | NominationPoolsConfig, PoolAssetsConfig, RuntimeGenesisConfig, SafeModeConfig, 47 | SessionConfig, SessionKeys, SocietyConfig, StakingConfig, SudoConfig, SystemConfig, 48 | TechnicalCommitteeConfig, TechnicalMembershipConfig, TransactionPaymentConfig, 49 | TransactionStorageConfig, TreasuryConfig, TxPauseConfig, VestingConfig, 50 | }; 51 | use pallet_grandpa::AuthorityId as GrandpaId; 52 | use pallet_im_online::sr25519::AuthorityId as ImOnlineId; 53 | use pallet_staking::StakerStatus; 54 | use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; 55 | use sp_consensus_babe::AuthorityId as BabeId; 56 | use sp_core::{sr25519::Public as MixnetId, Pair}; 57 | use sp_runtime::{app_crypto::ByteArray, BuildStorage}; 58 | 59 | const ENDOWMENT: Balance = 10_000_000 * DOLLARS; 60 | const STASH: Balance = ENDOWMENT / 1000; 61 | 62 | let beefy_pair = sp_consensus_beefy::ecdsa_crypto::Pair::generate().0; 63 | 64 | let stakers = vec![( 65 | [0; 32].into(), 66 | [0; 32].into(), 67 | STASH, 68 | StakerStatus::Validator, 69 | )]; 70 | 71 | let num_endowed_accounts = accounts.len(); 72 | 73 | let mut storage = RuntimeGenesisConfig { 74 | system: SystemConfig::default(), 75 | balances: BalancesConfig { 76 | balances: accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect(), 77 | dev_accounts: None, 78 | }, 79 | indices: IndicesConfig { indices: vec![] }, 80 | session: SessionConfig { 81 | keys: vec![( 82 | [0; 32].into(), 83 | [0; 32].into(), 84 | SessionKeys { 85 | grandpa: GrandpaId::from_slice(&[0; 32]).unwrap(), 86 | babe: BabeId::from_slice(&[0; 32]).unwrap(), 87 | beefy: beefy_pair.public(), 88 | im_online: ImOnlineId::from_slice(&[0; 32]).unwrap(), 89 | authority_discovery: AuthorityDiscoveryId::from_slice(&[0; 32]).unwrap(), 90 | mixnet: MixnetId::from_slice(&[0; 32]).unwrap().into(), 91 | }, 92 | )], 93 | non_authority_keys: vec![], 94 | }, 95 | beefy: BeefyConfig::default(), 96 | staking: StakingConfig { 97 | validator_count: 0u32, 98 | minimum_validator_count: 0u32, 99 | invulnerables: vec![[0; 32].into()], 100 | slash_reward_fraction: Perbill::from_percent(10), 101 | stakers, 102 | ..Default::default() 103 | }, 104 | democracy: DemocracyConfig::default(), 105 | elections: ElectionsConfig { 106 | members: accounts 107 | .iter() 108 | .take((num_endowed_accounts + 1) / 2) 109 | .cloned() 110 | .map(|member| (member, STASH)) 111 | .collect(), 112 | }, 113 | council: CouncilConfig::default(), 114 | technical_committee: TechnicalCommitteeConfig { 115 | members: accounts 116 | .iter() 117 | .take((num_endowed_accounts + 1) / 2) 118 | .cloned() 119 | .collect(), 120 | ..Default::default() 121 | }, 122 | sudo: SudoConfig { key: None }, 123 | babe: BabeConfig { 124 | authorities: vec![], 125 | epoch_config: kitchensink_runtime::BABE_GENESIS_EPOCH_CONFIG, 126 | ..Default::default() 127 | }, 128 | im_online: ImOnlineConfig { keys: vec![] }, 129 | authority_discovery: AuthorityDiscoveryConfig::default(), 130 | grandpa: GrandpaConfig::default(), 131 | technical_membership: TechnicalMembershipConfig::default(), 132 | treasury: TreasuryConfig::default(), 133 | society: SocietyConfig { pot: 0 }, 134 | vesting: VestingConfig::default(), 135 | assets: AssetsConfig { 136 | // This asset is used by the NIS pallet as counterpart currency. 137 | assets: vec![(9, [0; 32].into(), true, 1)], 138 | ..Default::default() 139 | }, 140 | transaction_storage: TransactionStorageConfig::default(), 141 | transaction_payment: TransactionPaymentConfig::default(), 142 | alliance: AllianceConfig::default(), 143 | alliance_motion: AllianceMotionConfig::default(), 144 | nomination_pools: NominationPoolsConfig { 145 | min_create_bond: 10 * DOLLARS, 146 | min_join_bond: DOLLARS, 147 | ..Default::default() 148 | }, 149 | glutton: GluttonConfig { 150 | compute: FixedU64::default(), 151 | storage: FixedU64::default(), 152 | trash_data_count: Default::default(), 153 | ..Default::default() 154 | }, 155 | pool_assets: PoolAssetsConfig::default(), 156 | safe_mode: SafeModeConfig::default(), 157 | tx_pause: TxPauseConfig::default(), 158 | mixnet: MixnetConfig::default(), 159 | broker: BrokerConfig::default(), 160 | } 161 | .build_storage() 162 | .unwrap(); 163 | BasicExternalities::execute_with_storage(&mut storage, || { 164 | // We set the configuration for the broker pallet 165 | Broker::configure( 166 | RuntimeOrigin::root(), 167 | pallet_broker::ConfigRecord { 168 | advance_notice: 2, 169 | interlude_length: 1, 170 | leadin_length: 1, 171 | ideal_bulk_proportion: Perbill::default(), 172 | limit_cores_offered: None, 173 | region_length: 3, 174 | renewal_bump: Perbill::from_percent(10), 175 | contribution_timeout: 5, 176 | }, 177 | ) 178 | .unwrap(); 179 | /* 180 | // WIP: found the society before each input 181 | RuntimeCall::Sudo(pallet_sudo::Call::sudo { 182 | call: RuntimeCall::Society(pallet_society::Call::found_society { 183 | founder: AccountId::from([0; 32]).into(), 184 | max_members: 2, 185 | max_intake: 2, 186 | max_strikes: 2, 187 | candidate_deposit: 1_000, 188 | rules: vec![0], 189 | }) 190 | .into(), 191 | }) 192 | .dispatch(RuntimeOrigin::root()) 193 | .unwrap(); 194 | */ 195 | }); 196 | storage 197 | } 198 | 199 | fn recursively_find_call(call: RuntimeCall, matches_on: fn(RuntimeCall) -> bool) -> bool { 200 | if let RuntimeCall::Utility( 201 | pallet_utility::Call::batch { calls } 202 | | pallet_utility::Call::force_batch { calls } 203 | | pallet_utility::Call::batch_all { calls }, 204 | ) = call 205 | { 206 | for call in calls { 207 | if recursively_find_call(call.clone(), matches_on) { 208 | return true; 209 | } 210 | } 211 | } else if let RuntimeCall::Lottery(pallet_lottery::Call::buy_ticket { call }) 212 | | RuntimeCall::Multisig(pallet_multisig::Call::as_multi_threshold_1 { 213 | call, .. 214 | }) 215 | | RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) 216 | | RuntimeCall::Proxy(pallet_proxy::Call::proxy { call, .. }) 217 | | RuntimeCall::Revive(pallet_revive::Call::dispatch_as_fallback_account { call }) 218 | | RuntimeCall::Council(pallet_collective::Call::propose { 219 | proposal: call, .. 220 | }) = call 221 | { 222 | return recursively_find_call(*call.clone(), matches_on); 223 | } else if matches_on(call) { 224 | return true; 225 | } 226 | false 227 | } 228 | 229 | fn process_input(accounts: &[AccountId], genesis: &Storage, data: &[u8]) { 230 | // We build the list of extrinsics we will execute 231 | let mut extrinsic_data = data; 232 | // Vec<(lapse, origin, extrinsic)> 233 | let extrinsics: Vec<(u8, u8, RuntimeCall)> = iter::from_fn(|| { 234 | DecodeLimit::decode_with_depth_limit(64, &mut extrinsic_data).ok() 235 | }) 236 | .filter(|(_, _, x): &(_, _, RuntimeCall)| { 237 | !recursively_find_call(x.clone(), |call| { 238 | // We disallow referenda calls with root origin 239 | matches!( 240 | &call, 241 | RuntimeCall::Referenda(pallet_referenda::Call::submit { 242 | proposal_origin: matching_origin, 243 | .. 244 | }) | RuntimeCall::RankedPolls(pallet_referenda::Call::submit { 245 | proposal_origin: matching_origin, 246 | .. 247 | }) if RuntimeOrigin::from(*matching_origin.clone()).caller() == RuntimeOrigin::root().caller() 248 | ) 249 | // We disallow batches of referenda 250 | // See https://github.com/paritytech/srlabs_findings/issues/296 251 | || matches!( 252 | &call, 253 | RuntimeCall::Referenda(pallet_referenda::Call::submit { .. }) 254 | ) 255 | // We filter out contracts call that will take too long because of fuzzer instrumentation 256 | || matches!( 257 | &call, 258 | RuntimeCall::Contracts( 259 | pallet_contracts::Call::instantiate_with_code { .. } | 260 | pallet_contracts::Call::upload_code { .. } | 261 | pallet_contracts::Call::instantiate_with_code_old_weight { .. } | 262 | pallet_contracts::Call::migrate { .. } 263 | ) 264 | ) 265 | || matches!( 266 | &call, 267 | RuntimeCall::Revive( 268 | pallet_revive::Call::instantiate_with_code { .. } | 269 | pallet_revive::Call::upload_code { .. } 270 | ) 271 | ) 272 | // We filter out safe_mode calls, as they block timestamps from being set. 273 | || matches!(&call, RuntimeCall::SafeMode(..)) 274 | // We filter out store extrinsics because BasicExternalities does not support them. 275 | || matches!( 276 | &call, 277 | RuntimeCall::TransactionStorage(pallet_transaction_storage::Call::store { .. }) 278 | | RuntimeCall::Remark(pallet_remark::Call::store { .. }) 279 | ) 280 | || matches!( 281 | &call, 282 | RuntimeCall::NominationPools(..) 283 | ) 284 | }) 285 | }) 286 | .collect(); 287 | if extrinsics.is_empty() { 288 | return; 289 | } 290 | 291 | let mut block: u32 = 1; 292 | let mut weight: Weight = Weight::zero(); 293 | let mut elapsed: Duration = Duration::ZERO; 294 | 295 | BasicExternalities::execute_with_storage(&mut genesis.clone(), || { 296 | let initial_total_issuance = TotalIssuance::::get(); 297 | 298 | initialize_block(block); 299 | 300 | for (lapse, origin, extrinsic) in extrinsics { 301 | if lapse > 0 { 302 | // We end the current block 303 | finalize_block(elapsed); 304 | 305 | block += u32::from(lapse) * 393; // 393 * 256 = 100608 which nearly corresponds to a week 306 | weight = Weight::zero(); 307 | elapsed = Duration::ZERO; 308 | 309 | // We start the next block 310 | initialize_block(block); 311 | } 312 | 313 | weight.saturating_accrue(extrinsic.get_dispatch_info().call_weight); 314 | if weight.ref_time() >= 2 * WEIGHT_REF_TIME_PER_SECOND { 315 | #[cfg(not(feature = "fuzzing"))] 316 | println!("Extrinsic would exhaust block weight, skipping"); 317 | continue; 318 | } 319 | 320 | let origin = accounts[origin as usize % accounts.len()].clone(); 321 | 322 | // We do not continue if the origin account does not have a free balance 323 | let account = Account::::get(&origin); 324 | if account.data.free == 0 { 325 | #[cfg(not(feature = "fuzzing"))] 326 | println!("\n origin {origin:?} does not have free balance, skipping"); 327 | return; 328 | } 329 | 330 | #[cfg(not(feature = "fuzzing"))] 331 | println!("\n origin: {origin:?}"); 332 | #[cfg(not(feature = "fuzzing"))] 333 | println!(" call: {extrinsic:?}"); 334 | 335 | let now = Instant::now(); // We get the current time for timing purposes. 336 | #[allow(unused_variables)] 337 | let res = extrinsic.dispatch(RuntimeOrigin::signed(origin)); 338 | elapsed += now.elapsed(); 339 | 340 | #[cfg(not(feature = "fuzzing"))] 341 | println!(" result: {res:?}"); 342 | } 343 | 344 | finalize_block(elapsed); 345 | 346 | check_invariants(block, initial_total_issuance); 347 | }); 348 | } 349 | fn initialize_block(block: u32) { 350 | #[cfg(not(feature = "fuzzing"))] 351 | println!("\ninitializing block {block}"); 352 | 353 | let pre_digest = Digest { 354 | logs: vec![DigestItem::PreRuntime( 355 | BABE_ENGINE_ID, 356 | PreDigest::SecondaryPlain(SecondaryPlainPreDigest { 357 | slot: Slot::from(u64::from(block)), 358 | authority_index: 42, 359 | }) 360 | .encode(), 361 | )], 362 | }; 363 | 364 | Executive::initialize_block(&Header::new( 365 | block, 366 | H256::default(), 367 | H256::default(), 368 | H256::default(), 369 | pre_digest, 370 | )); 371 | 372 | #[cfg(not(feature = "fuzzing"))] 373 | println!(" setting timestamp"); 374 | Timestamp::set(RuntimeOrigin::none(), u64::from(block) * SLOT_DURATION).unwrap(); 375 | } 376 | 377 | fn finalize_block(elapsed: Duration) { 378 | #[cfg(not(feature = "fuzzing"))] 379 | println!("\n time spent: {elapsed:?}"); 380 | assert!(elapsed.as_secs() <= 2, "block execution took too much time"); 381 | 382 | #[cfg(not(feature = "fuzzing"))] 383 | println!("\n finalizing block"); 384 | Executive::finalize_block(); 385 | } 386 | 387 | fn check_invariants(block: u32, initial_total_issuance: Balance) { 388 | // After execution of all blocks, we run invariants 389 | let mut counted_free: Balance = 0; 390 | let mut counted_reserved: Balance = 0; 391 | for (account, info) in Account::::iter() { 392 | let consumers = info.consumers; 393 | let providers = info.providers; 394 | assert!(!(consumers > 0 && providers == 0), "Invalid c/p state"); 395 | counted_free += info.data.free; 396 | counted_reserved += info.data.reserved; 397 | let max_lock: Balance = Balances::locks(&account) 398 | .iter() 399 | .map(|l| l.amount) 400 | .max() 401 | .unwrap_or_default(); 402 | assert_eq!( 403 | max_lock, info.data.frozen, 404 | "Max lock should be equal to frozen balance" 405 | ); 406 | let sum_holds: Balance = Holds::::get(&account) 407 | .iter() 408 | .map(|l| l.amount) 409 | .sum(); 410 | assert!( 411 | sum_holds <= info.data.reserved, 412 | "Sum of all holds ({sum_holds}) should be less than or equal to reserved balance {}", 413 | info.data.reserved 414 | ); 415 | } 416 | let total_issuance = TotalIssuance::::get(); 417 | let counted_issuance = counted_free + counted_reserved; 418 | assert_eq!(total_issuance, counted_issuance); 419 | assert!(total_issuance <= initial_total_issuance); 420 | // We run all developer-defined integrity tests 421 | AllPalletsWithSystem::integrity_test(); 422 | AllPalletsWithSystem::try_state(block, TryStateSelect::All).unwrap(); 423 | } 424 | -------------------------------------------------------------------------------- /templates/solochain/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solochain-template-fuzzer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | ziggy = { version = "1.3.2", default-features = false } 9 | 10 | solochain-template-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 11 | 12 | codec = { version = "3.7.4", features = ["derive", "max-encoded-len"], default-features = false, package = "parity-scale-codec" } 13 | 14 | frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 15 | frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 16 | 17 | sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 18 | sp-state-machine = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 19 | sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 20 | 21 | pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 22 | pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 23 | pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503", default-features = false } 24 | 25 | [features] 26 | default = ["std", "try-runtime"] 27 | fuzzing = [] 28 | std = [ 29 | "solochain-template-runtime/std", 30 | "sp-runtime/std", 31 | "frame-support/std", 32 | "pallet-timestamp/std", 33 | "sp-consensus-aura/std", 34 | ] 35 | try-runtime = [ 36 | "solochain-template-runtime/try-runtime", 37 | "frame-support/try-runtime", 38 | "pallet-timestamp/try-runtime", 39 | ] 40 | -------------------------------------------------------------------------------- /templates/solochain/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | use codec::{DecodeLimit, Encode}; 3 | use frame_support::{ 4 | dispatch::GetDispatchInfo, 5 | pallet_prelude::Weight, 6 | traits::{IntegrityTest, TryState, TryStateSelect}, 7 | weights::constants::WEIGHT_REF_TIME_PER_SECOND, 8 | }; 9 | use frame_system::Account; 10 | use pallet_balances::{Holds, TotalIssuance}; 11 | use solochain_template_runtime::{ 12 | AccountId, AllPalletsWithSystem, Balance, Balances, Executive, Runtime, RuntimeCall, 13 | RuntimeOrigin, Timestamp, SLOT_DURATION, 14 | }; 15 | use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; 16 | use sp_runtime::{ 17 | testing::H256, 18 | traits::{Dispatchable, Header}, 19 | Digest, DigestItem, Storage, 20 | }; 21 | use sp_state_machine::BasicExternalities; 22 | use std::{ 23 | iter, 24 | time::{Duration, Instant}, 25 | }; 26 | 27 | fn main() { 28 | let accounts: Vec = (0..5).map(|i| [i; 32].into()).collect(); 29 | let genesis = generate_genesis(&accounts); 30 | 31 | ziggy::fuzz!(|data: &[u8]| { 32 | process_input(&accounts, &genesis, data); 33 | }); 34 | } 35 | 36 | fn generate_genesis(accounts: &[AccountId]) -> Storage { 37 | use solochain_template_runtime::{ 38 | AuraConfig, BalancesConfig, GrandpaConfig, RuntimeGenesisConfig, SudoConfig, SystemConfig, 39 | TransactionPaymentConfig, 40 | }; 41 | use sp_consensus_aura::sr25519::AuthorityId as AuraId; 42 | use sp_runtime::{app_crypto::ByteArray, BuildStorage}; 43 | 44 | // Configure endowed accounts with initial balance of 1 << 60. 45 | let balances = accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(); 46 | let authorities = vec![AuraId::from_slice(&[0; 32]).unwrap()]; 47 | 48 | RuntimeGenesisConfig { 49 | system: SystemConfig::default(), 50 | balances: BalancesConfig { balances, dev_accounts: None }, 51 | aura: AuraConfig { authorities }, 52 | grandpa: GrandpaConfig::default(), 53 | sudo: SudoConfig { key: None }, // Assign no network admin rights. 54 | transaction_payment: TransactionPaymentConfig::default(), 55 | } 56 | .build_storage() 57 | .unwrap() 58 | } 59 | 60 | fn process_input(accounts: &[AccountId], genesis: &Storage, data: &[u8]) { 61 | let mut data = data; 62 | // We build the list of extrinsics we will execute 63 | let extrinsics: Vec<(/* lapse */ u8, /* origin */ u8, RuntimeCall)> = 64 | iter::from_fn(|| DecodeLimit::decode_with_depth_limit(64, &mut data).ok()) 65 | .filter(|(_, _, x)| !matches!(x, RuntimeCall::System(_))) 66 | .collect(); 67 | if extrinsics.is_empty() { 68 | return; 69 | } 70 | 71 | let mut block: u32 = 1; 72 | let mut weight: Weight = 0.into(); 73 | let mut elapsed: Duration = Duration::ZERO; 74 | 75 | BasicExternalities::execute_with_storage(&mut genesis.clone(), || { 76 | let initial_total_issuance = TotalIssuance::::get(); 77 | 78 | initialize_block(block); 79 | 80 | for (lapse, origin, extrinsic) in extrinsics { 81 | if lapse > 0 { 82 | finalize_block(elapsed); 83 | 84 | block += u32::from(lapse) * 393; // 393 * 256 = 100608 which nearly corresponds to a week 85 | weight = 0.into(); 86 | elapsed = Duration::ZERO; 87 | 88 | initialize_block(block); 89 | } 90 | 91 | weight.saturating_accrue(extrinsic.get_dispatch_info().call_weight); 92 | if weight.ref_time() >= 2 * WEIGHT_REF_TIME_PER_SECOND { 93 | #[cfg(not(feature = "fuzzing"))] 94 | println!("Extrinsic would exhaust block weight, skipping"); 95 | continue; 96 | } 97 | 98 | let origin = accounts[origin as usize % accounts.len()].clone(); 99 | 100 | #[cfg(not(feature = "fuzzing"))] 101 | println!("\n origin: {origin:?}"); 102 | #[cfg(not(feature = "fuzzing"))] 103 | println!(" call: {extrinsic:?}"); 104 | 105 | let now = Instant::now(); // We get the current time for timing purposes. 106 | #[allow(unused_variables)] 107 | let res = extrinsic.dispatch(RuntimeOrigin::signed(origin)); 108 | elapsed += now.elapsed(); 109 | 110 | #[cfg(not(feature = "fuzzing"))] 111 | println!(" result: {res:?}"); 112 | } 113 | 114 | finalize_block(elapsed); 115 | 116 | check_invariants(block, initial_total_issuance); 117 | }); 118 | } 119 | 120 | fn initialize_block(block: u32) { 121 | #[cfg(not(feature = "fuzzing"))] 122 | println!("\ninitializing block {block}"); 123 | 124 | Executive::initialize_block(&Header::new( 125 | block, 126 | H256::default(), 127 | H256::default(), 128 | H256::default(), 129 | Digest { 130 | logs: vec![DigestItem::PreRuntime( 131 | AURA_ENGINE_ID, 132 | Slot::from(u64::from(block)).encode(), 133 | )], 134 | }, 135 | )); 136 | 137 | #[cfg(not(feature = "fuzzing"))] 138 | println!(" setting timestamp"); 139 | Timestamp::set(RuntimeOrigin::none(), u64::from(block) * SLOT_DURATION).unwrap(); 140 | } 141 | 142 | fn finalize_block(elapsed: Duration) { 143 | #[cfg(not(feature = "fuzzing"))] 144 | println!("\n time spent: {elapsed:?}"); 145 | assert!(elapsed.as_secs() <= 2, "block execution took too much time"); 146 | 147 | #[cfg(not(feature = "fuzzing"))] 148 | println!(" finalizing block"); 149 | Executive::finalize_block(); 150 | } 151 | 152 | fn check_invariants(block: u32, initial_total_issuance: Balance) { 153 | let mut counted_free = 0; 154 | let mut counted_reserved = 0; 155 | for (account, info) in Account::::iter() { 156 | let consumers = info.consumers; 157 | let providers = info.providers; 158 | assert!(!(consumers > 0 && providers == 0), "Invalid c/p state"); 159 | counted_free += info.data.free; 160 | counted_reserved += info.data.reserved; 161 | let max_lock: Balance = Balances::locks(&account) 162 | .iter() 163 | .map(|l| l.amount) 164 | .max() 165 | .unwrap_or_default(); 166 | assert_eq!( 167 | max_lock, info.data.frozen, 168 | "Max lock should be equal to frozen balance" 169 | ); 170 | let sum_holds: Balance = Holds::::get(&account) 171 | .iter() 172 | .map(|l| l.amount) 173 | .sum(); 174 | assert!( 175 | sum_holds <= info.data.reserved, 176 | "Sum of all holds ({sum_holds}) should be less than or equal to reserved balance {}", 177 | info.data.reserved 178 | ); 179 | } 180 | let total_issuance = TotalIssuance::::get(); 181 | let counted_issuance = counted_free + counted_reserved; 182 | assert_eq!(total_issuance, counted_issuance); 183 | assert!(total_issuance <= initial_total_issuance); 184 | // We run all developer-defined integrity tests 185 | AllPalletsWithSystem::integrity_test(); 186 | AllPalletsWithSystem::try_state(block, TryStateSelect::All).unwrap(); 187 | } 188 | -------------------------------------------------------------------------------- /templates/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------