Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ members = [
"frame/utility",
"frame/vesting",
"frame/bags-list",
"frame/bags-list/fuzzer",
"primitives/api",
"primitives/api/proc-macro",
"primitives/api/test",
Expand Down
6 changes: 6 additions & 0 deletions frame/bags-list/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ runtime-benchmarks = [
"sp-tracing",
"frame-election-provider-support/runtime-benchmarks",
]
fuzz = [
"sp-core",
"sp-io",
"pallet-balances",
"sp-tracing",
]

2 changes: 2 additions & 0 deletions frame/bags-list/fuzzer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hfuzz_target
hfuzz_workspace
22 changes: 22 additions & 0 deletions frame/bags-list/fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "pallet-bags-list-fuzzer"
version = "4.0.0-dev"
authors = ["Parity Technologies <[email protected]>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "Fuzzer for FRAME pallet bags list"
readme = "README.md"
publish = false

[dependencies]
honggfuzz = "0.5"
rand = { version = "0.8", features = ["std", "small_rng"] }

pallet-bags-list = { version = "4.0.0-dev", features = ["fuzz"], path = ".." }
frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support", features = ["runtime-benchmarks"] }

[[bin]]
name = "bags-list"
path = "src/main.rs"
88 changes: 88 additions & 0 deletions frame/bags-list/fuzzer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// This file is part of Substrate.

// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # Running
//! Running this fuzzer can be done with `cargo hfuzz run bags-list`. `honggfuzz` CLI options can
//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
//!
//! # Debugging a panic
//! Once a panic is found, it can be debugged with
//! `cargo hfuzz run-debug fixed_point hfuzz_workspace/bags_list/*.fuzz`.
//!
//! # More information
//! More information about `honggfuzz` can be found
//! [here](https://docs.rs/honggfuzz/).

use frame_election_provider_support::{SortedListProvider, VoteWeight};
use honggfuzz::fuzz;
use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder};
use std::convert::From;

const ID_RANGE: AccountId = 25_000;

/// Actions of a `SortedListProvider` that we fuzz.
enum Action {
Insert,
Update,
Remove,
}

impl From<u32> for Action {
fn from(v: u32) -> Self {
let num_variants = Self::Remove as u32 + 1;
match v % num_variants {
_x if _x == Action::Insert as u32 => Action::Insert,
_x if _x == Action::Update as u32 => Action::Update,
_x if _x == Action::Remove as u32 => Action::Remove,
_ => unreachable!(),
}
}
}

fn main() {
ExtBuilder::default().build_and_execute(|| loop {
fuzz!(|data: (AccountId, VoteWeight, u32)| {
let (account_id_seed, vote_weight, action_seed) = data;

let id = account_id_seed % ID_RANGE;
let action = Action::from(action_seed);

match action {
Action::Insert => {
if BagsList::on_insert(id.clone(), vote_weight).is_err() {
// this was a duplicate id, which is ok. We can just update it.
BagsList::on_update(&id, vote_weight);
}
assert!(BagsList::contains(&id));
},
Action::Update => {
let already_contains = BagsList::contains(&id);
BagsList::on_update(&id, vote_weight);
if already_contains {
assert!(BagsList::contains(&id));
}
},
Action::Remove => {
BagsList::on_remove(&id);
assert!(!BagsList::contains(&id));
},
}

assert!(BagsList::sanity_check().is_ok());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be a good time for you to go and re-read sanity_check and make sure it is checking everything there is to check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one small check to add 7cca905

})
});
}
4 changes: 2 additions & 2 deletions frame/bags-list/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ use sp_std::prelude::*;
mod benchmarks;

mod list;
#[cfg(test)]
mod mock;
#[cfg(any(test, feature = "fuzz"))]
pub mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
Expand Down
14 changes: 8 additions & 6 deletions frame/bags-list/src/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,8 @@ impl<T: Config> List<T> {
///
/// * there are no duplicate ids,
/// * length of this list is in sync with `CounterForListNodes`,
/// * and sanity-checks all bags. This will cascade down all the checks and makes sure all bags
/// are checked per *any* update to `List`.
/// * and sanity-checks all bags and nodes. This will cascade down all the checks and makes sure
/// all bags and nodes are checked per *any* update to `List`.
#[cfg(feature = "std")]
pub(crate) fn sanity_check() -> Result<(), &'static str> {
use frame_support::ensure;
Expand All @@ -414,7 +414,6 @@ impl<T: Config> List<T> {
let thresholds = T::BagThresholds::get().iter().copied();
let thresholds: Vec<u64> = if thresholds.clone().last() == Some(VoteWeight::MAX) {
// in the event that they included it, we don't need to make any changes
// Box::new(thresholds.collect()
thresholds.collect()
} else {
// otherwise, insert it here.
Expand Down Expand Up @@ -774,10 +773,13 @@ impl<T: Config> Node<T> {
"node does not exist in the expected bag"
);

let non_terminal_check = !self.is_terminal() &&
expected_bag.head.as_ref() != Some(id) &&
expected_bag.tail.as_ref() != Some(id);
let terminal_check =
expected_bag.head.as_ref() == Some(id) || expected_bag.tail.as_ref() == Some(id);
frame_support::ensure!(
!self.is_terminal() ||
expected_bag.head.as_ref() == Some(id) ||
expected_bag.tail.as_ref() == Some(id),
non_terminal_check || terminal_check,
"a terminal node is neither its bag head or tail"
);

Expand Down
7 changes: 5 additions & 2 deletions frame/bags-list/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ pub(crate) const GENESIS_IDS: [(AccountId, VoteWeight); 4] =
[(1, 10), (2, 1_000), (3, 1_000), (4, 1_000)];

#[derive(Default)]
pub(crate) struct ExtBuilder {
pub struct ExtBuilder {
ids: Vec<(AccountId, VoteWeight)>,
}

impl ExtBuilder {
/// Add some AccountIds to insert into `List`.
#[cfg(test)]
pub(crate) fn add_ids(mut self, ids: Vec<(AccountId, VoteWeight)>) -> Self {
self.ids = ids;
self
Expand All @@ -126,18 +127,20 @@ impl ExtBuilder {
ext
}

pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) {
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
self.build().execute_with(|| {
test();
List::<Runtime>::sanity_check().expect("Sanity check post condition failed")
})
}

#[cfg(test)]
pub(crate) fn build_and_execute_no_post_check(self, test: impl FnOnce() -> ()) {
self.build().execute_with(test)
}
}

#[cfg(test)]
pub(crate) mod test_utils {
use super::*;
use list::Bag;
Expand Down