referrerpolicy=no-referrer-when-downgrade

pallet_revive/vm/
runtime_costs.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
18use crate::{
19	Config,
20	access_list::{StorageAccessKind, StorageOp, Warmth},
21	limits,
22	metering::Token,
23	weightinfo_extension::OnFinalizeBlockParts,
24	weights::WeightInfo,
25};
26use frame_support::{
27	traits::Get,
28	weights::{Weight, constants::WEIGHT_REF_TIME_PER_SECOND},
29};
30
31/// Current approximation of the gas/s consumption considering
32/// EVM execution over compiled WASM (on 4.4Ghz CPU).
33/// Given the 2000ms Weight, from which 75% only are used for transactions,
34/// the total EVM execution gas limit is: GAS_PER_SECOND * 2 * 0.75 ~= 60_000_000.
35const GAS_PER_SECOND: u64 = 40_000_000;
36
37/// Approximate ratio of the amount of Weight per Gas.
38/// u64 works for approximations because Weight is a very small unit compared to
39/// gas.
40const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND;
41
42#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
43#[derive(Copy, Clone)]
44pub enum RuntimeCosts {
45	/// Base Weight of calling a host function.
46	HostFn,
47	/// Weight charged for executing the extcodecopy instruction.
48	ExtCodeCopy(u32),
49	/// Weight charged for copying data from the sandbox.
50	CopyFromContract(u32),
51	/// Weight charged for copying data to the sandbox.
52	CopyToContract(u32),
53	/// Weight of calling `seal_call_data_load``.
54	CallDataLoad,
55	/// Weight of calling `seal_call_data_copy`.
56	CallDataCopy(u32),
57	/// Weight of calling `seal_caller`.
58	Caller,
59	/// Weight of calling `seal_call_data_size`.
60	CallDataSize,
61	/// Weight of calling `seal_return_data_size`.
62	ReturnDataSize,
63	/// Weight of calling `toAccountId` on the `System` pre-compile.
64	ToAccountId,
65	/// Weight of calling `seal_origin`.
66	Origin,
67	/// Weight of calling `seal_code_hash`.
68	CodeHash,
69	/// Weight of calling `ownCodeHash` on the `System` pre-compile.
70	OwnCodeHash,
71	/// Weight of calling `seal_code_size`.
72	CodeSize,
73	/// Weight of calling `callerIsOrigin` on the `System` pre-compile.
74	CallerIsOrigin,
75	/// Weight of calling `callerIsRoot` on the `System` pre-compile.
76	CallerIsRoot,
77	/// Weight of calling `originIsRoot` on the `System` pre-compile.
78	OriginIsRoot,
79	/// Weight of calling `seal_address`.
80	Address,
81	/// Weight of calling `seal_ref_time_left`.
82	RefTimeLeft,
83	/// Weight of calling `weightLeft` on the `System` pre-compile.
84	WeightLeft,
85	/// Weight of calling `seal_balance`.
86	Balance,
87	/// Weight of calling `seal_balance_of`.
88	BalanceOf,
89	/// Weight of calling `seal_value_transferred`.
90	ValueTransferred,
91	/// Weight of calling `minimumBalance` on the `System` pre-compile.
92	MinimumBalance,
93	/// Weight of calling `seal_block_number`.
94	BlockNumber,
95	/// Weight of calling `seal_block_hash`.
96	BlockHash,
97	/// Weight of calling `seal_block_author`.
98	BlockAuthor,
99	/// Weight of calling `seal_gas_price`.
100	GasPrice,
101	/// Weight of calling `seal_base_fee`.
102	BaseFee,
103	/// Weight of calling `seal_now`.
104	Now,
105	/// Weight of calling `seal_gas_limit`.
106	GasLimit,
107	/// Weight of calling `seal_terminate`.
108	Terminate { code_removed: bool },
109	/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
110	DepositEvent { num_topic: u32, len: u32 },
111	/// Weight of `seal_set_storage` / `seal_set_transient_storage`. `kind` picks
112	/// the persistent (cold/hot) or transient bench.
113	SetStorage { new_bytes: u32, old_bytes: u32, kind: StorageAccessKind },
114	/// Weight of the `clearStorage` precompile / `seal_clear_transient_storage`.
115	ClearStorage { len: u32, kind: StorageAccessKind },
116	/// Weight of the `containsStorage` precompile / `seal_contains_transient_storage`.
117	ContainsStorage { len: u32, kind: StorageAccessKind },
118	/// Weight of `seal_get_storage` / `seal_get_transient_storage`.
119	GetStorage { len: u32, kind: StorageAccessKind },
120	/// Weight of the `takeStorage` precompile / `seal_take_transient_storage`.
121	TakeStorage { len: u32, kind: StorageAccessKind },
122	/// Base weight of calling `seal_call`.
123	CallBase,
124	/// Weight of calling `seal_delegate_call` for the given input size.
125	DelegateCallBase,
126	/// Weight of calling a precompile.
127	PrecompileBase,
128	/// Weight of calling a precompile that has a contract info.
129	PrecompileWithInfoBase,
130	/// Weight of reading and decoding the input to a precompile.
131	PrecompileDecode(u32),
132	/// Weight of the transfer performed during a call.
133	/// parameter `dust_transfer` indicates whether the transfer has a `dust` value.
134	CallTransferSurcharge { dust_transfer: bool },
135	/// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag.
136	CallInputCloned(u32),
137	/// Weight of calling `seal_instantiate`.
138	Instantiate { input_data_len: u32, balance_transfer: bool, dust_transfer: bool },
139	/// Weight of calling `Create` opcode.
140	Create { init_code_len: u32, balance_transfer: bool, dust_transfer: bool },
141	/// Weight of calling `Ripemd160` precompile for the given input size.
142	Ripemd160(u32),
143	/// Weight of calling `Sha256` precompile for the given input size.
144	HashSha256(u32),
145	/// Weight of calling the `System::hashBlake256` precompile function for the given input
146	HashKeccak256(u32),
147	/// Weight of calling the `System::hash_blake2_256` precompile function for the given input
148	/// size.
149	HashBlake256(u32),
150	/// Weight of calling `System::hashBlake128` precompile function for the given input size.
151	HashBlake128(u32),
152	/// Weight of calling `ECERecover` precompile.
153	EcdsaRecovery,
154	/// Weight of calling `P256Verify` precompile.
155	P256Verify,
156	/// Weight of calling `seal_sr25519_verify` for the given input size.
157	Sr25519Verify(u32),
158	/// Weight charged by a precompile.
159	Precompile(Weight),
160	/// Weight of calling `ecdsa_to_eth_address`
161	EcdsaToEthAddress,
162	/// Weight of calling `get_immutable_dependency`
163	GetImmutableData(u32),
164	/// Weight of calling `set_immutable_dependency`
165	SetImmutableData(u32),
166	/// Weight of calling `Bn128Add` precompile
167	Bn128Add,
168	/// Weight of calling `Bn128Add` precompile
169	Bn128Mul,
170	/// Weight of calling `Bn128Pairing` precompile for the given number of input pairs.
171	Bn128Pairing(u32),
172	/// Weight of calling `Identity` precompile for the given number of input length.
173	Identity(u32),
174	/// Weight of calling `Blake2F` precompile for the given number of rounds.
175	Blake2F(u32),
176	/// Weight of calling `Modexp` precompile
177	Modexp(u64),
178}
179
180/// For functions that modify storage, benchmarks are performed with one item in the
181/// storage. To account for the worst-case scenario, the weight of the overhead of
182/// writing to or reading from full storage is included. For transient storage writes,
183/// the rollback weight is added to reflect the worst-case scenario for this operation.
184macro_rules! cost_storage {
185    (write_transient, $name:ident $(, $arg:expr )*) => {
186        T::WeightInfo::$name($( $arg ),*)
187            .saturating_add(T::WeightInfo::rollback_transient_storage())
188            .saturating_add(T::WeightInfo::set_transient_storage_full()
189            .saturating_sub(T::WeightInfo::set_transient_storage_empty()))
190    };
191
192    (read_transient, $name:ident $(, $arg:expr )*) => {
193        T::WeightInfo::$name($( $arg ),*)
194            .saturating_add(T::WeightInfo::get_transient_storage_full()
195            .saturating_sub(T::WeightInfo::get_transient_storage_empty()))
196    };
197
198    (write_cold, $name:ident $(, $arg:expr )*) => {
199        T::WeightInfo::$name($( $arg ),*)
200            .saturating_add(T::WeightInfo::set_storage_full()
201            .saturating_sub(T::WeightInfo::set_storage_empty()))
202    };
203
204    (read_cold, $name:ident $(, $arg:expr )*) => {
205        T::WeightInfo::$name($( $arg ),*)
206            .saturating_add(T::WeightInfo::get_storage_full()
207            .saturating_sub(T::WeightInfo::get_storage_empty()))
208    };
209}
210
211macro_rules! cost_args {
212	// cost_args!(name, a, b, c) -> T::WeightInfo::name(a, b, c).saturating_sub(T::WeightInfo::name(0, 0, 0))
213	($name:ident, $( $arg: expr ),+) => {
214		(T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+)))
215	};
216	// Transform T::WeightInfo::name(a, b, c) into T::WeightInfo::name(0, 0, 0)
217	(@call_zero $name:ident, $( $arg:expr ),*) => {
218		T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*)
219	};
220	// Replace the token with 0.
221	(@replace_token $_in:tt) => { 0 };
222}
223
224impl RuntimeCosts {
225	/// Extra ref_time a hot storage access pays to look up the block's overlay.
226	fn hot_storage_overlay_overhead<T: Config>() -> Weight {
227		let per_read = |weight_fn: fn(u32) -> Weight| weight_fn(1).saturating_sub(weight_fn(0));
228		per_read(T::WeightInfo::overlay_probe_full)
229			.saturating_sub(per_read(T::WeightInfo::overlay_probe_empty))
230	}
231
232	/// What a hot write pays on top of the cold read that warmed the slot:
233	/// re-hashing the slot's trie path when the block's storage root is
234	/// computed.
235	fn hot_write_surcharge<T: Config>() -> Weight {
236		let db = T::DbWeight::get();
237		db.writes(1).saturating_sub(db.reads(1))
238	}
239
240	/// Pick the matching storage bench for the access `kind`.
241	fn weight_for_storage_access<T: Config>(
242		op: StorageOp,
243		kind: StorageAccessKind,
244		cold: impl FnOnce() -> Weight,
245		hot: impl FnOnce() -> Weight,
246		transient: impl FnOnce() -> Weight,
247	) -> Weight {
248		match kind {
249			StorageAccessKind::Persistent(Warmth::Cold { revertible }) => {
250				let cost = cold()
251					.saturating_add(T::WeightInfo::access_list_touch_cold_full())
252					.saturating_sub(T::WeightInfo::access_list_touch_cold_empty());
253				if revertible {
254					cost.saturating_add(T::WeightInfo::access_list_rollback_amortization())
255				} else {
256					cost
257				}
258			},
259			StorageAccessKind::Persistent(Warmth::Hot { charged }) => hot()
260				.saturating_add(if charged.covers(op) {
261					Weight::zero()
262				} else {
263					Self::hot_write_surcharge::<T>()
264				})
265				.saturating_add(Self::hot_storage_overlay_overhead::<T>())
266				.saturating_add(T::WeightInfo::access_list_touch_hot_full())
267				.saturating_sub(T::WeightInfo::access_list_touch_hot_single_element()),
268			StorageAccessKind::Transient => transient(),
269		}
270	}
271}
272
273impl<T: Config> Token<T> for RuntimeCosts {
274	fn influence_lowest_weight_limit(&self) -> bool {
275		true
276	}
277
278	fn weight(&self) -> Weight {
279		use self::RuntimeCosts::*;
280		match *self {
281			HostFn => cost_args!(noop_host_fn, 1),
282			// `extcodecopy` charges `CodeSize` separately; subtract it so its read isn't counted
283			// twice.
284			ExtCodeCopy(len) => {
285				T::WeightInfo::extcodecopy(len).saturating_sub(T::WeightInfo::seal_code_size())
286			},
287			CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len),
288			CopyFromContract(len) => T::WeightInfo::seal_return(len),
289			CallDataSize => T::WeightInfo::seal_call_data_size(),
290			ReturnDataSize => T::WeightInfo::seal_return_data_size(),
291			CallDataLoad => T::WeightInfo::seal_call_data_load(),
292			CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len),
293			Caller => T::WeightInfo::seal_caller(),
294			Origin => T::WeightInfo::seal_origin(),
295			ToAccountId => T::WeightInfo::to_account_id(),
296			CodeHash => T::WeightInfo::seal_code_hash(),
297			CodeSize => T::WeightInfo::seal_code_size(),
298			OwnCodeHash => T::WeightInfo::own_code_hash(),
299			CallerIsOrigin => T::WeightInfo::caller_is_origin(),
300			CallerIsRoot => T::WeightInfo::caller_is_root(),
301			OriginIsRoot => T::WeightInfo::origin_is_root(),
302			Address => T::WeightInfo::seal_address(),
303			RefTimeLeft => T::WeightInfo::seal_ref_time_left(),
304			WeightLeft => T::WeightInfo::weight_left(),
305			Balance => T::WeightInfo::seal_balance(),
306			BalanceOf => T::WeightInfo::seal_balance_of(),
307			ValueTransferred => T::WeightInfo::seal_value_transferred(),
308			MinimumBalance => T::WeightInfo::minimum_balance(),
309			BlockNumber => T::WeightInfo::seal_block_number(),
310			BlockHash => T::WeightInfo::seal_block_hash(),
311			BlockAuthor => T::WeightInfo::seal_block_author(),
312			GasPrice => T::WeightInfo::seal_gas_price(),
313			BaseFee => T::WeightInfo::seal_base_fee(),
314			Now => T::WeightInfo::seal_now(),
315			GasLimit => T::WeightInfo::seal_gas_limit(),
316			Terminate { code_removed } => {
317				// logic only runs if code is removed
318				if code_removed {
319					T::WeightInfo::seal_terminate(code_removed.into())
320						.saturating_add(T::WeightInfo::seal_terminate_logic())
321				} else {
322					T::WeightInfo::seal_terminate(code_removed.into())
323				}
324			},
325			DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len)
326				.saturating_add(T::WeightInfo::on_finalize_block_per_event(len))
327				.saturating_add(Weight::from_parts(
328					limits::EXTRA_EVENT_CHARGE_PER_BYTE.saturating_mul(len.into()).into(),
329					0,
330				)),
331			SetStorage { new_bytes, old_bytes, kind } => Self::weight_for_storage_access::<T>(
332				StorageOp::Write,
333				kind,
334				|| cost_storage!(write_cold, seal_set_storage, new_bytes, old_bytes),
335				|| T::WeightInfo::seal_set_storage_hot(new_bytes, old_bytes),
336				|| cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes),
337			),
338			ClearStorage { len, kind } => Self::weight_for_storage_access::<T>(
339				StorageOp::Write,
340				kind,
341				|| cost_storage!(write_cold, clear_storage, len),
342				|| T::WeightInfo::clear_storage_hot(len),
343				|| cost_storage!(write_transient, seal_clear_transient_storage, len),
344			),
345			ContainsStorage { len, kind } => Self::weight_for_storage_access::<T>(
346				StorageOp::Read,
347				kind,
348				|| cost_storage!(read_cold, contains_storage, len),
349				|| T::WeightInfo::contains_storage_hot(len),
350				|| cost_storage!(read_transient, seal_contains_transient_storage, len),
351			),
352			GetStorage { len, kind } => Self::weight_for_storage_access::<T>(
353				StorageOp::Read,
354				kind,
355				|| cost_storage!(read_cold, seal_get_storage, len),
356				|| T::WeightInfo::seal_get_storage_hot(len),
357				|| cost_storage!(read_transient, seal_get_transient_storage, len),
358			),
359			TakeStorage { len, kind } => Self::weight_for_storage_access::<T>(
360				StorageOp::Write,
361				kind,
362				|| cost_storage!(write_cold, take_storage, len),
363				|| T::WeightInfo::take_storage_hot(len),
364				|| cost_storage!(write_transient, seal_take_transient_storage, len),
365			),
366			CallBase => T::WeightInfo::seal_call(0, 0, 0),
367			DelegateCallBase => T::WeightInfo::seal_delegate_call(),
368			PrecompileBase => T::WeightInfo::seal_call_precompile(0, 0),
369			PrecompileWithInfoBase => T::WeightInfo::seal_call_precompile(1, 0),
370			PrecompileDecode(len) => cost_args!(seal_call_precompile, 0, len),
371			CallTransferSurcharge { dust_transfer } => {
372				cost_args!(seal_call, 1, dust_transfer.into(), 0)
373			},
374			CallInputCloned(len) => cost_args!(seal_call, 0, 0, len),
375			Instantiate { input_data_len, balance_transfer, dust_transfer } => {
376				T::WeightInfo::seal_instantiate(
377					balance_transfer.into(),
378					dust_transfer.into(),
379					input_data_len,
380				)
381			},
382			Create { init_code_len, balance_transfer, dust_transfer } => {
383				T::WeightInfo::evm_instantiate(
384					balance_transfer.into(),
385					dust_transfer.into(),
386					init_code_len,
387				)
388			},
389			HashSha256(len) => T::WeightInfo::sha2_256(len),
390			Ripemd160(len) => T::WeightInfo::ripemd_160(len),
391			HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len),
392			HashBlake256(len) => T::WeightInfo::hash_blake2_256(len),
393			HashBlake128(len) => T::WeightInfo::hash_blake2_128(len),
394			EcdsaRecovery => T::WeightInfo::ecdsa_recover(),
395			P256Verify => T::WeightInfo::p256_verify(),
396			Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len),
397			Precompile(weight) => weight,
398			EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(),
399			GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len),
400			SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len),
401			Bn128Add => T::WeightInfo::bn128_add(),
402			Bn128Mul => T::WeightInfo::bn128_mul(),
403			Bn128Pairing(len) => T::WeightInfo::bn128_pairing(len),
404			Identity(len) => T::WeightInfo::identity(len),
405			Blake2F(rounds) => T::WeightInfo::blake2f(rounds),
406			Modexp(gas) => Weight::from_parts(gas.saturating_mul(WEIGHT_PER_GAS), 0),
407		}
408	}
409}
410
411#[cfg(test)]
412mod tests {
413	use super::*;
414	use crate::tests::Test;
415
416	#[test]
417	fn cold_hot_pricing_cold_is_strictly_more_expensive_than_hot() {
418		let len = 64u32;
419		let cold = StorageAccessKind::Persistent(Warmth::Cold { revertible: false });
420		let cold_revertible = StorageAccessKind::Persistent(Warmth::Cold { revertible: true });
421		let hot_kinds = [
422			StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Read }),
423			StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Write }),
424		];
425
426		let with_kind = |kind: StorageAccessKind| -> Vec<RuntimeCosts> {
427			vec![
428				RuntimeCosts::GetStorage { len, kind },
429				RuntimeCosts::SetStorage { new_bytes: len, old_bytes: len, kind },
430				RuntimeCosts::ClearStorage { len, kind },
431				RuntimeCosts::ContainsStorage { len, kind },
432				RuntimeCosts::TakeStorage { len, kind },
433			]
434		};
435
436		for hot in hot_kinds {
437			for (cold_cost, hot_cost) in with_kind(cold).into_iter().zip(with_kind(hot)) {
438				let cold_weight = <RuntimeCosts as Token<Test>>::weight(&cold_cost);
439				let hot_weight = <RuntimeCosts as Token<Test>>::weight(&hot_cost);
440				assert!(
441					cold_weight.ref_time() > hot_weight.ref_time(),
442					"expected cold > hot ref_time for {cold_cost:?}: \
443					 cold={cold_weight:?} hot={hot_weight:?}",
444				);
445				assert_eq!(
446					hot_weight.proof_size(),
447					0,
448					"hot proof_size {hot_cost:?}: {hot_weight:?}"
449				);
450				assert!(
451					cold_weight.proof_size() > 0,
452					"cold proof_size {cold_cost:?}: {cold_weight:?}",
453				);
454			}
455		}
456
457		for (rev_cost, non_rev_cost) in with_kind(cold_revertible).into_iter().zip(with_kind(cold))
458		{
459			let rev_weight = <RuntimeCosts as Token<Test>>::weight(&rev_cost);
460			let non_rev_weight = <RuntimeCosts as Token<Test>>::weight(&non_rev_cost);
461			assert!(
462				rev_weight.ref_time() > non_rev_weight.ref_time(),
463				"expected revertible > non-revertible ref_time for {rev_cost:?}: \
464				 rev={rev_weight:?} non={non_rev_weight:?}",
465			);
466			assert_eq!(
467				rev_weight.proof_size(),
468				non_rev_weight.proof_size(),
469				"proof_size differs {rev_cost:?}: rev={rev_weight:?} non={non_rev_weight:?}",
470			);
471		}
472	}
473
474	#[test]
475	fn the_first_hot_write_pays_the_surcharge() {
476		const LEN: u32 = 64;
477		let weight = |cost: &RuntimeCosts| <RuntimeCosts as Token<Test>>::weight(cost);
478
479		let surcharge = RuntimeCosts::hot_write_surcharge::<Test>();
480		let db = <Test as frame_system::Config>::DbWeight::get();
481		assert!(
482			surcharge.ref_time() > 0 && surcharge.ref_time() < db.writes(1).ref_time(),
483			"the surcharge is part of a write: above zero, below all of it: {surcharge:?}",
484		);
485		assert_eq!(surcharge.proof_size(), 0, "the surcharge adds no proof: {surcharge:?}");
486
487		let read_paid = StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Read });
488		let write_paid = StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Write });
489
490		let write_costs = |kind: StorageAccessKind| {
491			[
492				RuntimeCosts::SetStorage { new_bytes: LEN, old_bytes: LEN, kind },
493				RuntimeCosts::ClearStorage { len: LEN, kind },
494				RuntimeCosts::TakeStorage { len: LEN, kind },
495			]
496		};
497		for (write_to_read_paid_slot, write_to_write_paid_slot) in
498			write_costs(read_paid).into_iter().zip(write_costs(write_paid))
499		{
500			assert_eq!(
501				weight(&write_to_read_paid_slot).saturating_sub(weight(&write_to_write_paid_slot)),
502				surcharge,
503				"a write to a read-paid slot pays exactly the surcharge: \
504				 {write_to_read_paid_slot:?}",
505			);
506		}
507
508		let read_costs = |kind: StorageAccessKind| {
509			[
510				RuntimeCosts::GetStorage { len: LEN, kind },
511				RuntimeCosts::ContainsStorage { len: LEN, kind },
512			]
513		};
514		for (read_of_read_paid_slot, read_of_write_paid_slot) in
515			read_costs(read_paid).into_iter().zip(read_costs(write_paid))
516		{
517			assert_eq!(
518				weight(&read_of_read_paid_slot),
519				weight(&read_of_write_paid_slot),
520				"a read is covered at either paid level: {read_of_read_paid_slot:?}",
521			);
522		}
523	}
524
525	#[test]
526	fn hot_storage_overlay_overhead_is_not_zero() {
527		let overhead = RuntimeCosts::hot_storage_overlay_overhead::<Test>();
528		assert!(
529			overhead.ref_time() > 0,
530			"the per-read cost of overlay_probe_full must stay above overlay_probe_empty",
531		);
532		assert_eq!(overhead.proof_size(), 0, "the overlay probe is in-memory only: {overhead:?}");
533	}
534}