Skip to main content

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 crate::static_programmable_transactions::loading::ast::{DeserializedPackage, PackagePayload};
6use sui_protocol_config::ProtocolConfig;
7use sui_types::error::ExecutionErrorTrait;
8use sui_types::execution_status::ExecutionErrorKind;
9
10/// The [`TranslationMeter`] is responsible for metering gas usage for various operations
11/// during the translation of a transaction. It interacts with and exposes interfaces to the
12/// [`GasCharger`] it holds in order to deduct gas based on the operations performed.
13///
14/// It holds a reference to the `ProtocolConfig` to access protocol-specific configuration
15/// parameters that may influence gas costs and limits.
16pub struct TranslationMeter<'pc, 'gas> {
17    protocol_config: &'pc ProtocolConfig,
18    charger: &'gas mut GasCharger,
19}
20
21impl<'pc, 'gas> TranslationMeter<'pc, 'gas> {
22    pub fn new(
23        protocol_config: &'pc ProtocolConfig,
24        gas_charger: &'gas mut GasCharger,
25    ) -> TranslationMeter<'pc, 'gas> {
26        TranslationMeter {
27            protocol_config,
28            charger: gas_charger,
29        }
30    }
31
32    pub fn charge_base_inputs<E: ExecutionErrorTrait>(
33        &mut self,
34        num_inputs: usize,
35    ) -> Result<(), E> {
36        let amount = (num_inputs as u64)
37            .max(1)
38            .saturating_mul(self.protocol_config.translation_per_input_base_charge());
39        self.charge(amount)
40    }
41
42    pub fn charge_pure_input_bytes<E: ExecutionErrorTrait>(
43        &mut self,
44        num_bytes: usize,
45    ) -> Result<(), E> {
46        let amount = (num_bytes as u64).max(1).saturating_mul(
47            self.protocol_config
48                .translation_pure_input_per_byte_charge(),
49        );
50        self.charge(amount)
51    }
52
53    pub fn charge_base_command<E: ExecutionErrorTrait>(
54        &mut self,
55        num_args: usize,
56    ) -> Result<(), E> {
57        let amount = (num_args as u64)
58            .max(1)
59            .saturating_mul(self.protocol_config.translation_per_command_base_charge());
60        self.charge(amount)
61    }
62
63    /// Charge gas for loading types based on the number of type nodes loaded.
64    /// The cost is calculated as `num_type_nodes * TYPE_LOAD_PER_NODE_MULTIPLIER`.
65    /// This function assumes that `num_type_nodes` is non-zero.
66    pub fn charge_num_type_nodes<E: ExecutionErrorTrait>(
67        &mut self,
68        num_type_nodes: u64,
69    ) -> Result<(), E> {
70        let amount = num_type_nodes
71            .max(1)
72            .saturating_mul(self.protocol_config.translation_per_type_node_charge());
73        self.charge(amount)
74    }
75
76    pub fn charge_num_type_references<E: ExecutionErrorTrait>(
77        &mut self,
78        num_type_references: u64,
79    ) -> Result<(), E> {
80        let amount = self.reference_cost_formula(num_type_references.max(1))?;
81        let amount =
82            amount.saturating_mul(self.protocol_config.translation_per_reference_node_charge());
83        self.charge(amount)
84    }
85
86    /// Charge for the references live at a single command, should include ALL live references,
87    /// not just those created by the command
88    pub fn charge_num_live_references<E: ExecutionErrorTrait>(
89        &mut self,
90        num_live_references: u64,
91    ) -> Result<(), E> {
92        let amount = self
93            .live_reference_cost_formula(num_live_references)
94            .saturating_mul(self.protocol_config.translation_per_live_reference_charge());
95        if amount == 0 {
96            // a command with no live references has no additional cost
97            return Ok(());
98        }
99        self.charge(amount)
100    }
101
102    pub fn charge_num_linkage_entries<E: ExecutionErrorTrait>(
103        &mut self,
104        num_linkage_entries: usize,
105    ) -> Result<(), E> {
106        let amount = (num_linkage_entries as u64)
107            .saturating_mul(self.protocol_config.translation_per_linkage_entry_charge())
108            .max(1);
109        self.charge(amount)
110    }
111
112    pub fn charge_package_load<E: ExecutionErrorTrait>(
113        &mut self,
114        payload: &PackagePayload,
115    ) -> Result<(), E> {
116        match payload {
117            PackagePayload::Serialized(_) => {
118                // Payload stays serialized; deserialization (and charge) happen at execution time.
119                Ok(())
120            }
121            PackagePayload::Deserialized(DeserializedPackage { total_bytes, .. }) => {
122                self.charger.charge_publish_package(*total_bytes)?;
123                Ok(())
124            }
125        }
126    }
127
128    // We use a non-linear cost function for type references to account for the increased
129    // complexity they introduce. The cost is calculated as:
130    // cost = (num_type_references * (num_type_references + 1)) / 2
131    //
132    // Take &self to access protocol config if needed in the future.
133    fn reference_cost_formula<E: ExecutionErrorTrait>(&self, n: u64) -> Result<u64, E> {
134        let Some(n_succ) = n.checked_add(1) else {
135            invariant_violation!("u64 overflow when calculating type reference cost")
136        };
137        Ok(n.saturating_mul(n_succ) / 2)
138    }
139
140    // Live references are charged with a cubic cost function to account for the increased
141    // complexity in the underlying borrow graph:
142    // cost = (n * (n + 1) * (n + 2)) / 6
143    //
144    // Take &self to access protocol config if needed in the future.
145    fn live_reference_cost_formula(&self, n: u64) -> u64 {
146        let product = n
147            .saturating_mul(n.saturating_add(1))
148            .saturating_mul(n.saturating_add(2));
149        product / 6
150    }
151
152    // Charge gas using a point charge mechanism based on the cumulative number of units charged so
153    // far.
154    fn charge<E: ExecutionErrorTrait>(&mut self, amount: u64) -> Result<(), E> {
155        debug_assert!(amount > 0);
156        self.charger
157            .move_gas_status_mut()
158            .deduct_gas(amount.into())
159            .map_err(Self::gas_error)
160    }
161
162    fn gas_error<T, E>(e: T) -> E
163    where
164        T: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
165        E: ExecutionErrorTrait,
166    {
167        E::new_with_source(ExecutionErrorKind::InsufficientGas, e)
168    }
169}