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