From dfe3a5aca78ec16bc1cd0ee178e1e9bd84b92d9c Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 30 May 2024 16:41:43 -0300 Subject: [PATCH 01/11] Add `EXCESS_BALANCE` hint (#1777) * WIP * Add MarginParams * Impl imf * Start excess_balance fn * Add TODO comment * refactor + handle errors * Remove unused error * Finish implementing hint * Handle error * Fix var names * Setup hint test * Fix test * Fix test * Fix test * Fix test values * Fix * Update * Fix * Use constants * Remove unwrap * Remove allow * Remove unused feature * Add failure test case * Fix test values * Add no-std imports * Add no-std imports * Add hint code * Add changelog entry * Use checked arithmetic operations --- CHANGELOG.md | 2 + Cargo.lock | 11 + vm/Cargo.toml | 1 + .../builtin_hint_processor_definition.rs | 8 + .../builtin_hint_processor/dict_manager.rs | 11 + .../builtin_hint_processor/excess_balance.rs | 1225 +++++++++++++++++ .../builtin_hint_processor/hint_code.rs | 9 + .../builtin_hint_processor/mod.rs | 1 + vm/src/vm/errors/hint_errors.rs | 6 + 9 files changed, 1274 insertions(+) create mode 100644 vm/src/hint_processor/builtin_hint_processor/excess_balance.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index de8cf7b9d0..35b7c23019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Add `EXCESS_BALANCE` hint [#1777](https://github.com/lambdaclass/cairo-vm/pull/1777) + #### [0.9.2] - 2024-01-3 * Change ec_op_impl() to use ProjectivePoint [#1534](https://github.com/lambdaclass/cairo-vm/pull/1534) diff --git a/Cargo.lock b/Cargo.lock index e213786e36..66d942feb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,7 @@ dependencies = [ "proptest", "rand", "rstest", + "rust_decimal", "serde", "serde_json", "sha2", @@ -2208,6 +2209,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "num-traits 0.2.17", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index a101ab257a..64065503ea 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -58,6 +58,7 @@ keccak = { workspace = true } hashbrown = { workspace = true } anyhow = { workspace = true } thiserror-no-std = { workspace = true } +rust_decimal = { version = "1.35.0", default-features = false } # only for std num-prime = { version = "0.4.3", features = ["big-int"], optional = true } diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index 1c3c5f4b40..f01ce2bd5e 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -4,6 +4,7 @@ use super::{ ec_recover_divmod_n_packed, ec_recover_product_div_m, ec_recover_product_mod, ec_recover_sub_a_b, }, + excess_balance::excess_balance_hint, field_arithmetic::{u256_get_square_root, u384_get_square_root, uint384_div}, secp::{ ec_utils::{ @@ -815,6 +816,13 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::SPLIT_XX => split_xx(vm, &hint_data.ids_data, &hint_data.ap_tracking), #[cfg(feature = "skip_next_instruction_hint")] hint_code::SKIP_NEXT_INSTRUCTION => skip_next_instruction(vm), + hint_code::EXCESS_BALANCE => excess_balance_hint( + vm, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + exec_scopes, + ), code => Err(HintError::UnknownHint(code.to_string().into_boxed_str())), } } diff --git a/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs b/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs index 29d73a4a32..dd4ed198d2 100644 --- a/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs +++ b/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs @@ -191,6 +191,17 @@ impl DictTracker { } } + //Returns a reference to the contained dictionary, losing the dictionary type in the process + pub fn get_dictionary_ref(&self) -> &HashMap { + match &self.data { + Dictionary::SimpleDictionary(dict) => dict, + Dictionary::DefaultDictionary { + dict, + default_value: _, + } => dict, + } + } + pub fn get_value(&mut self, key: &MaybeRelocatable) -> Result<&MaybeRelocatable, HintError> { self.data .get(key) diff --git a/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs b/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs new file mode 100644 index 0000000000..c6eb5c4d64 --- /dev/null +++ b/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs @@ -0,0 +1,1225 @@ +use crate::{ + hint_processor::hint_processor_definition::HintReference, + serde::deserialize_program::ApTracking, + stdlib::collections::HashMap, + types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable}, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, +}; +use core::str::FromStr; + +use crate::felt::Felt252; +use num_bigint::{BigInt, BigUint}; +use rust_decimal::Decimal; + +use crate::{ + math_utils::isqrt, + stdlib::prelude::{String, ToString, Vec}, + types::relocatable::Relocatable, + vm::vm_memory::memory::Memory, +}; +use lazy_static::lazy_static; + +use super::{ + dict_manager::DictManager, + hint_utils::{ + get_constant_from_var_name, get_integer_from_var_name, get_ptr_from_var_name, + insert_value_from_var_name, + }, +}; + +// General helper functions + +lazy_static! { + static ref DECIMAL_ADJUSTMENT_POSITIVE: Decimal = Decimal::from_scientific("1e8").unwrap(); + static ref DECIMAL_ADJUSTMENT: Decimal = Decimal::from_scientific("1e-8").unwrap(); + static ref DECIMAL_ADJUSTMENT_HALVED: Decimal = Decimal::from_scientific("1e-4").unwrap(); +} + +fn felt_to_scaled_decimal(f: &Felt252) -> Option { + Some(Decimal::from_str_radix(&f.to_signed_felt().to_string(), 10).ok()? * *DECIMAL_ADJUSTMENT) +} + +fn felt_to_trimmed_str(f: &Felt252) -> Option { + Some( + core::str::from_utf8(&f.to_bytes_be()) + .ok()? + .trim_start_matches('\0') + .to_string(), + ) +} + +// Internal Data types + +#[derive(Debug, PartialEq, Eq, Hash)] +struct Position { + market: String, + amount: Decimal, + cost: Decimal, + cached_funding: Decimal, +} + +#[derive(Debug, PartialEq)] +struct MarginParams { + market: String, + imf_base: Decimal, + imf_factor: Decimal, + mmf_factor: Decimal, + imf_shift: Decimal, +} + +impl Position { + fn read_from_memory(memory: &Memory, read_ptr: Relocatable) -> Option { + Some(Position { + market: felt_to_trimmed_str(memory.get_integer(read_ptr).ok()?.as_ref())?, + amount: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 1_i32).ok()?).ok()?.as_ref(), + )?, + cost: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 2_i32).ok()?).ok()?.as_ref(), + )?, + cached_funding: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 3_i32).ok()?).ok()?.as_ref(), + )?, + }) + } +} + +impl MarginParams { + fn read_from_memory(memory: &Memory, read_ptr: Relocatable) -> Option { + Some(MarginParams { + market: felt_to_trimmed_str(memory.get_integer(read_ptr).ok()?.as_ref())?, + imf_base: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 4_i32).ok()?).ok()?.as_ref(), + )?, + imf_factor: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 5_i32).ok()?).ok()?.as_ref(), + )?, + mmf_factor: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 6_i32).ok()?).ok()?.as_ref(), + )?, + imf_shift: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 7_i32).ok()?).ok()?.as_ref(), + )?, + }) + } + + fn imf(&self, abs_value: Decimal) -> Option { + let diff = abs_value + .checked_sub(self.imf_shift)? + .checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?; + let max = BigUint::from_str(&Decimal::ZERO.max(diff.trunc()).to_string()).ok()?; + let part_sqrt = isqrt(&max).ok()?; + let part_sqrt = Decimal::from_str(&part_sqrt.to_string()) + .ok()? + .checked_mul(*DECIMAL_ADJUSTMENT_HALVED)?; + Some(self.imf_base.max(self.imf_factor.checked_mul(part_sqrt)?)) + } + + fn mmf(&self, abs_value: Decimal) -> Option { + self.mmf_factor.checked_mul(self.imf(abs_value)?) + } +} + +// Excess Balance helpers + +fn dict_ref_from_var_name<'a>( + var_name: &'a str, + vm: &'a VirtualMachine, + dict_manager: &'a DictManager, + ids_data: &'a HashMap, + ap_tracking: &'a ApTracking, +) -> Option<&'a HashMap> { + let prices_cache_ptr = get_ptr_from_var_name(var_name, vm, ids_data, ap_tracking).ok()?; + Some( + dict_manager + .get_tracker(prices_cache_ptr) + .ok()? + .get_dictionary_ref(), + ) +} + +fn prices_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let prices = + dict_ref_from_var_name("prices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, Decimal)> { + Some(( + felt_to_trimmed_str(k.get_int_ref()?)?, + felt_to_scaled_decimal(v.get_int_ref()?)?, + )) + }; + + prices + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn indices_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let indices = + dict_ref_from_var_name("indices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, Decimal)> { + Some(( + felt_to_trimmed_str(k.get_int_ref()?)?, + felt_to_scaled_decimal(v.get_int_ref()?)?, + )) + }; + + indices + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn perps_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let perps = dict_ref_from_var_name("perps_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, MarginParams)> { + Some(( + felt_to_trimmed_str(k.get_int_ref()?)?, + MarginParams::read_from_memory(&vm.segments.memory, v.get_relocatable()?)?, + )) + }; + + perps + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn fees_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let fees = dict_ref_from_var_name("fees_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(Felt252, Decimal)> { + Some(( + k.get_int_ref()?.clone(), + felt_to_scaled_decimal(v.get_int_ref()?)?, + )) + }; + + fees.iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn balances_list( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let balances = dict_ref_from_var_name( + "perps_balances_cache_ptr", + vm, + dict_manager, + ids_data, + ap_tracking, + )?; + + // Apply data type conversions + let apply_conversion = |_, v: &MaybeRelocatable| -> Option { + Position::read_from_memory(&vm.segments.memory, v.get_relocatable()?) + }; + + balances + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +pub fn excess_balance_hint( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + constants: &HashMap, + exec_scopes: &ExecutionScopes, +) -> Result<(), HintError> { + // Fetch constants & variables + let margin_check_type = + get_integer_from_var_name("margin_check_type", vm, ids_data, ap_tracking)?; + let margin_check_initial = get_constant_from_var_name("MARGIN_CHECK_INITIAL", constants)?; + let token_assets_value_d = + get_integer_from_var_name("token_assets_value_d", vm, ids_data, ap_tracking)?; + let account = get_integer_from_var_name("account", vm, ids_data, ap_tracking)?; + // Fetch DictManager + let dict_manager_rc = exec_scopes.get_dict_manager()?; + let dict_manager = dict_manager_rc.borrow(); + // Fetch dictionaries + let prices = prices_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("prices".into()))?; + let indices = indices_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("indices".into()))?; + let perps = perps_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("perps".into()))?; + let fees = fees_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("fees".into()))?; + let balances = balances_list(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("balances".into()))?; + + // Fetch settelement price + let settlement_asset = String::from("USDC-USD"); + let settlement_price = prices + .get(&settlement_asset) + .ok_or_else(|| HintError::ExcessBalanceKeyError("prices".into()))?; + + let mut unrealized_pnl = Decimal::ZERO; + let mut unrealized_funding_pnl = Decimal::ZERO; + let mut abs_balance_value = Decimal::ZERO; + let mut position_margin = Decimal::ZERO; + + for position in balances { + if position.market == settlement_asset { + continue; + } + + let price = prices + .get(&position.market) + .ok_or_else(|| HintError::ExcessBalanceKeyError("prices".into()))?; + let funding_index = indices + .get(&position.market) + .ok_or_else(|| HintError::ExcessBalanceKeyError("indices".into()))?; + let position_value = position + .amount + .checked_mul(*price) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("position_value".into()))?; + let position_value_abs = position_value.abs(); + + abs_balance_value = abs_balance_value + .checked_add(position_value_abs) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("abs_balance_value".into()))?; + + let market_perps = perps + .get(&position.market) + .ok_or_else(|| HintError::ExcessBalanceKeyError("perps".into()))?; + let margin_fraction = if margin_check_type.as_ref() == margin_check_initial { + market_perps.imf(position_value_abs) + } else { + market_perps.mmf(position_value_abs) + } + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_fraction".into()))?; + // position_margin += margin_fraction * position_value_abs + position_margin = margin_fraction + .checked_mul(position_value_abs) + .and_then(|mul| position_margin.checked_add(mul)) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("position_margin".into()))?; + // unrealized_pnl += position_value - position.cost * settlement_price + let calc_unrealized_pnl = |unrealized_pnl: Decimal, + position: &Position, + settlement_price: Decimal| + -> Option { + unrealized_pnl.checked_add( + position_value.checked_sub(position.cost.checked_mul(settlement_price)?)?, + ) + }; + unrealized_pnl = calc_unrealized_pnl(unrealized_pnl, &position, *settlement_price) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl".into()))?; + // unrealized_funding_pnl += (position.cached_funding - funding_index) * position.amount*settlement_price + let calc_unrealized_funding_pnl = |unrealized_funding_pnl: Decimal, + position: &Position, + funding_index: Decimal, + settlement_price: Decimal| + -> Option { + unrealized_funding_pnl.checked_add( + position + .cached_funding + .checked_sub(funding_index)? + .checked_mul(position.amount)? + .checked_mul(settlement_price)?, + ) + }; + unrealized_funding_pnl = calc_unrealized_funding_pnl( + unrealized_funding_pnl, + &position, + *funding_index, + *settlement_price, + ) + .ok_or_else(|| { + HintError::ExcessBalanceCalculationFailed("unrealized_funding_pnl".into()) + })?; + } + + // Calculate final results + let token_assets_value_d = felt_to_scaled_decimal(&token_assets_value_d) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; + let account_value = unrealized_pnl + .checked_add(unrealized_funding_pnl) + .and_then(|sum| sum.checked_add(token_assets_value_d)) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; + let fee_provision = fees + .get(&account) + .and_then(|fee| abs_balance_value.checked_mul(*fee)) + .ok_or_else(|| HintError::ExcessBalanceKeyError("fees".into()))?; + let margin_requirement = position_margin + .checked_add(fee_provision) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirements".into()))?; + let excess_balance = account_value + .checked_sub(margin_requirement) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?; + + // Convert final results to Felt + let felt_from_decimal = |d: Decimal| -> Option { + Some(Felt252::from( + BigInt::from_str( + &(d.checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?) + .trunc() + .to_string(), + ) + .ok()?, + )) + }; + + let account_value = felt_from_decimal(account_value) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; + let excess_balance = felt_from_decimal(excess_balance) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?; + let margin_requirement = felt_from_decimal(margin_requirement) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirement_d".into()))?; + let unrealized_pnl = felt_from_decimal(unrealized_pnl) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl_d".into()))?; + + // Write results into memory + insert_value_from_var_name( + "check_account_value", + account_value, + vm, + ids_data, + ap_tracking, + )?; + insert_value_from_var_name( + "check_excess_balance", + excess_balance, + vm, + ids_data, + ap_tracking, + )?; + insert_value_from_var_name( + "check_margin_requirement_d", + margin_requirement, + vm, + ids_data, + ap_tracking, + )?; + insert_value_from_var_name( + "check_unrealized_pnl_d", + unrealized_pnl, + vm, + ids_data, + ap_tracking, + ) +} + +#[cfg(test)] +mod tests { + use num_traits::{One, Zero}; + + use crate::stdlib::{cell::RefCell, rc::Rc}; + use core::str::FromStr; + + use super::*; + use crate::felt::felt_str; + use crate::utils::test_utils::*; + + #[test] + fn test_read_position() { + let memory = memory![ + ((0, 0), ("5176525270854594879110454268496", 10)), + ((0, 1), 1000000000), + ((0, 2), 20000), + ((0, 3), 0) + ]; + let expected_position = Position { + market: String::from("AVAX-USD-PERP"), + amount: Decimal::from_str("10.00000000").unwrap(), + cost: Decimal::from_str("0.00020000").unwrap(), + cached_funding: Decimal::from_scientific("0e-8").unwrap(), + }; + assert_eq!( + expected_position, + Position::read_from_memory(&memory, (0, 0).into()).unwrap() + ) + } + + #[test] + fn test_read_margin_params() { + let memory = memory![ + ((0, 0), ("20527877651862571847371805264", 10)), + ((0, 4), 5000000), + ((0, 5), 20000), + ((0, 6), 50000000), + ((0, 7), 20000000000000) + ]; + let expected_position = MarginParams { + market: String::from("BTC-USD-PERP"), + imf_base: Decimal::from_str("0.05000000").unwrap(), + imf_factor: Decimal::from_str("0.00020000").unwrap(), + mmf_factor: Decimal::from_str("0.50000000").unwrap(), + imf_shift: Decimal::from_str("200000.00000000").unwrap(), + }; + assert_eq!( + expected_position, + MarginParams::read_from_memory(&memory, (0, 0).into()).unwrap() + ) + } + + #[test] + fn test_imf() { + let abs_value = Decimal::from_str("459000.0000000000000000").unwrap(); + let margin_params = MarginParams { + market: String::from("BTC-USD-PERP"), + imf_base: Decimal::from_str("0.05000000").unwrap(), + imf_factor: Decimal::from_str("0.00020000").unwrap(), + mmf_factor: Decimal::from_str("0.50000000").unwrap(), + imf_shift: Decimal::from_str("200000.00000000").unwrap(), + }; + let expected_res = Decimal::from_str("0.101784080000").unwrap(); + assert_eq!(expected_res, margin_params.imf(abs_value).unwrap()); + } + + #[test] + fn run_excess_balance_hint_succesful_trade() { + // TEST DATA + + // INPUT VALUES + // ids.margin_check_type 1 + // ids.MARGIN_CHECK_INITIAL 1 + // ids.token_assets_value_d 1005149999998000 + // ids.account 200 + // DICTIONARIES + // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000} + // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0} + // perps {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=3092), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=3467), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=3842), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=4217), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=4592)} + // fees {100: 10000, 200: 10000} + // balances {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=6406), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=6625), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=6844), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=7063), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=18230)} + // MEMORY VALUES REFERENCED BY DICTIONARIES + // 1:3092 6044027408028715819619898970704 + // 1:3096 5000000 + // 1:3097 20000 + // 1:3098 50000000 + // 1:3099 20000000000000 + // 1:3467 25783120691025710696626475600 + // 1:3471 5000000 + // 1:3472 20000 + // 1:3473 50000000 + // 1:3474 20000000000000 + // 1:3842 5176525270854594879110454268496 + // 1:3846 5000000 + // 1:3847 20000 + // 1:3848 50000000 + // 1:3849 20000000000000 + // 1:4217 21456356293159021401772216912 + // 1:4221 5000000 + // 1:4222 20000 + // 1:4223 50000000 + // 1:4224 20000000000000 + // 1:4592 20527877651862571847371805264 + // 1:4596 5000000 + // 1:4597 20000 + // 1:4598 50000000 + // 1:4599 20000000000000 + // 1:6406 6044027408028715819619898970704 + // 1:6407 1000000000 + // 1:6408 20000 + // 1:6409 0 + // 1:6406 6044027408028715819619898970704 + // 1:6407 1000000000 + // 1:6408 20000 + // 1:6409 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 1000000000 + // 1:6627 20000 + // 1:6628 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 1000000000 + // 1:6627 20000 + // 1:6628 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 1000000000 + // 1:6846 20000 + // 1:6847 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 1000000000 + // 1:6846 20000 + // 1:6847 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 1000000000 + // 1:7065 20000 + // 1:7066 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 1000000000 + // 1:7065 20000 + // 1:7066 0 + // 1:18582 20527877651862571847371805264 + // 1:18583 900000000 + // 1:18584 18000 + // 1:18585 0 + // 1:18582 20527877651862571847371805264 + // 1:18583 900000000 + // 1:18584 18000 + // 1:18585 0 + // EXPECTED RESULTS + // ids.check_account_value 1255049999900000 + // ids.check_excess_balance 1227636643508000 + // ids.check_margin_requirement_d 27413356392000 + // ids.check_unrealized_pnl_d 249899999902000 + + // SETUP + let mut vm = vm!(); + // CONSTANTS + let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::one())]); + // IDS + vm.segments = segments!( + ((1, 0), 1), // ids.margin_check_type + ((1, 1), 1005149999998000), // ids.token_assets_value_d + ((1, 2), 200), // ids.account + ((1, 3), (2, 0)), // ids.prices_cache_ptr + ((1, 4), (3, 0)), // ids.indices_cache_ptr + ((1, 5), (4, 0)), // ids.perps_cache_ptr + ((1, 6), (5, 0)), // ids.fees_cache_ptr + ((1, 7), (6, 0)), // ids.perps_balances_cache_ptr + //((1, 8), ids.check_account_value) + //((1, 9), ids.check_excess_balance) + //((1, 10), ids.check_margin_requirement_d) + //((1, 11), ids.check_unrealized_pnl_d) + // Memory values referenced by hints + ((1, 3092), 6044027408028715819619898970704), + ((1, 3096), 5000000), + ((1, 3097), 20000), + ((1, 3098), 50000000), + ((1, 3099), 20000000000000), + ((1, 3467), 25783120691025710696626475600), + ((1, 3471), 5000000), + ((1, 3472), 20000), + ((1, 3473), 50000000), + ((1, 3474), 20000000000000), + ((1, 3842), 5176525270854594879110454268496), + ((1, 3846), 5000000), + ((1, 3847), 20000), + ((1, 3848), 50000000), + ((1, 3849), 20000000000000), + ((1, 4217), 21456356293159021401772216912), + ((1, 4221), 5000000), + ((1, 4222), 20000), + ((1, 4223), 50000000), + ((1, 4224), 20000000000000), + ((1, 4592), 20527877651862571847371805264), + ((1, 4596), 5000000), + ((1, 4597), 20000), + ((1, 4598), 50000000), + ((1, 4599), 20000000000000), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 1000000000), + ((1, 6408), 20000), + ((1, 6409), 0), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 1000000000), + ((1, 6408), 20000), + ((1, 6409), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 1000000000), + ((1, 6627), 20000), + ((1, 6628), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 1000000000), + ((1, 6627), 20000), + ((1, 6628), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 1000000000), + ((1, 6846), 20000), + ((1, 6847), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 1000000000), + ((1, 6846), 20000), + ((1, 6847), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 1000000000), + ((1, 7065), 20000), + ((1, 7066), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 1000000000), + ((1, 7065), 20000), + ((1, 7066), 0), + ((1, 18582), 20527877651862571847371805264), + ((1, 18583), 900000000), + ((1, 18584), 18000), + ((1, 18585), 0), + ((1, 18582), 20527877651862571847371805264), + ((1, 18583), 900000000), + ((1, 18584), 18000), + ((1, 18585), 0) + ); + vm.run_context.set_fp(12); + let ids = ids_data![ + "margin_check_type", + "token_assets_value_d", + "account", + "prices_cache_ptr", + "indices_cache_ptr", + "perps_cache_ptr", + "fees_cache_ptr", + "perps_balances_cache_ptr", + "check_account_value", + "check_excess_balance", + "check_margin_requirement_d", + "check_unrealized_pnl_d" + ]; + // DICTIONARIES + let mut exec_scopes = ExecutionScopes::new(); + let mut dict_manager = DictManager::new(); + // ids.prices_cache_ptr = (2, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("6148332971604923204").into(), + felt_str!("100000000").into(), + ), + ]), + ) + .unwrap(); + // ids.indices_cache_ptr = (3, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + Felt252::zero().into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + Felt252::zero().into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + Felt252::zero().into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + Felt252::zero().into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + Felt252::zero().into(), + ), + ]), + ) + .unwrap(); + // ids.perps_cache_ptr = (4, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 3092).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 3467).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 3842).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 4217).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 4592).into(), + ), + ]), + ) + .unwrap(); + // ids.fees_cache_ptr = (5, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + (Felt252::from(100).into(), Felt252::from(10000).into()), + (Felt252::from(200).into(), Felt252::from(10000).into()), + ]), + ) + .unwrap(); + // ids.perps_balances_cache_ptr = (6, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 6406).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 6625).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 6844).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 7063).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 18582).into(), + ), + ]), + ) + .unwrap(); + exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager))); + + // EXECUTION + assert!(excess_balance_hint( + &mut vm, + &ids, + &ApTracking::default(), + &constants, + &exec_scopes + ) + .is_ok()); + + // CHECK MEMORY VALUES + check_memory![ + vm.segments.memory, + // ids.check_account_value + ((1, 8), 1255049999900000), + // ids.check_excess_balance + ((1, 9), 1227636643508000), + // ids.check_margin_requirement_d + ((1, 10), 27413356392000), + // ids.check_unrealized_pnl_d + ((1, 11), 249899999902000) + ]; + } + + #[test] + fn run_excess_balance_hint_trade_failure() { + // TEST DATA + + // INPUT VALUES + // ids.margin_check_type 1 + // ids.MARGIN_CHECK_INITIAL 1 + // ids.token_assets_value_d 0 + // ids.account 100 + // DICTIONARIES + // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000} + // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0} + // perps {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=3092), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=3467), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=3842), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=4217), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=4592)} + // fees {100: 10000, 200: 10000} + // balances {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=6406), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=6625), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=6844), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=7063), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=18230)} + // MEMORY VALUES REFERENCED BY DICTIONARIES + // 1:3092 6044027408028715819619898970704 + // 1:3096 5000000 + // 1:3097 20000 + // 1:3098 50000000 + // 1:3099 20000000000000 + // 1:3467 25783120691025710696626475600 + // 1:3471 5000000 + // 1:3472 20000 + // 1:3473 50000000 + // 1:3474 20000000000000 + // 1:3842 5176525270854594879110454268496 + // 1:3846 5000000 + // 1:3847 20000 + // 1:3848 50000000 + // 1:3849 20000000000000 + // 1:4217 21456356293159021401772216912 + // 1:4221 5000000 + // 1:4222 20000 + // 1:4223 50000000 + // 1:4224 20000000000000 + // 1:4592 20527877651862571847371805264 + // 1:4596 5000000 + // 1:4597 20000 + // 1:4598 50000000 + // 1:4599 20000000000000 + // 1:6406 6044027408028715819619898970704 + // 1:6407 0 + // 1:6408 0 + // 1:6409 0 + // 1:6406 6044027408028715819619898970704 + // 1:6407 0 + // 1:6408 0 + // 1:6409 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 0 + // 1:6627 0 + // 1:6628 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 0 + // 1:6627 0 + // 1:6628 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 0 + // 1:6846 0 + // 1:6847 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 0 + // 1:6846 0 + // 1:6847 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 0 + // 1:7065 0 + // 1:7066 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 0 + // 1:7065 0 + // 1:7066 0 + // 1:18230 20527877651862571847371805264 + // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481 + // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481 + // 1:18233 0 + // 1:18230 20527877651862571847371805264 + // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481 + // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481 + // 1:18233 0 + // EXPECTED RESULTS + // ids.check_account_value 50000000000 + // ids.check_excess_balance 3618502788666131213697322783095070105623107215331596699973092055930362020481 + // ids.check_margin_requirement_d 255510000000 + // ids.check_unrealized_pnl_d 50000000000 + + // SETUP + let mut vm = vm!(); + // CONSTANTS + let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::one())]); + // IDS + vm.segments = segments!( + ((1, 0), 1), // ids.margin_check_type + ((1, 1), 0), // ids.token_assets_value_d + ((1, 2), 100), // ids.account + ((1, 3), (2, 0)), // ids.prices_cache_ptr + ((1, 4), (3, 0)), // ids.indices_cache_ptr + ((1, 5), (4, 0)), // ids.perps_cache_ptr + ((1, 6), (5, 0)), // ids.fees_cache_ptr + ((1, 7), (6, 0)), // ids.perps_balances_cache_ptr + //((1, 8), ids.check_account_value) + //((1, 9), ids.check_excess_balance) + //((1, 10), ids.check_margin_requirement_d) + //((1, 11), ids.check_unrealized_pnl_d) + // Memory values referenced by hints + ((1, 3092), 6044027408028715819619898970704), + ((1, 3096), 5000000), + ((1, 3097), 20000), + ((1, 3098), 50000000), + ((1, 3099), 20000000000000), + ((1, 3467), 25783120691025710696626475600), + ((1, 3471), 5000000), + ((1, 3472), 20000), + ((1, 3473), 50000000), + ((1, 3474), 20000000000000), + ((1, 3842), 5176525270854594879110454268496), + ((1, 3846), 5000000), + ((1, 3847), 20000), + ((1, 3848), 50000000), + ((1, 3849), 20000000000000), + ((1, 4217), 21456356293159021401772216912), + ((1, 4221), 5000000), + ((1, 4222), 20000), + ((1, 4223), 50000000), + ((1, 4224), 20000000000000), + ((1, 4592), 20527877651862571847371805264), + ((1, 4596), 5000000), + ((1, 4597), 20000), + ((1, 4598), 50000000), + ((1, 4599), 20000000000000), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 0), + ((1, 6408), 0), + ((1, 6409), 0), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 0), + ((1, 6408), 0), + ((1, 6409), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 0), + ((1, 6627), 0), + ((1, 6628), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 0), + ((1, 6627), 0), + ((1, 6628), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 0), + ((1, 6846), 0), + ((1, 6847), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 0), + ((1, 6846), 0), + ((1, 6847), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 0), + ((1, 7065), 0), + ((1, 7066), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 0), + ((1, 7065), 0), + ((1, 7066), 0), + ((1, 18230), 20527877651862571847371805264), + ( + (1, 18231), + ( + "3618502788666131213697322783095070105623107215331596699973092056135772020481", + 10 + ) + ), + ( + (1, 18232), + ( + "3618502788666131213697322783095070105623107215331596699973092050985872020481", + 10 + ) + ), + ((1, 18233), 0), + ((1, 18230), 20527877651862571847371805264), + ( + (1, 18231), + ( + "3618502788666131213697322783095070105623107215331596699973092056135772020481", + 10 + ) + ), + ( + (1, 18232), + ( + "3618502788666131213697322783095070105623107215331596699973092050985872020481", + 10 + ) + ), + ((1, 18233), 0), + ); + vm.run_context.set_fp(12); + let ids = ids_data![ + "margin_check_type", + "token_assets_value_d", + "account", + "prices_cache_ptr", + "indices_cache_ptr", + "perps_cache_ptr", + "fees_cache_ptr", + "perps_balances_cache_ptr", + "check_account_value", + "check_excess_balance", + "check_margin_requirement_d", + "check_unrealized_pnl_d" + ]; + // DICTIONARIES + let mut exec_scopes = ExecutionScopes::new(); + let mut dict_manager = DictManager::new(); + // ids.prices_cache_ptr = (2, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("6148332971604923204").into(), + felt_str!("100000000").into(), + ), + ]), + ) + .unwrap(); + // ids.indices_cache_ptr = (3, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + Felt252::zero().into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + Felt252::zero().into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + Felt252::zero().into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + Felt252::zero().into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + Felt252::zero().into(), + ), + ]), + ) + .unwrap(); + // ids.perps_cache_ptr = (4, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 3092).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 3467).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 3842).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 4217).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 4592).into(), + ), + ]), + ) + .unwrap(); + // ids.fees_cache_ptr = (5, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + (Felt252::from(100).into(), Felt252::from(10000).into()), + (Felt252::from(200).into(), Felt252::from(10000).into()), + ]), + ) + .unwrap(); + // ids.perps_balances_cache_ptr = (6, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 6406).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 6625).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 6844).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 7063).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 18230).into(), + ), + ]), + ) + .unwrap(); + exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager))); + + // EXECUTION + assert!(excess_balance_hint( + &mut vm, + &ids, + &ApTracking::default(), + &constants, + &exec_scopes + ) + .is_ok()); + + // CHECK MEMORY VALUES + check_memory![ + vm.segments.memory, + // ids.check_account_value + ((1, 8), 50000000000), + // ids.check_excess_balance + ( + (1, 9), + ( + "3618502788666131213697322783095070105623107215331596699973092055930362020481", + 10 + ) + ), + // ids.check_margin_requirement_d + ((1, 10), 255510000000), + // ids.check_unrealized_pnl_d + ((1, 11), 50000000000) + ]; + } +} diff --git a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs index 2b55c91f30..9ebe4ac139 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1409,3 +1409,12 @@ ids.x.low = x & ((1<<128)-1) ids.x.high = x >> 128"; #[cfg(feature = "skip_next_instruction_hint")] pub const SKIP_NEXT_INSTRUCTION: &str = "skip_next_instruction()"; + +pub const EXCESS_BALANCE: &str = r#"from excess_balance import excess_balance_func + +res = excess_balance_func(ids, memory, __dict_manager) + +ids.check_account_value = res["account_value"] +ids.check_excess_balance = res["excess_balance"] +ids.check_margin_requirement_d = res["margin_requirement"] +ids.check_unrealized_pnl_d = res["unrealized_pnl"]"#; diff --git a/vm/src/hint_processor/builtin_hint_processor/mod.rs b/vm/src/hint_processor/builtin_hint_processor/mod.rs index 0a3f067b5b..11480dcfc5 100644 --- a/vm/src/hint_processor/builtin_hint_processor/mod.rs +++ b/vm/src/hint_processor/builtin_hint_processor/mod.rs @@ -7,6 +7,7 @@ pub mod dict_hint_utils; pub mod dict_manager; pub mod ec_recover; pub mod ec_utils; +pub mod excess_balance; pub mod field_arithmetic; pub mod find_element_hint; pub mod garaga; diff --git a/vm/src/vm/errors/hint_errors.rs b/vm/src/vm/errors/hint_errors.rs index bcf8935c5d..9113db6013 100644 --- a/vm/src/vm/errors/hint_errors.rs +++ b/vm/src/vm/errors/hint_errors.rs @@ -184,6 +184,12 @@ pub enum HintError { NPairBitsTooLowM, #[error("{0}")] SyscallError(Box), + #[error("excess_balance_func: Failed to fetch {0} dictionary. It is either missing or has unexpected data types")] + ExcessBalanceFailedToFecthDict(Box), + #[error("excess_balance_func: Key not found in {0} dictionary")] + ExcessBalanceKeyError(Box), + #[error("excess_balance_func: Failed to calculate {0}")] + ExcessBalanceCalculationFailed(Box), } #[cfg(test)] From aeae644947e587ff6925a46da62da7605c1f2ba2 Mon Sep 17 00:00:00 2001 From: Federica Date: Thu, 6 Jun 2024 17:10:18 -0300 Subject: [PATCH 02/11] lock flamegraph dep --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 401f41ffe2..e79eb7d73a 100644 --- a/Makefile +++ b/Makefile @@ -177,7 +177,7 @@ build-cairo-2-compiler: cargo-deps: cargo install --version 0.3.1 iai-callgrind-runner cargo install --version 1.1.0 cargo-criterion - cargo install --version 0.6.1 flamegraph + cargo install --version 0.6.1 flamegraph --locked cargo install --version 1.14.0 hyperfine cargo install --version 0.9.49 cargo-nextest cargo install --version 0.5.9 cargo-llvm-cov From ecd4d70dcb4d7b7dfa5cd5e52f7b1a967653c6cd Mon Sep 17 00:00:00 2001 From: Federica Date: Thu, 6 Jun 2024 17:13:21 -0300 Subject: [PATCH 03/11] Lock more dependencies --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e79eb7d73a..148ecf4315 100644 --- a/Makefile +++ b/Makefile @@ -179,9 +179,9 @@ cargo-deps: cargo install --version 1.1.0 cargo-criterion cargo install --version 0.6.1 flamegraph --locked cargo install --version 1.14.0 hyperfine - cargo install --version 0.9.49 cargo-nextest + cargo install --version 0.9.49 cargo-nextest --locked cargo install --version 0.5.9 cargo-llvm-cov - cargo install --version 0.12.1 wasm-pack + cargo install --version 0.12.1 wasm-pack --locked cairo1-run-deps: cd cairo1-run; make deps From 2ff0c2dce3b67e7f637eb492474a052f8f6899c3 Mon Sep 17 00:00:00 2001 From: Federica Date: Thu, 6 Jun 2024 17:45:58 -0300 Subject: [PATCH 04/11] fix --- vm/src/hint_processor/builtin_hint_processor/excess_balance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs b/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs index c6eb5c4d64..7c75fa137f 100644 --- a/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs +++ b/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs @@ -380,7 +380,7 @@ pub fn excess_balance_hint( .and_then(|sum| sum.checked_add(token_assets_value_d)) .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; let fee_provision = fees - .get(&account) + .get(account.as_ref()) .and_then(|fee| abs_balance_value.checked_mul(*fee)) .ok_or_else(|| HintError::ExcessBalanceKeyError("fees".into()))?; let margin_requirement = position_margin From ee971e2536c154ba6387a5e45ceef4073187e1c4 Mon Sep 17 00:00:00 2001 From: Federica Date: Thu, 6 Jun 2024 18:07:33 -0300 Subject: [PATCH 05/11] Lock more dependencies --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 148ecf4315..19f1136e8e 100644 --- a/Makefile +++ b/Makefile @@ -180,7 +180,7 @@ cargo-deps: cargo install --version 0.6.1 flamegraph --locked cargo install --version 1.14.0 hyperfine cargo install --version 0.9.49 cargo-nextest --locked - cargo install --version 0.5.9 cargo-llvm-cov + cargo install --version 0.5.9 cargo-llvm-cov --locked cargo install --version 0.12.1 wasm-pack --locked cairo1-run-deps: From 90a7a1d249fd9ac5cab75e96889b1de908095444 Mon Sep 17 00:00:00 2001 From: Pedro Fontana Date: Fri, 9 Feb 2024 17:31:56 -0300 Subject: [PATCH 06/11] Update Rust version to 1.74.1 (#1605) * Update rust version to 1.74.1 * cargo fmt -all * Update Makefile * cargo clippy --fix * fix cargo clippy * restore fuzzer/Cargo.lock --------- Co-authored-by: Pedro Fontana --- .github/workflows/bench.yml | 2 +- .github/workflows/cairo_1_programs.yml | 2 +- .github/workflows/fresh_run.yml | 2 +- .github/workflows/hint_accountant.yml | 2 +- .github/workflows/hyperfine.yml | 2 +- .github/workflows/iai_main.yml | 2 +- .github/workflows/iai_pr.yml | 4 +- .github/workflows/publish.yml | 10 +- .github/workflows/rust.yml | 8 +- README.md | 2 +- cairo1-run/src/cairo_run.rs | 766 ++++++++++++++++++ rust-toolchain | 2 +- .../find_element_hint.rs | 2 +- vm/src/serde/deserialize_utils.rs | 2 +- vm/src/tests/mod.rs | 6 +- vm/src/vm/runners/cairo_pie.rs | 1 + 16 files changed, 787 insertions(+), 28 deletions(-) create mode 100644 cairo1-run/src/cairo_run.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f3443d5f68..cf4a54a96d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: rustfmt, clippy - uses: actions/checkout@v3 diff --git a/.github/workflows/cairo_1_programs.yml b/.github/workflows/cairo_1_programs.yml index d8a4c1f3f4..c2872a03b4 100644 --- a/.github/workflows/cairo_1_programs.yml +++ b/.github/workflows/cairo_1_programs.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/fresh_run.yml b/.github/workflows/fresh_run.yml index 4660f1f8a2..de76902a24 100644 --- a/.github/workflows/fresh_run.yml +++ b/.github/workflows/fresh_run.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Install Pyenv uses: "gabrielfalcao/pyenv-action@v13" diff --git a/.github/workflows/hint_accountant.yml b/.github/workflows/hint_accountant.yml index 4a3a1e023f..3bd9ca4ff0 100644 --- a/.github/workflows/hint_accountant.yml +++ b/.github/workflows/hint_accountant.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/hyperfine.yml b/.github/workflows/hyperfine.yml index 87bf26f3e3..6fb098b6a6 100644 --- a/.github/workflows/hyperfine.yml +++ b/.github/workflows/hyperfine.yml @@ -74,7 +74,7 @@ jobs: - name: Install Rust if: ${{ steps.cache.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Checkout if: ${{ steps.cache.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/iai_main.yml b/.github/workflows/iai_main.yml index e1217085ba..f61eef7e3e 100644 --- a/.github/workflows/iai_main.yml +++ b/.github/workflows/iai_main.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Python3 Build diff --git a/.github/workflows/iai_pr.yml b/.github/workflows/iai_pr.yml index db446adeb9..afb9e3a97c 100644 --- a/.github/workflows/iai_pr.yml +++ b/.github/workflows/iai_pr.yml @@ -23,7 +23,7 @@ jobs: - name: Install Rust if: ${{ steps.cache-iai-results.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache if: ${{ steps.cache-iai-results.outputs.cache-hit != 'true' }} uses: Swatinem/rust-cache@v2 @@ -51,7 +51,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Python3 Build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f8f5c9b81a..a06efe8806 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,15 +13,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.70.0 - - name: Publish crate cairo-felt - env: - CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish --token ${CRATES_TOKEN} --all-features --manifest-path ./felt/Cargo.toml - # FIXME: there should be a better way to make sure the index in crates.io is updated before publishing - # cairo-vm but right now the step throws timeout and fails. - - name: wait for index in crates.io - run: sleep 300 + uses: dtolnay/rust-toolchain@1.74.1 - name: Publish crate cairo-vm env: CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 32a20befbd..8b34694243 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: rustfmt, clippy - name: Set up cargo cache @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: wasm32-unknown-unknown @@ -220,7 +220,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: llvm-tools-preview - name: Set up cargo cache @@ -281,7 +281,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/README.md b/README.md index 52c2dd4043..db368a35db 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ It's Turing-complete and it was created by [Starkware](https://starkware.co/) as These are needed in order to compile and use the project. -- [Rust 1.70.0 or newer](https://www.rust-lang.org/tools/install) +- [Rust 1.74.1 or newer](https://www.rust-lang.org/tools/install) - Cargo #### Optional diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs new file mode 100644 index 0000000000..5dbda6c672 --- /dev/null +++ b/cairo1-run/src/cairo_run.rs @@ -0,0 +1,766 @@ +use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; +use cairo_lang_sierra::{ + extensions::{ + bitwise::BitwiseType, + core::{CoreLibfunc, CoreType}, + ec::EcOpType, + gas::{CostTokenType, GasBuiltinType}, + pedersen::PedersenType, + poseidon::PoseidonType, + range_check::RangeCheckType, + segment_arena::SegmentArenaType, + starknet::syscalls::SystemType, + ConcreteType, NamedType, + }, + ids::ConcreteTypeId, + program::{Function, Program as SierraProgram}, + program_registry::ProgramRegistry, +}; +use cairo_lang_sierra_ap_change::calc_ap_changes; +use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_to_casm::{ + compiler::CairoProgram, + metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, +}; +use cairo_lang_sierra_type_size::get_type_size_map; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_vm::{ + hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + serde::deserialize_program::{ + ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, + }, + types::{program::Program, relocatable::MaybeRelocatable}, + vm::{ + errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, + runners::{ + builtin_runner::{ + BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, + POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, + }, + cairo_runner::{CairoRunner, RunResources, RunnerMode}, + }, + vm_core::VirtualMachine, + }, + Felt252, +}; +use itertools::chain; +use std::collections::HashMap; + +use crate::{Error, FuncArg}; + +#[derive(Debug)] +pub struct Cairo1RunConfig<'a> { + pub args: &'a [FuncArg], + pub trace_enabled: bool, + pub relocate_mem: bool, + pub layout: &'a str, + pub proof_mode: bool, + // Should be true if either air_public_input or cairo_pie_output are needed + // Sets builtins stop_ptr by calling `final_stack` on each builtin + pub finalize_builtins: bool, +} + +impl Default for Cairo1RunConfig<'_> { + fn default() -> Self { + Self { + args: Default::default(), + trace_enabled: false, + relocate_mem: false, + layout: "plain", + proof_mode: false, + finalize_builtins: false, + } + } +} + +// Runs a Cairo 1 program +// Returns the runner & VM after execution + the return values +pub fn cairo_run_program( + sierra_program: &SierraProgram, + cairo_run_config: Cairo1RunConfig, +) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { + let metadata = create_metadata(sierra_program, Some(Default::default()))?; + let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; + let type_sizes = + get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); + let casm_program = + cairo_lang_sierra_to_casm::compiler::compile(sierra_program, &metadata, true)?; + + let main_func = find_function(sierra_program, "::main")?; + + let initial_gas = 9999999999999_usize; + + // Modified entry code to be compatible with custom cairo1 Proof Mode. + // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 + let (entry_code, builtins) = create_entry_code( + &sierra_program_registry, + &casm_program, + &type_sizes, + main_func, + initial_gas, + cairo_run_config.proof_mode, + cairo_run_config.args, + )?; + + // Fetch return type data + let return_type_id = main_func + .signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + + // This footer is used by lib funcs + let libfunc_footer = create_code_footer(); + + // Header used to initiate the infinite loop after executing the program + // Also appends return values to output segment + let proof_mode_header = if cairo_run_config.proof_mode { + create_proof_mode_header(builtins.len() as i16, return_type_size) + } else { + casm! {}.instructions + }; + + // This is the program we are actually running/proving + // With (embedded proof mode), cairo1 header and the libfunc footer + let instructions = chain!( + proof_mode_header.iter(), + entry_code.iter(), + casm_program.instructions.iter(), + libfunc_footer.iter(), + ); + + let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); + + let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); + + let data: Vec = instructions + .flat_map(|inst| inst.assemble().encode()) + .map(|x| Felt252::from(&x)) + .map(MaybeRelocatable::from) + .collect(); + + let data_len = data.len(); + + let program = if cairo_run_config.proof_mode { + Program::new_for_proof( + builtins, + data, + 0, + // Proof mode is on top + // jmp rel 0 is on PC == 2 + 2, + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + } else { + Program::new( + builtins, + data, + Some(0), + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + }; + + let runner_mode = if cairo_run_config.proof_mode { + RunnerMode::ProofModeCairo1 + } else { + RunnerMode::ExecutionMode + }; + + let mut runner = CairoRunner::new_v2(&program, cairo_run_config.layout, runner_mode)?; + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + let end = runner.initialize(&mut vm, cairo_run_config.proof_mode)?; + + additional_initialization(&mut vm, data_len)?; + + // Run it until the end / infinite loop in proof_mode + runner.run_until_pc(end, &mut vm, &mut hint_processor)?; + if cairo_run_config.proof_mode { + // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 + // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size + vm.segments + .segment_sizes + .insert(2, return_type_size as usize); + } + runner.end_run(false, false, &mut vm, &mut hint_processor)?; + + // Fetch return values + let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + + // Set stop pointers for builtins so we can obtain the air public input + if cairo_run_config.finalize_builtins { + finalize_builtins( + cairo_run_config.proof_mode, + &main_func.signature.ret_types, + &type_sizes, + &mut vm, + )?; + + // Build execution public memory + if cairo_run_config.proof_mode { + // As the output builtin is not used by the program we need to compute it's stop ptr manually + vm.set_output_stop_ptr_offset(return_type_size as usize); + + runner.finalize_segments(&mut vm)?; + } + } + + runner.relocate(&mut vm, true)?; + + Ok((runner, vm, return_values)) +} + +fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { + // Create the builtin cost segment + let builtin_cost_segment = vm.add_memory_segment(); + for token_type in CostTokenType::iter_precost() { + vm.insert_value( + (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) + .map_err(VirtualMachineError::Math)?, + Felt252::default(), + )? + } + // Put a pointer to the builtin cost segment at the end of the program (after the + // additional `ret` statement). + vm.insert_value( + (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, + builtin_cost_segment, + )?; + + Ok(()) +} + +#[allow(clippy::type_complexity)] +fn build_hints_vec<'b>( + instructions: impl Iterator, +) -> (Vec<(usize, Vec)>, HashMap>) { + let mut hints: Vec<(usize, Vec)> = Vec::new(); + let mut program_hints: HashMap> = HashMap::new(); + + let mut hint_offset = 0; + + for instruction in instructions { + if !instruction.hints.is_empty() { + hints.push((hint_offset, instruction.hints.clone())); + program_hints.insert( + hint_offset, + vec![HintParams { + code: hint_offset.to_string(), + accessible_scopes: Vec::new(), + flow_tracking_data: FlowTrackingData { + ap_tracking: ApTracking::default(), + reference_ids: HashMap::new(), + }, + }], + ); + } + hint_offset += instruction.body.op_size(); + } + (hints, program_hints) +} + +/// Finds first function ending with `name_suffix`. +fn find_function<'a>( + sierra_program: &'a SierraProgram, + name_suffix: &'a str, +) -> Result<&'a Function, RunnerError> { + sierra_program + .funcs + .iter() + .find(|f| { + if let Some(name) = &f.id.debug_name { + name.ends_with(name_suffix) + } else { + false + } + }) + .ok_or_else(|| RunnerError::MissingMain) +} + +/// Creates a list of instructions that will be appended to the program's bytecode. +fn create_code_footer() -> Vec { + casm! { + // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` + // and `pc` registers. + ret; + } + .instructions +} + +// Create proof_mode specific instructions +// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) +// wich call the firt program instruction and then initiate an infinite loop. +// And also appending the return values to the output builtin's memory segment +fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { + // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty + // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases + let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments + + // The pc offset where the original program should start + // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) + // and also 1 for each instruction added to copy each return value into the output segment + let program_start_offset: i16 = 4 + return_type_size; + + let mut ctx = casm! {}; + casm_extend! {ctx, + call rel program_start_offset; // Begin program execution by calling the first instruction in the original program + }; + // Append each return value to the output segment + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + casm_extend! {ctx, + jmp rel 0; // Infinite loop + }; + ctx.instructions +} + +/// Returns the instructions to add to the beginning of the code to successfully call the main +/// function, as well as the builtins required to execute the program. +fn create_entry_code( + sierra_program_registry: &ProgramRegistry, + casm_program: &CairoProgram, + type_sizes: &UnorderedHashMap, + func: &Function, + initial_gas: usize, + proof_mode: bool, + args: &[FuncArg], +) -> Result<(Vec, Vec), Error> { + let mut ctx = casm! {}; + // The builtins in the formatting expected by the runner. + let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); + + // Load all vecs to memory. + // Load all array args content to memory. + let mut array_args_data = vec![]; + let mut ap_offset: i16 = 0; + for arg in args { + let FuncArg::Array(values) = arg else { + continue; + }; + array_args_data.push(ap_offset); + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + for (i, v) in values.iter().enumerate() { + let arr_at = (i + 1) as i16; + casm_extend! {ctx, + [ap + 0] = (v.to_bigint()); + [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; + }; + } + ap_offset += (1 + values.len()) as i16; + } + let mut array_args_data_iter = array_args_data.iter(); + let after_arrays_data_offset = ap_offset; + let mut arg_iter = args.iter().enumerate(); + let mut param_index = 0; + let mut expected_arguments_size = 0; + if func.signature.param_types.iter().any(|ty| { + get_info(sierra_program_registry, ty) + .map(|x| x.long_id.generic_id == SegmentArenaType::ID) + .unwrap_or_default() + }) { + casm_extend! {ctx, + // SegmentArena segment. + %{ memory[ap + 0] = segments.add() %} + // Infos segment. + %{ memory[ap + 1] = segments.add() %} + ap += 2; + [ap + 0] = 0, ap++; + // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. + [ap - 2] = [[ap - 3]]; + [ap - 1] = [[ap - 3] + 1]; + [ap - 1] = [[ap - 3] + 2]; + } + ap_offset += 3; + } + for ty in func.signature.param_types.iter() { + let info = get_info(sierra_program_registry, ty) + .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; + let generic_ty = &info.long_id.generic_id; + if let Some(offset) = builtin_offset.get(generic_ty) { + let mut offset = *offset; + if proof_mode { + // Everything is off by 2 due to the proof mode header + offset += 2; + } + casm_extend! {ctx, + [ap + 0] = [fp - offset], ap++; + } + ap_offset += 1; + } else if generic_ty == &SystemType::ID { + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + ap_offset += 1; + } else if generic_ty == &GasBuiltinType::ID { + casm_extend! {ctx, + [ap + 0] = initial_gas, ap++; + } + ap_offset += 1; + } else if generic_ty == &SegmentArenaType::ID { + let offset = -ap_offset + after_arrays_data_offset; + casm_extend! {ctx, + [ap + 0] = [ap + offset] + 3, ap++; + } + ap_offset += 1; + } else { + let ty_size = type_sizes[ty]; + let param_ap_offset_end = ap_offset + ty_size; + expected_arguments_size += ty_size; + while ap_offset < param_ap_offset_end { + let Some((arg_index, arg)) = arg_iter.next() else { + break; + }; + match arg { + FuncArg::Single(value) => { + casm_extend! {ctx, + [ap + 0] = (value.to_bigint()), ap++; + } + ap_offset += 1; + } + FuncArg::Array(values) => { + let offset = -ap_offset + array_args_data_iter.next().unwrap(); + casm_extend! {ctx, + [ap + 0] = [ap + (offset)], ap++; + [ap + 0] = [ap - 1] + (values.len()), ap++; + } + ap_offset += 2; + if ap_offset > param_ap_offset_end { + return Err(Error::ArgumentUnaligned { + param_index, + arg_index, + }); + } + } + } + } + param_index += 1; + }; + } + let actual_args_size = args + .iter() + .map(|arg| match arg { + FuncArg::Single(_) => 1, + FuncArg::Array(_) => 2, + }) + .sum::(); + if expected_arguments_size != actual_args_size { + return Err(Error::ArgumentsSizeMismatch { + expected: expected_arguments_size, + actual: actual_args_size, + }); + } + + let before_final_call = ctx.current_code_offset; + let final_call_size = 3; + let offset = final_call_size + + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; + + casm_extend! {ctx, + call rel offset; + ret; + } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); + + Ok((ctx.instructions, builtins)) +} + +fn get_info<'a>( + sierra_program_registry: &'a ProgramRegistry, + ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, +) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { + sierra_program_registry + .get_type(ty) + .ok() + .map(|ctc| ctc.info()) +} + +/// Creates the metadata required for a Sierra program lowering to casm. +fn create_metadata( + sierra_program: &cairo_lang_sierra::program::Program, + metadata_config: Option, +) -> Result { + if let Some(metadata_config) = metadata_config { + calc_metadata(sierra_program, metadata_config).map_err(|err| match err { + MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, + MetadataError::CostError(_) => VirtualMachineError::Unexpected, + }) + } else { + Ok(Metadata { + ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) + .map_err(|_| VirtualMachineError::Unexpected)?, + gas_info: GasInfo { + variable_values: Default::default(), + function_costs: Default::default(), + }, + }) + } +} + +/// Type representing the Output builtin. +#[derive(Default)] +pub struct OutputType {} +impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { + const ID: cairo_lang_sierra::ids::GenericTypeId = + cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); + const STORABLE: bool = true; + const DUPLICATABLE: bool = false; + const DROPPABLE: bool = false; + const ZERO_SIZED: bool = false; +} + +fn get_function_builtins( + func: &Function, + proof_mode: bool, +) -> ( + Vec, + HashMap, +) { + let entry_params = &func.signature.param_types; + let mut builtins = Vec::new(); + let mut builtin_offset: HashMap = HashMap::new(); + let mut current_offset = 3; + // Fetch builtins from the entry_params in the standard order + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Poseidon".into())) + { + builtins.push(BuiltinName::poseidon); + builtin_offset.insert(PoseidonType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("EcOp".into())) + { + builtins.push(BuiltinName::ec_op); + builtin_offset.insert(EcOpType::ID, current_offset); + current_offset += 1 + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Bitwise".into())) + { + builtins.push(BuiltinName::bitwise); + builtin_offset.insert(BitwiseType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("RangeCheck".into())) + { + builtins.push(BuiltinName::range_check); + builtin_offset.insert(RangeCheckType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Pedersen".into())) + { + builtins.push(BuiltinName::pedersen); + builtin_offset.insert(PedersenType::ID, current_offset); + current_offset += 1; + } + // Force an output builtin so that we can write the program output into it's segment + if proof_mode { + builtins.push(BuiltinName::output); + builtin_offset.insert(OutputType::ID, current_offset); + } + builtins.reverse(); + (builtins, builtin_offset) +} + +fn fetch_return_values( + return_type_size: i16, + return_type_id: &ConcreteTypeId, + vm: &VirtualMachine, +) -> Result, Error> { + let mut return_values = vm.get_return_values(return_type_size as usize)?; + // Check if this result is a Panic result + if return_type_id + .debug_name + .as_ref() + .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? + .starts_with("core::panics::PanicResult::") + { + // Check the failure flag (aka first return value) + if return_values.first() != Some(&MaybeRelocatable::from(0)) { + // In case of failure, extract the error from the return values (aka last two values) + let panic_data_end = return_values + .last() + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data_start = return_values + .get(return_values.len() - 2) + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data = vm.get_integer_range( + panic_data_start, + (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, + )?; + return Err(Error::RunPanic( + panic_data.iter().map(|c| *c.as_ref()).collect(), + )); + } else { + if return_values.len() < 3 { + return Err(Error::FailedToExtractReturnValues); + } + return_values = return_values[2..].to_vec() + } + } + Ok(return_values) +} + +// Calculates builtins' final_stack setting each stop_ptr +// Calling this function is a must if either air_public_input or cairo_pie are needed +fn finalize_builtins( + proof_mode: bool, + main_ret_types: &[ConcreteTypeId], + type_sizes: &UnorderedHashMap, + vm: &mut VirtualMachine, +) -> Result<(), Error> { + // Set stop pointers for builtins so we can obtain the air public input + // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them + let ret_types_sizes = main_ret_types + .iter() + .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); + let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); + + let full_ret_types_size: i16 = ret_types_sizes.sum(); + let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) + .map_err(VirtualMachineError::Math)?; + + // Calculate the stack_ptr for each return builtin in the return values + let mut builtin_name_to_stack_pointer = HashMap::new(); + for (id, size) in ret_types_and_sizes { + if let Some(ref name) = id.debug_name { + let builtin_name = match &*name.to_string() { + "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, + "Poseidon" => POSEIDON_BUILTIN_NAME, + "EcOp" => EC_OP_BUILTIN_NAME, + "Bitwise" => BITWISE_BUILTIN_NAME, + "Pedersen" => HASH_BUILTIN_NAME, + "Output" => OUTPUT_BUILTIN_NAME, + "Ecdsa" => SIGNATURE_BUILTIN_NAME, + _ => { + stack_pointer.offset += size as usize; + continue; + } + }; + builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); + } + stack_pointer.offset += size as usize; + } + + // Set stop pointer for each builtin + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; + use cairo_vm::types::relocatable::Relocatable; + use rstest::rstest; + + fn compile_to_sierra(filename: &str) -> SierraProgram { + let compiler_config = CompilerConfig { + replace_ids: true, + ..CompilerConfig::default() + }; + + compile_cairo_project_at_path(Path::new(filename), compiler_config).unwrap() + } + + fn main_hash_panic_result(sierra_program: &SierraProgram) -> bool { + let main_func = find_function(sierra_program, "::main").unwrap(); + main_func + .signature + .ret_types + .last() + .and_then(|rt| { + rt.debug_name + .as_ref() + .map(|n| n.as_ref().starts_with("core::panics::PanicResult::")) + }) + .unwrap_or_default() + } + + #[rstest] + #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] + #[case("../cairo_programs/cairo-1-programs/array_get.cairo")] + #[case("../cairo_programs/cairo-1-programs/dictionaries.cairo")] + #[case("../cairo_programs/cairo-1-programs/enum_flow.cairo")] + #[case("../cairo_programs/cairo-1-programs/enum_match.cairo")] + #[case("../cairo_programs/cairo-1-programs/factorial.cairo")] + #[case("../cairo_programs/cairo-1-programs/fibonacci.cairo")] + #[case("../cairo_programs/cairo-1-programs/hello.cairo")] + #[case("../cairo_programs/cairo-1-programs/pedersen_example.cairo")] + #[case("../cairo_programs/cairo-1-programs/poseidon.cairo")] + #[case("../cairo_programs/cairo-1-programs/print.cairo")] + #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] + #[case("../cairo_programs/cairo-1-programs/recursion.cairo")] + #[case("../cairo_programs/cairo-1-programs/sample.cairo")] + #[case("../cairo_programs/cairo-1-programs/simple_struct.cairo")] + #[case("../cairo_programs/cairo-1-programs/simple.cairo")] + #[case("../cairo_programs/cairo-1-programs/struct_span_return.cairo")] + fn check_append_ret_values_to_output_segment(#[case] filename: &str) { + // Compile to sierra + let sierra_program = compile_to_sierra(filename); + // Set proof_mode + let cairo_run_config = Cairo1RunConfig { + proof_mode: true, + layout: "all_cairo", + ..Default::default() + }; + // Run program + let (_, vm, return_values) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); + // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values + // And handle the panics returning an error, so we need to add it here + let return_values = if main_hash_panic_result(&sierra_program) { + let mut rv = vec![Felt252::ZERO.into(), Felt252::ZERO.into()]; + rv.extend_from_slice(&return_values); + rv + } else { + return_values + }; + // Check that the output segment contains the return values + // The output builtin will always be the first builtin, so we know it's segment is 2 + let output_builtin_segment = vm + .get_continuous_range((2, 0).into(), return_values.len()) + .unwrap(); + assert_eq!(output_builtin_segment, return_values, "{}", filename); + // Just for consistency, we will check that there are no values in the output segment after the return values + assert!(vm + .get_maybe(&Relocatable::from((2_isize, return_values.len()))) + .is_none()); + } +} diff --git a/rust-toolchain b/rust-toolchain index 2d24a1e077..4da0ec7b8e 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,4 +1,4 @@ [toolchain] -channel = "1.70.0" +channel = "1.74.1" components = ["rustfmt", "clippy"] profile = "minimal" diff --git a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs index d9d7a56600..9ea30fb6e3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs +++ b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs @@ -162,7 +162,7 @@ mod tests { vm.segments.add(); } - let addresses = vec![ + let addresses = [ Relocatable::from((1, 0)), Relocatable::from((1, 1)), Relocatable::from((1, 2)), diff --git a/vm/src/serde/deserialize_utils.rs b/vm/src/serde/deserialize_utils.rs index 5a90ec863d..53bcce78ca 100644 --- a/vm/src/serde/deserialize_utils.rs +++ b/vm/src/serde/deserialize_utils.rs @@ -230,7 +230,7 @@ fn take_until_unbalanced( .ok_or_else(|| Err::Error(Error::from_error_kind(i, ErrorKind::TakeUntil)))? .chars(); match it.next().unwrap_or_default() { - c if c == '\\' => { + '\\' => { // Skip the escape char `\`. index += '\\'.len_utf8(); // Skip also the following char. diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index cecacbad8d..f66a602fb0 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -68,7 +68,7 @@ pub(self) fn run_program_with_error(data: &[u8], error: &str) { run_program(data, Some("all_cairo"), None, Some(error), None) } -pub(self) fn run_program( +fn run_program( data: &[u8], layout: Option<&str>, trace: Option<&[(usize, usize, usize)]>, @@ -109,7 +109,7 @@ pub(self) fn run_program( #[cfg(feature = "cairo-1-hints")] // Runs a contract entrypoint with given arguments and checks its return values // Doesn't use a syscall_handler -pub(self) fn run_cairo_1_entrypoint( +fn run_cairo_1_entrypoint( program_content: &[u8], entrypoint_offset: usize, args: &[MaybeRelocatable], @@ -215,7 +215,7 @@ pub(self) fn run_cairo_1_entrypoint( #[cfg(feature = "cairo-1-hints")] /// Equals to fn run_cairo_1_entrypoint /// But with run_resources as an input -pub(self) fn run_cairo_1_entrypoint_with_run_resources( +fn run_cairo_1_entrypoint_with_run_resources( contract_class: CasmContractClass, entrypoint_offset: usize, hint_processor: &mut Cairo1HintProcessor, diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index 60a0fa147e..2862a0fdbb 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -147,6 +147,7 @@ mod serde_impl { seq_serializer.end() } + #[allow(clippy::format_collect)] pub fn serialize_memory( values: &[((usize, usize), MaybeRelocatable)], serializer: S, From 47fd264590aee538fa6651145bccf091182bbb1d Mon Sep 17 00:00:00 2001 From: Federica Date: Fri, 7 Jun 2024 12:27:10 -0300 Subject: [PATCH 07/11] Apply clippy --- felt/src/lib_bigint_felt.rs | 2 +- vm/src/serde/deserialize_program.rs | 4 ++-- vm/src/tests/mod.rs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs index 1c9f3e0479..87472acfd1 100644 --- a/felt/src/lib_bigint_felt.rs +++ b/felt/src/lib_bigint_felt.rs @@ -377,7 +377,7 @@ impl Add<&Felt252> for u64 { }; // A single digit means this is effectively the sum of two `u64` numbers. let Some(h0) = rhs_digits.next() else { - return self.checked_add(low) + return self.checked_add(low); }; // Now we need to compare the 3 most significant digits. // There are two relevant cases from now on, either `rhs` behaves like a diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index 52c5e70a42..072ab32ce7 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -327,7 +327,7 @@ impl<'de> de::Visitor<'de> for Felt252Visitor { let no_prefix_hex = deserialize_utils::maybe_add_padding(no_prefix_hex.to_string()); Ok(Felt252::from_str_radix(&no_prefix_hex, 16).map_err(de::Error::custom)?) } else { - Err(String::from("hex prefix error")).map_err(de::Error::custom) + Err(de::Error::custom(String::from("hex prefix error"))) } } } @@ -355,7 +355,7 @@ impl<'de> de::Visitor<'de> for MaybeRelocatableVisitor { Felt252::from_str_radix(&no_prefix_hex, 16).map_err(de::Error::custom)?, )); } else { - return Err(String::from("hex prefix error")).map_err(de::Error::custom); + return Err(de::Error::custom(String::from("hex prefix error"))); }; } Ok(data) diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index f66a602fb0..8af7df8de2 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -45,26 +45,26 @@ mod skip_instruction_test; //For simple programs that should just succeed and have no special needs. //Checks memory holes == 0 -pub(self) fn run_program_simple(data: &[u8]) { + fn run_program_simple(data: &[u8]) { run_program(data, Some("all_cairo"), None, None, Some(0)) } //For simple programs that should just succeed and have no special needs. //Checks memory holes -pub(self) fn run_program_simple_with_memory_holes(data: &[u8], holes: usize) { + fn run_program_simple_with_memory_holes(data: &[u8], holes: usize) { run_program(data, Some("all_cairo"), None, None, Some(holes)) } //For simple programs that should just succeed but using small layout. -pub(self) fn run_program_small(data: &[u8]) { + fn run_program_small(data: &[u8]) { run_program(data, Some("small"), None, None, None) } -pub(self) fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { + fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { run_program(data, Some("all_cairo"), Some(trace), None, None) } -pub(self) fn run_program_with_error(data: &[u8], error: &str) { + fn run_program_with_error(data: &[u8], error: &str) { run_program(data, Some("all_cairo"), None, Some(error), None) } From 6dbf2f07df485076a622694171163cb42231339a Mon Sep 17 00:00:00 2001 From: Federica Date: Fri, 7 Jun 2024 12:27:24 -0300 Subject: [PATCH 08/11] fmt --- vm/src/tests/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 8af7df8de2..ec27c96aab 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -45,26 +45,26 @@ mod skip_instruction_test; //For simple programs that should just succeed and have no special needs. //Checks memory holes == 0 - fn run_program_simple(data: &[u8]) { +fn run_program_simple(data: &[u8]) { run_program(data, Some("all_cairo"), None, None, Some(0)) } //For simple programs that should just succeed and have no special needs. //Checks memory holes - fn run_program_simple_with_memory_holes(data: &[u8], holes: usize) { +fn run_program_simple_with_memory_holes(data: &[u8], holes: usize) { run_program(data, Some("all_cairo"), None, None, Some(holes)) } //For simple programs that should just succeed but using small layout. - fn run_program_small(data: &[u8]) { +fn run_program_small(data: &[u8]) { run_program(data, Some("small"), None, None, None) } - fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { +fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { run_program(data, Some("all_cairo"), Some(trace), None, None) } - fn run_program_with_error(data: &[u8], error: &str) { +fn run_program_with_error(data: &[u8], error: &str) { run_program(data, Some("all_cairo"), None, Some(error), None) } From ef5d214ea895e40c1818e3b54c71e56c6e0551f0 Mon Sep 17 00:00:00 2001 From: Federica Date: Fri, 7 Jun 2024 12:50:49 -0300 Subject: [PATCH 09/11] Remove cairo1-run --- cairo1-run/Cargo.toml | 33 -- cairo1-run/Makefile | 33 -- cairo1-run/README.md | 31 -- cairo1-run/src/cairo_run.rs | 766 ------------------------------------ cairo1-run/src/main.rs | 722 --------------------------------- 5 files changed, 1585 deletions(-) delete mode 100644 cairo1-run/Cargo.toml delete mode 100644 cairo1-run/Makefile delete mode 100644 cairo1-run/README.md delete mode 100644 cairo1-run/src/cairo_run.rs delete mode 100644 cairo1-run/src/main.rs diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml deleted file mode 100644 index 85b98e3927..0000000000 --- a/cairo1-run/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "cairo1-run" -version.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -keywords.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -cairo-vm = {workspace = true, features = ["std", "cairo-1-hints"]} - -cairo-lang-sierra-type-size = { version = "2.3.1", default-features = false } -cairo-lang-sierra-ap-change = { version = "2.3.1", default-features = false } -cairo-lang-sierra-gas = { version = "2.3.1", default-features = false } -cairo-lang-sierra-to-casm.workspace = true -cairo-lang-compiler.workspace = true -cairo-lang-sierra.workspace = true -cairo-lang-utils.workspace = true -cairo-lang-casm.workspace = true -itertools = "0.11.0" -clap = { version = "4.3.10", features = ["derive"] } -thiserror = { version = "1.0.40" } -bincode.workspace = true -assert_matches = "1.5.0" -rstest = "0.17.0" -mimalloc = { version = "0.1.37", default-features = false, optional = true } - -[features] -default = ["with_mimalloc"] -with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] -lambdaworks-felt = ["cairo-vm/lambdaworks-felt"] diff --git a/cairo1-run/Makefile b/cairo1-run/Makefile deleted file mode 100644 index 337cf3a53e..0000000000 --- a/cairo1-run/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -.PHONY: deps factorial fibonacci test clean run - -CAIRO_1_FOLDER=../cairo_programs/cairo-1-programs - -$(CAIRO_1_FOLDER)/%.trace: $(CAIRO_1_FOLDER)/%.cairo - cargo run --release $< --trace_file $@ --layout all_cairo - -$(CAIRO_1_FOLDER)/%.memory: $(CAIRO_1_FOLDER)/%.cairo - cargo run --release $< --memory_file $@ --layout all_cairo - -CAIRO_1_PROGRAMS=$(wildcard ../cairo_programs/cairo-1-programs/*.cairo) -TRACES:=$(patsubst $(CAIRO_1_FOLDER)/%.cairo, $(CAIRO_1_FOLDER)/%.trace, $(CAIRO_1_PROGRAMS)) -MEMORY:=$(patsubst $(CAIRO_1_FOLDER)/%.cairo, $(CAIRO_1_FOLDER)/%.memory, $(CAIRO_1_PROGRAMS)) - -deps: - git clone https://github.com/starkware-libs/cairo.git \ - && cd cairo \ - && git checkout v2.3.1 \ - && cd .. \ - && mv cairo/corelib/ . \ - && rm -rf cairo/ - -run: $(TRACES) $(MEMORY) - -test: - cargo test - -clean: - rm -rf corelib - rm -rf cairo - rm -rf ../cairo_programs/cairo-1-programs/*.memory - rm -rf ../cairo_programs/cairo-1-programs/*.trace - diff --git a/cairo1-run/README.md b/cairo1-run/README.md deleted file mode 100644 index 95be3b5ecb..0000000000 --- a/cairo1-run/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Cairo1-run - -A cairo-vm crate to run Cairo 1 Programs - -Once you are inside the `./cairo1-run` folder, use the CLI with the following commands - -To install the required dependencies(cairo corelib) run -```bash -make deps -``` - -Now that you have the dependencies necessary to run the tests, you can run: - -```bash -make test -``` - -To execute a cairo 1 program -```bash -cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo -``` - -Arguments to generate the trace and memory files -```bash -cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo --trace_file ../cairo_programs/cairo-1-programs/fibonacci.trace --memory_file ../cairo_programs/cairo-1-programs/fibonacci.memory -``` - -To execute all the cairo 1 programs inside `../cairo_programs/cairo-1-programs/` and generate the corresponding trace and the memory files -```bash -make run -``` diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs deleted file mode 100644 index 5dbda6c672..0000000000 --- a/cairo1-run/src/cairo_run.rs +++ /dev/null @@ -1,766 +0,0 @@ -use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; -use cairo_lang_sierra::{ - extensions::{ - bitwise::BitwiseType, - core::{CoreLibfunc, CoreType}, - ec::EcOpType, - gas::{CostTokenType, GasBuiltinType}, - pedersen::PedersenType, - poseidon::PoseidonType, - range_check::RangeCheckType, - segment_arena::SegmentArenaType, - starknet::syscalls::SystemType, - ConcreteType, NamedType, - }, - ids::ConcreteTypeId, - program::{Function, Program as SierraProgram}, - program_registry::ProgramRegistry, -}; -use cairo_lang_sierra_ap_change::calc_ap_changes; -use cairo_lang_sierra_gas::gas_info::GasInfo; -use cairo_lang_sierra_to_casm::{ - compiler::CairoProgram, - metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, -}; -use cairo_lang_sierra_type_size::get_type_size_map; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; -use cairo_vm::{ - hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, - serde::deserialize_program::{ - ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, - }, - types::{program::Program, relocatable::MaybeRelocatable}, - vm::{ - errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, - runners::{ - builtin_runner::{ - BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, - POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, - }, - cairo_runner::{CairoRunner, RunResources, RunnerMode}, - }, - vm_core::VirtualMachine, - }, - Felt252, -}; -use itertools::chain; -use std::collections::HashMap; - -use crate::{Error, FuncArg}; - -#[derive(Debug)] -pub struct Cairo1RunConfig<'a> { - pub args: &'a [FuncArg], - pub trace_enabled: bool, - pub relocate_mem: bool, - pub layout: &'a str, - pub proof_mode: bool, - // Should be true if either air_public_input or cairo_pie_output are needed - // Sets builtins stop_ptr by calling `final_stack` on each builtin - pub finalize_builtins: bool, -} - -impl Default for Cairo1RunConfig<'_> { - fn default() -> Self { - Self { - args: Default::default(), - trace_enabled: false, - relocate_mem: false, - layout: "plain", - proof_mode: false, - finalize_builtins: false, - } - } -} - -// Runs a Cairo 1 program -// Returns the runner & VM after execution + the return values -pub fn cairo_run_program( - sierra_program: &SierraProgram, - cairo_run_config: Cairo1RunConfig, -) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { - let metadata = create_metadata(sierra_program, Some(Default::default()))?; - let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; - let type_sizes = - get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); - let casm_program = - cairo_lang_sierra_to_casm::compiler::compile(sierra_program, &metadata, true)?; - - let main_func = find_function(sierra_program, "::main")?; - - let initial_gas = 9999999999999_usize; - - // Modified entry code to be compatible with custom cairo1 Proof Mode. - // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 - let (entry_code, builtins) = create_entry_code( - &sierra_program_registry, - &casm_program, - &type_sizes, - main_func, - initial_gas, - cairo_run_config.proof_mode, - cairo_run_config.args, - )?; - - // Fetch return type data - let return_type_id = main_func - .signature - .ret_types - .last() - .ok_or(Error::NoRetTypesInSignature)?; - let return_type_size = type_sizes - .get(return_type_id) - .cloned() - .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; - - // This footer is used by lib funcs - let libfunc_footer = create_code_footer(); - - // Header used to initiate the infinite loop after executing the program - // Also appends return values to output segment - let proof_mode_header = if cairo_run_config.proof_mode { - create_proof_mode_header(builtins.len() as i16, return_type_size) - } else { - casm! {}.instructions - }; - - // This is the program we are actually running/proving - // With (embedded proof mode), cairo1 header and the libfunc footer - let instructions = chain!( - proof_mode_header.iter(), - entry_code.iter(), - casm_program.instructions.iter(), - libfunc_footer.iter(), - ); - - let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); - - let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); - - let data: Vec = instructions - .flat_map(|inst| inst.assemble().encode()) - .map(|x| Felt252::from(&x)) - .map(MaybeRelocatable::from) - .collect(); - - let data_len = data.len(); - - let program = if cairo_run_config.proof_mode { - Program::new_for_proof( - builtins, - data, - 0, - // Proof mode is on top - // jmp rel 0 is on PC == 2 - 2, - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )? - } else { - Program::new( - builtins, - data, - Some(0), - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )? - }; - - let runner_mode = if cairo_run_config.proof_mode { - RunnerMode::ProofModeCairo1 - } else { - RunnerMode::ExecutionMode - }; - - let mut runner = CairoRunner::new_v2(&program, cairo_run_config.layout, runner_mode)?; - let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); - let end = runner.initialize(&mut vm, cairo_run_config.proof_mode)?; - - additional_initialization(&mut vm, data_len)?; - - // Run it until the end / infinite loop in proof_mode - runner.run_until_pc(end, &mut vm, &mut hint_processor)?; - if cairo_run_config.proof_mode { - // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 - // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size - vm.segments - .segment_sizes - .insert(2, return_type_size as usize); - } - runner.end_run(false, false, &mut vm, &mut hint_processor)?; - - // Fetch return values - let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; - - // Set stop pointers for builtins so we can obtain the air public input - if cairo_run_config.finalize_builtins { - finalize_builtins( - cairo_run_config.proof_mode, - &main_func.signature.ret_types, - &type_sizes, - &mut vm, - )?; - - // Build execution public memory - if cairo_run_config.proof_mode { - // As the output builtin is not used by the program we need to compute it's stop ptr manually - vm.set_output_stop_ptr_offset(return_type_size as usize); - - runner.finalize_segments(&mut vm)?; - } - } - - runner.relocate(&mut vm, true)?; - - Ok((runner, vm, return_values)) -} - -fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { - // Create the builtin cost segment - let builtin_cost_segment = vm.add_memory_segment(); - for token_type in CostTokenType::iter_precost() { - vm.insert_value( - (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) - .map_err(VirtualMachineError::Math)?, - Felt252::default(), - )? - } - // Put a pointer to the builtin cost segment at the end of the program (after the - // additional `ret` statement). - vm.insert_value( - (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, - builtin_cost_segment, - )?; - - Ok(()) -} - -#[allow(clippy::type_complexity)] -fn build_hints_vec<'b>( - instructions: impl Iterator, -) -> (Vec<(usize, Vec)>, HashMap>) { - let mut hints: Vec<(usize, Vec)> = Vec::new(); - let mut program_hints: HashMap> = HashMap::new(); - - let mut hint_offset = 0; - - for instruction in instructions { - if !instruction.hints.is_empty() { - hints.push((hint_offset, instruction.hints.clone())); - program_hints.insert( - hint_offset, - vec![HintParams { - code: hint_offset.to_string(), - accessible_scopes: Vec::new(), - flow_tracking_data: FlowTrackingData { - ap_tracking: ApTracking::default(), - reference_ids: HashMap::new(), - }, - }], - ); - } - hint_offset += instruction.body.op_size(); - } - (hints, program_hints) -} - -/// Finds first function ending with `name_suffix`. -fn find_function<'a>( - sierra_program: &'a SierraProgram, - name_suffix: &'a str, -) -> Result<&'a Function, RunnerError> { - sierra_program - .funcs - .iter() - .find(|f| { - if let Some(name) = &f.id.debug_name { - name.ends_with(name_suffix) - } else { - false - } - }) - .ok_or_else(|| RunnerError::MissingMain) -} - -/// Creates a list of instructions that will be appended to the program's bytecode. -fn create_code_footer() -> Vec { - casm! { - // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` - // and `pc` registers. - ret; - } - .instructions -} - -// Create proof_mode specific instructions -// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) -// wich call the firt program instruction and then initiate an infinite loop. -// And also appending the return values to the output builtin's memory segment -fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 4 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - jmp rel 0; // Infinite loop - }; - ctx.instructions -} - -/// Returns the instructions to add to the beginning of the code to successfully call the main -/// function, as well as the builtins required to execute the program. -fn create_entry_code( - sierra_program_registry: &ProgramRegistry, - casm_program: &CairoProgram, - type_sizes: &UnorderedHashMap, - func: &Function, - initial_gas: usize, - proof_mode: bool, - args: &[FuncArg], -) -> Result<(Vec, Vec), Error> { - let mut ctx = casm! {}; - // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); - - // Load all vecs to memory. - // Load all array args content to memory. - let mut array_args_data = vec![]; - let mut ap_offset: i16 = 0; - for arg in args { - let FuncArg::Array(values) = arg else { - continue; - }; - array_args_data.push(ap_offset); - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - for (i, v) in values.iter().enumerate() { - let arr_at = (i + 1) as i16; - casm_extend! {ctx, - [ap + 0] = (v.to_bigint()); - [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; - }; - } - ap_offset += (1 + values.len()) as i16; - } - let mut array_args_data_iter = array_args_data.iter(); - let after_arrays_data_offset = ap_offset; - let mut arg_iter = args.iter().enumerate(); - let mut param_index = 0; - let mut expected_arguments_size = 0; - if func.signature.param_types.iter().any(|ty| { - get_info(sierra_program_registry, ty) - .map(|x| x.long_id.generic_id == SegmentArenaType::ID) - .unwrap_or_default() - }) { - casm_extend! {ctx, - // SegmentArena segment. - %{ memory[ap + 0] = segments.add() %} - // Infos segment. - %{ memory[ap + 1] = segments.add() %} - ap += 2; - [ap + 0] = 0, ap++; - // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. - [ap - 2] = [[ap - 3]]; - [ap - 1] = [[ap - 3] + 1]; - [ap - 1] = [[ap - 3] + 2]; - } - ap_offset += 3; - } - for ty in func.signature.param_types.iter() { - let info = get_info(sierra_program_registry, ty) - .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; - let generic_ty = &info.long_id.generic_id; - if let Some(offset) = builtin_offset.get(generic_ty) { - let mut offset = *offset; - if proof_mode { - // Everything is off by 2 due to the proof mode header - offset += 2; - } - casm_extend! {ctx, - [ap + 0] = [fp - offset], ap++; - } - ap_offset += 1; - } else if generic_ty == &SystemType::ID { - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - ap_offset += 1; - } else if generic_ty == &GasBuiltinType::ID { - casm_extend! {ctx, - [ap + 0] = initial_gas, ap++; - } - ap_offset += 1; - } else if generic_ty == &SegmentArenaType::ID { - let offset = -ap_offset + after_arrays_data_offset; - casm_extend! {ctx, - [ap + 0] = [ap + offset] + 3, ap++; - } - ap_offset += 1; - } else { - let ty_size = type_sizes[ty]; - let param_ap_offset_end = ap_offset + ty_size; - expected_arguments_size += ty_size; - while ap_offset < param_ap_offset_end { - let Some((arg_index, arg)) = arg_iter.next() else { - break; - }; - match arg { - FuncArg::Single(value) => { - casm_extend! {ctx, - [ap + 0] = (value.to_bigint()), ap++; - } - ap_offset += 1; - } - FuncArg::Array(values) => { - let offset = -ap_offset + array_args_data_iter.next().unwrap(); - casm_extend! {ctx, - [ap + 0] = [ap + (offset)], ap++; - [ap + 0] = [ap - 1] + (values.len()), ap++; - } - ap_offset += 2; - if ap_offset > param_ap_offset_end { - return Err(Error::ArgumentUnaligned { - param_index, - arg_index, - }); - } - } - } - } - param_index += 1; - }; - } - let actual_args_size = args - .iter() - .map(|arg| match arg { - FuncArg::Single(_) => 1, - FuncArg::Array(_) => 2, - }) - .sum::(); - if expected_arguments_size != actual_args_size { - return Err(Error::ArgumentsSizeMismatch { - expected: expected_arguments_size, - actual: actual_args_size, - }); - } - - let before_final_call = ctx.current_code_offset; - let final_call_size = 3; - let offset = final_call_size - + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; - - casm_extend! {ctx, - call rel offset; - ret; - } - assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); - - Ok((ctx.instructions, builtins)) -} - -fn get_info<'a>( - sierra_program_registry: &'a ProgramRegistry, - ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, -) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { - sierra_program_registry - .get_type(ty) - .ok() - .map(|ctc| ctc.info()) -} - -/// Creates the metadata required for a Sierra program lowering to casm. -fn create_metadata( - sierra_program: &cairo_lang_sierra::program::Program, - metadata_config: Option, -) -> Result { - if let Some(metadata_config) = metadata_config { - calc_metadata(sierra_program, metadata_config).map_err(|err| match err { - MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, - MetadataError::CostError(_) => VirtualMachineError::Unexpected, - }) - } else { - Ok(Metadata { - ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) - .map_err(|_| VirtualMachineError::Unexpected)?, - gas_info: GasInfo { - variable_values: Default::default(), - function_costs: Default::default(), - }, - }) - } -} - -/// Type representing the Output builtin. -#[derive(Default)] -pub struct OutputType {} -impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { - const ID: cairo_lang_sierra::ids::GenericTypeId = - cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); - const STORABLE: bool = true; - const DUPLICATABLE: bool = false; - const DROPPABLE: bool = false; - const ZERO_SIZED: bool = false; -} - -fn get_function_builtins( - func: &Function, - proof_mode: bool, -) -> ( - Vec, - HashMap, -) { - let entry_params = &func.signature.param_types; - let mut builtins = Vec::new(); - let mut builtin_offset: HashMap = HashMap::new(); - let mut current_offset = 3; - // Fetch builtins from the entry_params in the standard order - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Poseidon".into())) - { - builtins.push(BuiltinName::poseidon); - builtin_offset.insert(PoseidonType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("EcOp".into())) - { - builtins.push(BuiltinName::ec_op); - builtin_offset.insert(EcOpType::ID, current_offset); - current_offset += 1 - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Bitwise".into())) - { - builtins.push(BuiltinName::bitwise); - builtin_offset.insert(BitwiseType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("RangeCheck".into())) - { - builtins.push(BuiltinName::range_check); - builtin_offset.insert(RangeCheckType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Pedersen".into())) - { - builtins.push(BuiltinName::pedersen); - builtin_offset.insert(PedersenType::ID, current_offset); - current_offset += 1; - } - // Force an output builtin so that we can write the program output into it's segment - if proof_mode { - builtins.push(BuiltinName::output); - builtin_offset.insert(OutputType::ID, current_offset); - } - builtins.reverse(); - (builtins, builtin_offset) -} - -fn fetch_return_values( - return_type_size: i16, - return_type_id: &ConcreteTypeId, - vm: &VirtualMachine, -) -> Result, Error> { - let mut return_values = vm.get_return_values(return_type_size as usize)?; - // Check if this result is a Panic result - if return_type_id - .debug_name - .as_ref() - .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? - .starts_with("core::panics::PanicResult::") - { - // Check the failure flag (aka first return value) - if return_values.first() != Some(&MaybeRelocatable::from(0)) { - // In case of failure, extract the error from the return values (aka last two values) - let panic_data_end = return_values - .last() - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data_start = return_values - .get(return_values.len() - 2) - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data = vm.get_integer_range( - panic_data_start, - (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, - )?; - return Err(Error::RunPanic( - panic_data.iter().map(|c| *c.as_ref()).collect(), - )); - } else { - if return_values.len() < 3 { - return Err(Error::FailedToExtractReturnValues); - } - return_values = return_values[2..].to_vec() - } - } - Ok(return_values) -} - -// Calculates builtins' final_stack setting each stop_ptr -// Calling this function is a must if either air_public_input or cairo_pie are needed -fn finalize_builtins( - proof_mode: bool, - main_ret_types: &[ConcreteTypeId], - type_sizes: &UnorderedHashMap, - vm: &mut VirtualMachine, -) -> Result<(), Error> { - // Set stop pointers for builtins so we can obtain the air public input - // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them - let ret_types_sizes = main_ret_types - .iter() - .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); - let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); - - let full_ret_types_size: i16 = ret_types_sizes.sum(); - let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) - .map_err(VirtualMachineError::Math)?; - - // Calculate the stack_ptr for each return builtin in the return values - let mut builtin_name_to_stack_pointer = HashMap::new(); - for (id, size) in ret_types_and_sizes { - if let Some(ref name) = id.debug_name { - let builtin_name = match &*name.to_string() { - "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, - "Poseidon" => POSEIDON_BUILTIN_NAME, - "EcOp" => EC_OP_BUILTIN_NAME, - "Bitwise" => BITWISE_BUILTIN_NAME, - "Pedersen" => HASH_BUILTIN_NAME, - "Output" => OUTPUT_BUILTIN_NAME, - "Ecdsa" => SIGNATURE_BUILTIN_NAME, - _ => { - stack_pointer.offset += size as usize; - continue; - } - }; - builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); - } - stack_pointer.offset += size as usize; - } - - // Set stop pointer for each builtin - vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::path::Path; - - use super::*; - use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; - use cairo_vm::types::relocatable::Relocatable; - use rstest::rstest; - - fn compile_to_sierra(filename: &str) -> SierraProgram { - let compiler_config = CompilerConfig { - replace_ids: true, - ..CompilerConfig::default() - }; - - compile_cairo_project_at_path(Path::new(filename), compiler_config).unwrap() - } - - fn main_hash_panic_result(sierra_program: &SierraProgram) -> bool { - let main_func = find_function(sierra_program, "::main").unwrap(); - main_func - .signature - .ret_types - .last() - .and_then(|rt| { - rt.debug_name - .as_ref() - .map(|n| n.as_ref().starts_with("core::panics::PanicResult::")) - }) - .unwrap_or_default() - } - - #[rstest] - #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] - #[case("../cairo_programs/cairo-1-programs/array_get.cairo")] - #[case("../cairo_programs/cairo-1-programs/dictionaries.cairo")] - #[case("../cairo_programs/cairo-1-programs/enum_flow.cairo")] - #[case("../cairo_programs/cairo-1-programs/enum_match.cairo")] - #[case("../cairo_programs/cairo-1-programs/factorial.cairo")] - #[case("../cairo_programs/cairo-1-programs/fibonacci.cairo")] - #[case("../cairo_programs/cairo-1-programs/hello.cairo")] - #[case("../cairo_programs/cairo-1-programs/pedersen_example.cairo")] - #[case("../cairo_programs/cairo-1-programs/poseidon.cairo")] - #[case("../cairo_programs/cairo-1-programs/print.cairo")] - #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] - #[case("../cairo_programs/cairo-1-programs/recursion.cairo")] - #[case("../cairo_programs/cairo-1-programs/sample.cairo")] - #[case("../cairo_programs/cairo-1-programs/simple_struct.cairo")] - #[case("../cairo_programs/cairo-1-programs/simple.cairo")] - #[case("../cairo_programs/cairo-1-programs/struct_span_return.cairo")] - fn check_append_ret_values_to_output_segment(#[case] filename: &str) { - // Compile to sierra - let sierra_program = compile_to_sierra(filename); - // Set proof_mode - let cairo_run_config = Cairo1RunConfig { - proof_mode: true, - layout: "all_cairo", - ..Default::default() - }; - // Run program - let (_, vm, return_values) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); - // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values - // And handle the panics returning an error, so we need to add it here - let return_values = if main_hash_panic_result(&sierra_program) { - let mut rv = vec![Felt252::ZERO.into(), Felt252::ZERO.into()]; - rv.extend_from_slice(&return_values); - rv - } else { - return_values - }; - // Check that the output segment contains the return values - // The output builtin will always be the first builtin, so we know it's segment is 2 - let output_builtin_segment = vm - .get_continuous_range((2, 0).into(), return_values.len()) - .unwrap(); - assert_eq!(output_builtin_segment, return_values, "{}", filename); - // Just for consistency, we will check that there are no values in the output segment after the return values - assert!(vm - .get_maybe(&Relocatable::from((2_isize, return_values.len()))) - .is_none()); - } -} diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs deleted file mode 100644 index ce711ca554..0000000000 --- a/cairo1-run/src/main.rs +++ /dev/null @@ -1,722 +0,0 @@ -#![allow(unused_imports)] -use bincode::enc::write::Writer; -use cairo_lang_casm::casm; -use cairo_lang_casm::casm_extend; -use cairo_lang_casm::hints::Hint; -use cairo_lang_casm::instructions::Instruction; -use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; -use cairo_lang_sierra::extensions::bitwise::BitwiseType; -use cairo_lang_sierra::extensions::core::{CoreLibfunc, CoreType}; -use cairo_lang_sierra::extensions::ec::EcOpType; -use cairo_lang_sierra::extensions::gas::GasBuiltinType; -use cairo_lang_sierra::extensions::pedersen::PedersenType; -use cairo_lang_sierra::extensions::poseidon::PoseidonType; -use cairo_lang_sierra::extensions::range_check::RangeCheckType; -use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType; -use cairo_lang_sierra::extensions::starknet::syscalls::SystemType; -use cairo_lang_sierra::extensions::ConcreteType; -use cairo_lang_sierra::extensions::NamedType; -use cairo_lang_sierra::ids::ConcreteTypeId; -use cairo_lang_sierra::program::Function; -use cairo_lang_sierra::program::Program as SierraProgram; -use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError}; -use cairo_lang_sierra::{extensions::gas::CostTokenType, ProgramParser}; -use cairo_lang_sierra_ap_change::calc_ap_changes; -use cairo_lang_sierra_gas::gas_info::GasInfo; -use cairo_lang_sierra_to_casm::compiler::CairoProgram; -use cairo_lang_sierra_to_casm::compiler::CompilationError; -use cairo_lang_sierra_to_casm::metadata::Metadata; -use cairo_lang_sierra_to_casm::metadata::MetadataComputationConfig; -use cairo_lang_sierra_to_casm::metadata::MetadataError; -use cairo_lang_sierra_to_casm::{compiler::compile, metadata::calc_metadata}; -use cairo_lang_sierra_type_size::get_type_size_map; -use cairo_lang_utils::ordered_hash_map::OrderedHashMap; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; -use cairo_vm::air_public_input::PublicInputError; -use cairo_vm::cairo_run; -use cairo_vm::cairo_run::EncodeTraceError; -use cairo_vm::hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor; -use cairo_vm::serde::deserialize_program::BuiltinName; -use cairo_vm::serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams}; -use cairo_vm::types::errors::program_errors::ProgramError; -use cairo_vm::types::relocatable::Relocatable; -use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; -use cairo_vm::vm::errors::memory_errors::MemoryError; -use cairo_vm::vm::errors::runner_errors::RunnerError; -use cairo_vm::vm::errors::trace_errors::TraceError; -use cairo_vm::vm::errors::vm_errors::VirtualMachineError; -use cairo_vm::{ - felt::Felt252, - serde::deserialize_program::ReferenceManager, - types::{program::Program, relocatable::MaybeRelocatable}, - vm::{ - runners::cairo_runner::{CairoRunner, RunResources}, - vm_core::VirtualMachine, - }, -}; -use clap::{CommandFactory, Parser, ValueHint}; -use itertools::{chain, Itertools}; -use std::borrow::Cow; -use std::io::BufWriter; -use std::io::Write; -use std::path::PathBuf; -use std::{collections::HashMap, io, path::Path}; -use thiserror::Error; - -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - #[clap(value_parser, value_hint=ValueHint::FilePath)] - filename: PathBuf, - #[clap(long = "trace_file", value_parser)] - trace_file: Option, - #[structopt(long = "memory_file")] - memory_file: Option, - #[clap(long = "layout", default_value = "plain", value_parser=validate_layout)] - layout: String, -} - -fn validate_layout(value: &str) -> Result { - match value { - "plain" - | "small" - | "dex" - | "starknet" - | "starknet_with_keccak" - | "recursive_large_output" - | "all_cairo" - | "all_solidity" - | "dynamic" => Ok(value.to_string()), - _ => Err(format!("{value} is not a valid layout")), - } -} - -#[derive(Debug, Error)] -enum Error { - #[error("Invalid arguments")] - Cli(#[from] clap::Error), - #[error("Failed to interact with the file system")] - IO(#[from] std::io::Error), - #[error(transparent)] - EncodeTrace(#[from] EncodeTraceError), - #[error(transparent)] - VirtualMachine(#[from] VirtualMachineError), - #[error(transparent)] - Trace(#[from] TraceError), - #[error(transparent)] - PublicInput(#[from] PublicInputError), - #[error(transparent)] - Runner(#[from] RunnerError), - #[error(transparent)] - ProgramRegistry(#[from] Box), - #[error(transparent)] - Compilation(#[from] Box), - #[error("Failed to compile to sierra:\n {0}")] - SierraCompilation(String), - #[error(transparent)] - Metadata(#[from] MetadataError), - #[error(transparent)] - Program(#[from] ProgramError), - #[error(transparent)] - Memory(#[from] MemoryError), - #[error("Program panicked with {0:?}")] - RunPanic(Vec), - #[error("Function signature has no return types")] - NoRetTypesInSignature, - #[error("No size for concrete type id: {0}")] - NoTypeSizeForId(ConcreteTypeId), - #[error("Concrete type id has no debug name: {0}")] - TypeIdNoDebugName(ConcreteTypeId), - #[error("No info in sierra program registry for concrete type id: {0}")] - NoInfoForType(ConcreteTypeId), - #[error("Failed to extract return values from VM")] - FailedToExtractReturnValues, -} - -pub struct FileWriter { - buf_writer: io::BufWriter, - bytes_written: usize, -} - -impl Writer for FileWriter { - fn write(&mut self, bytes: &[u8]) -> Result<(), bincode::error::EncodeError> { - self.buf_writer - .write_all(bytes) - .map_err(|e| bincode::error::EncodeError::Io { - inner: e, - index: self.bytes_written, - })?; - - self.bytes_written += bytes.len(); - - Ok(()) - } -} - -impl FileWriter { - fn new(buf_writer: io::BufWriter) -> Self { - Self { - buf_writer, - bytes_written: 0, - } - } - - fn flush(&mut self) -> io::Result<()> { - self.buf_writer.flush() - } -} - -fn run(args: impl Iterator) -> Result, Error> { - let args = Args::try_parse_from(args)?; - - let compiler_config = CompilerConfig { - replace_ids: true, - ..CompilerConfig::default() - }; - let sierra_program = (*compile_cairo_project_at_path(&args.filename, compiler_config) - .map_err(|err| Error::SierraCompilation(err.to_string()))?) - .clone(); - - let metadata_config = Some(Default::default()); - let gas_usage_check = metadata_config.is_some(); - let metadata = create_metadata(&sierra_program, metadata_config)?; - let sierra_program_registry = ProgramRegistry::::new(&sierra_program)?; - let type_sizes = - get_type_size_map(&sierra_program, &sierra_program_registry).unwrap_or_default(); - let casm_program = - cairo_lang_sierra_to_casm::compiler::compile(&sierra_program, &metadata, gas_usage_check)?; - - let main_func = find_function(&sierra_program, "::main")?; - - let initial_gas = 9999999999999_usize; - - // Entry code and footer are part of the whole instructions that are - // ran by the VM. - let (entry_code, builtins) = create_entry_code( - &sierra_program_registry, - &casm_program, - &type_sizes, - main_func, - initial_gas, - )?; - let footer = create_code_footer(); - - let check_gas_usage = true; - let metadata = calc_metadata(&sierra_program, Default::default(), false)?; - let casm_program = compile(&sierra_program, &metadata, check_gas_usage)?; - - let instructions = chain!( - entry_code.iter(), - casm_program.instructions.iter(), - footer.iter() - ); - - let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); - let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); - - let data: Vec = instructions - .flat_map(|inst| inst.assemble().encode()) - .map(Felt252::from) - .map(MaybeRelocatable::from) - .collect(); - - let data_len = data.len(); - - let program = Program::new( - builtins, - data, - Some(0), - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )?; - - let mut runner = CairoRunner::new(&program, &args.layout, false)?; - let mut vm = VirtualMachine::new(args.trace_file.is_some()); - let end = runner.initialize(&mut vm)?; - - additional_initialization(&mut vm, data_len)?; - - runner.run_until_pc(end, &mut vm, &mut hint_processor)?; - runner.end_run(true, false, &mut vm, &mut hint_processor)?; - - // Fetch return type data - let return_type_id = main_func - .signature - .ret_types - .last() - .ok_or(Error::NoRetTypesInSignature)?; - let return_type_size = type_sizes - .get(return_type_id) - .cloned() - .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; - - let mut return_values = vm.get_return_values(return_type_size as usize)?; - // Check if this result is a Panic result - if return_type_id - .debug_name - .as_ref() - .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? - .starts_with("core::panics::PanicResult::") - { - // Check the failure flag (aka first return value) - if return_values.first() != Some(&MaybeRelocatable::from(0)) { - // In case of failure, extract the error from teh return values (aka last two values) - let panic_data_end = return_values - .last() - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data_start = return_values - .get(return_values.len() - 2) - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data = vm.get_integer_range( - panic_data_start, - (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, - )?; - return Err(Error::RunPanic( - panic_data.iter().map(|c| c.as_ref().clone()).collect(), - )); - } else { - if return_values.len() < 3 { - return Err(Error::FailedToExtractReturnValues); - } - return_values = return_values[2..].to_vec() - } - } - - runner.relocate(&mut vm, true)?; - - if let Some(trace_path) = args.trace_file { - let relocated_trace = vm.get_relocated_trace()?; - let trace_file = std::fs::File::create(trace_path)?; - let mut trace_writer = - FileWriter::new(io::BufWriter::with_capacity(3 * 1024 * 1024, trace_file)); - - cairo_run::write_encoded_trace(relocated_trace, &mut trace_writer)?; - trace_writer.flush()?; - } - if let Some(memory_path) = args.memory_file { - let memory_file = std::fs::File::create(memory_path)?; - let mut memory_writer = - FileWriter::new(io::BufWriter::with_capacity(5 * 1024 * 1024, memory_file)); - - cairo_run::write_encoded_memory(&runner.relocated_memory, &mut memory_writer)?; - memory_writer.flush()?; - } - - Ok(return_values) -} - -fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { - // Create the builtin cost segment - let builtin_cost_segment = vm.add_memory_segment(); - for token_type in CostTokenType::iter_precost() { - vm.insert_value( - (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) - .map_err(VirtualMachineError::Math)?, - Felt252::default(), - )? - } - // Put a pointer to the builtin cost segment at the end of the program (after the - // additional `ret` statement). - vm.insert_value( - (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, - builtin_cost_segment, - )?; - - Ok(()) -} - -fn main() -> Result<(), Error> { - match run(std::env::args()) { - Err(Error::Cli(err)) => err.exit(), - Ok(return_values) => { - if !return_values.is_empty() { - let return_values_string_list = - return_values.iter().map(|m| m.to_string()).join(", "); - println!("Return values : [{}]", return_values_string_list); - } - Ok(()) - } - Err(Error::RunPanic(panic_data)) => { - if !panic_data.is_empty() { - let panic_data_string_list = panic_data - .iter() - .map(|m| { - // Try to parse to utf8 string - let msg = String::from_utf8(m.to_be_bytes().to_vec()); - if let Ok(msg) = msg { - format!("{} ('{}')", m, msg) - } else { - m.to_string() - } - }) - .join(", "); - println!("Run panicked with: [{}]", panic_data_string_list); - } - Ok(()) - } - Err(err) => Err(err), - } -} - -#[allow(clippy::type_complexity)] -fn build_hints_vec<'b>( - instructions: impl Iterator, -) -> (Vec<(usize, Vec)>, HashMap>) { - let mut hints: Vec<(usize, Vec)> = Vec::new(); - let mut program_hints: HashMap> = HashMap::new(); - - let mut hint_offset = 0; - - for instruction in instructions { - if !instruction.hints.is_empty() { - hints.push((hint_offset, instruction.hints.clone())); - program_hints.insert( - hint_offset, - vec![HintParams { - code: hint_offset.to_string(), - accessible_scopes: Vec::new(), - flow_tracking_data: FlowTrackingData { - ap_tracking: ApTracking::default(), - reference_ids: HashMap::new(), - }, - }], - ); - } - hint_offset += instruction.body.op_size(); - } - (hints, program_hints) -} - -/// Finds first function ending with `name_suffix`. -fn find_function<'a>( - sierra_program: &'a SierraProgram, - name_suffix: &'a str, -) -> Result<&'a Function, RunnerError> { - sierra_program - .funcs - .iter() - .find(|f| { - if let Some(name) = &f.id.debug_name { - name.ends_with(name_suffix) - } else { - false - } - }) - .ok_or_else(|| RunnerError::MissingMain) -} - -/// Creates a list of instructions that will be appended to the program's bytecode. -fn create_code_footer() -> Vec { - casm! { - // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` - // and `pc` registers. - ret; - } - .instructions -} - -/// Returns the instructions to add to the beginning of the code to successfully call the main -/// function, as well as the builtins required to execute the program. -fn create_entry_code( - sierra_program_registry: &ProgramRegistry, - casm_program: &CairoProgram, - type_sizes: &UnorderedHashMap, - func: &Function, - initial_gas: usize, -) -> Result<(Vec, Vec), Error> { - let mut ctx = casm! {}; - // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func); - // Load all vecs to memory. - let mut ap_offset: i16 = 0; - let after_vecs_offset = ap_offset; - if func.signature.param_types.iter().any(|ty| { - get_info(sierra_program_registry, ty) - .map(|x| x.long_id.generic_id == SegmentArenaType::ID) - .unwrap_or_default() - }) { - casm_extend! {ctx, - // SegmentArena segment. - %{ memory[ap + 0] = segments.add() %} - // Infos segment. - %{ memory[ap + 1] = segments.add() %} - ap += 2; - [ap + 0] = 0, ap++; - // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. - [ap - 2] = [[ap - 3]]; - [ap - 1] = [[ap - 3] + 1]; - [ap - 1] = [[ap - 3] + 2]; - } - ap_offset += 3; - } - for ty in func.signature.param_types.iter() { - let info = get_info(sierra_program_registry, ty) - .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; - let ty_size = type_sizes[ty]; - let generic_ty = &info.long_id.generic_id; - if let Some(offset) = builtin_offset.get(generic_ty) { - casm_extend! {ctx, - [ap + 0] = [fp - offset], ap++; - } - } else if generic_ty == &SystemType::ID { - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - } else if generic_ty == &GasBuiltinType::ID { - casm_extend! {ctx, - [ap + 0] = initial_gas, ap++; - } - } else if generic_ty == &SegmentArenaType::ID { - let offset = -ap_offset + after_vecs_offset; - casm_extend! {ctx, - [ap + 0] = [ap + offset] + 3, ap++; - } - // } else if let Some(Arg::Array(_)) = arg_iter.peek() { - // let values = extract_matches!(arg_iter.next().unwrap(), Arg::Array); - // let offset = -ap_offset + vecs.pop().unwrap(); - // expected_arguments_size += 1; - // casm_extend! {ctx, - // [ap + 0] = [ap + (offset)], ap++; - // [ap + 0] = [ap - 1] + (values.len()), ap++; - // } - // } else { - // let arg_size = ty_size; - // expected_arguments_size += arg_size as usize; - // for _ in 0..arg_size { - // if let Some(value) = arg_iter.next() { - // let value = extract_matches!(value, Arg::Value); - // casm_extend! {ctx, - // [ap + 0] = (value.to_bigint()), ap++; - // } - // } - // } - }; - ap_offset += ty_size; - } - // if expected_arguments_size != args.len() { - // return Err(RunnerError::ArgumentsSizeMismatch { - // expected: expected_arguments_size, - // actual: args.len(), - // }); - // } - let before_final_call = ctx.current_code_offset; - let final_call_size = 3; - let offset = final_call_size - + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; - casm_extend! {ctx, - call rel offset; - ret; - } - assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); - Ok((ctx.instructions, builtins)) -} - -fn get_info<'a>( - sierra_program_registry: &'a ProgramRegistry, - ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, -) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { - sierra_program_registry - .get_type(ty) - .ok() - .map(|ctc| ctc.info()) -} - -/// Creates the metadata required for a Sierra program lowering to casm. -fn create_metadata( - sierra_program: &cairo_lang_sierra::program::Program, - metadata_config: Option, -) -> Result { - if let Some(metadata_config) = metadata_config { - calc_metadata(sierra_program, metadata_config, false).map_err(|err| match err { - MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, - MetadataError::CostError(_) => VirtualMachineError::Unexpected, - }) - } else { - Ok(Metadata { - ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) - .map_err(|_| VirtualMachineError::Unexpected)?, - gas_info: GasInfo { - variable_values: Default::default(), - function_costs: Default::default(), - }, - }) - } -} - -fn get_function_builtins( - func: &Function, -) -> ( - Vec, - HashMap, -) { - let entry_params = &func.signature.param_types; - let mut builtins = Vec::new(); - let mut builtin_offset: HashMap = HashMap::new(); - let mut current_offset = 3; - // Fetch builtins from the entry_params in the standard order - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Poseidon".into())) - { - builtins.push(BuiltinName::poseidon); - builtin_offset.insert(PoseidonType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("EcOp".into())) - { - builtins.push(BuiltinName::ec_op); - builtin_offset.insert(EcOpType::ID, current_offset); - current_offset += 1 - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Bitwise".into())) - { - builtins.push(BuiltinName::bitwise); - builtin_offset.insert(BitwiseType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("RangeCheck".into())) - { - builtins.push(BuiltinName::range_check); - builtin_offset.insert(RangeCheckType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Pedersen".into())) - { - builtins.push(BuiltinName::pedersen); - builtin_offset.insert(PedersenType::ID, current_offset); - } - builtins.reverse(); - (builtins, builtin_offset) -} - -#[cfg(test)] -mod tests { - #![allow(clippy::too_many_arguments)] - use super::*; - use assert_matches::assert_matches; - use cairo_vm::felt::felt_str; - use rstest::rstest; - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/fibonacci.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_fibonacci_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(89)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/factorial.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_factorial_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(3628800)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/array_get.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_array_get_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(3)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/enum_flow.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_enum_flow_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(300)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/enum_match.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_enum_match_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(10), MaybeRelocatable::from(felt_str!("3618502788666131213697322783095070105623107215331596699973092056135872020471"))]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/hello.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_hello_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(1), MaybeRelocatable::from(1234)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/ops.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_ops_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(6)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/print.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_print_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/recursion.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_recursion_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1154076154663935037074198317650845438095734251249125412074882362667803016453"))]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/sample.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_sample_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("500000500000"))]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/poseidon.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_poseidon_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1099385018355113290651252669115094675591288647745213771718157553170111442461"))]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/poseidon_pedersen.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_poseidon_pedersen_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1036257840396636296853154602823055519264738423488122322497453114874087006398"))]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/pedersen_example.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_pedersen_example_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1089549915800264549621536909767699778745926517555586332772759280702396009108"))]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/simple.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_simple_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(1)]); - } - - #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/simple_struct.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] - fn test_run_simple_struct_ok(#[case] args: &[&str]) { - let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(100)]); - } -} From 2917b5b1a21609a89ed9a12cd5dd007b68b2d9b0 Mon Sep 17 00:00:00 2001 From: Federica Date: Fri, 7 Jun 2024 12:52:34 -0300 Subject: [PATCH 10/11] Revert "Remove cairo1-run" This reverts commit ef5d214ea895e40c1818e3b54c71e56c6e0551f0. --- cairo1-run/Cargo.toml | 33 ++ cairo1-run/Makefile | 33 ++ cairo1-run/README.md | 31 ++ cairo1-run/src/cairo_run.rs | 766 ++++++++++++++++++++++++++++++++++++ cairo1-run/src/main.rs | 722 +++++++++++++++++++++++++++++++++ 5 files changed, 1585 insertions(+) create mode 100644 cairo1-run/Cargo.toml create mode 100644 cairo1-run/Makefile create mode 100644 cairo1-run/README.md create mode 100644 cairo1-run/src/cairo_run.rs create mode 100644 cairo1-run/src/main.rs diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml new file mode 100644 index 0000000000..85b98e3927 --- /dev/null +++ b/cairo1-run/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "cairo1-run" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +keywords.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cairo-vm = {workspace = true, features = ["std", "cairo-1-hints"]} + +cairo-lang-sierra-type-size = { version = "2.3.1", default-features = false } +cairo-lang-sierra-ap-change = { version = "2.3.1", default-features = false } +cairo-lang-sierra-gas = { version = "2.3.1", default-features = false } +cairo-lang-sierra-to-casm.workspace = true +cairo-lang-compiler.workspace = true +cairo-lang-sierra.workspace = true +cairo-lang-utils.workspace = true +cairo-lang-casm.workspace = true +itertools = "0.11.0" +clap = { version = "4.3.10", features = ["derive"] } +thiserror = { version = "1.0.40" } +bincode.workspace = true +assert_matches = "1.5.0" +rstest = "0.17.0" +mimalloc = { version = "0.1.37", default-features = false, optional = true } + +[features] +default = ["with_mimalloc"] +with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] +lambdaworks-felt = ["cairo-vm/lambdaworks-felt"] diff --git a/cairo1-run/Makefile b/cairo1-run/Makefile new file mode 100644 index 0000000000..337cf3a53e --- /dev/null +++ b/cairo1-run/Makefile @@ -0,0 +1,33 @@ +.PHONY: deps factorial fibonacci test clean run + +CAIRO_1_FOLDER=../cairo_programs/cairo-1-programs + +$(CAIRO_1_FOLDER)/%.trace: $(CAIRO_1_FOLDER)/%.cairo + cargo run --release $< --trace_file $@ --layout all_cairo + +$(CAIRO_1_FOLDER)/%.memory: $(CAIRO_1_FOLDER)/%.cairo + cargo run --release $< --memory_file $@ --layout all_cairo + +CAIRO_1_PROGRAMS=$(wildcard ../cairo_programs/cairo-1-programs/*.cairo) +TRACES:=$(patsubst $(CAIRO_1_FOLDER)/%.cairo, $(CAIRO_1_FOLDER)/%.trace, $(CAIRO_1_PROGRAMS)) +MEMORY:=$(patsubst $(CAIRO_1_FOLDER)/%.cairo, $(CAIRO_1_FOLDER)/%.memory, $(CAIRO_1_PROGRAMS)) + +deps: + git clone https://github.com/starkware-libs/cairo.git \ + && cd cairo \ + && git checkout v2.3.1 \ + && cd .. \ + && mv cairo/corelib/ . \ + && rm -rf cairo/ + +run: $(TRACES) $(MEMORY) + +test: + cargo test + +clean: + rm -rf corelib + rm -rf cairo + rm -rf ../cairo_programs/cairo-1-programs/*.memory + rm -rf ../cairo_programs/cairo-1-programs/*.trace + diff --git a/cairo1-run/README.md b/cairo1-run/README.md new file mode 100644 index 0000000000..95be3b5ecb --- /dev/null +++ b/cairo1-run/README.md @@ -0,0 +1,31 @@ +# Cairo1-run + +A cairo-vm crate to run Cairo 1 Programs + +Once you are inside the `./cairo1-run` folder, use the CLI with the following commands + +To install the required dependencies(cairo corelib) run +```bash +make deps +``` + +Now that you have the dependencies necessary to run the tests, you can run: + +```bash +make test +``` + +To execute a cairo 1 program +```bash +cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo +``` + +Arguments to generate the trace and memory files +```bash +cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo --trace_file ../cairo_programs/cairo-1-programs/fibonacci.trace --memory_file ../cairo_programs/cairo-1-programs/fibonacci.memory +``` + +To execute all the cairo 1 programs inside `../cairo_programs/cairo-1-programs/` and generate the corresponding trace and the memory files +```bash +make run +``` diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs new file mode 100644 index 0000000000..5dbda6c672 --- /dev/null +++ b/cairo1-run/src/cairo_run.rs @@ -0,0 +1,766 @@ +use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; +use cairo_lang_sierra::{ + extensions::{ + bitwise::BitwiseType, + core::{CoreLibfunc, CoreType}, + ec::EcOpType, + gas::{CostTokenType, GasBuiltinType}, + pedersen::PedersenType, + poseidon::PoseidonType, + range_check::RangeCheckType, + segment_arena::SegmentArenaType, + starknet::syscalls::SystemType, + ConcreteType, NamedType, + }, + ids::ConcreteTypeId, + program::{Function, Program as SierraProgram}, + program_registry::ProgramRegistry, +}; +use cairo_lang_sierra_ap_change::calc_ap_changes; +use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_to_casm::{ + compiler::CairoProgram, + metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, +}; +use cairo_lang_sierra_type_size::get_type_size_map; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_vm::{ + hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + serde::deserialize_program::{ + ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, + }, + types::{program::Program, relocatable::MaybeRelocatable}, + vm::{ + errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, + runners::{ + builtin_runner::{ + BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, + POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, + }, + cairo_runner::{CairoRunner, RunResources, RunnerMode}, + }, + vm_core::VirtualMachine, + }, + Felt252, +}; +use itertools::chain; +use std::collections::HashMap; + +use crate::{Error, FuncArg}; + +#[derive(Debug)] +pub struct Cairo1RunConfig<'a> { + pub args: &'a [FuncArg], + pub trace_enabled: bool, + pub relocate_mem: bool, + pub layout: &'a str, + pub proof_mode: bool, + // Should be true if either air_public_input or cairo_pie_output are needed + // Sets builtins stop_ptr by calling `final_stack` on each builtin + pub finalize_builtins: bool, +} + +impl Default for Cairo1RunConfig<'_> { + fn default() -> Self { + Self { + args: Default::default(), + trace_enabled: false, + relocate_mem: false, + layout: "plain", + proof_mode: false, + finalize_builtins: false, + } + } +} + +// Runs a Cairo 1 program +// Returns the runner & VM after execution + the return values +pub fn cairo_run_program( + sierra_program: &SierraProgram, + cairo_run_config: Cairo1RunConfig, +) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { + let metadata = create_metadata(sierra_program, Some(Default::default()))?; + let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; + let type_sizes = + get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); + let casm_program = + cairo_lang_sierra_to_casm::compiler::compile(sierra_program, &metadata, true)?; + + let main_func = find_function(sierra_program, "::main")?; + + let initial_gas = 9999999999999_usize; + + // Modified entry code to be compatible with custom cairo1 Proof Mode. + // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 + let (entry_code, builtins) = create_entry_code( + &sierra_program_registry, + &casm_program, + &type_sizes, + main_func, + initial_gas, + cairo_run_config.proof_mode, + cairo_run_config.args, + )?; + + // Fetch return type data + let return_type_id = main_func + .signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + + // This footer is used by lib funcs + let libfunc_footer = create_code_footer(); + + // Header used to initiate the infinite loop after executing the program + // Also appends return values to output segment + let proof_mode_header = if cairo_run_config.proof_mode { + create_proof_mode_header(builtins.len() as i16, return_type_size) + } else { + casm! {}.instructions + }; + + // This is the program we are actually running/proving + // With (embedded proof mode), cairo1 header and the libfunc footer + let instructions = chain!( + proof_mode_header.iter(), + entry_code.iter(), + casm_program.instructions.iter(), + libfunc_footer.iter(), + ); + + let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); + + let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); + + let data: Vec = instructions + .flat_map(|inst| inst.assemble().encode()) + .map(|x| Felt252::from(&x)) + .map(MaybeRelocatable::from) + .collect(); + + let data_len = data.len(); + + let program = if cairo_run_config.proof_mode { + Program::new_for_proof( + builtins, + data, + 0, + // Proof mode is on top + // jmp rel 0 is on PC == 2 + 2, + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + } else { + Program::new( + builtins, + data, + Some(0), + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + }; + + let runner_mode = if cairo_run_config.proof_mode { + RunnerMode::ProofModeCairo1 + } else { + RunnerMode::ExecutionMode + }; + + let mut runner = CairoRunner::new_v2(&program, cairo_run_config.layout, runner_mode)?; + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + let end = runner.initialize(&mut vm, cairo_run_config.proof_mode)?; + + additional_initialization(&mut vm, data_len)?; + + // Run it until the end / infinite loop in proof_mode + runner.run_until_pc(end, &mut vm, &mut hint_processor)?; + if cairo_run_config.proof_mode { + // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 + // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size + vm.segments + .segment_sizes + .insert(2, return_type_size as usize); + } + runner.end_run(false, false, &mut vm, &mut hint_processor)?; + + // Fetch return values + let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + + // Set stop pointers for builtins so we can obtain the air public input + if cairo_run_config.finalize_builtins { + finalize_builtins( + cairo_run_config.proof_mode, + &main_func.signature.ret_types, + &type_sizes, + &mut vm, + )?; + + // Build execution public memory + if cairo_run_config.proof_mode { + // As the output builtin is not used by the program we need to compute it's stop ptr manually + vm.set_output_stop_ptr_offset(return_type_size as usize); + + runner.finalize_segments(&mut vm)?; + } + } + + runner.relocate(&mut vm, true)?; + + Ok((runner, vm, return_values)) +} + +fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { + // Create the builtin cost segment + let builtin_cost_segment = vm.add_memory_segment(); + for token_type in CostTokenType::iter_precost() { + vm.insert_value( + (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) + .map_err(VirtualMachineError::Math)?, + Felt252::default(), + )? + } + // Put a pointer to the builtin cost segment at the end of the program (after the + // additional `ret` statement). + vm.insert_value( + (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, + builtin_cost_segment, + )?; + + Ok(()) +} + +#[allow(clippy::type_complexity)] +fn build_hints_vec<'b>( + instructions: impl Iterator, +) -> (Vec<(usize, Vec)>, HashMap>) { + let mut hints: Vec<(usize, Vec)> = Vec::new(); + let mut program_hints: HashMap> = HashMap::new(); + + let mut hint_offset = 0; + + for instruction in instructions { + if !instruction.hints.is_empty() { + hints.push((hint_offset, instruction.hints.clone())); + program_hints.insert( + hint_offset, + vec![HintParams { + code: hint_offset.to_string(), + accessible_scopes: Vec::new(), + flow_tracking_data: FlowTrackingData { + ap_tracking: ApTracking::default(), + reference_ids: HashMap::new(), + }, + }], + ); + } + hint_offset += instruction.body.op_size(); + } + (hints, program_hints) +} + +/// Finds first function ending with `name_suffix`. +fn find_function<'a>( + sierra_program: &'a SierraProgram, + name_suffix: &'a str, +) -> Result<&'a Function, RunnerError> { + sierra_program + .funcs + .iter() + .find(|f| { + if let Some(name) = &f.id.debug_name { + name.ends_with(name_suffix) + } else { + false + } + }) + .ok_or_else(|| RunnerError::MissingMain) +} + +/// Creates a list of instructions that will be appended to the program's bytecode. +fn create_code_footer() -> Vec { + casm! { + // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` + // and `pc` registers. + ret; + } + .instructions +} + +// Create proof_mode specific instructions +// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) +// wich call the firt program instruction and then initiate an infinite loop. +// And also appending the return values to the output builtin's memory segment +fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { + // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty + // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases + let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments + + // The pc offset where the original program should start + // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) + // and also 1 for each instruction added to copy each return value into the output segment + let program_start_offset: i16 = 4 + return_type_size; + + let mut ctx = casm! {}; + casm_extend! {ctx, + call rel program_start_offset; // Begin program execution by calling the first instruction in the original program + }; + // Append each return value to the output segment + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + casm_extend! {ctx, + jmp rel 0; // Infinite loop + }; + ctx.instructions +} + +/// Returns the instructions to add to the beginning of the code to successfully call the main +/// function, as well as the builtins required to execute the program. +fn create_entry_code( + sierra_program_registry: &ProgramRegistry, + casm_program: &CairoProgram, + type_sizes: &UnorderedHashMap, + func: &Function, + initial_gas: usize, + proof_mode: bool, + args: &[FuncArg], +) -> Result<(Vec, Vec), Error> { + let mut ctx = casm! {}; + // The builtins in the formatting expected by the runner. + let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); + + // Load all vecs to memory. + // Load all array args content to memory. + let mut array_args_data = vec![]; + let mut ap_offset: i16 = 0; + for arg in args { + let FuncArg::Array(values) = arg else { + continue; + }; + array_args_data.push(ap_offset); + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + for (i, v) in values.iter().enumerate() { + let arr_at = (i + 1) as i16; + casm_extend! {ctx, + [ap + 0] = (v.to_bigint()); + [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; + }; + } + ap_offset += (1 + values.len()) as i16; + } + let mut array_args_data_iter = array_args_data.iter(); + let after_arrays_data_offset = ap_offset; + let mut arg_iter = args.iter().enumerate(); + let mut param_index = 0; + let mut expected_arguments_size = 0; + if func.signature.param_types.iter().any(|ty| { + get_info(sierra_program_registry, ty) + .map(|x| x.long_id.generic_id == SegmentArenaType::ID) + .unwrap_or_default() + }) { + casm_extend! {ctx, + // SegmentArena segment. + %{ memory[ap + 0] = segments.add() %} + // Infos segment. + %{ memory[ap + 1] = segments.add() %} + ap += 2; + [ap + 0] = 0, ap++; + // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. + [ap - 2] = [[ap - 3]]; + [ap - 1] = [[ap - 3] + 1]; + [ap - 1] = [[ap - 3] + 2]; + } + ap_offset += 3; + } + for ty in func.signature.param_types.iter() { + let info = get_info(sierra_program_registry, ty) + .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; + let generic_ty = &info.long_id.generic_id; + if let Some(offset) = builtin_offset.get(generic_ty) { + let mut offset = *offset; + if proof_mode { + // Everything is off by 2 due to the proof mode header + offset += 2; + } + casm_extend! {ctx, + [ap + 0] = [fp - offset], ap++; + } + ap_offset += 1; + } else if generic_ty == &SystemType::ID { + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + ap_offset += 1; + } else if generic_ty == &GasBuiltinType::ID { + casm_extend! {ctx, + [ap + 0] = initial_gas, ap++; + } + ap_offset += 1; + } else if generic_ty == &SegmentArenaType::ID { + let offset = -ap_offset + after_arrays_data_offset; + casm_extend! {ctx, + [ap + 0] = [ap + offset] + 3, ap++; + } + ap_offset += 1; + } else { + let ty_size = type_sizes[ty]; + let param_ap_offset_end = ap_offset + ty_size; + expected_arguments_size += ty_size; + while ap_offset < param_ap_offset_end { + let Some((arg_index, arg)) = arg_iter.next() else { + break; + }; + match arg { + FuncArg::Single(value) => { + casm_extend! {ctx, + [ap + 0] = (value.to_bigint()), ap++; + } + ap_offset += 1; + } + FuncArg::Array(values) => { + let offset = -ap_offset + array_args_data_iter.next().unwrap(); + casm_extend! {ctx, + [ap + 0] = [ap + (offset)], ap++; + [ap + 0] = [ap - 1] + (values.len()), ap++; + } + ap_offset += 2; + if ap_offset > param_ap_offset_end { + return Err(Error::ArgumentUnaligned { + param_index, + arg_index, + }); + } + } + } + } + param_index += 1; + }; + } + let actual_args_size = args + .iter() + .map(|arg| match arg { + FuncArg::Single(_) => 1, + FuncArg::Array(_) => 2, + }) + .sum::(); + if expected_arguments_size != actual_args_size { + return Err(Error::ArgumentsSizeMismatch { + expected: expected_arguments_size, + actual: actual_args_size, + }); + } + + let before_final_call = ctx.current_code_offset; + let final_call_size = 3; + let offset = final_call_size + + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; + + casm_extend! {ctx, + call rel offset; + ret; + } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); + + Ok((ctx.instructions, builtins)) +} + +fn get_info<'a>( + sierra_program_registry: &'a ProgramRegistry, + ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, +) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { + sierra_program_registry + .get_type(ty) + .ok() + .map(|ctc| ctc.info()) +} + +/// Creates the metadata required for a Sierra program lowering to casm. +fn create_metadata( + sierra_program: &cairo_lang_sierra::program::Program, + metadata_config: Option, +) -> Result { + if let Some(metadata_config) = metadata_config { + calc_metadata(sierra_program, metadata_config).map_err(|err| match err { + MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, + MetadataError::CostError(_) => VirtualMachineError::Unexpected, + }) + } else { + Ok(Metadata { + ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) + .map_err(|_| VirtualMachineError::Unexpected)?, + gas_info: GasInfo { + variable_values: Default::default(), + function_costs: Default::default(), + }, + }) + } +} + +/// Type representing the Output builtin. +#[derive(Default)] +pub struct OutputType {} +impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { + const ID: cairo_lang_sierra::ids::GenericTypeId = + cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); + const STORABLE: bool = true; + const DUPLICATABLE: bool = false; + const DROPPABLE: bool = false; + const ZERO_SIZED: bool = false; +} + +fn get_function_builtins( + func: &Function, + proof_mode: bool, +) -> ( + Vec, + HashMap, +) { + let entry_params = &func.signature.param_types; + let mut builtins = Vec::new(); + let mut builtin_offset: HashMap = HashMap::new(); + let mut current_offset = 3; + // Fetch builtins from the entry_params in the standard order + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Poseidon".into())) + { + builtins.push(BuiltinName::poseidon); + builtin_offset.insert(PoseidonType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("EcOp".into())) + { + builtins.push(BuiltinName::ec_op); + builtin_offset.insert(EcOpType::ID, current_offset); + current_offset += 1 + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Bitwise".into())) + { + builtins.push(BuiltinName::bitwise); + builtin_offset.insert(BitwiseType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("RangeCheck".into())) + { + builtins.push(BuiltinName::range_check); + builtin_offset.insert(RangeCheckType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Pedersen".into())) + { + builtins.push(BuiltinName::pedersen); + builtin_offset.insert(PedersenType::ID, current_offset); + current_offset += 1; + } + // Force an output builtin so that we can write the program output into it's segment + if proof_mode { + builtins.push(BuiltinName::output); + builtin_offset.insert(OutputType::ID, current_offset); + } + builtins.reverse(); + (builtins, builtin_offset) +} + +fn fetch_return_values( + return_type_size: i16, + return_type_id: &ConcreteTypeId, + vm: &VirtualMachine, +) -> Result, Error> { + let mut return_values = vm.get_return_values(return_type_size as usize)?; + // Check if this result is a Panic result + if return_type_id + .debug_name + .as_ref() + .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? + .starts_with("core::panics::PanicResult::") + { + // Check the failure flag (aka first return value) + if return_values.first() != Some(&MaybeRelocatable::from(0)) { + // In case of failure, extract the error from the return values (aka last two values) + let panic_data_end = return_values + .last() + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data_start = return_values + .get(return_values.len() - 2) + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data = vm.get_integer_range( + panic_data_start, + (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, + )?; + return Err(Error::RunPanic( + panic_data.iter().map(|c| *c.as_ref()).collect(), + )); + } else { + if return_values.len() < 3 { + return Err(Error::FailedToExtractReturnValues); + } + return_values = return_values[2..].to_vec() + } + } + Ok(return_values) +} + +// Calculates builtins' final_stack setting each stop_ptr +// Calling this function is a must if either air_public_input or cairo_pie are needed +fn finalize_builtins( + proof_mode: bool, + main_ret_types: &[ConcreteTypeId], + type_sizes: &UnorderedHashMap, + vm: &mut VirtualMachine, +) -> Result<(), Error> { + // Set stop pointers for builtins so we can obtain the air public input + // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them + let ret_types_sizes = main_ret_types + .iter() + .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); + let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); + + let full_ret_types_size: i16 = ret_types_sizes.sum(); + let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) + .map_err(VirtualMachineError::Math)?; + + // Calculate the stack_ptr for each return builtin in the return values + let mut builtin_name_to_stack_pointer = HashMap::new(); + for (id, size) in ret_types_and_sizes { + if let Some(ref name) = id.debug_name { + let builtin_name = match &*name.to_string() { + "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, + "Poseidon" => POSEIDON_BUILTIN_NAME, + "EcOp" => EC_OP_BUILTIN_NAME, + "Bitwise" => BITWISE_BUILTIN_NAME, + "Pedersen" => HASH_BUILTIN_NAME, + "Output" => OUTPUT_BUILTIN_NAME, + "Ecdsa" => SIGNATURE_BUILTIN_NAME, + _ => { + stack_pointer.offset += size as usize; + continue; + } + }; + builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); + } + stack_pointer.offset += size as usize; + } + + // Set stop pointer for each builtin + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; + use cairo_vm::types::relocatable::Relocatable; + use rstest::rstest; + + fn compile_to_sierra(filename: &str) -> SierraProgram { + let compiler_config = CompilerConfig { + replace_ids: true, + ..CompilerConfig::default() + }; + + compile_cairo_project_at_path(Path::new(filename), compiler_config).unwrap() + } + + fn main_hash_panic_result(sierra_program: &SierraProgram) -> bool { + let main_func = find_function(sierra_program, "::main").unwrap(); + main_func + .signature + .ret_types + .last() + .and_then(|rt| { + rt.debug_name + .as_ref() + .map(|n| n.as_ref().starts_with("core::panics::PanicResult::")) + }) + .unwrap_or_default() + } + + #[rstest] + #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] + #[case("../cairo_programs/cairo-1-programs/array_get.cairo")] + #[case("../cairo_programs/cairo-1-programs/dictionaries.cairo")] + #[case("../cairo_programs/cairo-1-programs/enum_flow.cairo")] + #[case("../cairo_programs/cairo-1-programs/enum_match.cairo")] + #[case("../cairo_programs/cairo-1-programs/factorial.cairo")] + #[case("../cairo_programs/cairo-1-programs/fibonacci.cairo")] + #[case("../cairo_programs/cairo-1-programs/hello.cairo")] + #[case("../cairo_programs/cairo-1-programs/pedersen_example.cairo")] + #[case("../cairo_programs/cairo-1-programs/poseidon.cairo")] + #[case("../cairo_programs/cairo-1-programs/print.cairo")] + #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] + #[case("../cairo_programs/cairo-1-programs/recursion.cairo")] + #[case("../cairo_programs/cairo-1-programs/sample.cairo")] + #[case("../cairo_programs/cairo-1-programs/simple_struct.cairo")] + #[case("../cairo_programs/cairo-1-programs/simple.cairo")] + #[case("../cairo_programs/cairo-1-programs/struct_span_return.cairo")] + fn check_append_ret_values_to_output_segment(#[case] filename: &str) { + // Compile to sierra + let sierra_program = compile_to_sierra(filename); + // Set proof_mode + let cairo_run_config = Cairo1RunConfig { + proof_mode: true, + layout: "all_cairo", + ..Default::default() + }; + // Run program + let (_, vm, return_values) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); + // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values + // And handle the panics returning an error, so we need to add it here + let return_values = if main_hash_panic_result(&sierra_program) { + let mut rv = vec![Felt252::ZERO.into(), Felt252::ZERO.into()]; + rv.extend_from_slice(&return_values); + rv + } else { + return_values + }; + // Check that the output segment contains the return values + // The output builtin will always be the first builtin, so we know it's segment is 2 + let output_builtin_segment = vm + .get_continuous_range((2, 0).into(), return_values.len()) + .unwrap(); + assert_eq!(output_builtin_segment, return_values, "{}", filename); + // Just for consistency, we will check that there are no values in the output segment after the return values + assert!(vm + .get_maybe(&Relocatable::from((2_isize, return_values.len()))) + .is_none()); + } +} diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs new file mode 100644 index 0000000000..ce711ca554 --- /dev/null +++ b/cairo1-run/src/main.rs @@ -0,0 +1,722 @@ +#![allow(unused_imports)] +use bincode::enc::write::Writer; +use cairo_lang_casm::casm; +use cairo_lang_casm::casm_extend; +use cairo_lang_casm::hints::Hint; +use cairo_lang_casm::instructions::Instruction; +use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; +use cairo_lang_sierra::extensions::bitwise::BitwiseType; +use cairo_lang_sierra::extensions::core::{CoreLibfunc, CoreType}; +use cairo_lang_sierra::extensions::ec::EcOpType; +use cairo_lang_sierra::extensions::gas::GasBuiltinType; +use cairo_lang_sierra::extensions::pedersen::PedersenType; +use cairo_lang_sierra::extensions::poseidon::PoseidonType; +use cairo_lang_sierra::extensions::range_check::RangeCheckType; +use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType; +use cairo_lang_sierra::extensions::starknet::syscalls::SystemType; +use cairo_lang_sierra::extensions::ConcreteType; +use cairo_lang_sierra::extensions::NamedType; +use cairo_lang_sierra::ids::ConcreteTypeId; +use cairo_lang_sierra::program::Function; +use cairo_lang_sierra::program::Program as SierraProgram; +use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError}; +use cairo_lang_sierra::{extensions::gas::CostTokenType, ProgramParser}; +use cairo_lang_sierra_ap_change::calc_ap_changes; +use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_to_casm::compiler::CairoProgram; +use cairo_lang_sierra_to_casm::compiler::CompilationError; +use cairo_lang_sierra_to_casm::metadata::Metadata; +use cairo_lang_sierra_to_casm::metadata::MetadataComputationConfig; +use cairo_lang_sierra_to_casm::metadata::MetadataError; +use cairo_lang_sierra_to_casm::{compiler::compile, metadata::calc_metadata}; +use cairo_lang_sierra_type_size::get_type_size_map; +use cairo_lang_utils::ordered_hash_map::OrderedHashMap; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_vm::air_public_input::PublicInputError; +use cairo_vm::cairo_run; +use cairo_vm::cairo_run::EncodeTraceError; +use cairo_vm::hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor; +use cairo_vm::serde::deserialize_program::BuiltinName; +use cairo_vm::serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams}; +use cairo_vm::types::errors::program_errors::ProgramError; +use cairo_vm::types::relocatable::Relocatable; +use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; +use cairo_vm::vm::errors::memory_errors::MemoryError; +use cairo_vm::vm::errors::runner_errors::RunnerError; +use cairo_vm::vm::errors::trace_errors::TraceError; +use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +use cairo_vm::{ + felt::Felt252, + serde::deserialize_program::ReferenceManager, + types::{program::Program, relocatable::MaybeRelocatable}, + vm::{ + runners::cairo_runner::{CairoRunner, RunResources}, + vm_core::VirtualMachine, + }, +}; +use clap::{CommandFactory, Parser, ValueHint}; +use itertools::{chain, Itertools}; +use std::borrow::Cow; +use std::io::BufWriter; +use std::io::Write; +use std::path::PathBuf; +use std::{collections::HashMap, io, path::Path}; +use thiserror::Error; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(value_parser, value_hint=ValueHint::FilePath)] + filename: PathBuf, + #[clap(long = "trace_file", value_parser)] + trace_file: Option, + #[structopt(long = "memory_file")] + memory_file: Option, + #[clap(long = "layout", default_value = "plain", value_parser=validate_layout)] + layout: String, +} + +fn validate_layout(value: &str) -> Result { + match value { + "plain" + | "small" + | "dex" + | "starknet" + | "starknet_with_keccak" + | "recursive_large_output" + | "all_cairo" + | "all_solidity" + | "dynamic" => Ok(value.to_string()), + _ => Err(format!("{value} is not a valid layout")), + } +} + +#[derive(Debug, Error)] +enum Error { + #[error("Invalid arguments")] + Cli(#[from] clap::Error), + #[error("Failed to interact with the file system")] + IO(#[from] std::io::Error), + #[error(transparent)] + EncodeTrace(#[from] EncodeTraceError), + #[error(transparent)] + VirtualMachine(#[from] VirtualMachineError), + #[error(transparent)] + Trace(#[from] TraceError), + #[error(transparent)] + PublicInput(#[from] PublicInputError), + #[error(transparent)] + Runner(#[from] RunnerError), + #[error(transparent)] + ProgramRegistry(#[from] Box), + #[error(transparent)] + Compilation(#[from] Box), + #[error("Failed to compile to sierra:\n {0}")] + SierraCompilation(String), + #[error(transparent)] + Metadata(#[from] MetadataError), + #[error(transparent)] + Program(#[from] ProgramError), + #[error(transparent)] + Memory(#[from] MemoryError), + #[error("Program panicked with {0:?}")] + RunPanic(Vec), + #[error("Function signature has no return types")] + NoRetTypesInSignature, + #[error("No size for concrete type id: {0}")] + NoTypeSizeForId(ConcreteTypeId), + #[error("Concrete type id has no debug name: {0}")] + TypeIdNoDebugName(ConcreteTypeId), + #[error("No info in sierra program registry for concrete type id: {0}")] + NoInfoForType(ConcreteTypeId), + #[error("Failed to extract return values from VM")] + FailedToExtractReturnValues, +} + +pub struct FileWriter { + buf_writer: io::BufWriter, + bytes_written: usize, +} + +impl Writer for FileWriter { + fn write(&mut self, bytes: &[u8]) -> Result<(), bincode::error::EncodeError> { + self.buf_writer + .write_all(bytes) + .map_err(|e| bincode::error::EncodeError::Io { + inner: e, + index: self.bytes_written, + })?; + + self.bytes_written += bytes.len(); + + Ok(()) + } +} + +impl FileWriter { + fn new(buf_writer: io::BufWriter) -> Self { + Self { + buf_writer, + bytes_written: 0, + } + } + + fn flush(&mut self) -> io::Result<()> { + self.buf_writer.flush() + } +} + +fn run(args: impl Iterator) -> Result, Error> { + let args = Args::try_parse_from(args)?; + + let compiler_config = CompilerConfig { + replace_ids: true, + ..CompilerConfig::default() + }; + let sierra_program = (*compile_cairo_project_at_path(&args.filename, compiler_config) + .map_err(|err| Error::SierraCompilation(err.to_string()))?) + .clone(); + + let metadata_config = Some(Default::default()); + let gas_usage_check = metadata_config.is_some(); + let metadata = create_metadata(&sierra_program, metadata_config)?; + let sierra_program_registry = ProgramRegistry::::new(&sierra_program)?; + let type_sizes = + get_type_size_map(&sierra_program, &sierra_program_registry).unwrap_or_default(); + let casm_program = + cairo_lang_sierra_to_casm::compiler::compile(&sierra_program, &metadata, gas_usage_check)?; + + let main_func = find_function(&sierra_program, "::main")?; + + let initial_gas = 9999999999999_usize; + + // Entry code and footer are part of the whole instructions that are + // ran by the VM. + let (entry_code, builtins) = create_entry_code( + &sierra_program_registry, + &casm_program, + &type_sizes, + main_func, + initial_gas, + )?; + let footer = create_code_footer(); + + let check_gas_usage = true; + let metadata = calc_metadata(&sierra_program, Default::default(), false)?; + let casm_program = compile(&sierra_program, &metadata, check_gas_usage)?; + + let instructions = chain!( + entry_code.iter(), + casm_program.instructions.iter(), + footer.iter() + ); + + let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); + let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); + + let data: Vec = instructions + .flat_map(|inst| inst.assemble().encode()) + .map(Felt252::from) + .map(MaybeRelocatable::from) + .collect(); + + let data_len = data.len(); + + let program = Program::new( + builtins, + data, + Some(0), + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )?; + + let mut runner = CairoRunner::new(&program, &args.layout, false)?; + let mut vm = VirtualMachine::new(args.trace_file.is_some()); + let end = runner.initialize(&mut vm)?; + + additional_initialization(&mut vm, data_len)?; + + runner.run_until_pc(end, &mut vm, &mut hint_processor)?; + runner.end_run(true, false, &mut vm, &mut hint_processor)?; + + // Fetch return type data + let return_type_id = main_func + .signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + + let mut return_values = vm.get_return_values(return_type_size as usize)?; + // Check if this result is a Panic result + if return_type_id + .debug_name + .as_ref() + .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? + .starts_with("core::panics::PanicResult::") + { + // Check the failure flag (aka first return value) + if return_values.first() != Some(&MaybeRelocatable::from(0)) { + // In case of failure, extract the error from teh return values (aka last two values) + let panic_data_end = return_values + .last() + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data_start = return_values + .get(return_values.len() - 2) + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data = vm.get_integer_range( + panic_data_start, + (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, + )?; + return Err(Error::RunPanic( + panic_data.iter().map(|c| c.as_ref().clone()).collect(), + )); + } else { + if return_values.len() < 3 { + return Err(Error::FailedToExtractReturnValues); + } + return_values = return_values[2..].to_vec() + } + } + + runner.relocate(&mut vm, true)?; + + if let Some(trace_path) = args.trace_file { + let relocated_trace = vm.get_relocated_trace()?; + let trace_file = std::fs::File::create(trace_path)?; + let mut trace_writer = + FileWriter::new(io::BufWriter::with_capacity(3 * 1024 * 1024, trace_file)); + + cairo_run::write_encoded_trace(relocated_trace, &mut trace_writer)?; + trace_writer.flush()?; + } + if let Some(memory_path) = args.memory_file { + let memory_file = std::fs::File::create(memory_path)?; + let mut memory_writer = + FileWriter::new(io::BufWriter::with_capacity(5 * 1024 * 1024, memory_file)); + + cairo_run::write_encoded_memory(&runner.relocated_memory, &mut memory_writer)?; + memory_writer.flush()?; + } + + Ok(return_values) +} + +fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { + // Create the builtin cost segment + let builtin_cost_segment = vm.add_memory_segment(); + for token_type in CostTokenType::iter_precost() { + vm.insert_value( + (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) + .map_err(VirtualMachineError::Math)?, + Felt252::default(), + )? + } + // Put a pointer to the builtin cost segment at the end of the program (after the + // additional `ret` statement). + vm.insert_value( + (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, + builtin_cost_segment, + )?; + + Ok(()) +} + +fn main() -> Result<(), Error> { + match run(std::env::args()) { + Err(Error::Cli(err)) => err.exit(), + Ok(return_values) => { + if !return_values.is_empty() { + let return_values_string_list = + return_values.iter().map(|m| m.to_string()).join(", "); + println!("Return values : [{}]", return_values_string_list); + } + Ok(()) + } + Err(Error::RunPanic(panic_data)) => { + if !panic_data.is_empty() { + let panic_data_string_list = panic_data + .iter() + .map(|m| { + // Try to parse to utf8 string + let msg = String::from_utf8(m.to_be_bytes().to_vec()); + if let Ok(msg) = msg { + format!("{} ('{}')", m, msg) + } else { + m.to_string() + } + }) + .join(", "); + println!("Run panicked with: [{}]", panic_data_string_list); + } + Ok(()) + } + Err(err) => Err(err), + } +} + +#[allow(clippy::type_complexity)] +fn build_hints_vec<'b>( + instructions: impl Iterator, +) -> (Vec<(usize, Vec)>, HashMap>) { + let mut hints: Vec<(usize, Vec)> = Vec::new(); + let mut program_hints: HashMap> = HashMap::new(); + + let mut hint_offset = 0; + + for instruction in instructions { + if !instruction.hints.is_empty() { + hints.push((hint_offset, instruction.hints.clone())); + program_hints.insert( + hint_offset, + vec![HintParams { + code: hint_offset.to_string(), + accessible_scopes: Vec::new(), + flow_tracking_data: FlowTrackingData { + ap_tracking: ApTracking::default(), + reference_ids: HashMap::new(), + }, + }], + ); + } + hint_offset += instruction.body.op_size(); + } + (hints, program_hints) +} + +/// Finds first function ending with `name_suffix`. +fn find_function<'a>( + sierra_program: &'a SierraProgram, + name_suffix: &'a str, +) -> Result<&'a Function, RunnerError> { + sierra_program + .funcs + .iter() + .find(|f| { + if let Some(name) = &f.id.debug_name { + name.ends_with(name_suffix) + } else { + false + } + }) + .ok_or_else(|| RunnerError::MissingMain) +} + +/// Creates a list of instructions that will be appended to the program's bytecode. +fn create_code_footer() -> Vec { + casm! { + // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` + // and `pc` registers. + ret; + } + .instructions +} + +/// Returns the instructions to add to the beginning of the code to successfully call the main +/// function, as well as the builtins required to execute the program. +fn create_entry_code( + sierra_program_registry: &ProgramRegistry, + casm_program: &CairoProgram, + type_sizes: &UnorderedHashMap, + func: &Function, + initial_gas: usize, +) -> Result<(Vec, Vec), Error> { + let mut ctx = casm! {}; + // The builtins in the formatting expected by the runner. + let (builtins, builtin_offset) = get_function_builtins(func); + // Load all vecs to memory. + let mut ap_offset: i16 = 0; + let after_vecs_offset = ap_offset; + if func.signature.param_types.iter().any(|ty| { + get_info(sierra_program_registry, ty) + .map(|x| x.long_id.generic_id == SegmentArenaType::ID) + .unwrap_or_default() + }) { + casm_extend! {ctx, + // SegmentArena segment. + %{ memory[ap + 0] = segments.add() %} + // Infos segment. + %{ memory[ap + 1] = segments.add() %} + ap += 2; + [ap + 0] = 0, ap++; + // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. + [ap - 2] = [[ap - 3]]; + [ap - 1] = [[ap - 3] + 1]; + [ap - 1] = [[ap - 3] + 2]; + } + ap_offset += 3; + } + for ty in func.signature.param_types.iter() { + let info = get_info(sierra_program_registry, ty) + .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; + let ty_size = type_sizes[ty]; + let generic_ty = &info.long_id.generic_id; + if let Some(offset) = builtin_offset.get(generic_ty) { + casm_extend! {ctx, + [ap + 0] = [fp - offset], ap++; + } + } else if generic_ty == &SystemType::ID { + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + } else if generic_ty == &GasBuiltinType::ID { + casm_extend! {ctx, + [ap + 0] = initial_gas, ap++; + } + } else if generic_ty == &SegmentArenaType::ID { + let offset = -ap_offset + after_vecs_offset; + casm_extend! {ctx, + [ap + 0] = [ap + offset] + 3, ap++; + } + // } else if let Some(Arg::Array(_)) = arg_iter.peek() { + // let values = extract_matches!(arg_iter.next().unwrap(), Arg::Array); + // let offset = -ap_offset + vecs.pop().unwrap(); + // expected_arguments_size += 1; + // casm_extend! {ctx, + // [ap + 0] = [ap + (offset)], ap++; + // [ap + 0] = [ap - 1] + (values.len()), ap++; + // } + // } else { + // let arg_size = ty_size; + // expected_arguments_size += arg_size as usize; + // for _ in 0..arg_size { + // if let Some(value) = arg_iter.next() { + // let value = extract_matches!(value, Arg::Value); + // casm_extend! {ctx, + // [ap + 0] = (value.to_bigint()), ap++; + // } + // } + // } + }; + ap_offset += ty_size; + } + // if expected_arguments_size != args.len() { + // return Err(RunnerError::ArgumentsSizeMismatch { + // expected: expected_arguments_size, + // actual: args.len(), + // }); + // } + let before_final_call = ctx.current_code_offset; + let final_call_size = 3; + let offset = final_call_size + + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; + casm_extend! {ctx, + call rel offset; + ret; + } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); + Ok((ctx.instructions, builtins)) +} + +fn get_info<'a>( + sierra_program_registry: &'a ProgramRegistry, + ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, +) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { + sierra_program_registry + .get_type(ty) + .ok() + .map(|ctc| ctc.info()) +} + +/// Creates the metadata required for a Sierra program lowering to casm. +fn create_metadata( + sierra_program: &cairo_lang_sierra::program::Program, + metadata_config: Option, +) -> Result { + if let Some(metadata_config) = metadata_config { + calc_metadata(sierra_program, metadata_config, false).map_err(|err| match err { + MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, + MetadataError::CostError(_) => VirtualMachineError::Unexpected, + }) + } else { + Ok(Metadata { + ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) + .map_err(|_| VirtualMachineError::Unexpected)?, + gas_info: GasInfo { + variable_values: Default::default(), + function_costs: Default::default(), + }, + }) + } +} + +fn get_function_builtins( + func: &Function, +) -> ( + Vec, + HashMap, +) { + let entry_params = &func.signature.param_types; + let mut builtins = Vec::new(); + let mut builtin_offset: HashMap = HashMap::new(); + let mut current_offset = 3; + // Fetch builtins from the entry_params in the standard order + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Poseidon".into())) + { + builtins.push(BuiltinName::poseidon); + builtin_offset.insert(PoseidonType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("EcOp".into())) + { + builtins.push(BuiltinName::ec_op); + builtin_offset.insert(EcOpType::ID, current_offset); + current_offset += 1 + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Bitwise".into())) + { + builtins.push(BuiltinName::bitwise); + builtin_offset.insert(BitwiseType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("RangeCheck".into())) + { + builtins.push(BuiltinName::range_check); + builtin_offset.insert(RangeCheckType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Pedersen".into())) + { + builtins.push(BuiltinName::pedersen); + builtin_offset.insert(PedersenType::ID, current_offset); + } + builtins.reverse(); + (builtins, builtin_offset) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::too_many_arguments)] + use super::*; + use assert_matches::assert_matches; + use cairo_vm::felt::felt_str; + use rstest::rstest; + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/fibonacci.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_fibonacci_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(89)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/factorial.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_factorial_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(3628800)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/array_get.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_array_get_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(3)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/enum_flow.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_enum_flow_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(300)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/enum_match.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_enum_match_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(10), MaybeRelocatable::from(felt_str!("3618502788666131213697322783095070105623107215331596699973092056135872020471"))]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/hello.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_hello_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(1), MaybeRelocatable::from(1234)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/ops.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_ops_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(6)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/print.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_print_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/recursion.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_recursion_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1154076154663935037074198317650845438095734251249125412074882362667803016453"))]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/sample.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_sample_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("500000500000"))]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/poseidon.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_poseidon_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1099385018355113290651252669115094675591288647745213771718157553170111442461"))]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/poseidon_pedersen.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_poseidon_pedersen_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1036257840396636296853154602823055519264738423488122322497453114874087006398"))]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/pedersen_example.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_pedersen_example_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("1089549915800264549621536909767699778745926517555586332772759280702396009108"))]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/simple.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_simple_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(1)]); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/simple_struct.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_simple_struct_ok(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(100)]); + } +} From f2737135d2e8f6fbbeaf9d351c59eac400b14e2d Mon Sep 17 00:00:00 2001 From: Federica Date: Fri, 7 Jun 2024 12:56:56 -0300 Subject: [PATCH 11/11] Remove changes to cairo1-run crate --- cairo1-run/src/cairo_run.rs | 766 ------------------------------------ 1 file changed, 766 deletions(-) delete mode 100644 cairo1-run/src/cairo_run.rs diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs deleted file mode 100644 index 5dbda6c672..0000000000 --- a/cairo1-run/src/cairo_run.rs +++ /dev/null @@ -1,766 +0,0 @@ -use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; -use cairo_lang_sierra::{ - extensions::{ - bitwise::BitwiseType, - core::{CoreLibfunc, CoreType}, - ec::EcOpType, - gas::{CostTokenType, GasBuiltinType}, - pedersen::PedersenType, - poseidon::PoseidonType, - range_check::RangeCheckType, - segment_arena::SegmentArenaType, - starknet::syscalls::SystemType, - ConcreteType, NamedType, - }, - ids::ConcreteTypeId, - program::{Function, Program as SierraProgram}, - program_registry::ProgramRegistry, -}; -use cairo_lang_sierra_ap_change::calc_ap_changes; -use cairo_lang_sierra_gas::gas_info::GasInfo; -use cairo_lang_sierra_to_casm::{ - compiler::CairoProgram, - metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, -}; -use cairo_lang_sierra_type_size::get_type_size_map; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; -use cairo_vm::{ - hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, - serde::deserialize_program::{ - ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, - }, - types::{program::Program, relocatable::MaybeRelocatable}, - vm::{ - errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, - runners::{ - builtin_runner::{ - BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, - POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, - }, - cairo_runner::{CairoRunner, RunResources, RunnerMode}, - }, - vm_core::VirtualMachine, - }, - Felt252, -}; -use itertools::chain; -use std::collections::HashMap; - -use crate::{Error, FuncArg}; - -#[derive(Debug)] -pub struct Cairo1RunConfig<'a> { - pub args: &'a [FuncArg], - pub trace_enabled: bool, - pub relocate_mem: bool, - pub layout: &'a str, - pub proof_mode: bool, - // Should be true if either air_public_input or cairo_pie_output are needed - // Sets builtins stop_ptr by calling `final_stack` on each builtin - pub finalize_builtins: bool, -} - -impl Default for Cairo1RunConfig<'_> { - fn default() -> Self { - Self { - args: Default::default(), - trace_enabled: false, - relocate_mem: false, - layout: "plain", - proof_mode: false, - finalize_builtins: false, - } - } -} - -// Runs a Cairo 1 program -// Returns the runner & VM after execution + the return values -pub fn cairo_run_program( - sierra_program: &SierraProgram, - cairo_run_config: Cairo1RunConfig, -) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { - let metadata = create_metadata(sierra_program, Some(Default::default()))?; - let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; - let type_sizes = - get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); - let casm_program = - cairo_lang_sierra_to_casm::compiler::compile(sierra_program, &metadata, true)?; - - let main_func = find_function(sierra_program, "::main")?; - - let initial_gas = 9999999999999_usize; - - // Modified entry code to be compatible with custom cairo1 Proof Mode. - // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 - let (entry_code, builtins) = create_entry_code( - &sierra_program_registry, - &casm_program, - &type_sizes, - main_func, - initial_gas, - cairo_run_config.proof_mode, - cairo_run_config.args, - )?; - - // Fetch return type data - let return_type_id = main_func - .signature - .ret_types - .last() - .ok_or(Error::NoRetTypesInSignature)?; - let return_type_size = type_sizes - .get(return_type_id) - .cloned() - .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; - - // This footer is used by lib funcs - let libfunc_footer = create_code_footer(); - - // Header used to initiate the infinite loop after executing the program - // Also appends return values to output segment - let proof_mode_header = if cairo_run_config.proof_mode { - create_proof_mode_header(builtins.len() as i16, return_type_size) - } else { - casm! {}.instructions - }; - - // This is the program we are actually running/proving - // With (embedded proof mode), cairo1 header and the libfunc footer - let instructions = chain!( - proof_mode_header.iter(), - entry_code.iter(), - casm_program.instructions.iter(), - libfunc_footer.iter(), - ); - - let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); - - let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); - - let data: Vec = instructions - .flat_map(|inst| inst.assemble().encode()) - .map(|x| Felt252::from(&x)) - .map(MaybeRelocatable::from) - .collect(); - - let data_len = data.len(); - - let program = if cairo_run_config.proof_mode { - Program::new_for_proof( - builtins, - data, - 0, - // Proof mode is on top - // jmp rel 0 is on PC == 2 - 2, - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )? - } else { - Program::new( - builtins, - data, - Some(0), - program_hints, - ReferenceManager { - references: Vec::new(), - }, - HashMap::new(), - vec![], - None, - )? - }; - - let runner_mode = if cairo_run_config.proof_mode { - RunnerMode::ProofModeCairo1 - } else { - RunnerMode::ExecutionMode - }; - - let mut runner = CairoRunner::new_v2(&program, cairo_run_config.layout, runner_mode)?; - let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); - let end = runner.initialize(&mut vm, cairo_run_config.proof_mode)?; - - additional_initialization(&mut vm, data_len)?; - - // Run it until the end / infinite loop in proof_mode - runner.run_until_pc(end, &mut vm, &mut hint_processor)?; - if cairo_run_config.proof_mode { - // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 - // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size - vm.segments - .segment_sizes - .insert(2, return_type_size as usize); - } - runner.end_run(false, false, &mut vm, &mut hint_processor)?; - - // Fetch return values - let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; - - // Set stop pointers for builtins so we can obtain the air public input - if cairo_run_config.finalize_builtins { - finalize_builtins( - cairo_run_config.proof_mode, - &main_func.signature.ret_types, - &type_sizes, - &mut vm, - )?; - - // Build execution public memory - if cairo_run_config.proof_mode { - // As the output builtin is not used by the program we need to compute it's stop ptr manually - vm.set_output_stop_ptr_offset(return_type_size as usize); - - runner.finalize_segments(&mut vm)?; - } - } - - runner.relocate(&mut vm, true)?; - - Ok((runner, vm, return_values)) -} - -fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { - // Create the builtin cost segment - let builtin_cost_segment = vm.add_memory_segment(); - for token_type in CostTokenType::iter_precost() { - vm.insert_value( - (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) - .map_err(VirtualMachineError::Math)?, - Felt252::default(), - )? - } - // Put a pointer to the builtin cost segment at the end of the program (after the - // additional `ret` statement). - vm.insert_value( - (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, - builtin_cost_segment, - )?; - - Ok(()) -} - -#[allow(clippy::type_complexity)] -fn build_hints_vec<'b>( - instructions: impl Iterator, -) -> (Vec<(usize, Vec)>, HashMap>) { - let mut hints: Vec<(usize, Vec)> = Vec::new(); - let mut program_hints: HashMap> = HashMap::new(); - - let mut hint_offset = 0; - - for instruction in instructions { - if !instruction.hints.is_empty() { - hints.push((hint_offset, instruction.hints.clone())); - program_hints.insert( - hint_offset, - vec![HintParams { - code: hint_offset.to_string(), - accessible_scopes: Vec::new(), - flow_tracking_data: FlowTrackingData { - ap_tracking: ApTracking::default(), - reference_ids: HashMap::new(), - }, - }], - ); - } - hint_offset += instruction.body.op_size(); - } - (hints, program_hints) -} - -/// Finds first function ending with `name_suffix`. -fn find_function<'a>( - sierra_program: &'a SierraProgram, - name_suffix: &'a str, -) -> Result<&'a Function, RunnerError> { - sierra_program - .funcs - .iter() - .find(|f| { - if let Some(name) = &f.id.debug_name { - name.ends_with(name_suffix) - } else { - false - } - }) - .ok_or_else(|| RunnerError::MissingMain) -} - -/// Creates a list of instructions that will be appended to the program's bytecode. -fn create_code_footer() -> Vec { - casm! { - // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` - // and `pc` registers. - ret; - } - .instructions -} - -// Create proof_mode specific instructions -// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) -// wich call the firt program instruction and then initiate an infinite loop. -// And also appending the return values to the output builtin's memory segment -fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 4 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - jmp rel 0; // Infinite loop - }; - ctx.instructions -} - -/// Returns the instructions to add to the beginning of the code to successfully call the main -/// function, as well as the builtins required to execute the program. -fn create_entry_code( - sierra_program_registry: &ProgramRegistry, - casm_program: &CairoProgram, - type_sizes: &UnorderedHashMap, - func: &Function, - initial_gas: usize, - proof_mode: bool, - args: &[FuncArg], -) -> Result<(Vec, Vec), Error> { - let mut ctx = casm! {}; - // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); - - // Load all vecs to memory. - // Load all array args content to memory. - let mut array_args_data = vec![]; - let mut ap_offset: i16 = 0; - for arg in args { - let FuncArg::Array(values) = arg else { - continue; - }; - array_args_data.push(ap_offset); - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - for (i, v) in values.iter().enumerate() { - let arr_at = (i + 1) as i16; - casm_extend! {ctx, - [ap + 0] = (v.to_bigint()); - [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; - }; - } - ap_offset += (1 + values.len()) as i16; - } - let mut array_args_data_iter = array_args_data.iter(); - let after_arrays_data_offset = ap_offset; - let mut arg_iter = args.iter().enumerate(); - let mut param_index = 0; - let mut expected_arguments_size = 0; - if func.signature.param_types.iter().any(|ty| { - get_info(sierra_program_registry, ty) - .map(|x| x.long_id.generic_id == SegmentArenaType::ID) - .unwrap_or_default() - }) { - casm_extend! {ctx, - // SegmentArena segment. - %{ memory[ap + 0] = segments.add() %} - // Infos segment. - %{ memory[ap + 1] = segments.add() %} - ap += 2; - [ap + 0] = 0, ap++; - // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. - [ap - 2] = [[ap - 3]]; - [ap - 1] = [[ap - 3] + 1]; - [ap - 1] = [[ap - 3] + 2]; - } - ap_offset += 3; - } - for ty in func.signature.param_types.iter() { - let info = get_info(sierra_program_registry, ty) - .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; - let generic_ty = &info.long_id.generic_id; - if let Some(offset) = builtin_offset.get(generic_ty) { - let mut offset = *offset; - if proof_mode { - // Everything is off by 2 due to the proof mode header - offset += 2; - } - casm_extend! {ctx, - [ap + 0] = [fp - offset], ap++; - } - ap_offset += 1; - } else if generic_ty == &SystemType::ID { - casm_extend! {ctx, - %{ memory[ap + 0] = segments.add() %} - ap += 1; - } - ap_offset += 1; - } else if generic_ty == &GasBuiltinType::ID { - casm_extend! {ctx, - [ap + 0] = initial_gas, ap++; - } - ap_offset += 1; - } else if generic_ty == &SegmentArenaType::ID { - let offset = -ap_offset + after_arrays_data_offset; - casm_extend! {ctx, - [ap + 0] = [ap + offset] + 3, ap++; - } - ap_offset += 1; - } else { - let ty_size = type_sizes[ty]; - let param_ap_offset_end = ap_offset + ty_size; - expected_arguments_size += ty_size; - while ap_offset < param_ap_offset_end { - let Some((arg_index, arg)) = arg_iter.next() else { - break; - }; - match arg { - FuncArg::Single(value) => { - casm_extend! {ctx, - [ap + 0] = (value.to_bigint()), ap++; - } - ap_offset += 1; - } - FuncArg::Array(values) => { - let offset = -ap_offset + array_args_data_iter.next().unwrap(); - casm_extend! {ctx, - [ap + 0] = [ap + (offset)], ap++; - [ap + 0] = [ap - 1] + (values.len()), ap++; - } - ap_offset += 2; - if ap_offset > param_ap_offset_end { - return Err(Error::ArgumentUnaligned { - param_index, - arg_index, - }); - } - } - } - } - param_index += 1; - }; - } - let actual_args_size = args - .iter() - .map(|arg| match arg { - FuncArg::Single(_) => 1, - FuncArg::Array(_) => 2, - }) - .sum::(); - if expected_arguments_size != actual_args_size { - return Err(Error::ArgumentsSizeMismatch { - expected: expected_arguments_size, - actual: actual_args_size, - }); - } - - let before_final_call = ctx.current_code_offset; - let final_call_size = 3; - let offset = final_call_size - + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; - - casm_extend! {ctx, - call rel offset; - ret; - } - assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); - - Ok((ctx.instructions, builtins)) -} - -fn get_info<'a>( - sierra_program_registry: &'a ProgramRegistry, - ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, -) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { - sierra_program_registry - .get_type(ty) - .ok() - .map(|ctc| ctc.info()) -} - -/// Creates the metadata required for a Sierra program lowering to casm. -fn create_metadata( - sierra_program: &cairo_lang_sierra::program::Program, - metadata_config: Option, -) -> Result { - if let Some(metadata_config) = metadata_config { - calc_metadata(sierra_program, metadata_config).map_err(|err| match err { - MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, - MetadataError::CostError(_) => VirtualMachineError::Unexpected, - }) - } else { - Ok(Metadata { - ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) - .map_err(|_| VirtualMachineError::Unexpected)?, - gas_info: GasInfo { - variable_values: Default::default(), - function_costs: Default::default(), - }, - }) - } -} - -/// Type representing the Output builtin. -#[derive(Default)] -pub struct OutputType {} -impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { - const ID: cairo_lang_sierra::ids::GenericTypeId = - cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); - const STORABLE: bool = true; - const DUPLICATABLE: bool = false; - const DROPPABLE: bool = false; - const ZERO_SIZED: bool = false; -} - -fn get_function_builtins( - func: &Function, - proof_mode: bool, -) -> ( - Vec, - HashMap, -) { - let entry_params = &func.signature.param_types; - let mut builtins = Vec::new(); - let mut builtin_offset: HashMap = HashMap::new(); - let mut current_offset = 3; - // Fetch builtins from the entry_params in the standard order - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Poseidon".into())) - { - builtins.push(BuiltinName::poseidon); - builtin_offset.insert(PoseidonType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("EcOp".into())) - { - builtins.push(BuiltinName::ec_op); - builtin_offset.insert(EcOpType::ID, current_offset); - current_offset += 1 - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Bitwise".into())) - { - builtins.push(BuiltinName::bitwise); - builtin_offset.insert(BitwiseType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("RangeCheck".into())) - { - builtins.push(BuiltinName::range_check); - builtin_offset.insert(RangeCheckType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Pedersen".into())) - { - builtins.push(BuiltinName::pedersen); - builtin_offset.insert(PedersenType::ID, current_offset); - current_offset += 1; - } - // Force an output builtin so that we can write the program output into it's segment - if proof_mode { - builtins.push(BuiltinName::output); - builtin_offset.insert(OutputType::ID, current_offset); - } - builtins.reverse(); - (builtins, builtin_offset) -} - -fn fetch_return_values( - return_type_size: i16, - return_type_id: &ConcreteTypeId, - vm: &VirtualMachine, -) -> Result, Error> { - let mut return_values = vm.get_return_values(return_type_size as usize)?; - // Check if this result is a Panic result - if return_type_id - .debug_name - .as_ref() - .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? - .starts_with("core::panics::PanicResult::") - { - // Check the failure flag (aka first return value) - if return_values.first() != Some(&MaybeRelocatable::from(0)) { - // In case of failure, extract the error from the return values (aka last two values) - let panic_data_end = return_values - .last() - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data_start = return_values - .get(return_values.len() - 2) - .ok_or(Error::FailedToExtractReturnValues)? - .get_relocatable() - .ok_or(Error::FailedToExtractReturnValues)?; - let panic_data = vm.get_integer_range( - panic_data_start, - (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, - )?; - return Err(Error::RunPanic( - panic_data.iter().map(|c| *c.as_ref()).collect(), - )); - } else { - if return_values.len() < 3 { - return Err(Error::FailedToExtractReturnValues); - } - return_values = return_values[2..].to_vec() - } - } - Ok(return_values) -} - -// Calculates builtins' final_stack setting each stop_ptr -// Calling this function is a must if either air_public_input or cairo_pie are needed -fn finalize_builtins( - proof_mode: bool, - main_ret_types: &[ConcreteTypeId], - type_sizes: &UnorderedHashMap, - vm: &mut VirtualMachine, -) -> Result<(), Error> { - // Set stop pointers for builtins so we can obtain the air public input - // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them - let ret_types_sizes = main_ret_types - .iter() - .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); - let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); - - let full_ret_types_size: i16 = ret_types_sizes.sum(); - let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) - .map_err(VirtualMachineError::Math)?; - - // Calculate the stack_ptr for each return builtin in the return values - let mut builtin_name_to_stack_pointer = HashMap::new(); - for (id, size) in ret_types_and_sizes { - if let Some(ref name) = id.debug_name { - let builtin_name = match &*name.to_string() { - "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, - "Poseidon" => POSEIDON_BUILTIN_NAME, - "EcOp" => EC_OP_BUILTIN_NAME, - "Bitwise" => BITWISE_BUILTIN_NAME, - "Pedersen" => HASH_BUILTIN_NAME, - "Output" => OUTPUT_BUILTIN_NAME, - "Ecdsa" => SIGNATURE_BUILTIN_NAME, - _ => { - stack_pointer.offset += size as usize; - continue; - } - }; - builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); - } - stack_pointer.offset += size as usize; - } - - // Set stop pointer for each builtin - vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::path::Path; - - use super::*; - use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; - use cairo_vm::types::relocatable::Relocatable; - use rstest::rstest; - - fn compile_to_sierra(filename: &str) -> SierraProgram { - let compiler_config = CompilerConfig { - replace_ids: true, - ..CompilerConfig::default() - }; - - compile_cairo_project_at_path(Path::new(filename), compiler_config).unwrap() - } - - fn main_hash_panic_result(sierra_program: &SierraProgram) -> bool { - let main_func = find_function(sierra_program, "::main").unwrap(); - main_func - .signature - .ret_types - .last() - .and_then(|rt| { - rt.debug_name - .as_ref() - .map(|n| n.as_ref().starts_with("core::panics::PanicResult::")) - }) - .unwrap_or_default() - } - - #[rstest] - #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] - #[case("../cairo_programs/cairo-1-programs/array_get.cairo")] - #[case("../cairo_programs/cairo-1-programs/dictionaries.cairo")] - #[case("../cairo_programs/cairo-1-programs/enum_flow.cairo")] - #[case("../cairo_programs/cairo-1-programs/enum_match.cairo")] - #[case("../cairo_programs/cairo-1-programs/factorial.cairo")] - #[case("../cairo_programs/cairo-1-programs/fibonacci.cairo")] - #[case("../cairo_programs/cairo-1-programs/hello.cairo")] - #[case("../cairo_programs/cairo-1-programs/pedersen_example.cairo")] - #[case("../cairo_programs/cairo-1-programs/poseidon.cairo")] - #[case("../cairo_programs/cairo-1-programs/print.cairo")] - #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] - #[case("../cairo_programs/cairo-1-programs/recursion.cairo")] - #[case("../cairo_programs/cairo-1-programs/sample.cairo")] - #[case("../cairo_programs/cairo-1-programs/simple_struct.cairo")] - #[case("../cairo_programs/cairo-1-programs/simple.cairo")] - #[case("../cairo_programs/cairo-1-programs/struct_span_return.cairo")] - fn check_append_ret_values_to_output_segment(#[case] filename: &str) { - // Compile to sierra - let sierra_program = compile_to_sierra(filename); - // Set proof_mode - let cairo_run_config = Cairo1RunConfig { - proof_mode: true, - layout: "all_cairo", - ..Default::default() - }; - // Run program - let (_, vm, return_values) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); - // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values - // And handle the panics returning an error, so we need to add it here - let return_values = if main_hash_panic_result(&sierra_program) { - let mut rv = vec![Felt252::ZERO.into(), Felt252::ZERO.into()]; - rv.extend_from_slice(&return_values); - rv - } else { - return_values - }; - // Check that the output segment contains the return values - // The output builtin will always be the first builtin, so we know it's segment is 2 - let output_builtin_segment = vm - .get_continuous_range((2, 0).into(), return_values.len()) - .unwrap(); - assert_eq!(output_builtin_segment, return_values, "{}", filename); - // Just for consistency, we will check that there are no values in the output segment after the return values - assert!(vm - .get_maybe(&Relocatable::from((2_isize, return_values.len()))) - .is_none()); - } -}