1use 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
31const GAS_PER_SECOND: u64 = 40_000_000;
36
37const 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 HostFn,
47 ExtCodeCopy(u32),
49 CopyFromContract(u32),
51 CopyToContract(u32),
53 CallDataLoad,
55 CallDataCopy(u32),
57 Caller,
59 CallDataSize,
61 ReturnDataSize,
63 ToAccountId,
65 Origin,
67 CodeHash,
69 OwnCodeHash,
71 CodeSize,
73 CallerIsOrigin,
75 CallerIsRoot,
77 OriginIsRoot,
79 Address,
81 RefTimeLeft,
83 WeightLeft,
85 Balance,
87 BalanceOf,
89 ValueTransferred,
91 MinimumBalance,
93 BlockNumber,
95 BlockHash,
97 BlockAuthor,
99 GasPrice,
101 BaseFee,
103 Now,
105 GasLimit,
107 Terminate { code_removed: bool },
109 DepositEvent { num_topic: u32, len: u32 },
111 SetStorage { new_bytes: u32, old_bytes: u32, kind: StorageAccessKind },
114 ClearStorage { len: u32, kind: StorageAccessKind },
116 ContainsStorage { len: u32, kind: StorageAccessKind },
118 GetStorage { len: u32, kind: StorageAccessKind },
120 TakeStorage { len: u32, kind: StorageAccessKind },
122 CallBase,
124 DelegateCallBase,
126 PrecompileBase,
128 PrecompileWithInfoBase,
130 PrecompileDecode(u32),
132 CallTransferSurcharge { dust_transfer: bool },
135 CallInputCloned(u32),
137 Instantiate { input_data_len: u32, balance_transfer: bool, dust_transfer: bool },
139 Create { init_code_len: u32, balance_transfer: bool, dust_transfer: bool },
141 Ripemd160(u32),
143 HashSha256(u32),
145 HashKeccak256(u32),
147 HashBlake256(u32),
150 HashBlake128(u32),
152 EcdsaRecovery,
154 P256Verify,
156 Sr25519Verify(u32),
158 Precompile(Weight),
160 EcdsaToEthAddress,
162 GetImmutableData(u32),
164 SetImmutableData(u32),
166 Bn128Add,
168 Bn128Mul,
170 Bn128Pairing(u32),
172 Identity(u32),
174 Blake2F(u32),
176 Modexp(u64),
178 Delegations { new_accounts: u32, existing_accounts: u32, invalid_accounts: u32 },
187}
188
189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub enum StorageAccessKind {
192 Persistent(Warmth),
194 Transient,
196}
197
198impl StorageAccessKind {
199 pub fn new(transient: bool, warmth: impl FnOnce() -> Warmth) -> Self {
201 if transient { Self::Transient } else { Self::Persistent(warmth()) }
202 }
203}
204
205macro_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 ($name:ident, $( $arg: expr ),+) => {
239 (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+)))
240 };
241 (@call_zero $name:ident, $( $arg:expr ),*) => {
243 T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*)
244 };
245 (@replace_token $_in:tt) => { 0 };
247}
248
249impl RuntimeCosts {
250 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 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 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(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 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}