referrerpolicy=no-referrer-when-downgrade

frame_support/storage/
storage_noop_guard.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18// Feature gated since it can panic.
19#![cfg(any(feature = "std", feature = "runtime-benchmarks", feature = "try-runtime", test))]
20
21//! Contains the [`crate::StorageNoopGuard`] for conveniently asserting
22//! that no storage mutation has been made by a whole code block.
23
24/// Asserts that no storage changes took place between con- and destruction of [`Self`].
25///
26/// This is easier than wrapping the whole code-block inside a `assert_storage_noop!`.
27///
28/// # Example
29///
30/// ```should_panic
31/// use frame_support::{StorageNoopGuard, storage::unhashed::put};
32///
33/// sp_io::TestExternalities::default().execute_with(|| {
34/// 	let _guard = frame_support::StorageNoopGuard::default();
35/// 	put(b"key", b"value");
36/// 	// Panics since there are storage changes.
37/// });
38/// ```
39#[must_use]
40pub struct StorageNoopGuard<'a> {
41	storage_root: alloc::vec::Vec<u8>,
42	error_message: &'a str,
43}
44
45impl<'a> Default for StorageNoopGuard<'a> {
46	fn default() -> Self {
47		Self {
48			storage_root: sp_io::storage::root(sp_runtime::StateVersion::V1),
49			error_message: "`StorageNoopGuard` detected an attempted storage change.",
50		}
51	}
52}
53
54impl<'a> StorageNoopGuard<'a> {
55	/// Alias to `default()`.
56	pub fn new() -> Self {
57		Self::default()
58	}
59
60	/// Creates a new [`StorageNoopGuard`] with a custom error message.
61	pub fn from_error_message(error_message: &'a str) -> Self {
62		Self { storage_root: sp_io::storage::root(sp_runtime::StateVersion::V1), error_message }
63	}
64
65	/// Sets a custom error message for a [`StorageNoopGuard`].
66	pub fn set_error_message(&mut self, error_message: &'a str) {
67		self.error_message = error_message;
68	}
69}
70
71impl<'a> Drop for StorageNoopGuard<'a> {
72	fn drop(&mut self) {
73		// No need to double panic, eg. inside a test assertion failure.
74		#[cfg(feature = "std")]
75		if std::thread::panicking() {
76			return
77		}
78		assert_eq!(
79			sp_io::storage::root(sp_runtime::StateVersion::V1),
80			self.storage_root,
81			"{}",
82			self.error_message,
83		);
84	}
85}
86
87#[cfg(test)]
88mod tests {
89	use sp_io::TestExternalities;
90
91	use super::*;
92
93	#[test]
94	#[should_panic(expected = "`StorageNoopGuard` detected an attempted storage change.")]
95	fn storage_noop_guard_panics_on_changed() {
96		TestExternalities::default().execute_with(|| {
97			let _guard = StorageNoopGuard::default();
98			frame_support::storage::unhashed::put(b"key", b"value");
99		});
100	}
101
102	#[test]
103	fn storage_noop_guard_works_on_unchanged() {
104		TestExternalities::default().execute_with(|| {
105			let _guard = StorageNoopGuard::default();
106			frame_support::storage::unhashed::put(b"key", b"value");
107			frame_support::storage::unhashed::kill(b"key");
108		});
109	}
110
111	#[test]
112	#[should_panic(expected = "`StorageNoopGuard` detected an attempted storage change.")]
113	fn storage_noop_guard_panics_on_early_drop() {
114		TestExternalities::default().execute_with(|| {
115			let guard = StorageNoopGuard::default();
116			frame_support::storage::unhashed::put(b"key", b"value");
117			std::mem::drop(guard);
118			frame_support::storage::unhashed::kill(b"key");
119		});
120	}
121
122	#[test]
123	fn storage_noop_guard_works_on_changed_forget() {
124		TestExternalities::default().execute_with(|| {
125			let guard = StorageNoopGuard::default();
126			frame_support::storage::unhashed::put(b"key", b"value");
127			std::mem::forget(guard);
128		});
129	}
130
131	#[test]
132	#[should_panic(expected = "Something else")]
133	fn storage_noop_guard_does_not_double_panic() {
134		TestExternalities::default().execute_with(|| {
135			let _guard = StorageNoopGuard::default();
136			frame_support::storage::unhashed::put(b"key", b"value");
137			panic!("Something else");
138		});
139	}
140
141	#[test]
142	#[should_panic(expected = "`StorageNoopGuard` found unexpected storage changes.")]
143	fn storage_noop_guard_panics_created_from_error_message() {
144		TestExternalities::default().execute_with(|| {
145			let _guard = StorageNoopGuard::from_error_message(
146				"`StorageNoopGuard` found unexpected storage changes.",
147			);
148			frame_support::storage::unhashed::put(b"key", b"value");
149		});
150	}
151
152	#[test]
153	#[should_panic(expected = "`StorageNoopGuard` found unexpected storage changes.")]
154	fn storage_noop_guard_panics_with_set_error_message() {
155		TestExternalities::default().execute_with(|| {
156			let mut guard = StorageNoopGuard::default();
157			guard.set_error_message("`StorageNoopGuard` found unexpected storage changes.");
158			frame_support::storage::unhashed::put(b"key", b"value");
159		});
160	}
161
162	#[test]
163	#[should_panic(expected = "`StorageNoopGuard` detected an attempted storage change.")]
164	fn storage_noop_guard_panics_new_alias() {
165		TestExternalities::default().execute_with(|| {
166			let _guard = StorageNoopGuard::new();
167			frame_support::storage::unhashed::put(b"key", b"value");
168		});
169	}
170}