oasis_runtime_sdk/modules/core/
mod.rs

1//! Core definitions module.
2use std::{
3    collections::{BTreeMap, BTreeSet},
4    convert::{TryFrom, TryInto},
5    fmt::Display,
6};
7
8use anyhow::anyhow;
9use oasis_runtime_sdk_macros::{handler, sdk_derive};
10use thiserror::Error;
11
12use crate::{
13    callformat,
14    context::Context,
15    core::consensus::beacon::EpochTime,
16    dispatcher,
17    error::Error as SDKError,
18    keymanager, migration,
19    module::{
20        self, CallResult, InvariantHandler as _, MethodHandler as _, Module as _,
21        ModuleInfoHandler as _,
22    },
23    sender::SenderMeta,
24    state::{CurrentState, Mode, Options, TransactionWithMeta},
25    storage::{self},
26    types::{
27        token::{self, Denomination},
28        transaction::{
29            self, AddressSpec, AuthProof, Call, CallFormat, CallerAddress, SignerInfo, Transaction,
30            UnverifiedTransaction,
31        },
32    },
33    Runtime,
34};
35
36use self::types::RuntimeInfoResponse;
37
38#[cfg(test)]
39mod test;
40pub mod types;
41
42/// Unique module name.
43pub const MODULE_NAME: &str = "core";
44
45/// Errors emitted by the core module.
46#[derive(Error, Debug, oasis_runtime_sdk_macros::Error)]
47pub enum Error {
48    #[error("malformed transaction: {0}")]
49    #[sdk_error(code = 1)]
50    MalformedTransaction(#[source] anyhow::Error),
51
52    #[error("invalid transaction: {0}")]
53    #[sdk_error(code = 2)]
54    InvalidTransaction(#[from] transaction::Error),
55
56    #[error("invalid method: {0}")]
57    #[sdk_error(code = 3)]
58    InvalidMethod(String),
59
60    #[error("invalid nonce")]
61    #[sdk_error(code = 4)]
62    InvalidNonce,
63
64    #[error("insufficient balance to pay fees")]
65    #[sdk_error(code = 5)]
66    InsufficientFeeBalance,
67
68    #[error("out of message slots")]
69    #[sdk_error(code = 6)]
70    OutOfMessageSlots,
71
72    #[error("message handler not invoked")]
73    #[sdk_error(code = 8)]
74    MessageHandlerNotInvoked,
75
76    #[error("missing message handler")]
77    #[sdk_error(code = 9)]
78    MessageHandlerMissing(u32),
79
80    #[error("invalid argument: {0}")]
81    #[sdk_error(code = 10)]
82    InvalidArgument(#[source] anyhow::Error),
83
84    #[error("gas overflow")]
85    #[sdk_error(code = 11)]
86    GasOverflow,
87
88    #[error("out of gas (limit: {0} wanted: {1})")]
89    #[sdk_error(code = 12)]
90    OutOfGas(u64, u64),
91
92    #[error("too many authentication slots")]
93    #[sdk_error(code = 15)]
94    TooManyAuth,
95
96    #[error("multisig too many signers")]
97    #[sdk_error(code = 16)]
98    MultisigTooManySigners,
99
100    #[error("invariant violation: {0}")]
101    #[sdk_error(code = 17)]
102    InvariantViolation(String),
103
104    #[error("invalid call format: {0}")]
105    #[sdk_error(code = 18)]
106    InvalidCallFormat(#[source] anyhow::Error),
107
108    #[error("{0}")]
109    #[sdk_error(transparent, abort)]
110    Abort(#[source] dispatcher::Error),
111
112    #[error("no module could authenticate the transaction")]
113    #[sdk_error(code = 19)]
114    NotAuthenticated,
115
116    #[error("gas price too low")]
117    #[sdk_error(code = 20)]
118    GasPriceTooLow,
119
120    #[error("forbidden in secure build")]
121    #[sdk_error(code = 21)]
122    ForbiddenInSecureBuild,
123
124    #[error("forbidden by node policy")]
125    #[sdk_error(code = 22)]
126    Forbidden,
127
128    #[error("transaction is too large")]
129    #[sdk_error(code = 23)]
130    OversizedTransaction,
131
132    #[error("transaction is expired or not yet valid")]
133    #[sdk_error(code = 24)]
134    ExpiredTransaction,
135
136    #[error("read-only transaction attempted modifications")]
137    #[sdk_error(code = 25)]
138    ReadOnlyTransaction,
139
140    #[error("future nonce")]
141    #[sdk_error(code = 26)]
142    FutureNonce,
143
144    #[error("call depth exceeded (depth: {0} max: {1})")]
145    #[sdk_error(code = 27)]
146    CallDepthExceeded(u16, u16),
147
148    #[error("{0}")]
149    #[sdk_error(transparent)]
150    TxSimulationFailed(#[from] TxSimulationFailure),
151}
152
153impl Error {
154    /// Generate a proper OutOfGas error, depending on whether the module is configured to emit gas
155    /// use information or not.
156    pub fn out_of_gas<Cfg: Config>(limit: u64, wanted: u64) -> Self {
157        if Cfg::EMIT_GAS_USED_EVENTS {
158            Self::OutOfGas(limit, wanted)
159        } else {
160            // Mask gas used information.
161            Self::OutOfGas(0, 0)
162        }
163    }
164}
165
166/// Simulation failure error.
167#[derive(Error, Debug)]
168pub struct TxSimulationFailure {
169    message: String,
170    module_name: String,
171    code: u32,
172}
173
174impl TxSimulationFailure {
175    /// Returns true if the failure is "core::Error::OutOfGas".
176    pub fn is_error_core_out_of_gas(&self) -> bool {
177        self.module_name == MODULE_NAME && self.code == 12
178    }
179}
180
181impl Display for TxSimulationFailure {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        write!(f, "{}", self.message)
184    }
185}
186
187impl SDKError for TxSimulationFailure {
188    fn module_name(&self) -> &str {
189        &self.module_name
190    }
191
192    fn code(&self) -> u32 {
193        self.code
194    }
195}
196
197impl TryFrom<CallResult> for TxSimulationFailure {
198    type Error = anyhow::Error;
199
200    fn try_from(value: CallResult) -> Result<Self, Self::Error> {
201        match value {
202            CallResult::Failed {
203                module,
204                code,
205                message,
206            } => Ok(TxSimulationFailure {
207                code,
208                module_name: module,
209                message,
210            }),
211            _ => Err(anyhow!("CallResult not Failed")),
212        }
213    }
214}
215
216/// Events emitted by the core module.
217#[derive(Debug, PartialEq, Eq, cbor::Encode, oasis_runtime_sdk_macros::Event)]
218#[cbor(untagged)]
219pub enum Event {
220    #[sdk_event(code = 1)]
221    GasUsed { amount: u64 },
222}
223
224/// Gas costs.
225#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
226pub struct GasCosts {
227    pub tx_byte: u64,
228    pub storage_byte: u64,
229
230    pub auth_signature: u64,
231    pub auth_multisig_signer: u64,
232
233    pub callformat_x25519_deoxysii: u64,
234}
235
236/// Dynamic min gas price parameters.
237#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
238pub struct DynamicMinGasPrice {
239    /// Enables the dynamic min gas price feature which dynamically adjusts the minimum gas price
240    /// based on block fullness, inspired by EIP-1559.
241    ///
242    /// Only takes effect if `min_gas_price`(s) are set.
243    pub enabled: bool,
244
245    /// Target block gas usage indicates the desired block gas usage as a percentage of the total
246    /// block gas limit.
247    ///
248    /// The min gas price will adjust up or down depending on whether the actual gas usage is above
249    /// or below this target.
250    pub target_block_gas_usage_percentage: u8,
251    /// Represents a constant value used to limit the rate at which the min price can change
252    /// between blocks.
253    ///
254    /// For example, if `min_price_max_change_denominator` is set to 8, the maximum change in
255    /// min price is 12.5% between blocks.
256    pub min_price_max_change_denominator: u8,
257}
258
259/// Errors emitted during core parameter validation.
260#[derive(Error, Debug)]
261pub enum ParameterValidationError {
262    #[error("invalid dynamic target block gas usage percentage (10-100)")]
263    InvalidTargetBlockGasUsagePercentage,
264    #[error("invalid dynamic min price max change denominator (1-50)")]
265    InvalidMinPriceMaxChangeDenominator,
266}
267/// Parameters for the core module.
268#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
269pub struct Parameters {
270    pub max_batch_gas: u64,
271    pub max_tx_size: u32,
272    pub max_tx_signers: u32,
273    pub max_multisig_signers: u32,
274    pub gas_costs: GasCosts,
275    pub min_gas_price: BTreeMap<token::Denomination, u128>,
276    pub dynamic_min_gas_price: DynamicMinGasPrice,
277}
278
279impl module::Parameters for Parameters {
280    type Error = ParameterValidationError;
281
282    fn validate_basic(&self) -> Result<(), Self::Error> {
283        // Validate dynamic min gas price parameters.
284        let dmgp = &self.dynamic_min_gas_price;
285        if dmgp.enabled {
286            if dmgp.target_block_gas_usage_percentage < 10
287                || dmgp.target_block_gas_usage_percentage > 100
288            {
289                return Err(ParameterValidationError::InvalidTargetBlockGasUsagePercentage);
290            }
291            if dmgp.min_price_max_change_denominator < 1
292                || dmgp.min_price_max_change_denominator > 50
293            {
294                return Err(ParameterValidationError::InvalidMinPriceMaxChangeDenominator);
295            }
296        }
297        Ok(())
298    }
299}
300
301/// Interface that can be called from other modules.
302pub trait API {
303    /// Module configuration.
304    type Config: Config;
305
306    /// Attempt to use gas. If the gas specified would cause either total used to exceed
307    /// its limit, fails with Error::OutOfGas or Error::BatchOutOfGas, and neither gas usage is
308    /// increased.
309    fn use_batch_gas(gas: u64) -> Result<(), Error>;
310
311    /// Attempt to use gas. If the gas specified would cause either total used to exceed
312    /// its limit, fails with Error::OutOfGas or Error::BatchOutOfGas, and neither gas usage is
313    /// increased.
314    fn use_tx_gas(gas: u64) -> Result<(), Error>;
315
316    /// Returns the remaining batch-wide gas.
317    fn remaining_batch_gas() -> u64;
318
319    /// Returns the total batch-wide gas used.
320    fn used_batch_gas() -> u64;
321
322    /// Return the remaining tx-wide gas.
323    fn remaining_tx_gas() -> u64;
324
325    /// Return the used tx-wide gas.
326    fn used_tx_gas() -> u64;
327
328    /// Configured maximum amount of gas that can be used in a batch.
329    fn max_batch_gas() -> u64;
330
331    /// Configured minimum gas price.
332    fn min_gas_price(denom: &token::Denomination) -> Option<u128>;
333
334    /// Sets the transaction priority to the provided amount.
335    fn set_priority(priority: u64);
336
337    /// Takes and returns the stored transaction priority.
338    fn take_priority() -> u64;
339
340    /// Set transaction sender metadata.
341    fn set_sender_meta(meta: SenderMeta);
342
343    /// Takes and returns the stored transaction sender metadata.
344    fn take_sender_meta() -> SenderMeta;
345
346    /// Returns the configured max iterations in the binary search for the estimate
347    /// gas.
348    fn estimate_gas_search_max_iters<C: Context>(ctx: &C) -> u64;
349
350    /// Check whether the epoch has changed since last processed block.
351    fn has_epoch_changed() -> bool;
352}
353
354/// Genesis state for the accounts module.
355#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
356pub struct Genesis {
357    pub parameters: Parameters,
358}
359
360/// Local configuration that can be provided by the node operator.
361#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
362pub struct LocalConfig {
363    /// Minimum gas price to accept.
364    #[cbor(optional)]
365    pub min_gas_price: BTreeMap<token::Denomination, u128>,
366
367    /// When estimating gas in `core.EstimateGas`, simulate the tx (and report) only up to this much
368    /// used gas. This limit is more likely to be relevant if `estimate_gas_by_simulating_contracts` is
369    /// enabled in the local config. The special value of 0 means that the maximum amount of gas in a
370    /// batch will be used.
371    #[cbor(optional)]
372    pub max_estimated_gas: u64,
373
374    /// The maximum number of iterations of the binary search to be done when simulating contracts for
375    /// gas estimation in `core.EstimateGas`.
376    /// The special value of 0 means that binary search won't be performed, and the transaction will be
377    /// simulated using maximum possible gas, which might return an overestimation in some special cases.
378    /// This setting should likely be kept at 0, unless the runtime is using the EVM module.
379    #[cbor(optional)]
380    pub estimate_gas_search_max_iters: u64,
381}
382
383/// State schema constants.
384pub mod state {
385    /// Runtime metadata.
386    pub const METADATA: &[u8] = &[0x01];
387    /// Map of message idx to message handlers for messages emitted in previous round.
388    pub const MESSAGE_HANDLERS: &[u8] = &[0x02];
389    /// Last processed epoch for detecting epoch changes.
390    pub const LAST_EPOCH: &[u8] = &[0x03];
391    /// Dynamic min gas price.
392    pub const DYNAMIC_MIN_GAS_PRICE: &[u8] = &[0x04];
393}
394
395/// Module configuration.
396#[allow(clippy::declare_interior_mutable_const)]
397pub trait Config: 'static {
398    /// Default local minimum gas price configuration that is used in case no overrides are set in
399    /// local per-node configuration.
400    const DEFAULT_LOCAL_MIN_GAS_PRICE: once_cell::unsync::Lazy<
401        BTreeMap<token::Denomination, u128>,
402    > = once_cell::unsync::Lazy::new(BTreeMap::new);
403
404    /// Default local estimate gas max search iterations configuration that is used in case no overrides
405    /// are set in the local per-node configuration.
406    const DEFAULT_LOCAL_ESTIMATE_GAS_SEARCH_MAX_ITERS: u64 = 0;
407
408    /// Estimated gas amount to be added to failed transaction simulations for selected methods.
409    const ESTIMATE_GAS_EXTRA_FAIL: once_cell::unsync::Lazy<BTreeMap<&'static str, u64>> =
410        once_cell::unsync::Lazy::new(BTreeMap::new);
411
412    /// Methods which are exempt from minimum gas price requirements.
413    const MIN_GAS_PRICE_EXEMPT_METHODS: once_cell::unsync::Lazy<BTreeSet<&'static str>> =
414        once_cell::unsync::Lazy::new(BTreeSet::new);
415
416    /// Whether gas used events should be emitted for every transaction.
417    ///
418    /// Confidential runtimes may want to disable this as it is a possible side channel.
419    const EMIT_GAS_USED_EVENTS: bool = true;
420
421    /// Whether to allow submission of read-only transactions in an interactive way.
422    ///
423    /// Note that execution of such transactions is allowed to access confidential state.
424    const ALLOW_INTERACTIVE_READ_ONLY_TRANSACTIONS: bool = false;
425
426    /// The gas cost of the internal call to retrieve the current calldata public key.
427    const GAS_COST_CALL_CALLDATA_PUBLIC_KEY: u64 = 20;
428    /// The gas cost of the internal call to retrieve the current epoch.
429    const GAS_COST_CALL_CURRENT_EPOCH: u64 = 10;
430}
431
432pub struct Module<Cfg: Config> {
433    _cfg: std::marker::PhantomData<Cfg>,
434}
435
436const CONTEXT_KEY_GAS_USED: &str = "core.GasUsed";
437const CONTEXT_KEY_PRIORITY: &str = "core.Priority";
438const CONTEXT_KEY_SENDER_META: &str = "core.SenderMeta";
439const CONTEXT_KEY_EPOCH_CHANGED: &str = "core.EpochChanged";
440
441impl<Cfg: Config> API for Module<Cfg> {
442    type Config = Cfg;
443
444    fn use_batch_gas(gas: u64) -> Result<(), Error> {
445        // Do not enforce batch limits for checks.
446        if CurrentState::with_env(|env| env.is_check_only()) {
447            return Ok(());
448        }
449        let batch_gas_limit = Self::params().max_batch_gas;
450        let batch_gas_used = Self::used_batch_gas();
451        // NOTE: Going over the batch limit should trigger an abort as the scheduler should never
452        //       allow scheduling past the batch limit but a malicious proposer might include too
453        //       many transactions. Make sure to vote for failure in this case.
454        let batch_new_gas_used = batch_gas_used
455            .checked_add(gas)
456            .ok_or(Error::Abort(dispatcher::Error::BatchOutOfGas))?;
457        if batch_new_gas_used > batch_gas_limit {
458            return Err(Error::Abort(dispatcher::Error::BatchOutOfGas));
459        }
460
461        CurrentState::with(|state| {
462            state
463                .block_value::<u64>(CONTEXT_KEY_GAS_USED)
464                .set(batch_new_gas_used);
465        });
466
467        Ok(())
468    }
469
470    fn use_tx_gas(gas: u64) -> Result<(), Error> {
471        let (gas_limit, gas_used) = CurrentState::with(|state| {
472            (
473                state.env().tx_auth_info().fee.gas,
474                *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default(),
475            )
476        });
477        let new_gas_used = {
478            let sum = gas_used.checked_add(gas).ok_or(Error::GasOverflow)?;
479            if sum > gas_limit {
480                return Err(Error::out_of_gas::<Cfg>(gas_limit, sum));
481            }
482            sum
483        };
484
485        Self::use_batch_gas(gas)?;
486
487        CurrentState::with(|state| {
488            *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default() = new_gas_used;
489        });
490
491        Ok(())
492    }
493
494    fn remaining_batch_gas() -> u64 {
495        let batch_gas_limit = Self::params().max_batch_gas;
496        batch_gas_limit.saturating_sub(Self::used_batch_gas())
497    }
498
499    fn used_batch_gas() -> u64 {
500        CurrentState::with(|state| {
501            state
502                .block_value::<u64>(CONTEXT_KEY_GAS_USED)
503                .get()
504                .cloned()
505                .unwrap_or_default()
506        })
507    }
508
509    fn remaining_tx_gas() -> u64 {
510        let (gas_limit, gas_used) = CurrentState::with(|state| {
511            (
512                state.env().tx_auth_info().fee.gas,
513                *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default(),
514            )
515        });
516        let remaining_tx = gas_limit.saturating_sub(gas_used);
517        // Also check remaining batch gas limit and return the minimum of the two.
518        let remaining_batch = Self::remaining_batch_gas();
519        std::cmp::min(remaining_tx, remaining_batch)
520    }
521
522    fn used_tx_gas() -> u64 {
523        CurrentState::with(|state| *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default())
524    }
525
526    fn max_batch_gas() -> u64 {
527        Self::params().max_batch_gas
528    }
529
530    fn min_gas_price(denom: &token::Denomination) -> Option<u128> {
531        Self::min_gas_prices().get(denom).copied()
532    }
533
534    fn set_priority(priority: u64) {
535        CurrentState::with(|state| {
536            state.block_value::<u64>(CONTEXT_KEY_PRIORITY).set(priority);
537        })
538    }
539
540    fn take_priority() -> u64 {
541        CurrentState::with(|state| {
542            state
543                .block_value::<u64>(CONTEXT_KEY_PRIORITY)
544                .take()
545                .unwrap_or_default()
546        })
547    }
548
549    fn set_sender_meta(meta: SenderMeta) {
550        CurrentState::with(|state| {
551            state
552                .block_value::<SenderMeta>(CONTEXT_KEY_SENDER_META)
553                .set(meta);
554        });
555    }
556
557    fn take_sender_meta() -> SenderMeta {
558        CurrentState::with(|state| {
559            state
560                .block_value::<SenderMeta>(CONTEXT_KEY_SENDER_META)
561                .take()
562                .unwrap_or_default()
563        })
564    }
565
566    fn estimate_gas_search_max_iters<C: Context>(ctx: &C) -> u64 {
567        ctx.local_config(MODULE_NAME)
568            .as_ref()
569            .map(|cfg: &LocalConfig| cfg.estimate_gas_search_max_iters)
570            .unwrap_or(Cfg::DEFAULT_LOCAL_ESTIMATE_GAS_SEARCH_MAX_ITERS)
571    }
572
573    fn has_epoch_changed() -> bool {
574        CurrentState::with(|state| {
575            *state
576                .block_value(CONTEXT_KEY_EPOCH_CHANGED)
577                .get()
578                .unwrap_or(&false)
579        })
580    }
581}
582
583#[sdk_derive(Module)]
584impl<Cfg: Config> Module<Cfg> {
585    const NAME: &'static str = MODULE_NAME;
586    type Error = Error;
587    type Event = Event;
588    type Parameters = Parameters;
589    type Genesis = Genesis;
590
591    #[migration(init)]
592    pub fn init(genesis: Genesis) {
593        // Set genesis parameters.
594        Self::set_params(genesis.parameters);
595    }
596
597    /// Run a transaction in simulation and return how much gas it uses. This looks up the method
598    /// in the context's method registry. Transactions that fail still use gas, and this query will
599    /// estimate that and return successfully, so do not use this query to see if a transaction will
600    /// succeed.
601    #[handler(query = "core.EstimateGas", allow_private_km)]
602    pub fn query_estimate_gas<C: Context>(
603        ctx: &C,
604        mut args: types::EstimateGasQuery,
605    ) -> Result<u64, Error> {
606        let mut extra_gas = 0u64;
607        // In case the runtime is confidential we are unable to authenticate the caller so we must
608        // make sure to zeroize it to avoid leaking private information.
609        if ctx.is_confidential() {
610            args.caller = Some(
611                args.caller
612                    .unwrap_or_else(|| {
613                        args.tx
614                            .auth_info
615                            .signer_info
616                            .first()
617                            .map(|si| si.address_spec.caller_address())
618                            .unwrap_or(CallerAddress::Address(Default::default()))
619                    })
620                    .zeroized(),
621            );
622            args.propagate_failures = false; // Likely to fail as caller is zeroized.
623        }
624        // Assume maximum amount of gas in a batch, a reasonable maximum fee and maximum amount of consensus messages.
625        args.tx.auth_info.fee.gas = {
626            let local_max_estimated_gas = Self::get_local_max_estimated_gas(ctx);
627            if local_max_estimated_gas == 0 {
628                Self::params().max_batch_gas
629            } else {
630                local_max_estimated_gas
631            }
632        };
633        args.tx.auth_info.fee.amount =
634            token::BaseUnits::new(u64::MAX.into(), token::Denomination::NATIVE);
635        args.tx.auth_info.fee.consensus_messages = ctx.max_messages();
636        // Estimate transaction size. Since the transaction given to us is not signed, we need to
637        // estimate how large each of the auth proofs would be.
638        let auth_proofs: Result<_, Error> = args
639            .tx
640            .auth_info
641            .signer_info
642            .iter()
643            .map(|si| match si.address_spec {
644                // For the signature address spec we assume a signature auth proof of 64 bytes.
645                transaction::AddressSpec::Signature(_) => {
646                    Ok(transaction::AuthProof::Signature(vec![0; 64].into()))
647                }
648                // For the multisig address spec assume all the signers sign with a 64-byte signature.
649                transaction::AddressSpec::Multisig(ref cfg) => {
650                    Ok(transaction::AuthProof::Multisig(
651                        cfg.signers
652                            .iter()
653                            .map(|_| Some(vec![0; 64].into()))
654                            .collect(),
655                    ))
656                }
657                // Internal address specs should never appear as they are not serializable.
658                transaction::AddressSpec::Internal(_) => Err(Error::MalformedTransaction(anyhow!(
659                    "internal address spec used"
660                ))),
661            })
662            .collect();
663        let tx_envelope =
664            transaction::UnverifiedTransaction(cbor::to_vec(args.tx.clone()), auth_proofs?);
665        let tx_size: u32 = cbor::to_vec(tx_envelope)
666            .len()
667            .try_into()
668            .map_err(|_| Error::InvalidArgument(anyhow!("transaction too large")))?;
669        let propagate_failures = args.propagate_failures;
670        let bs_max_iters = Self::estimate_gas_search_max_iters(ctx);
671
672        // Update the address used within the transaction when caller address is passed.
673        if let Some(caller) = args.caller.clone() {
674            // Include additional gas for each signature verification since we will be overwriting
675            // the signer infos below.
676            extra_gas = extra_gas.saturating_add(
677                Self::compute_signature_verification_cost(
678                    &Self::params(),
679                    &args.tx.auth_info.signer_info,
680                )
681                .unwrap_or_default(),
682            );
683
684            args.tx.auth_info.signer_info = vec![transaction::SignerInfo {
685                address_spec: transaction::AddressSpec::Internal(caller),
686                nonce: args
687                    .tx
688                    .auth_info
689                    .signer_info
690                    .first()
691                    .map(|si| si.nonce)
692                    .unwrap_or_default(),
693            }];
694        }
695
696        // Determine if we need to add any extra gas for failing calls.
697        #[allow(clippy::borrow_interior_mutable_const)]
698        let extra_gas_fail = *Cfg::ESTIMATE_GAS_EXTRA_FAIL
699            .get(args.tx.call.method.as_str())
700            .unwrap_or(&0);
701
702        // Simulates transaction with a specific gas limit.
703        let simulate = |tx: &transaction::Transaction, gas: u64, report_failure: bool| {
704            let mut tx = tx.clone();
705            tx.auth_info.fee.gas = gas;
706            let call = tx.call.clone(); // TODO: Avoid clone.
707
708            CurrentState::with_transaction_opts(
709                Options::new()
710                    .with_mode(Mode::Simulate)
711                    .with_tx(TransactionWithMeta {
712                        data: tx,
713                        size: tx_size,
714                        index: 0,
715                        hash: Default::default(),
716                    }),
717                || {
718                    let (result, _) = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx_call(
719                        ctx,
720                        call,
721                        &Default::default(),
722                    );
723                    if !result.is_success() && report_failure {
724                        // Report failure.
725                        let err: TxSimulationFailure = result.try_into().unwrap(); // Guaranteed to be a Failed CallResult.
726                        return Err(Error::TxSimulationFailed(err));
727                    }
728                    // Don't report success or failure. If the call fails, we still report
729                    // how much gas it uses while it fails.
730                    let gas_used = Self::used_batch_gas();
731                    if result.is_success() {
732                        Ok(gas_used)
733                    } else {
734                        Ok(gas_used.saturating_add(extra_gas_fail).clamp(0, gas))
735                    }
736                },
737            )
738        };
739
740        // Do a binary search for exact gas limit.
741        let (cap, mut lo, mut hi) = (
742            args.tx.auth_info.fee.gas,
743            10_u128,
744            args.tx.auth_info.fee.gas as u128,
745        ); // Use u128 to avoid overflows when computing the mid point.
746
747        // Count iterations, and remember if fast path was tried.
748        let (mut iters, mut fast_path_tried) = (0, false);
749        // The following two variables are used to control the special case where a transaction fails
750        // and we check if the error is due to out-of-gas by re-simulating the transaction with maximum
751        // gas limit. This is needed due to EVM transactions failing with a "reverted" error when
752        // not having enough gas for EIP-150 (and not with "out-of-gas").
753        let (mut has_succeeded, mut tried_with_max_gas) = (false, false);
754        while (lo + 1 < hi) && iters < bs_max_iters {
755            iters += 1;
756
757            let mid = (hi + lo) / 2;
758            match simulate(&args.tx, mid as u64, true) {
759                Ok(r) => {
760                    // Estimate success. Try with lower gas.
761                    hi = mid;
762
763                    // The transaction succeeded at least once, meaning any future failure is due
764                    // to insufficient gas limit.
765                    has_succeeded = true;
766
767                    // Optimization: In vast majority of cases the initially returned gas estimate
768                    // might already be a good one. Check if this is the case to speed up the convergence.
769                    if !fast_path_tried && (lo + 1 < hi) {
770                        fast_path_tried = true;
771
772                        // If simulate with the returned estimate succeeds, we can further shrink the
773                        // high limit of the binary search.
774                        match simulate(&args.tx, r, true) {
775                            Ok(_) => hi = r as u128,
776                            _ => continue,
777                        }
778                        // If simulate with one unit of gas smaller fails, we know the exact estimate.
779                        match simulate(&args.tx, r - 1, true) {
780                            Err(_) => {
781                                // Stop the gas search.
782                                break;
783                            }
784                            _ => continue,
785                        }
786                    }
787                }
788                Err(_) if has_succeeded => {
789                    // Transaction previously succeeded. Transaction failed due to insufficient gas limit,
790                    // regardless of the actual returned error.
791                    // Try with higher gas.
792                    lo = mid
793                }
794                Err(Error::TxSimulationFailed(failure)) if failure.is_error_core_out_of_gas() => {
795                    // Estimate failed due to insufficient gas limit. Try with higher gas.
796                    lo = mid
797                }
798                r @ Err(_) => {
799                    let mut res = r;
800                    if !tried_with_max_gas {
801                        tried_with_max_gas = true;
802                        // Transaction failed and simulation with max gas was not yet tried.
803                        // Try simulating with maximum gas once:
804                        //  - if fails, the transaction will always fail, stop the binary search.
805                        //  - if succeeds, remember that transaction is failing due to insufficient gas
806                        //    and continue the search.
807                        res = simulate(&args.tx, cap, true)
808                    }
809                    match res {
810                        Ok(_) => {
811                            has_succeeded = true;
812                            // Transaction can succeed. Try with higher gas.
813                            lo = mid
814                        }
815                        err if propagate_failures => {
816                            // Estimate failed (not with out-of-gas) and caller wants error propagation -> early exit and return the error.
817                            return err;
818                        }
819                        _ => {
820                            // Estimate failed (not with out-of-gas) but caller wants to know the gas usage.
821                            // Exit loop and do one final estimate without error propagation.
822                            // NOTE: don't continue the binary search for failing transactions as the convergence
823                            // for these could take somewhat long and the estimate with default max gas is likely good.
824                            break;
825                        }
826                    }
827                }
828            }
829        }
830
831        // hi == cap if binary search is disabled or this is a failing transaction.
832        let result = if hi == Into::<u128>::into(cap) {
833            // Simulate one last time with maximum gas limit.
834            simulate(&args.tx, cap, propagate_failures)
835        } else {
836            Ok(hi as u64)
837        };
838
839        // Make sure the final result is clamped.
840        result.map(|est| est.saturating_add(extra_gas).clamp(0, cap))
841    }
842
843    /// Check invariants of all modules in the runtime.
844    #[handler(query = "core.CheckInvariants", expensive)]
845    fn query_check_invariants<C: Context>(ctx: &C, _args: ()) -> Result<(), Error> {
846        <C::Runtime as Runtime>::Modules::check_invariants(ctx)
847    }
848
849    fn calldata_public_key_common<C: Context>(
850        ctx: &C,
851    ) -> Result<types::CallDataPublicKeyQueryResponse, Error> {
852        let key_manager = ctx
853            .key_manager()
854            .ok_or_else(|| Error::InvalidArgument(anyhow!("key manager not available")))?;
855        let epoch = ctx.epoch();
856        let public_key = key_manager
857            .get_public_ephemeral_key(callformat::get_key_pair_id(epoch), epoch)
858            .map_err(|err| match err {
859                keymanager::KeyManagerError::InvalidEpoch(..) => {
860                    Error::InvalidCallFormat(anyhow!("invalid epoch"))
861                }
862                _ => Error::Abort(err.into()),
863            })?;
864
865        Ok(types::CallDataPublicKeyQueryResponse { public_key, epoch })
866    }
867
868    /// Retrieve the public key for encrypting call data.
869    #[handler(query = "core.CallDataPublicKey")]
870    fn query_calldata_public_key<C: Context>(
871        ctx: &C,
872        _args: (),
873    ) -> Result<types::CallDataPublicKeyQueryResponse, Error> {
874        Self::calldata_public_key_common(ctx)
875    }
876
877    /// Retrieve the public key for encrypting call data (internally exposed call).
878    #[handler(call = "core.CallDataPublicKey", internal)]
879    fn internal_calldata_public_key<C: Context>(
880        ctx: &C,
881        _args: (),
882    ) -> Result<types::CallDataPublicKeyQueryResponse, Error> {
883        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_CALLDATA_PUBLIC_KEY)?;
884        Self::calldata_public_key_common(ctx)
885    }
886
887    /// Retrieve the current epoch.
888    #[handler(call = "core.CurrentEpoch", internal)]
889    fn internal_current_epoch<C: Context>(ctx: &C, _args: ()) -> Result<u64, Error> {
890        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_CURRENT_EPOCH)?;
891        Ok(ctx.epoch())
892    }
893
894    /// Query the minimum gas price.
895    #[handler(query = "core.MinGasPrice")]
896    fn query_min_gas_price<C: Context>(
897        ctx: &C,
898        _args: (),
899    ) -> Result<BTreeMap<token::Denomination, u128>, Error> {
900        let mut mgp = Self::min_gas_prices();
901
902        // Generate a combined view with local overrides.
903        for (denom, price) in mgp.iter_mut() {
904            let local_mgp = Self::get_local_min_gas_price(ctx, denom);
905            if local_mgp > *price {
906                *price = local_mgp;
907            }
908        }
909
910        Ok(mgp)
911    }
912
913    /// Return basic information about the module and the containing runtime.
914    #[handler(query = "core.RuntimeInfo")]
915    fn query_runtime_info<C: Context>(ctx: &C, _args: ()) -> Result<RuntimeInfoResponse, Error> {
916        Ok(RuntimeInfoResponse {
917            runtime_version: <C::Runtime as Runtime>::VERSION,
918            state_version: <C::Runtime as Runtime>::STATE_VERSION,
919            modules: <C::Runtime as Runtime>::Modules::module_info(ctx),
920        })
921    }
922
923    /// Execute a read-only transaction in an interactive mode.
924    ///
925    /// # Warning
926    ///
927    /// This query is allowed access to private key manager state.
928    #[handler(query = "core.ExecuteReadOnlyTx", expensive, allow_private_km)]
929    fn query_execute_read_only_tx<C: Context>(
930        ctx: &C,
931        args: types::ExecuteReadOnlyTxQuery,
932    ) -> Result<types::ExecuteReadOnlyTxResponse, Error> {
933        if !Cfg::ALLOW_INTERACTIVE_READ_ONLY_TRANSACTIONS {
934            return Err(Error::Forbidden);
935        }
936
937        CurrentState::with_transaction_opts(Options::new().with_mode(Mode::Simulate), || {
938            // TODO: Use separate batch gas limit for query execution.
939
940            // Decode transaction and verify signature.
941            let tx_size = args
942                .tx
943                .len()
944                .try_into()
945                .map_err(|_| Error::OversizedTransaction)?;
946            let tx = dispatcher::Dispatcher::<C::Runtime>::decode_tx(ctx, &args.tx)?;
947
948            // Only read-only transactions are allowed in interactive queries.
949            if !tx.call.read_only {
950                return Err(Error::InvalidArgument(anyhow::anyhow!(
951                    "only read-only transactions are allowed"
952                )));
953            }
954            // Only transactions with expiry are allowed in interactive queries.
955            if tx.auth_info.not_before.is_none() || tx.auth_info.not_after.is_none() {
956                return Err(Error::InvalidArgument(anyhow::anyhow!(
957                    "only read-only transactions with expiry are allowed"
958                )));
959            }
960
961            // Execute transaction.
962            let (result, _) = dispatcher::Dispatcher::<C::Runtime>::execute_tx_opts(
963                ctx,
964                tx,
965                &dispatcher::DispatchOptions {
966                    tx_size,
967                    method_authorizer: Some(&|method| {
968                        // Ensure that the inner method is allowed to be called from an interactive
969                        // context to avoid unexpected pitfalls.
970                        <C::Runtime as Runtime>::Modules::is_allowed_interactive_call(method)
971                            && <C::Runtime as Runtime>::is_allowed_interactive_call(method)
972                    }),
973                    ..Default::default()
974                },
975            )
976            .map_err(|err| Error::InvalidArgument(err.into()))?;
977
978            Ok(types::ExecuteReadOnlyTxResponse { result })
979        })
980    }
981}
982
983impl<Cfg: Config> Module<Cfg> {
984    fn min_gas_prices() -> BTreeMap<Denomination, u128> {
985        let params = Self::params();
986        if params.dynamic_min_gas_price.enabled {
987            CurrentState::with_store(|store| {
988                let store =
989                    storage::TypedStore::new(storage::PrefixStore::new(store, &MODULE_NAME));
990                store
991                    .get(state::DYNAMIC_MIN_GAS_PRICE)
992                    // Use static min gas price when dynamic price was not yet computed.
993                    .unwrap_or(params.min_gas_price)
994            })
995        } else {
996            params.min_gas_price
997        }
998    }
999
1000    fn get_local_min_gas_price<C: Context>(ctx: &C, denom: &token::Denomination) -> u128 {
1001        #[allow(clippy::borrow_interior_mutable_const)]
1002        ctx.local_config(MODULE_NAME)
1003            .as_ref()
1004            .map(|cfg: &LocalConfig| cfg.min_gas_price.get(denom).copied())
1005            .unwrap_or_else(|| Cfg::DEFAULT_LOCAL_MIN_GAS_PRICE.get(denom).copied())
1006            .unwrap_or_default()
1007    }
1008
1009    fn get_local_max_estimated_gas<C: Context>(ctx: &C) -> u64 {
1010        ctx.local_config(MODULE_NAME)
1011            .as_ref()
1012            .map(|cfg: &LocalConfig| cfg.max_estimated_gas)
1013            .unwrap_or_default()
1014    }
1015
1016    fn enforce_min_gas_price<C: Context>(ctx: &C, call: &Call) -> Result<(), Error> {
1017        // If the method is exempt from min gas price requirements, checks always pass.
1018        #[allow(clippy::borrow_interior_mutable_const)]
1019        if Cfg::MIN_GAS_PRICE_EXEMPT_METHODS.contains(call.method.as_str()) {
1020            return Ok(());
1021        }
1022
1023        let fee = CurrentState::with_env(|env| env.tx_auth_info().fee.clone());
1024        let denom = fee.amount.denomination();
1025
1026        match Self::min_gas_price(denom) {
1027            // If the denomination is not among the global set, reject.
1028            None => return Err(Error::GasPriceTooLow),
1029
1030            // Otherwise, allow overrides during local checks.
1031            Some(min_gas_price) => {
1032                if CurrentState::with_env(|env| env.is_check_only()) {
1033                    let local_mgp = Self::get_local_min_gas_price(ctx, denom);
1034
1035                    // Reject during local checks.
1036                    if fee.gas_price() < local_mgp {
1037                        return Err(Error::GasPriceTooLow);
1038                    }
1039                }
1040
1041                if fee.gas_price() < min_gas_price {
1042                    return Err(Error::GasPriceTooLow);
1043                }
1044            }
1045        }
1046
1047        Ok(())
1048    }
1049
1050    fn compute_signature_verification_cost(
1051        params: &Parameters,
1052        signer_info: &[SignerInfo],
1053    ) -> Option<u64> {
1054        let mut num_signature: u64 = 0;
1055        let mut num_multisig_signer: u64 = 0;
1056        for si in signer_info {
1057            match &si.address_spec {
1058                AddressSpec::Signature(_) => {
1059                    num_signature = num_signature.checked_add(1)?;
1060                }
1061                AddressSpec::Multisig(config) => {
1062                    num_multisig_signer =
1063                        num_multisig_signer.checked_add(config.signers.len() as u64)?;
1064                }
1065                AddressSpec::Internal(_) => {}
1066            }
1067        }
1068
1069        let signature_cost = num_signature.checked_mul(params.gas_costs.auth_signature)?;
1070        let multisig_signer_cost =
1071            num_multisig_signer.checked_mul(params.gas_costs.auth_multisig_signer)?;
1072        let sum = signature_cost.checked_add(multisig_signer_cost)?;
1073
1074        Some(sum)
1075    }
1076}
1077
1078impl<Cfg: Config> module::TransactionHandler for Module<Cfg> {
1079    fn approve_raw_tx<C: Context>(_ctx: &C, tx: &[u8]) -> Result<(), Error> {
1080        let params = Self::params();
1081        if tx.len() > TryInto::<usize>::try_into(params.max_tx_size).unwrap() {
1082            return Err(Error::OversizedTransaction);
1083        }
1084        Ok(())
1085    }
1086
1087    fn approve_unverified_tx<C: Context>(
1088        _ctx: &C,
1089        utx: &UnverifiedTransaction,
1090    ) -> Result<(), Error> {
1091        let params = Self::params();
1092        if utx.1.len() > params.max_tx_signers as usize {
1093            return Err(Error::TooManyAuth);
1094        }
1095        for auth_proof in &utx.1 {
1096            if let AuthProof::Multisig(config) = auth_proof {
1097                if config.len() > params.max_multisig_signers as usize {
1098                    return Err(Error::MultisigTooManySigners);
1099                }
1100            }
1101        }
1102        Ok(())
1103    }
1104
1105    fn authenticate_tx<C: Context>(
1106        ctx: &C,
1107        tx: &Transaction,
1108    ) -> Result<module::AuthDecision, Error> {
1109        // Check whether the transaction is currently valid.
1110        let round = ctx.runtime_header().round;
1111        if let Some(not_before) = tx.auth_info.not_before {
1112            if round < not_before {
1113                // Too early.
1114                return Err(Error::ExpiredTransaction);
1115            }
1116        }
1117        if let Some(not_after) = tx.auth_info.not_after {
1118            if round > not_after {
1119                // Too late.
1120                return Err(Error::ExpiredTransaction);
1121            }
1122        }
1123
1124        Ok(module::AuthDecision::Continue)
1125    }
1126
1127    fn before_handle_call<C: Context>(ctx: &C, call: &Call) -> Result<(), Error> {
1128        // Ensure that specified gas limit is not greater than batch gas limit.
1129        let params = Self::params();
1130        let fee = CurrentState::with_env(|env| env.tx_auth_info().fee.clone());
1131        if fee.gas > params.max_batch_gas {
1132            return Err(Error::GasOverflow);
1133        }
1134        if fee.consensus_messages > ctx.max_messages() {
1135            return Err(Error::OutOfMessageSlots);
1136        }
1137
1138        // Skip additional checks/gas payment for internally generated transactions.
1139        if CurrentState::with_env(|env| env.is_internal()) {
1140            return Ok(());
1141        }
1142
1143        // Enforce minimum gas price constraints.
1144        Self::enforce_min_gas_price(ctx, call)?;
1145
1146        // Charge gas for transaction size.
1147        let tx_size = CurrentState::with_env(|env| env.tx_size());
1148        Self::use_tx_gas(
1149            params
1150                .gas_costs
1151                .tx_byte
1152                .checked_mul(tx_size.into())
1153                .ok_or(Error::GasOverflow)?,
1154        )?;
1155
1156        // Charge gas for signature verification.
1157        let total = CurrentState::with_env(|env| {
1158            Self::compute_signature_verification_cost(&params, &env.tx_auth_info().signer_info)
1159        })
1160        .ok_or(Error::GasOverflow)?;
1161        Self::use_tx_gas(total)?;
1162
1163        // Charge gas for callformat.
1164        match call.format {
1165            CallFormat::Plain => {} // No additional gas required.
1166            CallFormat::EncryptedX25519DeoxysII => {
1167                Self::use_tx_gas(params.gas_costs.callformat_x25519_deoxysii)?
1168            }
1169        }
1170
1171        Ok(())
1172    }
1173
1174    fn after_handle_call<C: Context>(
1175        ctx: &C,
1176        result: module::CallResult,
1177    ) -> Result<module::CallResult, Error> {
1178        // Skip handling for internally generated calls.
1179        if CurrentState::with_env(|env| env.is_internal()) {
1180            return Ok(result);
1181        }
1182
1183        let params = Self::params();
1184
1185        // Compute storage update gas cost.
1186        let storage_gas = if params.gas_costs.storage_byte > 0 {
1187            let storage_update_bytes =
1188                CurrentState::with(|state| state.pending_store_update_byte_size());
1189            params
1190                .gas_costs
1191                .storage_byte
1192                .saturating_mul(storage_update_bytes as u64)
1193        } else {
1194            0
1195        };
1196
1197        // Compute message gas cost.
1198        let message_gas = {
1199            let emitted_message_count =
1200                CurrentState::with(|state| state.emitted_messages_local_count());
1201            // Determine how much each message emission costs based on max_batch_gas and the number
1202            // of messages that can be emitted per batch.
1203            let message_gas_cost = params
1204                .max_batch_gas
1205                .checked_div(ctx.max_messages().into())
1206                .unwrap_or(u64::MAX); // If no messages are allowed, cost is infinite.
1207            message_gas_cost.saturating_mul(emitted_message_count as u64)
1208        };
1209
1210        // Compute the gas amount that the transaction should pay in the end.
1211        let used_gas = Self::used_tx_gas();
1212        let max_gas = std::cmp::max(used_gas, std::cmp::max(storage_gas, message_gas));
1213
1214        // Make sure the transaction actually pays for the maximum gas. Note that failure here is
1215        // fine since the extra resources (storage updates or emitted consensus messages) have not
1216        // actually been spent yet (this happens at the end of the round).
1217        let maybe_out_of_gas = Self::use_tx_gas(max_gas - used_gas); // Cannot overflow as max_gas >= used_gas.
1218
1219        // Emit gas used event.
1220        if Cfg::EMIT_GAS_USED_EVENTS {
1221            let used_gas = Self::used_tx_gas();
1222            CurrentState::with(|state| {
1223                state.emit_unconditional_event(Event::GasUsed { amount: used_gas });
1224            });
1225        }
1226
1227        // Evaluate the result of the above `use_tx_gas` here to make sure we emit the event.
1228        maybe_out_of_gas?;
1229
1230        Ok(result)
1231    }
1232}
1233
1234/// Computes the new minimum gas price based on the current gas usage and the target gas usage.
1235///
1236/// The new price is computed as follows (inspired by EIP-1559):
1237///  - If the actual gas used is greater than the target gas used, increase the minimum gas price.
1238///  - If the actual gas used is less than the target gas used, decrease the minimum gas price.
1239///
1240/// The price change is controlled by the `min_price_max_change_denominator` parameter.
1241fn min_gas_price_update(
1242    gas_used: u128,
1243    target_gas_used: u128,
1244    min_price_max_change_denominator: u128,
1245    current_price: u128,
1246) -> u128 {
1247    // If the target gas used is zero or the denominator is zero, don't change the price.
1248    if target_gas_used == 0 || min_price_max_change_denominator == 0 {
1249        return current_price;
1250    }
1251
1252    // Calculate the difference (as a percentage) between the actual gas used in the block and the target gas used.
1253    let delta = (gas_used.max(target_gas_used) - gas_used.min(target_gas_used)).saturating_mul(100)
1254        / target_gas_used;
1255
1256    // Calculate the change in gas price and divide by `min_price_max_change_denominator`.
1257    let price_change =
1258        (current_price.saturating_mul(delta) / 100) / min_price_max_change_denominator;
1259
1260    // Adjust the current price based on whether the gas used was above or below the target.
1261    if gas_used > target_gas_used {
1262        current_price.saturating_add(price_change)
1263    } else {
1264        current_price.saturating_sub(price_change)
1265    }
1266}
1267
1268impl<Cfg: Config> module::BlockHandler for Module<Cfg> {
1269    fn begin_block<C: Context>(ctx: &C) {
1270        CurrentState::with(|state| {
1271            let epoch = ctx.epoch();
1272
1273            // Load previous epoch.
1274            let mut store = storage::PrefixStore::new(state.store(), &MODULE_NAME);
1275            let mut tstore = storage::TypedStore::new(&mut store);
1276            let previous_epoch: EpochTime = tstore.get(state::LAST_EPOCH).unwrap_or_default();
1277            if epoch != previous_epoch {
1278                tstore.insert(state::LAST_EPOCH, epoch);
1279            }
1280
1281            // Set the epoch changed key as needed.
1282            state
1283                .block_value(CONTEXT_KEY_EPOCH_CHANGED)
1284                .set(epoch != previous_epoch);
1285        });
1286    }
1287
1288    fn end_block<C: Context>(_ctx: &C) {
1289        let params = Self::params();
1290        if !params.dynamic_min_gas_price.enabled {
1291            return;
1292        }
1293
1294        // Update dynamic min gas price for next block, inspired by EIP-1559.
1295        //
1296        // Adjust the min gas price for each block based on the gas used in the previous block and the desired target
1297        // gas usage set by `target_block_gas_usage_percentage`.
1298        let gas_used = Self::used_batch_gas() as u128;
1299        let max_batch_gas = Self::max_batch_gas() as u128;
1300        let target_gas_used = max_batch_gas.saturating_mul(
1301            params
1302                .dynamic_min_gas_price
1303                .target_block_gas_usage_percentage as u128,
1304        ) / 100;
1305
1306        // Compute new prices.
1307        let mut mgp = Self::min_gas_prices();
1308        mgp.iter_mut().for_each(|(d, price)| {
1309            let mut new_min_price = min_gas_price_update(
1310                gas_used,
1311                target_gas_used,
1312                params
1313                    .dynamic_min_gas_price
1314                    .min_price_max_change_denominator as u128,
1315                *price,
1316            );
1317
1318            // Ensure that the new price is at least the minimum gas price.
1319            if let Some(min_price) = params.min_gas_price.get(d) {
1320                if new_min_price < *min_price {
1321                    new_min_price = *min_price;
1322                }
1323            }
1324            *price = new_min_price;
1325        });
1326
1327        // Update min prices.
1328        CurrentState::with_store(|store| {
1329            let mut store = storage::PrefixStore::new(store, &MODULE_NAME);
1330            let mut tstore = storage::TypedStore::new(&mut store);
1331            tstore.insert(state::DYNAMIC_MIN_GAS_PRICE, mgp);
1332        });
1333    }
1334}
1335
1336impl<Cfg: Config> module::InvariantHandler for Module<Cfg> {}