1use crate::{
19 Config,
20 access_list::{StorageAccessKind, 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}
179
180macro_rules! cost_storage {
185 (write_transient, $name:ident $(, $arg:expr )*) => {
186 T::WeightInfo::$name($( $arg ),*)
187 .saturating_add(T::WeightInfo::rollback_transient_storage())
188 .saturating_add(T::WeightInfo::set_transient_storage_full()
189 .saturating_sub(T::WeightInfo::set_transient_storage_empty()))
190 };
191
192 (read_transient, $name:ident $(, $arg:expr )*) => {
193 T::WeightInfo::$name($( $arg ),*)
194 .saturating_add(T::WeightInfo::get_transient_storage_full()
195 .saturating_sub(T::WeightInfo::get_transient_storage_empty()))
196 };
197
198 (write_cold, $name:ident $(, $arg:expr )*) => {
199 T::WeightInfo::$name($( $arg ),*)
200 .saturating_add(T::WeightInfo::set_storage_full()
201 .saturating_sub(T::WeightInfo::set_storage_empty()))
202 };
203
204 (read_cold, $name:ident $(, $arg:expr )*) => {
205 T::WeightInfo::$name($( $arg ),*)
206 .saturating_add(T::WeightInfo::get_storage_full()
207 .saturating_sub(T::WeightInfo::get_storage_empty()))
208 };
209}
210
211macro_rules! cost_args {
212 ($name:ident, $( $arg: expr ),+) => {
214 (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+)))
215 };
216 (@call_zero $name:ident, $( $arg:expr ),*) => {
218 T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*)
219 };
220 (@replace_token $_in:tt) => { 0 };
222}
223
224impl RuntimeCosts {
225 fn hot_storage_overlay_overhead<T: Config>() -> Weight {
227 let per_read = |weight_fn: fn(u32) -> Weight| weight_fn(1).saturating_sub(weight_fn(0));
228 per_read(T::WeightInfo::overlay_probe_full)
229 .saturating_sub(per_read(T::WeightInfo::overlay_probe_empty))
230 }
231
232 fn hot_write_surcharge<T: Config>() -> Weight {
236 let db = T::DbWeight::get();
237 db.writes(1).saturating_sub(db.reads(1))
238 }
239
240 fn weight_for_storage_access<T: Config>(
242 op: StorageOp,
243 kind: StorageAccessKind,
244 cold: impl FnOnce() -> Weight,
245 hot: impl FnOnce() -> Weight,
246 transient: impl FnOnce() -> Weight,
247 ) -> Weight {
248 match kind {
249 StorageAccessKind::Persistent(Warmth::Cold { revertible }) => {
250 let cost = cold()
251 .saturating_add(T::WeightInfo::access_list_touch_cold_full())
252 .saturating_sub(T::WeightInfo::access_list_touch_cold_empty());
253 if revertible {
254 cost.saturating_add(T::WeightInfo::access_list_rollback_amortization())
255 } else {
256 cost
257 }
258 },
259 StorageAccessKind::Persistent(Warmth::Hot { charged }) => hot()
260 .saturating_add(if charged.covers(op) {
261 Weight::zero()
262 } else {
263 Self::hot_write_surcharge::<T>()
264 })
265 .saturating_add(Self::hot_storage_overlay_overhead::<T>())
266 .saturating_add(T::WeightInfo::access_list_touch_hot_full())
267 .saturating_sub(T::WeightInfo::access_list_touch_hot_single_element()),
268 StorageAccessKind::Transient => transient(),
269 }
270 }
271}
272
273impl<T: Config> Token<T> for RuntimeCosts {
274 fn influence_lowest_weight_limit(&self) -> bool {
275 true
276 }
277
278 fn weight(&self) -> Weight {
279 use self::RuntimeCosts::*;
280 match *self {
281 HostFn => cost_args!(noop_host_fn, 1),
282 ExtCodeCopy(len) => {
285 T::WeightInfo::extcodecopy(len).saturating_sub(T::WeightInfo::seal_code_size())
286 },
287 CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len),
288 CopyFromContract(len) => T::WeightInfo::seal_return(len),
289 CallDataSize => T::WeightInfo::seal_call_data_size(),
290 ReturnDataSize => T::WeightInfo::seal_return_data_size(),
291 CallDataLoad => T::WeightInfo::seal_call_data_load(),
292 CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len),
293 Caller => T::WeightInfo::seal_caller(),
294 Origin => T::WeightInfo::seal_origin(),
295 ToAccountId => T::WeightInfo::to_account_id(),
296 CodeHash => T::WeightInfo::seal_code_hash(),
297 CodeSize => T::WeightInfo::seal_code_size(),
298 OwnCodeHash => T::WeightInfo::own_code_hash(),
299 CallerIsOrigin => T::WeightInfo::caller_is_origin(),
300 CallerIsRoot => T::WeightInfo::caller_is_root(),
301 OriginIsRoot => T::WeightInfo::origin_is_root(),
302 Address => T::WeightInfo::seal_address(),
303 RefTimeLeft => T::WeightInfo::seal_ref_time_left(),
304 WeightLeft => T::WeightInfo::weight_left(),
305 Balance => T::WeightInfo::seal_balance(),
306 BalanceOf => T::WeightInfo::seal_balance_of(),
307 ValueTransferred => T::WeightInfo::seal_value_transferred(),
308 MinimumBalance => T::WeightInfo::minimum_balance(),
309 BlockNumber => T::WeightInfo::seal_block_number(),
310 BlockHash => T::WeightInfo::seal_block_hash(),
311 BlockAuthor => T::WeightInfo::seal_block_author(),
312 GasPrice => T::WeightInfo::seal_gas_price(),
313 BaseFee => T::WeightInfo::seal_base_fee(),
314 Now => T::WeightInfo::seal_now(),
315 GasLimit => T::WeightInfo::seal_gas_limit(),
316 Terminate { code_removed } => {
317 if code_removed {
319 T::WeightInfo::seal_terminate(code_removed.into())
320 .saturating_add(T::WeightInfo::seal_terminate_logic())
321 } else {
322 T::WeightInfo::seal_terminate(code_removed.into())
323 }
324 },
325 DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len)
326 .saturating_add(T::WeightInfo::on_finalize_block_per_event(len))
327 .saturating_add(Weight::from_parts(
328 limits::EXTRA_EVENT_CHARGE_PER_BYTE.saturating_mul(len.into()).into(),
329 0,
330 )),
331 SetStorage { new_bytes, old_bytes, kind } => Self::weight_for_storage_access::<T>(
332 StorageOp::Write,
333 kind,
334 || cost_storage!(write_cold, seal_set_storage, new_bytes, old_bytes),
335 || T::WeightInfo::seal_set_storage_hot(new_bytes, old_bytes),
336 || cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes),
337 ),
338 ClearStorage { len, kind } => Self::weight_for_storage_access::<T>(
339 StorageOp::Write,
340 kind,
341 || cost_storage!(write_cold, clear_storage, len),
342 || T::WeightInfo::clear_storage_hot(len),
343 || cost_storage!(write_transient, seal_clear_transient_storage, len),
344 ),
345 ContainsStorage { len, kind } => Self::weight_for_storage_access::<T>(
346 StorageOp::Read,
347 kind,
348 || cost_storage!(read_cold, contains_storage, len),
349 || T::WeightInfo::contains_storage_hot(len),
350 || cost_storage!(read_transient, seal_contains_transient_storage, len),
351 ),
352 GetStorage { len, kind } => Self::weight_for_storage_access::<T>(
353 StorageOp::Read,
354 kind,
355 || cost_storage!(read_cold, seal_get_storage, len),
356 || T::WeightInfo::seal_get_storage_hot(len),
357 || cost_storage!(read_transient, seal_get_transient_storage, len),
358 ),
359 TakeStorage { len, kind } => Self::weight_for_storage_access::<T>(
360 StorageOp::Write,
361 kind,
362 || cost_storage!(write_cold, take_storage, len),
363 || T::WeightInfo::take_storage_hot(len),
364 || cost_storage!(write_transient, seal_take_transient_storage, len),
365 ),
366 CallBase => T::WeightInfo::seal_call(0, 0, 0),
367 DelegateCallBase => T::WeightInfo::seal_delegate_call(),
368 PrecompileBase => T::WeightInfo::seal_call_precompile(0, 0),
369 PrecompileWithInfoBase => T::WeightInfo::seal_call_precompile(1, 0),
370 PrecompileDecode(len) => cost_args!(seal_call_precompile, 0, len),
371 CallTransferSurcharge { dust_transfer } => {
372 cost_args!(seal_call, 1, dust_transfer.into(), 0)
373 },
374 CallInputCloned(len) => cost_args!(seal_call, 0, 0, len),
375 Instantiate { input_data_len, balance_transfer, dust_transfer } => {
376 T::WeightInfo::seal_instantiate(
377 balance_transfer.into(),
378 dust_transfer.into(),
379 input_data_len,
380 )
381 },
382 Create { init_code_len, balance_transfer, dust_transfer } => {
383 T::WeightInfo::evm_instantiate(
384 balance_transfer.into(),
385 dust_transfer.into(),
386 init_code_len,
387 )
388 },
389 HashSha256(len) => T::WeightInfo::sha2_256(len),
390 Ripemd160(len) => T::WeightInfo::ripemd_160(len),
391 HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len),
392 HashBlake256(len) => T::WeightInfo::hash_blake2_256(len),
393 HashBlake128(len) => T::WeightInfo::hash_blake2_128(len),
394 EcdsaRecovery => T::WeightInfo::ecdsa_recover(),
395 P256Verify => T::WeightInfo::p256_verify(),
396 Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len),
397 Precompile(weight) => weight,
398 EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(),
399 GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len),
400 SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len),
401 Bn128Add => T::WeightInfo::bn128_add(),
402 Bn128Mul => T::WeightInfo::bn128_mul(),
403 Bn128Pairing(len) => T::WeightInfo::bn128_pairing(len),
404 Identity(len) => T::WeightInfo::identity(len),
405 Blake2F(rounds) => T::WeightInfo::blake2f(rounds),
406 Modexp(gas) => Weight::from_parts(gas.saturating_mul(WEIGHT_PER_GAS), 0),
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::tests::Test;
415
416 #[test]
417 fn cold_hot_pricing_cold_is_strictly_more_expensive_than_hot() {
418 let len = 64u32;
419 let cold = StorageAccessKind::Persistent(Warmth::Cold { revertible: false });
420 let cold_revertible = StorageAccessKind::Persistent(Warmth::Cold { revertible: true });
421 let hot_kinds = [
422 StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Read }),
423 StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Write }),
424 ];
425
426 let with_kind = |kind: StorageAccessKind| -> Vec<RuntimeCosts> {
427 vec![
428 RuntimeCosts::GetStorage { len, kind },
429 RuntimeCosts::SetStorage { new_bytes: len, old_bytes: len, kind },
430 RuntimeCosts::ClearStorage { len, kind },
431 RuntimeCosts::ContainsStorage { len, kind },
432 RuntimeCosts::TakeStorage { len, kind },
433 ]
434 };
435
436 for hot in hot_kinds {
437 for (cold_cost, hot_cost) in with_kind(cold).into_iter().zip(with_kind(hot)) {
438 let cold_weight = <RuntimeCosts as Token<Test>>::weight(&cold_cost);
439 let hot_weight = <RuntimeCosts as Token<Test>>::weight(&hot_cost);
440 assert!(
441 cold_weight.ref_time() > hot_weight.ref_time(),
442 "expected cold > hot ref_time for {cold_cost:?}: \
443 cold={cold_weight:?} hot={hot_weight:?}",
444 );
445 assert_eq!(
446 hot_weight.proof_size(),
447 0,
448 "hot proof_size {hot_cost:?}: {hot_weight:?}"
449 );
450 assert!(
451 cold_weight.proof_size() > 0,
452 "cold proof_size {cold_cost:?}: {cold_weight:?}",
453 );
454 }
455 }
456
457 for (rev_cost, non_rev_cost) in with_kind(cold_revertible).into_iter().zip(with_kind(cold))
458 {
459 let rev_weight = <RuntimeCosts as Token<Test>>::weight(&rev_cost);
460 let non_rev_weight = <RuntimeCosts as Token<Test>>::weight(&non_rev_cost);
461 assert!(
462 rev_weight.ref_time() > non_rev_weight.ref_time(),
463 "expected revertible > non-revertible ref_time for {rev_cost:?}: \
464 rev={rev_weight:?} non={non_rev_weight:?}",
465 );
466 assert_eq!(
467 rev_weight.proof_size(),
468 non_rev_weight.proof_size(),
469 "proof_size differs {rev_cost:?}: rev={rev_weight:?} non={non_rev_weight:?}",
470 );
471 }
472 }
473
474 #[test]
475 fn the_first_hot_write_pays_the_surcharge() {
476 const LEN: u32 = 64;
477 let weight = |cost: &RuntimeCosts| <RuntimeCosts as Token<Test>>::weight(cost);
478
479 let surcharge = RuntimeCosts::hot_write_surcharge::<Test>();
480 let db = <Test as frame_system::Config>::DbWeight::get();
481 assert!(
482 surcharge.ref_time() > 0 && surcharge.ref_time() < db.writes(1).ref_time(),
483 "the surcharge is part of a write: above zero, below all of it: {surcharge:?}",
484 );
485 assert_eq!(surcharge.proof_size(), 0, "the surcharge adds no proof: {surcharge:?}");
486
487 let read_paid = StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Read });
488 let write_paid = StorageAccessKind::Persistent(Warmth::Hot { charged: StorageOp::Write });
489
490 let write_costs = |kind: StorageAccessKind| {
491 [
492 RuntimeCosts::SetStorage { new_bytes: LEN, old_bytes: LEN, kind },
493 RuntimeCosts::ClearStorage { len: LEN, kind },
494 RuntimeCosts::TakeStorage { len: LEN, kind },
495 ]
496 };
497 for (write_to_read_paid_slot, write_to_write_paid_slot) in
498 write_costs(read_paid).into_iter().zip(write_costs(write_paid))
499 {
500 assert_eq!(
501 weight(&write_to_read_paid_slot).saturating_sub(weight(&write_to_write_paid_slot)),
502 surcharge,
503 "a write to a read-paid slot pays exactly the surcharge: \
504 {write_to_read_paid_slot:?}",
505 );
506 }
507
508 let read_costs = |kind: StorageAccessKind| {
509 [
510 RuntimeCosts::GetStorage { len: LEN, kind },
511 RuntimeCosts::ContainsStorage { len: LEN, kind },
512 ]
513 };
514 for (read_of_read_paid_slot, read_of_write_paid_slot) in
515 read_costs(read_paid).into_iter().zip(read_costs(write_paid))
516 {
517 assert_eq!(
518 weight(&read_of_read_paid_slot),
519 weight(&read_of_write_paid_slot),
520 "a read is covered at either paid level: {read_of_read_paid_slot:?}",
521 );
522 }
523 }
524
525 #[test]
526 fn hot_storage_overlay_overhead_is_not_zero() {
527 let overhead = RuntimeCosts::hot_storage_overlay_overhead::<Test>();
528 assert!(
529 overhead.ref_time() > 0,
530 "the per-read cost of overlay_probe_full must stay above overlay_probe_empty",
531 );
532 assert_eq!(overhead.proof_size(), 0, "the overlay probe is in-memory only: {overhead:?}");
533 }
534}