1use cairo_lang_eq_solver::Expr;
6use cairo_lang_sierra::extensions::circuit::{CircuitInfo, CircuitTypeConcrete, ConcreteCircuit};
7use cairo_lang_sierra::extensions::core::{
8 CoreConcreteLibfunc, CoreLibfunc, CoreType, CoreTypeConcrete,
9};
10use cairo_lang_sierra::extensions::coupon::CouponConcreteLibfunc;
11use cairo_lang_sierra::extensions::gas::{CostTokenMap, CostTokenType, GasConcreteLibfunc};
12use cairo_lang_sierra::ids::{ConcreteLibfuncId, ConcreteTypeId, FunctionId};
13use cairo_lang_sierra::program::{Program, Statement, StatementIdx};
14use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError};
15use cairo_lang_sierra_type_size::{ProgramRegistryInfo, TypeSizeMap};
16use cairo_lang_utils::casts::IntoOrPanic;
17use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
18use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
19use compute_costs::PostCostTypeEx;
20use core_libfunc_cost_base::InvocationCostInfoProvider;
21use core_libfunc_cost_expr::CostExprMap;
22use cost_expr::Var;
23use gas_info::GasInfo;
24use generate_equations::StatementFutureCost;
25use itertools::Itertools;
26use objects::CostInfoProvider;
27use thiserror::Error;
28
29pub mod compute_costs;
30pub mod core_libfunc_cost;
31mod core_libfunc_cost_base;
32mod core_libfunc_cost_expr;
33mod cost_expr;
34pub mod gas_info;
35mod generate_equations;
36pub mod objects;
37mod starknet_libfunc_cost_base;
38
39#[cfg(test)]
40mod test;
41
42#[derive(Error, Debug, Eq, PartialEq)]
44pub enum CostError {
45 #[error("error from the program registry")]
46 ProgramRegistryError(#[from] Box<ProgramRegistryError>),
47 #[error("failed solving the symbol tables")]
48 SolvingGasEquationFailed,
49 #[error("found an unexpected cycle during cost computation")]
50 UnexpectedCycle,
51 #[error("failed to enforce function cost")]
52 EnforceWalletValueFailed(StatementIdx),
53}
54
55struct InvocationCostInfoProviderForEqGen<
57 'a,
58 TokenUsages: Fn(CostTokenType) -> usize,
59 ApChangeVarValue: Fn() -> usize,
60> {
61 type_sizes: &'a TypeSizeMap,
63 token_usages: TokenUsages,
65 ap_change_var_value: ApChangeVarValue,
67}
68
69impl<TokenUsages: Fn(CostTokenType) -> usize, ApChangeVarValue: Fn() -> usize>
70 InvocationCostInfoProvider
71 for InvocationCostInfoProviderForEqGen<'_, TokenUsages, ApChangeVarValue>
72{
73 fn type_size(&self, ty: &ConcreteTypeId) -> usize {
74 self.type_sizes[ty].into_or_panic()
75 }
76
77 fn token_usages(&self, token_type: CostTokenType) -> usize {
78 (self.token_usages)(token_type)
79 }
80
81 fn ap_change_var_value(&self) -> usize {
82 (self.ap_change_var_value)()
83 }
84
85 fn circuit_info(&self, _ty: &ConcreteTypeId) -> &CircuitInfo {
86 unimplemented!("circuits are not supported for old gas solver");
87 }
88}
89
90pub fn calc_gas_precost_info(
95 program: &Program,
96 program_info: &ProgramRegistryInfo,
97 function_set_costs: OrderedHashMap<FunctionId, CostTokenMap<i32>>,
98) -> Result<GasInfo, CostError> {
99 let cost_provider = ComputeCostInfoProvider::new(program_info);
100 let mut info = calc_gas_info_inner(
101 program,
102 |statement_future_cost, idx, libfunc_id| -> Vec<CostTokenMap<Expr<Var>>> {
103 let libfunc = program_info
104 .registry
105 .get_libfunc(libfunc_id)
106 .expect("Program registry creation would have already failed.");
107 core_libfunc_cost_expr::core_libfunc_precost_expr(
108 statement_future_cost,
109 idx,
110 libfunc,
111 &cost_provider,
112 )
113 },
114 function_set_costs,
115 &program_info.registry,
116 )?;
117 for (i, statement) in program.statements.iter().enumerate() {
119 let Statement::Invocation(invocation) = statement else {
120 continue;
121 };
122 let Ok(libfunc) = program_info.registry.get_libfunc(&invocation.libfunc_id) else {
123 continue;
124 };
125 let is_withdraw_gas =
126 matches!(libfunc, CoreConcreteLibfunc::Gas(GasConcreteLibfunc::WithdrawGas(_)));
127 let is_refund =
128 matches!(libfunc, CoreConcreteLibfunc::Coupon(CouponConcreteLibfunc::Refund(_)));
129 if is_withdraw_gas || is_refund {
130 for token in CostTokenType::iter_precost() {
131 assert_eq!(info.variable_values.insert((StatementIdx(i), *token), 0), None);
133 }
134 }
135 }
136 Ok(info)
137}
138
139struct ComputeCostInfoProvider<'a> {
141 pub program_info: &'a ProgramRegistryInfo,
142}
143
144impl<'a> ComputeCostInfoProvider<'a> {
145 fn new(program_info: &'a ProgramRegistryInfo) -> Self {
146 Self { program_info }
147 }
148}
149
150impl CostInfoProvider for ComputeCostInfoProvider<'_> {
152 fn type_size(&self, ty: &ConcreteTypeId) -> usize {
153 self.program_info.type_sizes[ty].into_or_panic()
154 }
155
156 fn circuit_info(&self, ty: &ConcreteTypeId) -> &CircuitInfo {
157 let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(ConcreteCircuit {
158 circuit_info,
159 ..
160 })) = self.program_info.registry.get_type(ty).unwrap()
161 else {
162 panic!("Expected a circuit type, got {ty:?}.")
163 };
164 circuit_info
165 }
166}
167
168pub fn compute_precost_info(
170 program: &Program,
171 program_info: &ProgramRegistryInfo,
172) -> Result<GasInfo, CostError> {
173 let cost_provider = ComputeCostInfoProvider::new(program_info);
174 compute_costs::compute_costs(
175 program,
176 &(|libfunc_id| {
177 let core_libfunc = cost_provider
178 .program_info
179 .registry
180 .get_libfunc(libfunc_id)
181 .expect("Program registry creation would have already failed.");
182 core_libfunc_cost_base::core_libfunc_cost(core_libfunc, &cost_provider)
183 }),
184 &compute_costs::PreCostContext {},
185 &Default::default(),
186 )
187}
188
189pub fn calc_gas_postcost_info<ApChangeVarValue: Fn(StatementIdx) -> usize>(
194 program: &Program,
195 program_info: &ProgramRegistryInfo,
196 function_set_costs: OrderedHashMap<FunctionId, CostTokenMap<i32>>,
197 precost_gas_info: &GasInfo,
198 ap_change_var_value: ApChangeVarValue,
199) -> Result<GasInfo, CostError> {
200 let mut info = calc_gas_info_inner(
201 program,
202 |statement_future_cost, idx, libfunc_id| {
203 let libfunc = program_info
204 .registry
205 .get_libfunc(libfunc_id)
206 .expect("Program registry creation would have already failed.");
207 core_libfunc_cost_expr::core_libfunc_postcost_expr(
208 statement_future_cost,
209 idx,
210 libfunc,
211 &InvocationCostInfoProviderForEqGen {
212 type_sizes: &program_info.type_sizes,
213 token_usages: |token_type| {
214 precost_gas_info.variable_values[&(idx, token_type)].into_or_panic()
215 },
216 ap_change_var_value: || ap_change_var_value(idx),
217 },
218 )
219 },
220 function_set_costs,
221 &program_info.registry,
222 )?;
223 for (i, statement) in program.statements.iter().enumerate() {
225 let Statement::Invocation(invocation) = statement else {
226 continue;
227 };
228 let Ok(libfunc) = program_info.registry.get_libfunc(&invocation.libfunc_id) else {
229 continue;
230 };
231 let is_refund =
232 matches!(libfunc, CoreConcreteLibfunc::Coupon(CouponConcreteLibfunc::Refund(_)));
233 if is_refund {
234 assert_eq!(
236 info.variable_values.insert((StatementIdx(i), CostTokenType::Const), 0),
237 None
238 );
239 }
240 }
241 Ok(info)
242}
243
244fn calc_gas_info_inner<
246 GetCost: Fn(&mut dyn StatementFutureCost, StatementIdx, &ConcreteLibfuncId) -> Vec<CostExprMap>,
247>(
248 program: &Program,
249 get_cost: GetCost,
250 function_set_costs: OrderedHashMap<FunctionId, CostTokenMap<i32>>,
251 registry: &ProgramRegistry<CoreType, CoreLibfunc>,
252) -> Result<GasInfo, CostError> {
253 let mut equations = generate_equations::generate_equations(program, get_cost)?;
254 let non_set_cost_func_entry_points: UnorderedHashSet<_> = program
255 .funcs
256 .iter()
257 .filter(|f| !function_set_costs.contains_key(&f.id))
258 .map(|f| f.entry_point)
259 .collect();
260 for (func_id, cost_terms) in function_set_costs {
261 for token_type in CostTokenType::iter_casm_tokens() {
262 equations.get_mut(token_type).unwrap().push(
263 Expr::from_var(Var::StatementFuture(
264 registry.get_function(&func_id)?.entry_point,
265 *token_type,
266 )) - Expr::from_const(cost_terms.get(token_type).copied().unwrap_or_default()),
267 );
268 }
269 }
270
271 let mut variable_values = OrderedHashMap::default();
272 let mut function_costs = OrderedHashMap::default();
273 for (token_type, token_equations) in equations {
274 let mut minimization_vars = vec![vec![], vec![], vec![]];
290 for v in token_equations.iter().flat_map(|eq| eq.var_to_coef.keys()).unique() {
291 minimization_vars[match v {
292 Var::LibfuncImplicitGasVariable(idx, _) => {
293 match program.get_statement(*idx).unwrap() {
294 Statement::Invocation(invocation) => {
295 match registry.get_libfunc(&invocation.libfunc_id).unwrap() {
296 CoreConcreteLibfunc::BranchAlign(_) => 2,
297 CoreConcreteLibfunc::Gas(GasConcreteLibfunc::WithdrawGas(_)) => 1,
298 CoreConcreteLibfunc::Gas(
299 GasConcreteLibfunc::BuiltinWithdrawGas(_),
300 ) => 0,
301 CoreConcreteLibfunc::Gas(GasConcreteLibfunc::RedepositGas(_)) => {
303 continue;
304 }
305 _ => unreachable!(
306 "Gas variables cannot originate from {}.",
307 invocation.libfunc_id
308 ),
309 }
310 }
311 Statement::Return(_) => continue,
312 }
313 }
314 Var::StatementFuture(idx, _) if non_set_cost_func_entry_points.contains(idx) => 0,
315 Var::StatementFuture(_, _) => {
316 continue;
317 }
318 }]
319 .push(v.clone())
320 }
321 let solution =
322 cairo_lang_eq_solver::try_solve_equations(token_equations, minimization_vars)
323 .ok_or(CostError::SolvingGasEquationFailed)?;
324 for func in &program.funcs {
325 let id = &func.id;
326 if !function_costs.contains_key(id) {
327 function_costs.insert(id.clone(), CostTokenMap::default());
328 }
329 if let Some(value) = solution.get(&Var::StatementFuture(func.entry_point, token_type))
334 && *value != 0
335 {
336 function_costs.get_mut(id).unwrap().insert(token_type, *value);
337 }
338 }
339 for (var, value) in solution {
340 if let Var::LibfuncImplicitGasVariable(idx, var_token_type) = var {
341 assert_eq!(
342 token_type, var_token_type,
343 "Unexpected variable of type {var_token_type:?} while handling {token_type:?}."
344 );
345 variable_values.insert((idx, var_token_type), value);
346 }
347 }
348 }
349 Ok(GasInfo { variable_values, function_costs })
350}
351
352pub fn compute_postcost_info<CostType: PostCostTypeEx>(
357 program: &Program,
358 program_info: &ProgramRegistryInfo,
359 get_ap_change_fn: &impl Fn(StatementIdx) -> usize,
360 precost_gas_info: &GasInfo,
361 enforced_function_costs: &OrderedHashMap<FunctionId, CostType>,
362) -> Result<GasInfo, CostError> {
363 let cost_provider = ComputeCostInfoProvider::new(program_info);
364 let specific_cost_context =
365 compute_costs::PostcostContext { get_ap_change_fn, precost_gas_info };
366 compute_costs::compute_costs(
367 program,
368 &(|libfunc_id| {
369 let core_libfunc = cost_provider
370 .program_info
371 .registry
372 .get_libfunc(libfunc_id)
373 .expect("Program registry creation would have already failed.");
374 core_libfunc_cost_base::core_libfunc_cost(core_libfunc, &cost_provider)
375 }),
376 &specific_cost_context,
377 &enforced_function_costs
378 .iter()
379 .map(|(func, val)| {
380 (
381 cost_provider
382 .program_info
383 .registry
384 .get_function(func)
385 .expect("Program registry creation would have already failed.")
386 .entry_point,
387 *val,
388 )
389 })
390 .collect(),
391 )
392}