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::{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	/// Weight of processing EIP-7702 authorization tuples.
179	///
180	/// `invalid_accounts` covers every tuple that produced no state change: those that
181	/// fail the chain-id check, fail signature recovery, or pass recovery but then fail
182	/// validation (bad nonce, non-EOA authority, etc.) or post-validation (set_delegation
183	/// error). All are billed at the signature-recovery cost — a conservative over-estimate
184	/// for the chain-id failures, which bail before recovery — and incur no
185	/// account creation/update work.
186	Delegations { new_accounts: u32, existing_accounts: u32, invalid_accounts: u32 },
187}
188
189/// How a storage access is priced.
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub enum StorageAccessKind {
192	/// Persistent storage, priced by its access-list warmth.
193	Persistent(Warmth),
194	/// Transient storage, every access costs the same.
195	Transient,
196}
197
198impl StorageAccessKind {
199	/// Builds the storage access kind. `warmth` is called only for persistent storage.
200	pub fn new(transient: bool, warmth: impl FnOnce() -> Warmth) -> Self {
201		if transient { Self::Transient } else { Self::Persistent(warmth()) }
202	}
203}
204
205/// For functions that modify storage, benchmarks are performed with one item in the
206/// storage. To account for the worst-case scenario, the weight of the overhead of
207/// writing to or reading from full storage is included. For transient storage writes,
208/// the rollback weight is added to reflect the worst-case scenario for this operation.
209macro_rules! cost_storage {
210    (write_transient, $name:ident $(, $arg:expr )*) => {
211        T::WeightInfo::$name($( $arg ),*)
212            .saturating_add(T::WeightInfo::rollback_transient_storage())
213            .saturating_add(T::WeightInfo::set_transient_storage_full()
214            .saturating_sub(T::WeightInfo::set_transient_storage_empty()))
215    };
216
217    (read_transient, $name:ident $(, $arg:expr )*) => {
218        T::WeightInfo::$name($( $arg ),*)
219            .saturating_add(T::WeightInfo::get_transient_storage_full()
220            .saturating_sub(T::WeightInfo::get_transient_storage_empty()))
221    };
222
223    (write_cold, $name:ident $(, $arg:expr )*) => {
224        T::WeightInfo::$name($( $arg ),*)
225            .saturating_add(T::WeightInfo::set_storage_full()
226            .saturating_sub(T::WeightInfo::set_storage_empty()))
227    };
228
229    (read_cold, $name:ident $(, $arg:expr )*) => {
230        T::WeightInfo::$name($( $arg ),*)
231            .saturating_add(T::WeightInfo::get_storage_full()
232            .saturating_sub(T::WeightInfo::get_storage_empty()))
233    };
234}
235
236macro_rules! cost_args {
237	// cost_args!(name, a, b, c) -> T::WeightInfo::name(a, b, c).saturating_sub(T::WeightInfo::name(0, 0, 0))
238	($name:ident, $( $arg: expr ),+) => {
239		(T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+)))
240	};
241	// Transform T::WeightInfo::name(a, b, c) into T::WeightInfo::name(0, 0, 0)
242	(@call_zero $name:ident, $( $arg:expr ),*) => {
243		T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*)
244	};
245	// Replace the token with 0.
246	(@replace_token $_in:tt) => { 0 };
247}
248
249impl RuntimeCosts {
250	/// Extra ref_time a hot storage access pays to look up the block's overlay.
251	fn hot_storage_overlay_overhead<T: Config>() -> Weight {
252		let per_read = |weight_fn: fn(u32) -> Weight| weight_fn(1).saturating_sub(weight_fn(0));
253		per_read(T::WeightInfo::overlay_probe_full)
254			.saturating_sub(per_read(T::WeightInfo::overlay_probe_empty))
255	}
256
257	/// What a hot write pays on top of the cold read that warmed the slot:
258	/// re-hashing the slot's trie path when the block's storage root is
259	/// computed.
260	fn hot_write_surcharge<T: Config>() -> Weight {
261		let db = T::DbWeight::get();
262		db.writes(1).saturating_sub(db.reads(1))
263	}
264
265	/// Pick the matching storage bench for the access `kind`.
266	fn weight_for_storage_access<T: Config>(
267		op: StorageOp,
268		kind: StorageAccessKind,
269		cold: impl FnOnce() -> Weight,
270		hot: impl FnOnce() -> Weight,
271		transient: impl FnOnce() -> Weight,
272	) -> Weight {
273		match kind {
274			StorageAccessKind::Persistent(Warmth::Cold { revertible }) => {
275				let cost = cold()
276					.saturating_add(T::WeightInfo::access_list_touch_cold_full())
277					.saturating_sub(T::WeightInfo::access_list_touch_cold_empty());
278				if revertible {
279					cost.saturating_add(T::WeightInfo::access_list_rollback_amortization())
280				} else {
281					cost
282				}
283			},
284			StorageAccessKind::Persistent(Warmth::Hot { charged }) => hot()
285				.saturating_add(if charged.covers(op) {
286					Weight::zero()
287				} else {
288					Self::hot_write_surcharge::<T>()
289				})
290				.saturating_add(Self::hot_storage_overlay_overhead::<T>())
291				.saturating_add(T::WeightInfo::access_list_touch_hot_full())
292				.saturating_sub(T::WeightInfo::access_list_touch_hot_single_element()),
293			StorageAccessKind::Transient => transient(),
294		}
295	}
296}
297
298impl<T: Config> Token<T> for RuntimeCosts {
299	fn influence_lowest_weight_limit(&self) -> bool {
300		true
301	}
302
303	fn weight(&self) -> Weight {
304		use self::RuntimeCosts::*;
305		match *self {
306			HostFn => cost_args!(noop_host_fn, 1),
307			// `extcodecopy` charges `CodeSize` separately; subtract it so its read isn't counted
308			// twice.
309			ExtCodeCopy(len) => {
310				T::WeightInfo::extcodecopy(len).saturating_sub(T::WeightInfo::seal_code_size())
311			},
312			CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len),
313			CopyFromContract(len) => T::WeightInfo::seal_return(len),
314			CallDataSize => T::WeightInfo::seal_call_data_size(),
315			ReturnDataSize => T::WeightInfo::seal_return_data_size(),
316			CallDataLoad => T::WeightInfo::seal_call_data_load(),
317			CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len),
318			Caller => T::WeightInfo::seal_caller(),
319			Origin => T::WeightInfo::seal_origin(),
320			ToAccountId => T::WeightInfo::to_account_id(),
321			CodeHash => T::WeightInfo::seal_code_hash(),
322			CodeSize => T::WeightInfo::seal_code_size(),
323			OwnCodeHash => T::WeightInfo::own_code_hash(),
324			CallerIsOrigin => T::WeightInfo::caller_is_origin(),
325			CallerIsRoot => T::WeightInfo::caller_is_root(),
326			OriginIsRoot => T::WeightInfo::origin_is_root(),
327			Address => T::WeightInfo::seal_address(),
328			RefTimeLeft => T::WeightInfo::seal_ref_time_left(),
329			WeightLeft => T::WeightInfo::weight_left(),
330			Balance => T::WeightInfo::seal_balance(),
331			BalanceOf => T::WeightInfo::seal_balance_of(),
332			ValueTransferred => T::WeightInfo::seal_value_transferred(),
333			MinimumBalance => T::WeightInfo::minimum_balance(),
334			BlockNumber => T::WeightInfo::seal_block_number(),
335			BlockHash => T::WeightInfo::seal_block_hash(),
336			BlockAuthor => T::WeightInfo::seal_block_author(),
337			GasPrice => T::WeightInfo::seal_gas_price(),
338			BaseFee => T::WeightInfo::seal_base_fee(),
339			Now => T::WeightInfo::seal_now(),
340			GasLimit => T::WeightInfo::seal_gas_limit(),
341			Terminate { code_removed } => {
342				// logic only runs if code is removed
343				if code_removed {
344					T::WeightInfo::seal_terminate(code_removed.into())
345						.saturating_add(T::WeightInfo::seal_terminate_logic())
346				} else {
347					T::WeightInfo::seal_terminate(code_removed.into())
348				}
349			},
350			DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len)
351				.saturating_add(T::WeightInfo::on_finalize_block_per_event(len))
352				.saturating_add(Weight::from_parts(
353					limits::EXTRA_EVENT_CHARGE_PER_BYTE.saturating_mul(len.into()).into(),
354					0,
355				)),
356			SetStorage { new_bytes, old_bytes, kind } => Self::weight_for_storage_access::<T>(
357				StorageOp::Write,
358				kind,
359				|| cost_storage!(write_cold, seal_set_storage, new_bytes, old_bytes),
360				|| T::WeightInfo::seal_set_storage_hot(new_bytes, old_bytes),
361				|| cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes),
362			),
363			ClearStorage { len, kind } => Self::weight_for_storage_access::<T>(
364				StorageOp::Write,
365				kind,
366				|| cost_storage!(write_cold, clear_storage, len),
367				|| T::WeightInfo::clear_storage_hot(len),
368				|| cost_storage!(write_transient, seal_clear_transient_storage, len),
369			),
370			ContainsStorage { len, kind } => Self::weight_for_storage_access::<T>(
371				StorageOp::Read,
372				kind,
373				|| cost_storage!(read_cold, contains_storage, len),
374				|| T::WeightInfo::contains_storage_hot(len),
375				|| cost_storage!(read_transient, seal_contains_transient_storage, len),
376			),
377			GetStorage { len, kind } => Self::weight_for_storage_access::<T>(
378				StorageOp::Read,
379				kind,
380				|| cost_storage!(read_cold, seal_get_storage, len),
381				|| T::WeightInfo::seal_get_storage_hot(len),
382				|| cost_storage!(read_transient, seal_get_transient_storage, len),
383			),
384			TakeStorage { len, kind } => Self::weight_for_storage_access::<T>(
385				StorageOp::Write,
386				kind,
387				|| cost_storage!(write_cold, take_storage, len),
388				|| T::WeightInfo::take_storage_hot(len),
389				|| cost_storage!(write_transient, seal_take_transient_storage, len),
390			),
391			CallBase => T::WeightInfo::seal_call(0, 0, 0),
392			DelegateCallBase => T::WeightInfo::seal_delegate_call(),
393			PrecompileBase => T::WeightInfo::seal_call_precompile(0, 0),
394			PrecompileWithInfoBase => T::WeightInfo::seal_call_precompile(1, 0),
395			PrecompileDecode(len) => cost_args!(seal_call_precompile, 0, len),
396			CallTransferSurcharge { dust_transfer } => {
397				cost_args!(seal_call, 1, dust_transfer.into(), 0)
398			},
399			CallInputCloned(len) => cost_args!(seal_call, 0, 0, len),
400			Instantiate { input_data_len, balance_transfer, dust_transfer } => {
401				T::WeightInfo::seal_instantiate(
402					balance_transfer.into(),
403					dust_transfer.into(),
404					input_data_len,
405				)
406			},
407			Create { init_code_len, balance_transfer, dust_transfer } => {
408				T::WeightInfo::evm_instantiate(
409					balance_transfer.into(),
410					dust_transfer.into(),
411					init_code_len,
412				)
413			},
414			HashSha256(len) => T::WeightInfo::sha2_256(len),
415			Ripemd160(len) => T::WeightInfo::ripemd_160(len),
416			HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len),
417			HashBlake256(len) => T::WeightInfo::hash_blake2_256(len),
418			HashBlake128(len) => T::WeightInfo::hash_blake2_128(len),
419			EcdsaRecovery => T::WeightInfo::ecdsa_recover(),
420			P256Verify => T::WeightInfo::p256_verify(),
421			Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len),
422			Precompile(weight) => weight,
423			EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(),
424			GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len),
425			SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len),
426			Bn128Add => T::WeightInfo::bn128_add(),
427			Bn128Mul => T::WeightInfo::bn128_mul(),
428			Bn128Pairing(len) => T::WeightInfo::bn128_pairing(len),
429			Identity(len) => T::WeightInfo::identity(len),
430			Blake2F(rounds) => T::WeightInfo::blake2f(rounds),
431			Modexp(gas) => Weight::from_parts(gas.saturating_mul(WEIGHT_PER_GAS), 0),
432			Delegations { new_accounts, existing_accounts, invalid_accounts } => {
433				T::WeightInfo::process_new_account_authorization(new_accounts)
434					.saturating_add(T::WeightInfo::process_existing_account_authorization(
435						existing_accounts,
436					))
437					.saturating_add(T::WeightInfo::process_invalid_authorization(invalid_accounts))
438			},
439		}
440	}
441}
442
443#[cfg(test)]
444mod tests {
445	use super::*;
446	use crate::tests::Test;
447
448	#[test]
449	fn storage_pricing_by_access_kind() {
450		let len = 64u32;
451		let cold = StorageAccessKind::Persistent(Warmth::Cold { revertible: false });
452		let cold_revertible = StorageAccessKind::Persistent(Warmth::Cold { revertible: true });
453		let hot_kinds = [
454			StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Read }),
455			StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Write }),
456		];
457
458		let with_kind = |kind: StorageAccessKind| -> Vec<RuntimeCosts> {
459			vec![
460				RuntimeCosts::GetStorage { len, kind },
461				RuntimeCosts::SetStorage { new_bytes: len, old_bytes: len, kind },
462				RuntimeCosts::ClearStorage { len, kind },
463				RuntimeCosts::ContainsStorage { len, kind },
464				RuntimeCosts::TakeStorage { len, kind },
465			]
466		};
467
468		for hot in hot_kinds {
469			for (cold_cost, hot_cost) in with_kind(cold).into_iter().zip(with_kind(hot)) {
470				let cold_weight = <RuntimeCosts as Token<Test>>::weight(&cold_cost);
471				let hot_weight = <RuntimeCosts as Token<Test>>::weight(&hot_cost);
472				assert!(
473					cold_weight.ref_time() > hot_weight.ref_time(),
474					"expected cold > hot ref_time for {cold_cost:?}: \
475					 cold={cold_weight:?} hot={hot_weight:?}",
476				);
477				assert_eq!(
478					hot_weight.proof_size(),
479					0,
480					"hot proof_size {hot_cost:?}: {hot_weight:?}"
481				);
482				assert!(
483					cold_weight.proof_size() > 0,
484					"cold proof_size {cold_cost:?}: {cold_weight:?}",
485				);
486			}
487		}
488
489		for (rev_cost, non_rev_cost) in with_kind(cold_revertible).into_iter().zip(with_kind(cold))
490		{
491			let rev_weight = <RuntimeCosts as Token<Test>>::weight(&rev_cost);
492			let non_rev_weight = <RuntimeCosts as Token<Test>>::weight(&non_rev_cost);
493			assert!(
494				rev_weight.ref_time() > non_rev_weight.ref_time(),
495				"expected revertible > non-revertible ref_time for {rev_cost:?}: \
496				 rev={rev_weight:?} non={non_rev_weight:?}",
497			);
498			assert_eq!(
499				rev_weight.proof_size(),
500				non_rev_weight.proof_size(),
501				"proof_size differs {rev_cost:?}: rev={rev_weight:?} non={non_rev_weight:?}",
502			);
503		}
504
505		for transient_cost in with_kind(StorageAccessKind::Transient) {
506			let weight = <RuntimeCosts as Token<Test>>::weight(&transient_cost);
507			assert_eq!(
508				weight.proof_size(),
509				0,
510				"transient storage is priced without proof: {transient_cost:?}: {weight:?}"
511			);
512			assert!(
513				weight.ref_time() > 0,
514				"transient storage ref_time must be above zero: {transient_cost:?}: {weight:?}"
515			);
516		}
517	}
518
519	#[test]
520	fn the_first_hot_write_pays_the_surcharge() {
521		const LEN: u32 = 64;
522		let weight = |cost: &RuntimeCosts| <RuntimeCosts as Token<Test>>::weight(cost);
523
524		let surcharge = RuntimeCosts::hot_write_surcharge::<Test>();
525		let db = <Test as frame_system::Config>::DbWeight::get();
526		assert!(
527			surcharge.ref_time() > 0 && surcharge.ref_time() < db.writes(1).ref_time(),
528			"the surcharge is part of a write: above zero, below all of it: {surcharge:?}",
529		);
530		assert_eq!(surcharge.proof_size(), 0, "the surcharge adds no proof: {surcharge:?}");
531
532		let read_paid = StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Read });
533		let write_paid = StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Write });
534
535		let write_costs = |kind: StorageAccessKind| {
536			[
537				RuntimeCosts::SetStorage { new_bytes: LEN, old_bytes: LEN, kind },
538				RuntimeCosts::ClearStorage { len: LEN, kind },
539				RuntimeCosts::TakeStorage { len: LEN, kind },
540			]
541		};
542		for (write_to_read_paid_slot, write_to_write_paid_slot) in
543			write_costs(read_paid).into_iter().zip(write_costs(write_paid))
544		{
545			assert_eq!(
546				weight(&write_to_read_paid_slot).saturating_sub(weight(&write_to_write_paid_slot)),
547				surcharge,
548				"a write to a read-paid slot pays exactly the surcharge: \
549				 {write_to_read_paid_slot:?}",
550			);
551		}
552
553		let read_costs = |kind: StorageAccessKind| {
554			[
555				RuntimeCosts::GetStorage { len: LEN, kind },
556				RuntimeCosts::ContainsStorage { len: LEN, kind },
557			]
558		};
559		for (read_of_read_paid_slot, read_of_write_paid_slot) in
560			read_costs(read_paid).into_iter().zip(read_costs(write_paid))
561		{
562			assert_eq!(
563				weight(&read_of_read_paid_slot),
564				weight(&read_of_write_paid_slot),
565				"a read is covered at either paid level: {read_of_read_paid_slot:?}",
566			);
567		}
568	}
569
570	#[test]
571	fn a_transient_access_never_consults_the_access_list() {
572		assert_eq!(
573			StorageAccessKind::new(true, || unreachable!("transient storage has no warmth")),
574			StorageAccessKind::Transient,
575		);
576	}
577
578	#[test]
579	fn hot_storage_overlay_overhead_is_not_zero() {
580		let overhead = RuntimeCosts::hot_storage_overlay_overhead::<Test>();
581		assert!(
582			overhead.ref_time() > 0,
583			"the per-read cost of overlay_probe_full must stay above overlay_probe_empty",
584		);
585		assert_eq!(overhead.proof_size(), 0, "the overlay probe is in-memory only: {overhead:?}");
586	}
587}