Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 26d69bc

Browse files
emostovgui1117
andauthored
Fuzzer for Pallet Bags List (#9851)
* Fuzzer for Pallet Bags List * Some small updates * Fuzzer for Pallet Bags List This PR adds a fuzzer for the `SortedListProvider` API exposed by pallet-bags-list. * Feature gate code NOT used by fuzz feature * Create Enum for list actions * fix some small mistakes * try and make CI happy * fmt * Do not insert before updating * clean up some misc. comments * marginally improve Node::sanity_check * Change ID_RANGE to 25_000 * comma * try improve correct feature gating so no unused code Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
1 parent b63e1c5 commit 26d69bc

File tree

9 files changed

+144
-10
lines changed

9 files changed

+144
-10
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ members = [
135135
"frame/utility",
136136
"frame/vesting",
137137
"frame/bags-list",
138+
"frame/bags-list/fuzzer",
138139
"primitives/api",
139140
"primitives/api/proc-macro",
140141
"primitives/api/test",

frame/bags-list/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,10 @@ runtime-benchmarks = [
6363
"sp-tracing",
6464
"frame-election-provider-support/runtime-benchmarks",
6565
]
66+
fuzz = [
67+
"sp-core",
68+
"sp-io",
69+
"pallet-balances",
70+
"sp-tracing",
71+
]
6672

frame/bags-list/fuzzer/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
hfuzz_target
2+
hfuzz_workspace

frame/bags-list/fuzzer/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "pallet-bags-list-fuzzer"
3+
version = "4.0.0-dev"
4+
authors = ["Parity Technologies <admin@parity.io>"]
5+
edition = "2018"
6+
license = "Apache-2.0"
7+
homepage = "https://substrate.dev"
8+
repository = "https://github.com/paritytech/substrate/"
9+
description = "Fuzzer for FRAME pallet bags list"
10+
readme = "README.md"
11+
publish = false
12+
13+
[dependencies]
14+
honggfuzz = "0.5"
15+
rand = { version = "0.8", features = ["std", "small_rng"] }
16+
17+
pallet-bags-list = { version = "4.0.0-dev", features = ["fuzz"], path = ".." }
18+
frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support", features = ["runtime-benchmarks"] }
19+
20+
[[bin]]
21+
name = "bags-list"
22+
path = "src/main.rs"

frame/bags-list/fuzzer/src/main.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! # Running
19+
//! Running this fuzzer can be done with `cargo hfuzz run bags-list`. `honggfuzz` CLI options can
20+
//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
21+
//!
22+
//! # Debugging a panic
23+
//! Once a panic is found, it can be debugged with
24+
//! `cargo hfuzz run-debug fixed_point hfuzz_workspace/bags_list/*.fuzz`.
25+
//!
26+
//! # More information
27+
//! More information about `honggfuzz` can be found
28+
//! [here](https://docs.rs/honggfuzz/).
29+
30+
use frame_election_provider_support::{SortedListProvider, VoteWeight};
31+
use honggfuzz::fuzz;
32+
use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder};
33+
use std::convert::From;
34+
35+
const ID_RANGE: AccountId = 25_000;
36+
37+
/// Actions of a `SortedListProvider` that we fuzz.
38+
enum Action {
39+
Insert,
40+
Update,
41+
Remove,
42+
}
43+
44+
impl From<u32> for Action {
45+
fn from(v: u32) -> Self {
46+
let num_variants = Self::Remove as u32 + 1;
47+
match v % num_variants {
48+
_x if _x == Action::Insert as u32 => Action::Insert,
49+
_x if _x == Action::Update as u32 => Action::Update,
50+
_x if _x == Action::Remove as u32 => Action::Remove,
51+
_ => unreachable!(),
52+
}
53+
}
54+
}
55+
56+
fn main() {
57+
ExtBuilder::default().build_and_execute(|| loop {
58+
fuzz!(|data: (AccountId, VoteWeight, u32)| {
59+
let (account_id_seed, vote_weight, action_seed) = data;
60+
61+
let id = account_id_seed % ID_RANGE;
62+
let action = Action::from(action_seed);
63+
64+
match action {
65+
Action::Insert => {
66+
if BagsList::on_insert(id.clone(), vote_weight).is_err() {
67+
// this was a duplicate id, which is ok. We can just update it.
68+
BagsList::on_update(&id, vote_weight);
69+
}
70+
assert!(BagsList::contains(&id));
71+
},
72+
Action::Update => {
73+
let already_contains = BagsList::contains(&id);
74+
BagsList::on_update(&id, vote_weight);
75+
if already_contains {
76+
assert!(BagsList::contains(&id));
77+
}
78+
},
79+
Action::Remove => {
80+
BagsList::on_remove(&id);
81+
assert!(!BagsList::contains(&id));
82+
},
83+
}
84+
85+
assert!(BagsList::sanity_check().is_ok());
86+
})
87+
});
88+
}

frame/bags-list/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ use sp_std::prelude::*;
5959
mod benchmarks;
6060

6161
mod list;
62-
#[cfg(test)]
63-
mod mock;
62+
#[cfg(any(test, feature = "fuzz"))]
63+
pub mod mock;
6464
#[cfg(test)]
6565
mod tests;
6666
pub mod weights;

frame/bags-list/src/list/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,8 @@ impl<T: Config> List<T> {
391391
///
392392
/// * there are no duplicate ids,
393393
/// * length of this list is in sync with `CounterForListNodes`,
394-
/// * and sanity-checks all bags. This will cascade down all the checks and makes sure all bags
395-
/// are checked per *any* update to `List`.
394+
/// * and sanity-checks all bags and nodes. This will cascade down all the checks and makes sure
395+
/// all bags and nodes are checked per *any* update to `List`.
396396
#[cfg(feature = "std")]
397397
pub(crate) fn sanity_check() -> Result<(), &'static str> {
398398
use frame_support::ensure;
@@ -414,7 +414,6 @@ impl<T: Config> List<T> {
414414
let thresholds = T::BagThresholds::get().iter().copied();
415415
let thresholds: Vec<u64> = if thresholds.clone().last() == Some(VoteWeight::MAX) {
416416
// in the event that they included it, we don't need to make any changes
417-
// Box::new(thresholds.collect()
418417
thresholds.collect()
419418
} else {
420419
// otherwise, insert it here.
@@ -774,10 +773,13 @@ impl<T: Config> Node<T> {
774773
"node does not exist in the expected bag"
775774
);
776775

776+
let non_terminal_check = !self.is_terminal() &&
777+
expected_bag.head.as_ref() != Some(id) &&
778+
expected_bag.tail.as_ref() != Some(id);
779+
let terminal_check =
780+
expected_bag.head.as_ref() == Some(id) || expected_bag.tail.as_ref() == Some(id);
777781
frame_support::ensure!(
778-
!self.is_terminal() ||
779-
expected_bag.head.as_ref() == Some(id) ||
780-
expected_bag.tail.as_ref() == Some(id),
782+
non_terminal_check || terminal_check,
781783
"a terminal node is neither its bag head or tail"
782784
);
783785

frame/bags-list/src/mock.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,13 @@ pub(crate) const GENESIS_IDS: [(AccountId, VoteWeight); 4] =
101101
[(1, 10), (2, 1_000), (3, 1_000), (4, 1_000)];
102102

103103
#[derive(Default)]
104-
pub(crate) struct ExtBuilder {
104+
pub struct ExtBuilder {
105105
ids: Vec<(AccountId, VoteWeight)>,
106106
}
107107

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

129-
pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) {
130+
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
130131
self.build().execute_with(|| {
131132
test();
132133
List::<Runtime>::sanity_check().expect("Sanity check post condition failed")
133134
})
134135
}
135136

137+
#[cfg(test)]
136138
pub(crate) fn build_and_execute_no_post_check(self, test: impl FnOnce() -> ()) {
137139
self.build().execute_with(test)
138140
}
139141
}
140142

143+
#[cfg(test)]
141144
pub(crate) mod test_utils {
142145
use super::*;
143146
use list::Bag;

0 commit comments

Comments
 (0)