sui_types/gas_model/
gas_v2.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5pub use checked::*;
6
7#[sui_macros::with_checked_arithmetic]
8mod checked {
9    use crate::error::{UserInputError, UserInputResult};
10    use crate::gas::{self, GasCostSummary, GasUsageReport, SuiGasStatusAPI};
11    use crate::gas_model::gas_predicates::{cost_table_for_version, txn_base_cost_as_multiplier};
12    use crate::gas_model::units_types::CostTable;
13    use crate::transaction::ObjectReadResult;
14    use crate::{
15        ObjectID,
16        error::{ExecutionError, ExecutionErrorKind},
17        gas_model::tables::{GasStatus, ZERO_COST_SCHEDULE},
18    };
19    use move_core_types::vm_status::StatusCode;
20    use serde::{Deserialize, Serialize};
21    use sui_protocol_config::*;
22
23    /// A bucket defines a range of units that will be priced the same.
24    /// After execution a call to `GasStatus::bucketize` will round the computation
25    /// cost to `cost` for the bucket ([`min`, `max`]) the gas used falls into.
26    #[allow(dead_code)]
27    pub(crate) struct ComputationBucket {
28        min: u64,
29        max: u64,
30        cost: u64,
31    }
32
33    impl ComputationBucket {
34        fn new(min: u64, max: u64, cost: u64) -> Self {
35            ComputationBucket { min, max, cost }
36        }
37
38        fn simple(min: u64, max: u64) -> Self {
39            Self::new(min, max, max)
40        }
41    }
42
43    fn get_bucket_cost(table: &[ComputationBucket], computation_cost: u64) -> u64 {
44        for bucket in table {
45            if bucket.max >= computation_cost {
46                return bucket.cost;
47            }
48        }
49        match table.last() {
50            // maybe not a literal here could be better?
51            None => 5_000_000,
52            Some(bucket) => bucket.cost,
53        }
54    }
55
56    // define the bucket table for computation charging
57    // If versioning defines multiple functions and
58    fn computation_bucket(max_bucket_cost: u64) -> Vec<ComputationBucket> {
59        assert!(max_bucket_cost >= 5_000_000);
60        vec![
61            ComputationBucket::simple(0, 1_000),
62            ComputationBucket::simple(1_000, 5_000),
63            ComputationBucket::simple(5_000, 10_000),
64            ComputationBucket::simple(10_000, 20_000),
65            ComputationBucket::simple(20_000, 50_000),
66            ComputationBucket::simple(50_000, 200_000),
67            ComputationBucket::simple(200_000, 1_000_000),
68            ComputationBucket::simple(1_000_000, max_bucket_cost),
69        ]
70    }
71
72    /// Portion of the storage rebate that gets passed on to the transaction sender. The remainder
73    /// will be burned, then re-minted + added to the storage fund at the next epoch change
74    fn sender_rebate(storage_rebate: u64, storage_rebate_rate: u64) -> u64 {
75        // we round storage rebate such that `>= x.5` goes to x+1 (rounds up) and
76        // `< x.5` goes to x (truncates). We replicate `f32/64::round()`
77        const BASIS_POINTS: u128 = 10000;
78        (((storage_rebate as u128 * storage_rebate_rate as u128)
79        + (BASIS_POINTS / 2)) // integer rounding adds half of the BASIS_POINTS (denominator)
80        / BASIS_POINTS) as u64
81    }
82
83    /// A list of constant costs of various operations in Sui.
84    pub struct SuiCostTable {
85        /// A flat fee charged for every transaction. This is also the minimum amount of
86        /// gas charged for a transaction.
87        pub(crate) min_transaction_cost: u64,
88        /// Maximum allowable budget for a transaction.
89        pub(crate) max_gas_budget: u64,
90        /// Computation cost per byte charged for package publish. This cost is primarily
91        /// determined by the cost to verify and link a package. Note that this does not
92        /// include the cost of writing the package to the store.
93        package_publish_per_byte_cost: u64,
94        /// Per byte cost to read objects from the store. This is computation cost instead of
95        /// storage cost because it does not change the amount of data stored on the db.
96        object_read_per_byte_cost: u64,
97        /// Unit cost of a byte in the storage. This will be used both for charging for
98        /// new storage as well as rebating for deleting storage. That is, we expect users to
99        /// get full refund on the object storage when it's deleted.
100        storage_per_byte_cost: u64,
101        /// Execution cost table to be used.
102        pub execution_cost_table: CostTable,
103        /// Computation buckets to cost transaction in price groups
104        computation_bucket: Vec<ComputationBucket>,
105        /// Max gas price for aborted transactions.
106        max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,
107    }
108
109    impl std::fmt::Debug for SuiCostTable {
110        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111            // TODO: dump the fields.
112            write!(f, "SuiCostTable(...)")
113        }
114    }
115
116    impl SuiCostTable {
117        pub(crate) fn new(c: &ProtocolConfig, gas_price: u64) -> Self {
118            // gas_price here is the Reference Gas Price, however we may decide
119            // to change it to be the price passed in the transaction
120            let min_transaction_cost = if txn_base_cost_as_multiplier(c) {
121                c.base_tx_cost_fixed() * gas_price
122            } else {
123                c.base_tx_cost_fixed()
124            };
125            Self {
126                min_transaction_cost,
127                max_gas_budget: c.max_tx_gas(),
128                package_publish_per_byte_cost: c.package_publish_cost_per_byte(),
129                object_read_per_byte_cost: c.obj_access_cost_read_per_byte(),
130                storage_per_byte_cost: c.obj_data_cost_refundable(),
131                execution_cost_table: cost_table_for_version(c.gas_model_version()),
132                computation_bucket: computation_bucket(c.max_gas_computation_bucket()),
133                max_gas_price_rgp_factor_for_aborted_transactions: c
134                    .max_gas_price_rgp_factor_for_aborted_transactions_as_option(),
135            }
136        }
137
138        pub(crate) fn unmetered() -> Self {
139            Self {
140                min_transaction_cost: 0,
141                max_gas_budget: u64::MAX,
142                package_publish_per_byte_cost: 0,
143                object_read_per_byte_cost: 0,
144                storage_per_byte_cost: 0,
145                execution_cost_table: ZERO_COST_SCHEDULE.clone(),
146                // should not matter
147                computation_bucket: computation_bucket(5_000_000),
148                max_gas_price_rgp_factor_for_aborted_transactions: None,
149            }
150        }
151    }
152
153    #[derive(Debug, Clone, Serialize, Deserialize)]
154    pub struct PerObjectStorage {
155        /// storage_cost is the total storage gas to charge. This is computed
156        /// at the end of execution while determining storage charges.
157        /// It tracks `storage_bytes * obj_data_cost_refundable` as
158        /// described in `storage_gas_price`
159        /// It has been multiplied by the storage gas price. This is the new storage rebate.
160        pub storage_cost: u64,
161        /// storage_rebate is the storage rebate (in Sui) for in this object.
162        /// This is computed at the end of execution while determining storage charges.
163        /// The value is in Sui.
164        pub storage_rebate: u64,
165        /// The object size post-transaction in bytes
166        pub new_size: u64,
167    }
168
169    #[derive(Debug, Clone, Copy)]
170    enum GasRoundingMode {
171        /// Bucketize the computation cost according to predefined buckets.
172        Bucketize,
173        /// Rounding value to round up gas charges.
174        Stepped(u64),
175        /// Round by keeping just over half digits
176        KeepHalfDigits,
177    }
178
179    #[allow(dead_code)]
180    #[derive(Debug)]
181    pub struct SuiGasStatus {
182        // GasStatus as used by the VM, that is all the VM sees
183        pub gas_status: GasStatus,
184        // Cost table contains a set of constant/config for the gas model/charging
185        cost_table: SuiCostTable,
186        // Gas budget for this gas status instance.
187        // Typically the gas budget as defined in the `TransactionData::GasData`
188        gas_budget: u64,
189        // Computation cost after execution. This is the result of the gas used by the `GasStatus`
190        // properly bucketized.
191        // Starts at 0 and it is assigned in `bucketize_computation`.
192        computation_cost: u64,
193        // Whether to charge or go unmetered
194        charge: bool,
195        // Gas price for computation.
196        // This is a multiplier on the final charge as related to the RGP (reference gas price).
197        // Checked at signing: `gas_price >= reference_gas_price`
198        // and then conceptually
199        // `final_computation_cost = total_computation_cost * gas_price / reference_gas_price`
200        gas_price: u64,
201        // RGP as defined in the protocol config.
202        reference_gas_price: u64,
203        // Gas price for storage. This is a multiplier on the final charge
204        // as related to the storage gas price defined in the system
205        // (`ProtocolConfig::storage_gas_price`).
206        // Conceptually, given a constant `obj_data_cost_refundable`
207        // (defined in `ProtocolConfig::obj_data_cost_refundable`)
208        // `total_storage_cost = storage_bytes * obj_data_cost_refundable`
209        // `final_storage_cost = total_storage_cost * storage_gas_price`
210        storage_gas_price: u64,
211        /// Per Object Storage Cost and Storage Rebate, used to get accumulated values at the
212        /// end of execution to determine storage charges and rebates.
213        per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
214        // storage rebate rate as defined in the ProtocolConfig
215        rebate_rate: u64,
216        /// Amount of storage rebate accumulated when we are running in unmetered mode (i.e. system transaction).
217        /// This allows us to track how much storage rebate we need to retain in system transactions.
218        unmetered_storage_rebate: u64,
219        /// Rounding mode for gas charges.
220        gas_rounding_mode: GasRoundingMode,
221    }
222
223    impl SuiGasStatus {
224        fn new(
225            move_gas_status: GasStatus,
226            gas_budget: u64,
227            charge: bool,
228            gas_price: u64,
229            reference_gas_price: u64,
230            storage_gas_price: u64,
231            rebate_rate: u64,
232            gas_rounding_mode: GasRoundingMode,
233            cost_table: SuiCostTable,
234        ) -> SuiGasStatus {
235            let gas_rounding_mode = match gas_rounding_mode {
236                GasRoundingMode::Bucketize => GasRoundingMode::Bucketize,
237                GasRoundingMode::Stepped(val) => GasRoundingMode::Stepped(val.max(1)),
238                GasRoundingMode::KeepHalfDigits => GasRoundingMode::KeepHalfDigits,
239            };
240            SuiGasStatus {
241                gas_status: move_gas_status,
242                gas_budget,
243                charge,
244                computation_cost: 0,
245                gas_price,
246                reference_gas_price,
247                storage_gas_price,
248                per_object_storage: Vec::new(),
249                rebate_rate,
250                unmetered_storage_rebate: 0,
251                gas_rounding_mode,
252                cost_table,
253            }
254        }
255
256        pub(crate) fn new_with_budget(
257            gas_budget: u64,
258            gas_price: u64,
259            reference_gas_price: u64,
260            config: &ProtocolConfig,
261        ) -> SuiGasStatus {
262            let storage_gas_price = config.storage_gas_price();
263            let max_computation_budget = config.max_gas_computation_bucket() * gas_price;
264            let computation_budget = if gas_budget > max_computation_budget {
265                max_computation_budget
266            } else {
267                gas_budget
268            };
269            let sui_cost_table = SuiCostTable::new(config, gas_price);
270            let gas_rounding_mode = if config.gas_rounding_halve_digits() {
271                GasRoundingMode::KeepHalfDigits
272            } else if let Some(step) = config.gas_rounding_step_as_option() {
273                GasRoundingMode::Stepped(step)
274            } else {
275                GasRoundingMode::Bucketize
276            };
277            Self::new(
278                GasStatus::new(
279                    sui_cost_table.execution_cost_table.clone(),
280                    computation_budget,
281                    gas_price,
282                    config.gas_model_version(),
283                ),
284                gas_budget,
285                true,
286                gas_price,
287                reference_gas_price,
288                storage_gas_price,
289                config.storage_rebate_rate(),
290                gas_rounding_mode,
291                sui_cost_table,
292            )
293        }
294
295        pub fn new_unmetered() -> SuiGasStatus {
296            Self::new(
297                GasStatus::new_unmetered(),
298                0,
299                false,
300                0,
301                0,
302                0,
303                0,
304                GasRoundingMode::Bucketize,
305                SuiCostTable::unmetered(),
306            )
307        }
308
309        pub fn reference_gas_price(&self) -> u64 {
310            self.reference_gas_price
311        }
312
313        // Check whether gas arguments are legit:
314        // 1. Gas object has an address owner.
315        // 2. Gas budget is between min and max budget allowed
316        // 3. Gas balance (all gas coins together) is bigger or equal to budget
317        pub(crate) fn check_gas_balance(
318            &self,
319            gas_objs: &[&ObjectReadResult],
320            gas_budget: u64,
321        ) -> UserInputResult {
322            self.check_gas_objects(gas_objs)?;
323            self.check_gas_data(gas_objs, gas_budget)
324        }
325
326        // Check gas objects have an address owner.
327        pub(crate) fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
328            // All gas objects have an address owner
329            for gas_object in gas_objs {
330                // if as_object() returns None, it means the object has been deleted (and therefore
331                // must be a shared object).
332                if let Some(obj) = gas_object.as_object() {
333                    if !obj.is_address_owned() {
334                        return Err(UserInputError::GasObjectNotOwnedObject {
335                            owner: obj.owner.clone(),
336                        });
337                    }
338                } else {
339                    // This case should never happen (because gas can't be a shared object), but we
340                    // handle this case for future-proofing
341                    return Err(UserInputError::MissingGasPayment);
342                }
343            }
344            Ok(())
345        }
346
347        // Gas data is consistent
348        pub(crate) fn check_gas_data(
349            &self,
350            gas_objs: &[&ObjectReadResult],
351            gas_budget: u64,
352        ) -> UserInputResult {
353            // Gas budget is between min and max budget allowed
354            if gas_budget > self.cost_table.max_gas_budget {
355                return Err(UserInputError::GasBudgetTooHigh {
356                    gas_budget,
357                    max_budget: self.cost_table.max_gas_budget,
358                });
359            }
360            if gas_budget < self.cost_table.min_transaction_cost {
361                return Err(UserInputError::GasBudgetTooLow {
362                    gas_budget,
363                    min_budget: self.cost_table.min_transaction_cost,
364                });
365            }
366
367            // Gas balance (all gas coins together) is bigger or equal to budget
368            let mut gas_balance = 0u128;
369            for gas_obj in gas_objs {
370                gas_balance += gas::get_gas_balance(gas_obj.as_object().ok_or(
371                    UserInputError::InvalidGasObject {
372                        object_id: gas_obj.id(),
373                    },
374                )?)? as u128;
375            }
376            if gas_balance < gas_budget as u128 {
377                Err(UserInputError::GasBalanceTooLow {
378                    gas_balance,
379                    needed_gas_amount: gas_budget as u128,
380                })
381            } else {
382                Ok(())
383            }
384        }
385
386        fn storage_cost(&self) -> u64 {
387            self.storage_gas_units()
388        }
389
390        pub fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
391            &self.per_object_storage
392        }
393    }
394
395    impl SuiGasStatusAPI for SuiGasStatus {
396        fn is_unmetered(&self) -> bool {
397            !self.charge
398        }
399
400        fn move_gas_status(&self) -> &GasStatus {
401            &self.gas_status
402        }
403
404        fn move_gas_status_mut(&mut self) -> &mut GasStatus {
405            &mut self.gas_status
406        }
407
408        fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
409            let gas_used = self.gas_status.gas_used_pre_gas_price();
410            let effective_gas_price = if self
411                .cost_table
412                .max_gas_price_rgp_factor_for_aborted_transactions
413                .is_some()
414                && aborted.unwrap_or(false)
415            {
416                // For aborts, cap at max but don't exceed user's price
417                // This minimizes the risk of competing for priority execution in the case that the txn may be aborted.
418                let max_gas_price_for_aborted_txns = self
419                    .cost_table
420                    .max_gas_price_rgp_factor_for_aborted_transactions
421                    .unwrap()
422                    * self.reference_gas_price;
423                self.gas_price.min(max_gas_price_for_aborted_txns)
424            } else {
425                // For all other cases, use the user's gas price
426                self.gas_price
427            };
428            let gas_used = match self.gas_rounding_mode {
429                GasRoundingMode::KeepHalfDigits => {
430                    half_digits_rounding(gas_used) * effective_gas_price
431                }
432                GasRoundingMode::Stepped(gas_rounding) => {
433                    if gas_used > 0 && gas_used % gas_rounding == 0 {
434                        gas_used * effective_gas_price
435                    } else {
436                        ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
437                    }
438                }
439                GasRoundingMode::Bucketize => {
440                    let bucket_cost =
441                        get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
442                    // charge extra on top of `computation_cost` to make the total computation
443                    // cost a bucket value
444                    bucket_cost * effective_gas_price
445                }
446            };
447            if self.gas_budget <= gas_used {
448                self.computation_cost = self.gas_budget;
449                Err(ExecutionErrorKind::InsufficientGas.into())
450            } else {
451                self.computation_cost = gas_used;
452                Ok(())
453            }
454        }
455
456        /// Returns the final (computation cost, storage cost, storage rebate) of the gas meter.
457        /// We use initial budget, combined with remaining gas and storage cost to derive
458        /// computation cost.
459        fn summary(&self) -> GasCostSummary {
460            // compute storage rebate, both rebate and non refundable fee
461            let storage_rebate = self.storage_rebate();
462            let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
463            assert!(sender_rebate <= storage_rebate);
464            let non_refundable_storage_fee = storage_rebate - sender_rebate;
465            GasCostSummary {
466                computation_cost: self.computation_cost,
467                storage_cost: self.storage_cost(),
468                storage_rebate: sender_rebate,
469                non_refundable_storage_fee,
470            }
471        }
472
473        fn gas_budget(&self) -> u64 {
474            self.gas_budget
475        }
476
477        fn gas_price(&self) -> u64 {
478            self.gas_price
479        }
480
481        fn reference_gas_price(&self) -> u64 {
482            self.reference_gas_price
483        }
484
485        fn storage_gas_units(&self) -> u64 {
486            self.per_object_storage
487                .iter()
488                .map(|(_, per_object)| per_object.storage_cost)
489                .sum()
490        }
491
492        fn storage_rebate(&self) -> u64 {
493            self.per_object_storage
494                .iter()
495                .map(|(_, per_object)| per_object.storage_rebate)
496                .sum()
497        }
498
499        fn unmetered_storage_rebate(&self) -> u64 {
500            self.unmetered_storage_rebate
501        }
502
503        fn gas_used(&self) -> u64 {
504            self.gas_status.gas_used_pre_gas_price()
505        }
506
507        fn reset_storage_cost_and_rebate(&mut self) {
508            self.per_object_storage = Vec::new();
509            self.unmetered_storage_rebate = 0;
510        }
511
512        fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
513            self.gas_status
514                .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
515                .map_err(|e| {
516                    debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
517                    ExecutionErrorKind::InsufficientGas.into()
518                })
519        }
520
521        fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
522            self.gas_status
523                .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
524                .map_err(|e| {
525                    debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
526                    ExecutionErrorKind::InsufficientGas.into()
527                })
528        }
529
530        /// Update `storage_rebate` and `storage_gas_units` for each object in the transaction.
531        /// There is no charge in this function. Charges will all be applied together at the end
532        /// (`track_storage_mutation`).
533        /// Return the new storage rebate (cost of object storage) according to `new_size`.
534        fn track_storage_mutation(
535            &mut self,
536            object_id: ObjectID,
537            new_size: usize,
538            storage_rebate: u64,
539        ) -> u64 {
540            if self.is_unmetered() {
541                self.unmetered_storage_rebate += storage_rebate;
542                return 0;
543            }
544
545            // compute and track cost (based on size)
546            let new_size = new_size as u64;
547            let storage_cost =
548                new_size * self.cost_table.storage_per_byte_cost * self.storage_gas_price;
549            // track rebate
550
551            self.per_object_storage.push((
552                object_id,
553                PerObjectStorage {
554                    storage_cost,
555                    storage_rebate,
556                    new_size,
557                },
558            ));
559            // return the new object rebate (object storage cost)
560            storage_cost
561        }
562
563        fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
564            let storage_rebate = self.storage_rebate();
565            let storage_cost = self.storage_cost();
566            let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
567            assert!(sender_rebate <= storage_rebate);
568            if sender_rebate >= storage_cost {
569                // there is more rebate than cost, when deducting gas we are adding
570                // to whatever is the current amount charged so we are `Ok`
571                Ok(())
572            } else {
573                let gas_left = self.gas_budget - self.computation_cost;
574                // we have to charge for storage and may go out of gas, check
575                if gas_left < storage_cost - sender_rebate {
576                    // Running out of gas would cause the temporary store to reset
577                    // and zero storage and rebate.
578                    // The remaining_gas will be 0 and we will charge all in computation
579                    Err(ExecutionErrorKind::InsufficientGas.into())
580                } else {
581                    Ok(())
582                }
583            }
584        }
585
586        fn adjust_computation_on_out_of_gas(&mut self) {
587            self.per_object_storage = Vec::new();
588            self.computation_cost = self.gas_budget;
589        }
590
591        fn gas_usage_report(&self) -> GasUsageReport {
592            GasUsageReport {
593                cost_summary: self.summary(),
594                gas_used: self.gas_used(),
595                gas_price: self.gas_price(),
596                reference_gas_price: self.reference_gas_price(),
597                per_object_storage: self.per_object_storage().clone(),
598                gas_budget: self.gas_budget(),
599                storage_gas_price: self.storage_gas_price,
600                rebate_rate: self.rebate_rate,
601            }
602        }
603    }
604
605    fn half_digits_rounding(n: u64) -> u64 {
606        if n < 1000 {
607            return 1000;
608        }
609        let digits = n.ilog10();
610        let drop = digits / 2;
611        let base = 10u64.pow(drop);
612        n.div_ceil(base) * base
613    }
614
615    #[test]
616    fn test_half_digits_rounding() {
617        assert_eq!(half_digits_rounding(0), 1000);
618        assert_eq!(half_digits_rounding(1), 1000);
619        assert_eq!(half_digits_rounding(999), 1000);
620        assert_eq!(half_digits_rounding(1000), 1000);
621        assert_eq!(half_digits_rounding(1001), 1010);
622        assert_eq!(half_digits_rounding(1050), 1050);
623        assert_eq!(half_digits_rounding(1999), 2000);
624        assert_eq!(half_digits_rounding(20_000), 20_000);
625        assert_eq!(half_digits_rounding(20_001), 20_100);
626        assert_eq!(half_digits_rounding(20_500), 20_500);
627        assert_eq!(half_digits_rounding(29_999), 30_000);
628        assert_eq!(half_digits_rounding(300_000), 300_000);
629        assert_eq!(half_digits_rounding(300_001), 300_100);
630        assert_eq!(half_digits_rounding(305_500), 305_500);
631        assert_eq!(half_digits_rounding(305_501), 305_600);
632        assert_eq!(half_digits_rounding(999_999), 1_000_000);
633        assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
634        assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
635        assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
636        assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
637        assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
638        assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
639        assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
640    }
641}