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