sui_adapter_latest/static_programmable_transactions/metering/
translation_meter.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::gas_charger::GasCharger;
5use sui_protocol_config::ProtocolConfig;
6use sui_types::error::{ExecutionError, ExecutionErrorKind};
7
8/// The [`TranslationMeter`] is responsible for metering gas usage for various operations
9/// during the translation of a transaction. It interacts with and exposes interfaces to the
10/// [`GasCharger`] it holds in order to deduct gas based on the operations performed.
11///
12/// It holds a reference to the `ProtocolConfig` to access protocol-specific configuration
13/// parameters that may influence gas costs and limits.
14pub struct TranslationMeter<'pc, 'gas> {
15    protocol_config: &'pc ProtocolConfig,
16    charger: &'gas mut GasCharger,
17    charged: u64,
18}
19
20impl<'pc, 'gas> TranslationMeter<'pc, 'gas> {
21    pub fn new(
22        protocol_config: &'pc ProtocolConfig,
23        gas_charger: &'gas mut GasCharger,
24    ) -> TranslationMeter<'pc, 'gas> {
25        TranslationMeter {
26            protocol_config,
27            charger: gas_charger,
28            charged: 0,
29        }
30    }
31
32    pub fn charge_base_inputs(&mut self, num_inputs: usize) -> Result<(), ExecutionError> {
33        let amount = (num_inputs as u64)
34            .max(1)
35            .saturating_mul(self.protocol_config.translation_per_input_base_charge());
36        self.charge(amount)
37    }
38
39    pub fn charge_pure_input_bytes(&mut self, num_bytes: usize) -> Result<(), ExecutionError> {
40        let amount = (num_bytes as u64).max(1).saturating_mul(
41            self.protocol_config
42                .translation_pure_input_per_byte_charge(),
43        );
44        self.charge(amount)
45    }
46
47    pub fn charge_base_command(&mut self, num_args: usize) -> Result<(), ExecutionError> {
48        let amount = (num_args as u64)
49            .max(1)
50            .saturating_mul(self.protocol_config.translation_per_command_base_charge());
51        self.charge(amount)
52    }
53
54    /// Charge gas for loading types based on the number of type nodes loaded.
55    /// The cost is calculated as `num_type_nodes * TYPE_LOAD_PER_NODE_MULTIPLIER`.
56    /// This function assumes that `num_type_nodes` is non-zero.
57    pub fn charge_num_type_nodes(&mut self, num_type_nodes: u64) -> Result<(), ExecutionError> {
58        let amount = num_type_nodes
59            .max(1)
60            .saturating_mul(self.protocol_config.translation_per_type_node_charge());
61        self.charge(amount)
62    }
63
64    pub fn charge_num_type_references(
65        &mut self,
66        num_type_references: u64,
67    ) -> Result<(), ExecutionError> {
68        let amount = self.reference_cost_formula(num_type_references.max(1));
69        let amount =
70            amount.saturating_mul(self.protocol_config.translation_per_reference_node_charge());
71        self.charge(amount)
72    }
73
74    pub fn charge_num_linkage_entries(
75        &mut self,
76        num_linkage_entries: usize,
77    ) -> Result<(), ExecutionError> {
78        let amount = (num_linkage_entries as u64)
79            .saturating_mul(self.protocol_config.translation_per_linkage_entry_charge())
80            .max(1);
81        self.charge(amount)
82    }
83
84    // We use a non-linear cost function for type references to account for the increased
85    // complexity they introduce. The cost is calculated as:
86    // cost = (num_type_references * (num_type_references + 1)) / 2
87    //
88    // Take &self to access protocol config if needed in the future.
89    fn reference_cost_formula(&self, n: u64) -> u64 {
90        (n.saturating_mul(n + 1)) / 2
91    }
92
93    // Charge gas using a point charge mechanism based on the cumulative number of units charged so
94    // far.
95    fn charge(&mut self, amount: u64) -> Result<(), ExecutionError> {
96        debug_assert!(amount > 0);
97        let scaled_charge = self.calculate_point_charge(amount);
98        self.charger
99            .move_gas_status_mut()
100            .deduct_gas(scaled_charge.into())
101            .map_err(Self::gas_error)
102    }
103
104    // The point charge is calculated as:
105    // point_multiplier = (n / translation_metering_step_resolution)^2
106    // point_charge = point_multiplier * amount
107    // where `n` is the cumulative number of units charged so far.
108    //
109    // This function updates the `charged` field with the new cumulative charge once the point
110    // charge has been determined.
111    fn calculate_point_charge(&mut self, amount: u64) -> u64 {
112        debug_assert!(self.protocol_config.translation_metering_step_resolution() > 0);
113        let point_multiplier = self
114            .charged
115            .saturating_div(self.protocol_config.translation_metering_step_resolution())
116            .max(1);
117        debug_assert!(point_multiplier > 0);
118        debug_assert!(amount > 0);
119        let point_charge = point_multiplier
120            .saturating_mul(point_multiplier)
121            .saturating_mul(amount);
122        self.charged = self.charged.saturating_add(point_charge);
123        point_charge
124    }
125
126    fn gas_error<E>(e: E) -> ExecutionError
127    where
128        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
129    {
130        ExecutionError::new_with_source(ExecutionErrorKind::InsufficientGas, e)
131    }
132}