1use 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
28const GAS_PER_SECOND: u64 = 40_000_000;
33
34const 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 HostFn,
44 ExtCodeCopy(u32),
46 CopyFromContract(u32),
48 CopyToContract(u32),
50 CallDataLoad,
52 CallDataCopy(u32),
54 Caller,
56 CallDataSize,
58 ReturnDataSize,
60 ToAccountId,
62 Origin,
64 CodeHash,
66 OwnCodeHash,
68 CodeSize,
70 CallerIsOrigin,
72 CallerIsRoot,
74 Address,
76 RefTimeLeft,
78 WeightLeft,
80 Balance,
82 BalanceOf,
84 ValueTransferred,
86 MinimumBalance,
88 BlockNumber,
90 BlockHash,
92 BlockAuthor,
94 GasPrice,
96 BaseFee,
98 Now,
100 GasLimit,
102 Terminate { code_removed: bool },
104 DepositEvent { num_topic: u32, len: u32 },
106 SetStorage { new_bytes: u32, old_bytes: u32, kind: StorageAccessKind },
109 ClearStorage { len: u32, kind: StorageAccessKind },
111 ContainsStorage { len: u32, kind: StorageAccessKind },
113 GetStorage { len: u32, kind: StorageAccessKind },
115 TakeStorage { len: u32, kind: StorageAccessKind },
117 CallBase,
119 DelegateCallBase,
121 PrecompileBase,
123 PrecompileWithInfoBase,
125 PrecompileDecode(u32),
127 CallTransferSurcharge { dust_transfer: bool },
130 CallInputCloned(u32),
132 Instantiate { input_data_len: u32, balance_transfer: bool, dust_transfer: bool },
134 Create { init_code_len: u32, balance_transfer: bool, dust_transfer: bool },
136 Ripemd160(u32),
138 HashSha256(u32),
140 HashKeccak256(u32),
142 HashBlake256(u32),
145 HashBlake128(u32),
147 EcdsaRecovery,
149 P256Verify,
151 Sr25519Verify(u32),
153 Precompile(Weight),
155 EcdsaToEthAddress,
157 GetImmutableData(u32),
159 SetImmutableData(u32),
161 Bn128Add,
163 Bn128Mul,
165 Bn128Pairing(u32),
167 Identity(u32),
169 Blake2F(u32),
171 Modexp(u64),
173}
174
175macro_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 ($name:ident, $( $arg: expr ),+) => {
209 (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+)))
210 };
211 (@call_zero $name:ident, $( $arg:expr ),*) => {
213 T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*)
214 };
215 (@replace_token $_in:tt) => { 0 };
217}
218
219impl RuntimeCosts {
220 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 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(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 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}