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