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
39const 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 HostFn,
54 ExtCodeCopy(u32),
56 CopyFromContract(u32),
58 CopyToContract(u32),
60 CallDataLoad,
62 CallDataCopy(u32),
64 Caller,
66 CallDataSize,
68 ReturnDataSize,
70 ToAccountId,
72 Origin,
74 CodeHash,
76 OwnCodeHash,
78 CodeSize,
80 CallerIsOrigin,
82 CallerIsRoot,
84 Address,
86 RefTimeLeft,
88 WeightLeft,
90 Balance,
92 BalanceOf,
94 ValueTransferred,
96 MinimumBalance,
98 BlockNumber,
100 BlockHash,
102 BlockAuthor,
104 GasPrice,
106 BaseFee,
108 Now,
110 GasLimit,
112 Terminate { code_removed: bool },
114 DepositEvent { num_topic: u32, len: u32 },
116 SetStorage { new_bytes: u32, old_bytes: u32, kind: StorageAccessKind },
119 ClearStorage { len: u32, kind: StorageAccessKind },
121 ContainsStorage { len: u32, kind: StorageAccessKind },
123 GetStorage { len: u32, kind: StorageAccessKind },
125 TakeStorage { len: u32, kind: StorageAccessKind },
127 CallBase,
129 DelegateCallBase,
131 PrecompileBase,
133 PrecompileWithInfoBase,
135 PrecompileDecode(u32),
137 CallTransferSurcharge { dust_transfer: bool },
140 CallInputCloned(u32),
142 Instantiate { input_data_len: u32, balance_transfer: bool, dust_transfer: bool },
144 Create { init_code_len: u32, balance_transfer: bool, dust_transfer: bool },
146 Ripemd160(u32),
148 HashSha256(u32),
150 HashKeccak256(u32),
152 HashBlake256(u32),
155 HashBlake128(u32),
157 EcdsaRecovery,
159 P256Verify,
161 Sr25519Verify(u32),
163 Precompile(Weight),
165 EcdsaToEthAddress,
167 GetImmutableData(u32),
169 SetImmutableData(u32),
171 Bn128Add,
173 Bn128Mul,
175 Bn128Pairing(u32),
177 Identity(u32),
179 Blake2F(u32),
181 Modexp(u64),
183}
184
185macro_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 ($name:ident, $( $arg: expr ),+) => {
219 (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+)))
220 };
221 (@call_zero $name:ident, $( $arg:expr ),*) => {
223 T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*)
224 };
225 (@replace_token $_in:tt) => { 0 };
227}
228
229impl RuntimeCosts {
230 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(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 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}