cairo_lang_sierra/extensions/modules/
gas.rs

1use convert_case::Casing;
2use itertools::chain;
3use serde::{Deserialize, Serialize};
4
5use super::int::unsigned128::Uint128Type;
6// Module providing the gas related extensions.
7use super::range_check::RangeCheckType;
8use crate::define_libfunc_hierarchy;
9use crate::extensions::lib_func::{
10    BranchSignature, DeferredOutputKind, LibfuncSignature, OutputVarInfo, ParamSignature,
11    SierraApChange, SignatureSpecializationContext,
12};
13use crate::extensions::{
14    NamedType, NoGenericArgsGenericLibfunc, NoGenericArgsGenericType, OutputVarReferenceInfo,
15    SpecializationError,
16};
17use crate::ids::GenericTypeId;
18
19/// Type for gas actions.
20#[derive(Default)]
21pub struct GasBuiltinType {}
22impl NoGenericArgsGenericType for GasBuiltinType {
23    const ID: GenericTypeId = GenericTypeId::new_inline("GasBuiltin");
24    const STORABLE: bool = true;
25    const DUPLICATABLE: bool = false;
26    const DROPPABLE: bool = false;
27    const ZERO_SIZED: bool = false;
28}
29
30define_libfunc_hierarchy! {
31    pub enum GasLibfunc {
32        WithdrawGas(WithdrawGasLibfunc),
33        RedepositGas(RedepositGasLibfunc),
34        GetAvailableGas(GetAvailableGasLibfunc),
35        GetUnspentGas(GetUnspentGasLibfunc),
36        BuiltinWithdrawGas(BuiltinCostWithdrawGasLibfunc),
37        GetBuiltinCosts(BuiltinCostGetBuiltinCostsLibfunc),
38    }, GasConcreteLibfunc
39}
40
41/// Libfunc for withdrawing gas.
42#[derive(Default)]
43pub struct WithdrawGasLibfunc {}
44impl NoGenericArgsGenericLibfunc for WithdrawGasLibfunc {
45    const STR_ID: &'static str = "withdraw_gas";
46
47    fn specialize_signature(
48        &self,
49        context: &dyn SignatureSpecializationContext,
50    ) -> Result<LibfuncSignature, SpecializationError> {
51        let gas_builtin_type = context.get_concrete_type(GasBuiltinType::id(), &[])?;
52        let range_check_type = context.get_concrete_type(RangeCheckType::id(), &[])?;
53        let rc_output_info = OutputVarInfo::new_builtin(range_check_type.clone(), 0);
54        Ok(LibfuncSignature {
55            param_signatures: vec![
56                ParamSignature::new(range_check_type).with_allow_add_const(),
57                ParamSignature::new(gas_builtin_type.clone()),
58            ],
59            branch_signatures: vec![
60                // Success:
61                BranchSignature {
62                    vars: vec![
63                        rc_output_info.clone(),
64                        OutputVarInfo {
65                            ty: gas_builtin_type.clone(),
66                            ref_info: OutputVarReferenceInfo::NewTempVar { idx: 0 },
67                        },
68                    ],
69                    ap_change: SierraApChange::Known { new_vars_only: false },
70                },
71                // Failure:
72                BranchSignature {
73                    vars: vec![
74                        rc_output_info,
75                        OutputVarInfo {
76                            ty: gas_builtin_type,
77                            ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 1 },
78                        },
79                    ],
80                    ap_change: SierraApChange::Known { new_vars_only: false },
81                },
82            ],
83            fallthrough: Some(0),
84        })
85    }
86}
87
88/// Libfunc for returning unused gas.
89#[derive(Default)]
90pub struct RedepositGasLibfunc {}
91impl NoGenericArgsGenericLibfunc for RedepositGasLibfunc {
92    const STR_ID: &'static str = "redeposit_gas";
93
94    fn specialize_signature(
95        &self,
96        context: &dyn SignatureSpecializationContext,
97    ) -> Result<LibfuncSignature, SpecializationError> {
98        let gas_builtin_type = context.get_concrete_type(GasBuiltinType::id(), &[])?;
99        Ok(LibfuncSignature::new_non_branch(
100            vec![gas_builtin_type.clone()],
101            vec![OutputVarInfo {
102                ty: gas_builtin_type,
103                ref_info: OutputVarReferenceInfo::Deferred(DeferredOutputKind::Generic),
104            }],
105            SierraApChange::Known { new_vars_only: false },
106        ))
107    }
108}
109
110/// Libfunc for returning the amount of available gas.
111#[derive(Default)]
112pub struct GetAvailableGasLibfunc {}
113impl NoGenericArgsGenericLibfunc for GetAvailableGasLibfunc {
114    const STR_ID: &'static str = "get_available_gas";
115
116    fn specialize_signature(
117        &self,
118        context: &dyn SignatureSpecializationContext,
119    ) -> Result<LibfuncSignature, SpecializationError> {
120        let gas_builtin_type = context.get_concrete_type(GasBuiltinType::id(), &[])?;
121        Ok(LibfuncSignature::new_non_branch(
122            vec![gas_builtin_type.clone()],
123            vec![
124                OutputVarInfo {
125                    ty: gas_builtin_type,
126                    ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 0 },
127                },
128                OutputVarInfo {
129                    ty: context.get_concrete_type(Uint128Type::id(), &[])?,
130                    ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 0 },
131                },
132            ],
133            SierraApChange::Known { new_vars_only: true },
134        ))
135    }
136}
137
138/// Libfunc for returning the amount of available gas, including gas in local wallet.
139#[derive(Default)]
140pub struct GetUnspentGasLibfunc {}
141impl NoGenericArgsGenericLibfunc for GetUnspentGasLibfunc {
142    const STR_ID: &'static str = "get_unspent_gas";
143
144    fn specialize_signature(
145        &self,
146        context: &dyn SignatureSpecializationContext,
147    ) -> Result<LibfuncSignature, SpecializationError> {
148        let gas_builtin_type = context.get_concrete_type(GasBuiltinType::id(), &[])?;
149        Ok(LibfuncSignature::new_non_branch(
150            vec![gas_builtin_type.clone()],
151            vec![
152                OutputVarInfo {
153                    ty: gas_builtin_type,
154                    ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 0 },
155                },
156                OutputVarInfo {
157                    ty: context.get_concrete_type(Uint128Type::id(), &[])?,
158                    ref_info: OutputVarReferenceInfo::SimpleDerefs,
159                },
160            ],
161            SierraApChange::Known { new_vars_only: false },
162        ))
163    }
164}
165
166/// Represents different types of costs.
167/// Note that if you add a type here you should update 'iter_precost'
168#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
169pub enum CostTokenType {
170    /// A compile time known cost unit. This is a linear combination of the runtime tokens
171    /// ([CostTokenType::Step], [CostTokenType::Hole], [CostTokenType::RangeCheck],
172    /// [CostTokenType::RangeCheck96]).
173    Const,
174
175    // Runtime post-cost token types:
176    /// The number of steps.
177    Step,
178    /// The number of memory holes (untouched memory addresses).
179    Hole,
180    /// The number of 128-bit range check builtins.
181    RangeCheck,
182    /// The number of 96-bit range check builtins.
183    RangeCheck96,
184
185    // Pre-cost token types (builtins):
186    /// One invocation of the pedersen hash function.
187    Pedersen,
188    /// One invocation of the Poseidon hades permutation.
189    Poseidon,
190    /// One invocation of the bitwise builtin.
191    Bitwise,
192    /// One invocation of the EC op builtin.
193    EcOp,
194    // Add mod builtin.
195    AddMod,
196    // mul mod builtin.
197    MulMod,
198}
199impl CostTokenType {
200    /// Iterates over the pre-cost token types.
201    pub fn iter_precost() -> std::slice::Iter<'static, Self> {
202        [
203            CostTokenType::Pedersen,
204            CostTokenType::Poseidon,
205            CostTokenType::Bitwise,
206            CostTokenType::EcOp,
207            CostTokenType::AddMod,
208            CostTokenType::MulMod,
209        ]
210        .iter()
211    }
212
213    /// Iterates over the tokens that are used in the Sierra->Casm compilation (pre-cost token types
214    /// and [CostTokenType::Const]).
215    pub fn iter_casm_tokens()
216    -> std::iter::Chain<std::slice::Iter<'static, Self>, std::slice::Iter<'static, Self>> {
217        chain!(Self::iter_precost(), [CostTokenType::Const].iter())
218    }
219
220    /// Returns the name of the token type, in snake_case.
221    pub fn name(&self) -> String {
222        match self {
223            CostTokenType::Const => "const",
224            CostTokenType::Step => "step",
225            CostTokenType::Hole => "hole",
226            CostTokenType::RangeCheck => "range_check",
227            CostTokenType::RangeCheck96 => "range_check96",
228            CostTokenType::Pedersen => "pedersen",
229            CostTokenType::Bitwise => "bitwise",
230            CostTokenType::EcOp => "ec_op",
231            CostTokenType::Poseidon => "poseidon",
232            CostTokenType::AddMod => "add_mod",
233            CostTokenType::MulMod => "mul_mod",
234        }
235        .into()
236    }
237
238    pub fn camel_case_name(&self) -> String {
239        self.name().to_case(convert_case::Case::UpperCamel)
240    }
241
242    pub fn offset_in_builtin_costs(&self) -> i16 {
243        match self {
244            CostTokenType::Const
245            | CostTokenType::Step
246            | CostTokenType::Hole
247            | CostTokenType::RangeCheck
248            | CostTokenType::RangeCheck96 => {
249                panic!("offset_in_builtin_costs is not supported for '{}'.", self.camel_case_name())
250            }
251
252            CostTokenType::Pedersen => 0,
253            CostTokenType::Bitwise => 1,
254            CostTokenType::EcOp => 2,
255            CostTokenType::Poseidon => 3,
256            // TODO(ilya): Update the actual table.
257            CostTokenType::AddMod => 4,
258            CostTokenType::MulMod => 5,
259        }
260    }
261}
262
263/// Represents a pointer to an array with the builtin costs.
264/// Every element in the array is the cost of a single invocation of a builtin.
265///
266/// Offsets to the array are given by [CostTokenType::offset_in_builtin_costs].
267#[derive(Default)]
268pub struct BuiltinCostsType {}
269impl NoGenericArgsGenericType for BuiltinCostsType {
270    const ID: GenericTypeId = GenericTypeId::new_inline("BuiltinCosts");
271    const STORABLE: bool = true;
272    const DUPLICATABLE: bool = true;
273    const DROPPABLE: bool = true;
274    const ZERO_SIZED: bool = false;
275}
276impl BuiltinCostsType {
277    /// Returns the number of steps required for the computation of the requested cost, given the
278    /// number of requested token usages. The number of steps is also the change in `ap`.
279    /// If `table_available` is false, the number of steps includes the cost of fetching the builtin
280    /// cost table.
281    pub fn cost_computation_steps<TokenUsages: Fn(CostTokenType) -> usize>(
282        table_available: bool,
283        token_usages: TokenUsages,
284    ) -> usize {
285        let calculation_steps = CostTokenType::iter_precost()
286            .map(|token_type| match token_usages(*token_type) {
287                0 => 0,
288                1 => 2,
289                _ => 3,
290            })
291            .sum();
292        if calculation_steps > 0 && !table_available {
293            calculation_steps + 4
294        } else {
295            calculation_steps
296        }
297    }
298}
299
300/// Libfunc for withdrawing gas to be used by a builtin.
301#[derive(Default)]
302pub struct BuiltinCostWithdrawGasLibfunc;
303impl NoGenericArgsGenericLibfunc for BuiltinCostWithdrawGasLibfunc {
304    const STR_ID: &'static str = "withdraw_gas_all";
305
306    fn specialize_signature(
307        &self,
308        context: &dyn SignatureSpecializationContext,
309    ) -> Result<LibfuncSignature, SpecializationError> {
310        let gas_builtin_type = context.get_concrete_type(GasBuiltinType::id(), &[])?;
311        let range_check_type = context.get_concrete_type(RangeCheckType::id(), &[])?;
312        let builtin_costs_type = context.get_concrete_type(BuiltinCostsType::id(), &[])?;
313        let rc_output_info = OutputVarInfo::new_builtin(range_check_type.clone(), 0);
314        Ok(LibfuncSignature {
315            param_signatures: vec![
316                ParamSignature::new(range_check_type).with_allow_add_const(),
317                ParamSignature::new(gas_builtin_type.clone()),
318                ParamSignature::new(builtin_costs_type),
319            ],
320            branch_signatures: vec![
321                // Success:
322                BranchSignature {
323                    vars: vec![
324                        rc_output_info.clone(),
325                        OutputVarInfo {
326                            ty: gas_builtin_type.clone(),
327                            ref_info: OutputVarReferenceInfo::NewTempVar { idx: 0 },
328                        },
329                    ],
330                    ap_change: SierraApChange::Known { new_vars_only: false },
331                },
332                // Failure:
333                BranchSignature {
334                    vars: vec![
335                        rc_output_info,
336                        OutputVarInfo {
337                            ty: gas_builtin_type,
338                            ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 1 },
339                        },
340                    ],
341                    ap_change: SierraApChange::Known { new_vars_only: false },
342                },
343            ],
344            fallthrough: Some(0),
345        })
346    }
347}
348
349/// Libfunc for getting the pointer to the gas cost array.
350/// See [BuiltinCostsType].
351#[derive(Default)]
352pub struct BuiltinCostGetBuiltinCostsLibfunc {}
353
354impl NoGenericArgsGenericLibfunc for BuiltinCostGetBuiltinCostsLibfunc {
355    const STR_ID: &'static str = "get_builtin_costs";
356
357    fn specialize_signature(
358        &self,
359        context: &dyn SignatureSpecializationContext,
360    ) -> Result<LibfuncSignature, SpecializationError> {
361        let builtin_costs_type = context.get_concrete_type(BuiltinCostsType::id(), &[])?;
362        Ok(LibfuncSignature::new_non_branch(
363            vec![],
364            vec![OutputVarInfo {
365                ty: builtin_costs_type,
366                ref_info: OutputVarReferenceInfo::Deferred(DeferredOutputKind::Generic),
367            }],
368            SierraApChange::Known { new_vars_only: false },
369        ))
370    }
371}