diff --git a/corelib/src/test/testing_test.cairo b/corelib/src/test/testing_test.cairo index 94dc1d37b55..bc8f6b55d9e 100644 --- a/corelib/src/test/testing_test.cairo +++ b/corelib/src/test/testing_test.cairo @@ -129,3 +129,21 @@ fn test_get_available_gas_with_gas_supply() { fn test_assert_eq_path_requiring_inference() { assert_eq!(Option::::None, Option::None); } + +#[inline(never)] +fn identity(t: T) -> T { + t +} + +#[test] +fn test_get_unspent_gas() { + let one = identity(1); + let two = identity(2); + let prev = crate::testing::get_unspent_gas(); + let _three = identity(one + two); + let after = crate::testing::get_unspent_gas(); + let expected_cost = 100 // `one + two`. + + 300 // `identity(...)`. + + 2300; // `get_unspent_gas()`. + assert_eq!(prev - after, expected_cost); +} diff --git a/corelib/src/testing.cairo b/corelib/src/testing.cairo index b23e96e3cb8..d2cfdb0eaa2 100644 --- a/corelib/src/testing.cairo +++ b/corelib/src/testing.cairo @@ -37,3 +37,26 @@ use crate::gas::GasBuiltin; /// } /// ``` pub extern fn get_available_gas() -> u128 implicits(GasBuiltin) nopanic; + +/// Returns the amount of gas available in the `GasBuiltin`, as well as the amount of gas unused in +/// the local wallet. +/// +/// Useful for asserting that a certain amount of gas used. +/// Note: This function call costs exactly `2300` gas, so this may be ignored in calculations. +/// # Examples +/// +/// ``` +/// use core::testing::get_unspent_gas; +/// +/// fn gas_heavy_function() { +/// // ... some gas-intensive code +/// } +/// +/// fn test_gas_consumption() { +/// let gas_before = get_unspent_gas(); +/// gas_heavy_function(); +/// let gas_after = get_unspent_gas(); +/// assert!(gas_after - gas_before < 100_000); +/// } +/// ``` +pub extern fn get_unspent_gas() -> u128 implicits(GasBuiltin) nopanic; diff --git a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs index 175ea1903a5..2d06edc6fbf 100644 --- a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs +++ b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs @@ -194,7 +194,12 @@ pub fn core_libfunc_ap_change( |token_type| info_provider.token_usages(token_type), ))] } - GasConcreteLibfunc::GetAvailableGas(_) => vec![ApChange::Known(0)], + GasConcreteLibfunc::GetAvailableGas(_) => { + vec![ApChange::Known(0)] + } + GasConcreteLibfunc::GetUnspentGas(_) => { + vec![ApChange::Known(BuiltinCostsType::cost_computation_steps(false, |_| 2) + 1)] + } GasConcreteLibfunc::BuiltinWithdrawGas(_) => { let cost_computation_ap_change: usize = BuiltinCostsType::cost_computation_steps(true, |token_type| { diff --git a/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs b/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs index 1e128140d91..af1fff62434 100644 --- a/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs +++ b/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs @@ -25,7 +25,7 @@ use cairo_lang_sierra::extensions::felt252_dict::{ }; use cairo_lang_sierra::extensions::function_call::SignatureAndFunctionConcreteLibfunc; use cairo_lang_sierra::extensions::gas::GasConcreteLibfunc::{ - BuiltinWithdrawGas, GetAvailableGas, GetBuiltinCosts, RedepositGas, WithdrawGas, + BuiltinWithdrawGas, GetAvailableGas, GetBuiltinCosts, GetUnspentGas, RedepositGas, WithdrawGas, }; use cairo_lang_sierra::extensions::gas::{BuiltinCostsType, CostTokenType}; use cairo_lang_sierra::extensions::int::signed::{SintConcrete, SintTraits}; @@ -244,6 +244,13 @@ pub fn core_libfunc_cost( ], RedepositGas(_) => vec![BranchCost::RedepositGas], GetAvailableGas(_) => vec![ConstCost::default().into()], + GetUnspentGas(_) => vec![ + ConstCost::steps( + BuiltinCostsType::cost_computation_steps(false, |_| 2).into_or_panic::() + + 1, + ) + .into(), + ], BuiltinWithdrawGas(_) => { vec![ BranchCost::WithdrawGas(WithdrawGasBranchInfo { diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/gas.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/gas.rs index 2c24375491e..5649c8fe806 100644 --- a/crates/cairo-lang-sierra-to-casm/src/invocations/gas.rs +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/gas.rs @@ -25,6 +25,7 @@ pub fn build( GasConcreteLibfunc::WithdrawGas(_) => build_withdraw_gas(builder), GasConcreteLibfunc::RedepositGas(_) => build_redeposit_gas(builder), GasConcreteLibfunc::GetAvailableGas(_) => misc::build_dup(builder), + GasConcreteLibfunc::GetUnspentGas(_) => build_get_unspent_gas(builder), GasConcreteLibfunc::BuiltinWithdrawGas(_) => build_builtin_withdraw_gas(builder), GasConcreteLibfunc::GetBuiltinCosts(_) => build_get_builtin_costs(builder), } @@ -139,6 +140,48 @@ fn build_redeposit_gas( )) } +/// Handles the get_unspent_gas invocation. +fn build_get_unspent_gas( + builder: CompiledInvocationBuilder<'_>, +) -> Result { + let [gas_counter] = builder.try_get_single_cells()?; + let mut casm_builder = CasmBuilder::default(); + add_input_variables! {casm_builder, + deref gas_counter; + }; + let (pre_instructions, cost_builtin_ptr) = add_cost_builtin_ptr_fetch_code(&mut casm_builder); + + let get_token_count = |token: CostTokenType| { + if let GasWallet::Value(wallet) = &builder.environment.gas_wallet { + wallet.get(&token).copied().unwrap_or_default() + } else { + 0 + } + }; + casm_build_extend! {casm_builder, + tempvar builtin_cost = cost_builtin_ptr; + const const_count = get_token_count(CostTokenType::Const); + tempvar total_unspent = gas_counter + const_count; + }; + let mut total_unspent = total_unspent; + for token_type in CostTokenType::iter_precost() { + let index = token_type.offset_in_builtin_costs(); + casm_build_extend! {casm_builder, + tempvar single_cost = builtin_cost[index]; + const count = get_token_count(*token_type); + tempvar multi_cost = single_cost * count; + tempvar updated_total_unspent = total_unspent + multi_cost; + }; + total_unspent = updated_total_unspent; + } + Ok(builder.build_from_casm_builder_ex( + casm_builder, + [("Fallthrough", &[&[gas_counter], &[total_unspent]], None)], + Default::default(), + pre_instructions, + )) +} + /// Handles the withdraw_gas invocation with the builtin costs argument. fn build_builtin_withdraw_gas( builder: CompiledInvocationBuilder<'_>, diff --git a/crates/cairo-lang-sierra/src/extensions/modules/gas.rs b/crates/cairo-lang-sierra/src/extensions/modules/gas.rs index 994df216a02..256128cc48b 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/gas.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/gas.rs @@ -32,6 +32,7 @@ define_libfunc_hierarchy! { WithdrawGas(WithdrawGasLibfunc), RedepositGas(RedepositGasLibfunc), GetAvailableGas(GetAvailableGasLibfunc), + GetUnspentGas(GetUnspentGasLibfunc), BuiltinWithdrawGas(BuiltinCostWithdrawGasLibfunc), GetBuiltinCosts(BuiltinCostGetBuiltinCostsLibfunc), }, GasConcreteLibfunc @@ -128,6 +129,34 @@ impl NoGenericArgsGenericLibfunc for GetAvailableGasLibfunc { } } +/// Libfunc for returning the amount of available gas, including gas in local wallet. +#[derive(Default)] +pub struct GetUnspentGasLibfunc {} +impl NoGenericArgsGenericLibfunc for GetUnspentGasLibfunc { + const STR_ID: &'static str = "get_unspent_gas"; + + fn specialize_signature( + &self, + context: &dyn SignatureSpecializationContext, + ) -> Result { + let gas_builtin_type = context.get_concrete_type(GasBuiltinType::id(), &[])?; + Ok(LibfuncSignature::new_non_branch( + vec![gas_builtin_type.clone()], + vec![ + OutputVarInfo { + ty: gas_builtin_type, + ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 0 }, + }, + OutputVarInfo { + ty: context.get_concrete_type(Uint128Type::id(), &[])?, + ref_info: OutputVarReferenceInfo::SimpleDerefs, + }, + ], + SierraApChange::Known { new_vars_only: false }, + )) + } +} + /// Represents different type of costs. /// Note that if you add a type here you should update 'iter_precost' #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/cairo-lang-sierra/src/simulation/core.rs b/crates/cairo-lang-sierra/src/simulation/core.rs index a6f7c56858a..75f3395832a 100644 --- a/crates/cairo-lang-sierra/src/simulation/core.rs +++ b/crates/cairo-lang-sierra/src/simulation/core.rs @@ -117,7 +117,9 @@ pub fn simulate< (vec![CoreValue::GasBuiltin(gas_counter), CoreValue::Uint128(gas_counter as u128)], 0) } CoreConcreteLibfunc::Gas( - GasConcreteLibfunc::BuiltinWithdrawGas(_) | GasConcreteLibfunc::GetBuiltinCosts(_), + GasConcreteLibfunc::BuiltinWithdrawGas(_) + | GasConcreteLibfunc::GetBuiltinCosts(_) + | GasConcreteLibfunc::GetUnspentGas(_), ) => { unimplemented!("Simulation of the builtin cost functionality is not implemented yet.") } diff --git a/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_test.rs b/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_test.rs index c773346e49a..aa477aa2fcf 100644 --- a/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_test.rs +++ b/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_test.rs @@ -10,7 +10,7 @@ use super::{ #[test] fn all_list_includes_all_supported() { - let blocked_libfuncs = ["print", "cheatcode", "get_available_gas"]; + let blocked_libfuncs = ["print", "cheatcode", "get_available_gas", "get_unspent_gas"]; pretty_assertions::assert_eq!( lookup_allowed_libfuncs_list(ListSelector::ListName(BUILTIN_ALL_LIBFUNCS_LIST.to_string())) .unwrap()