diff --git a/Cargo.lock b/Cargo.lock index 6de14744..3488401b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2667,6 +2667,7 @@ dependencies = [ "pathfinder-crypto", "reqwest 0.12.24", "serde", + "serde_json", "starknet", "starknet-crypto 0.7.4", "starknet-types-core 0.1.9", diff --git a/crates/dry_hint_processor/Cargo.toml b/crates/dry_hint_processor/Cargo.toml index 91aa3625..77f0a7d5 100644 --- a/crates/dry_hint_processor/Cargo.toml +++ b/crates/dry_hint_processor/Cargo.toml @@ -21,4 +21,5 @@ strum_macros.workspace = true syscall_handler.workspace = true tokio.workspace = true tracing.workspace = true -types.workspace = true \ No newline at end of file +types.workspace = true +serde_json.workspace = true \ No newline at end of file diff --git a/crates/dry_hint_processor/src/lib.rs b/crates/dry_hint_processor/src/lib.rs index 75f5d435..295e36ac 100644 --- a/crates/dry_hint_processor/src/lib.rs +++ b/crates/dry_hint_processor/src/lib.rs @@ -34,6 +34,7 @@ use crate::syscall_handler::{injected_state, unconstrained}; pub struct CustomHintProcessor { inputs: HDPDryRunInput, + output_preimage_path: std::path::PathBuf, builtin_hint_proc: BuiltinHintProcessor, cairo1_builtin_hint_proc: Cairo1HintProcessor, hints: HashMap, @@ -44,6 +45,7 @@ impl CustomHintProcessor { pub fn new(inputs: HDPDryRunInput) -> Self { Self { inputs, + output_preimage_path: std::path::PathBuf::from("dry_run_output_preimage.json"), builtin_hint_proc: BuiltinHintProcessor::new_empty(), cairo1_builtin_hint_proc: Cairo1HintProcessor::new(Default::default(), Default::default(), true), hints: Self::hints(), @@ -51,6 +53,10 @@ impl CustomHintProcessor { } } + pub fn set_output_preimage_path(&mut self, path: std::path::PathBuf) { + self.output_preimage_path = path; + } + #[rustfmt::skip] fn hints() -> HashMap { let mut hints = hints(); @@ -91,6 +97,7 @@ impl HintProcessorLogic for CustomHintProcessor { let res = match hint_code { crate::input::HINT_INPUT => self.hint_input(vm, exec_scopes, hpd, constants), crate::output::HINT_OUTPUT => self.hint_output(vm, exec_scopes, hpd, constants), + crate::output::HINT_SAVE_OUTPUT_PREIMAGE => self.hint_save_output_preimage(vm, exec_scopes, hpd, constants), _ => Err(HintError::UnknownHint(hint_code.to_string().into_boxed_str())), }; diff --git a/crates/dry_hint_processor/src/output.rs b/crates/dry_hint_processor/src/output.rs index f26df6dc..af28e03c 100644 --- a/crates/dry_hint_processor/src/output.rs +++ b/crates/dry_hint_processor/src/output.rs @@ -1,18 +1,24 @@ use std::collections::HashMap; use cairo_vm::{ - hint_processor::builtin_hint_processor::{ - builtin_hint_processor_definition::HintProcessorData, hint_utils::get_relocatable_from_var_name, + hint_processor::{ + builtin_hint_processor::{ + builtin_hint_processor_definition::HintProcessorData, + hint_utils::{get_integer_from_var_name, get_relocatable_from_var_name}, + }, + hint_processor_utils::felt_to_usize, }, - types::exec_scope::ExecutionScopes, + types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable}, vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, Felt252, }; use hints::vars; +use serde_json; use super::CustomHintProcessor; pub const HINT_OUTPUT: &str = "print(\"result\", [hex(ids.result.low), hex(ids.result.high)])"; +pub const HINT_SAVE_OUTPUT_PREIMAGE: &str = "save_output_preimage(ids.retdata, ids.retdata_size)"; impl CustomHintProcessor { pub fn hint_output( @@ -33,4 +39,53 @@ impl CustomHintProcessor { println!("result: {}, {}", result[0], result[1]); Ok(()) } + + pub fn hint_save_output_preimage( + &mut self, + vm: &mut VirtualMachine, + _exec_scopes: &mut ExecutionScopes, + hint_data: &HintProcessorData, + _constants: &HashMap, + ) -> Result<(), HintError> { + let retdata_ptr = get_relocatable_from_var_name("retdata", vm, &hint_data.ids_data, &hint_data.ap_tracking)?; + let retdata_size = get_integer_from_var_name("retdata_size", vm, &hint_data.ids_data, &hint_data.ap_tracking)?; + let len = felt_to_usize(&retdata_size)?; + + let cells = vm.get_continuous_range(retdata_ptr, len)?; + + let mut values: Vec = Vec::new(); + if len > 0 { + // Relocatable pointer at retdata[0] + let ptr = match &cells[0] { + MaybeRelocatable::RelocatableValue(ptr) => *ptr, + _ => { + return Err(HintError::CustomHint( + "Expected relocatable pointer at retdata[0]".to_owned().into_boxed_str(), + )); + } + }; + let slice = vm.get_continuous_range(ptr, len).map_err(HintError::Memory)?; + for el in slice.into_iter() { + match el { + MaybeRelocatable::Int(f) => values.push(f), + MaybeRelocatable::RelocatableValue(_) => { + return Err(HintError::CustomHint( + "Unexpected relocatable inside output array".to_owned().into_boxed_str(), + )); + } + } + } + } + + // Write directly to file using the path stored in the hint processor + let json_bytes = serde_json::to_vec(&values) + .map_err(|e| HintError::CustomHint(format!("Failed to serialize output preimage: {}", e).into_boxed_str()))?; + std::fs::write(&self.output_preimage_path, json_bytes).map_err(|e| { + HintError::CustomHint( + format!("Failed to write output preimage to {}: {}", self.output_preimage_path.display(), e).into_boxed_str(), + ) + })?; + + Ok(()) + } } diff --git a/crates/dry_run/src/lib.rs b/crates/dry_run/src/lib.rs index 66e092b0..e15b0ba4 100644 --- a/crates/dry_run/src/lib.rs +++ b/crates/dry_run/src/lib.rs @@ -48,6 +48,12 @@ pub struct Args { help = "Path where the output JSON will be written" )] pub output: PathBuf, + #[arg( + long = "output_preimage", + default_value = "dry_run_output_preimage.json", + help = "Path where the output preimage JSON will be written" + )] + pub output_preimage: PathBuf, #[arg( long = "print_output", default_value_t = false, @@ -62,6 +68,7 @@ pub struct Args { pub fn run( program_path: PathBuf, input: HDPDryRunInput, + output_preimage_path: PathBuf, ) -> Result< ( SyscallHandler< @@ -86,6 +93,10 @@ pub fn run( let program = Program::from_bytes(&program_file, Some(cairo_run_config.entrypoint))?; let mut hint_processor = CustomHintProcessor::new(input); + + // Set output preimage path in execution scopes before running (will be used by the hint) + hint_processor.set_output_preimage_path(output_preimage_path.clone()); + let mut cairo_runner = cairo_run_program(&program, &cairo_run_config, &mut hint_processor).map_err(Box::new)?; debug!("{:?}", cairo_runner.get_execution_resources()); @@ -139,6 +150,7 @@ pub async fn run_with_args(args: Args) -> Result<(), Error> { params, injected_state, }, + args.output_preimage.clone(), )?; if args.print_output { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 623c5ea6..d7905698 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -49,6 +49,10 @@ pub const OPTIMISM_MAINNET_CHAIN_ID: u128 = 0xa; pub const OPTIMISM_TESTNET_CHAIN_ID: u128 = 0xaa37dc; pub const STARKNET_MAINNET_CHAIN_ID: u128 = 0x534e5f4d41494e; pub const STARKNET_TESTNET_CHAIN_ID: u128 = 0x534e5f5345504f4c4941; +pub const ARBITRUM_MAINNET_CHAIN_ID: u128 = 0xa4b1; +pub const ARBITRUM_TESTNET_CHAIN_ID: u128 = 0x66eee; +pub const BASE_MAINNET_CHAIN_ID: u128 = 0x2105; +pub const BASE_TESTNET_CHAIN_ID: u128 = 0x14a34; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct ProofsData { @@ -111,6 +115,10 @@ pub enum ChainIds { StarknetSepolia, OptimismMainnet, OptimismSepolia, + ArbitrumMainnet, + ArbitrumSepolia, + BaseMainnet, + BaseSepolia, } impl fmt::Display for ChainIds { @@ -122,6 +130,10 @@ impl fmt::Display for ChainIds { ChainIds::StarknetSepolia => write!(f, "starknet-sepolia"), ChainIds::OptimismMainnet => write!(f, "optimism-mainnet"), ChainIds::OptimismSepolia => write!(f, "optimism-sepolia"), + ChainIds::ArbitrumMainnet => write!(f, "arbitrum-mainnet"), + ChainIds::ArbitrumSepolia => write!(f, "arbitrum-sepolia"), + ChainIds::BaseMainnet => write!(f, "base-mainnet"), + ChainIds::BaseSepolia => write!(f, "base-sepolia"), } } } @@ -137,6 +149,10 @@ impl FromStr for ChainIds { "starknet-sepolia" | "starknet_sepolia" | "starknetsepolia" => Ok(Self::StarknetSepolia), "optimism-mainnet" | "optimism_mainnet" | "optimismmainnet" => Ok(Self::OptimismMainnet), "optimism-sepolia" | "optimism_sepolia" | "optimismsepolia" => Ok(Self::OptimismSepolia), + "arbitrum-mainnet" | "arbitrum_mainnet" | "arbitrummainnet" => Ok(Self::ArbitrumMainnet), + "arbitrum-sepolia" | "arbitrum_sepolia" | "arbitrumsepolia" => Ok(Self::ArbitrumSepolia), + "base-mainnet" | "base_mainnet" | "basemainnet" => Ok(Self::BaseMainnet), + "base-sepolia" | "base_sepolia" | "basesepolia" => Ok(Self::BaseSepolia), _ => Err(format!("Invalid chain ID: {}", s)), } } @@ -151,6 +167,10 @@ impl ChainIds { STARKNET_TESTNET_CHAIN_ID => Some(Self::StarknetSepolia), OPTIMISM_MAINNET_CHAIN_ID => Some(Self::OptimismMainnet), OPTIMISM_TESTNET_CHAIN_ID => Some(Self::OptimismSepolia), + ARBITRUM_MAINNET_CHAIN_ID => Some(Self::ArbitrumMainnet), + ARBITRUM_TESTNET_CHAIN_ID => Some(Self::ArbitrumSepolia), + BASE_MAINNET_CHAIN_ID => Some(Self::BaseMainnet), + BASE_TESTNET_CHAIN_ID => Some(Self::BaseSepolia), _ => None, } } diff --git a/src/contract_bootloader/contract_dry_run.cairo b/src/contract_bootloader/contract_dry_run.cairo index 34957350..ec444077 100644 --- a/src/contract_bootloader/contract_dry_run.cairo +++ b/src/contract_bootloader/contract_dry_run.cairo @@ -161,6 +161,8 @@ func main{ let (leafs: Uint256*) = alloc(); felt_array_to_uint256s(counter=retdata_size, retdata=retdata, leafs=leafs); + %{ save_output_preimage(ids.retdata, ids.retdata_size) %} + with keccak_ptr { let output_root = compute_merkle_root(leafs, retdata_size); } diff --git a/src/utils/chain_info.cairo b/src/utils/chain_info.cairo index 3227d3ba..d9dd2c12 100644 --- a/src/utils/chain_info.cairo +++ b/src/utils/chain_info.cairo @@ -60,6 +60,62 @@ func fetch_chain_info(chain_id: felt) -> (info: ChainInfo) { ); } + // Arbitrum Mainnet + if (chain_id == 42161) { + return ( + info=ChainInfo( + id=42161, + id_bytes_len=2, + encoded_id=0x82A4B1, + encoded_id_bytes_len=3, + byzantium=0, + layout=Layout.EVM, + ), + ); + } + + // Arbitrum Sepolia + if (chain_id == 421614) { + return ( + info=ChainInfo( + id=421614, + id_bytes_len=3, + encoded_id=0x83066EEE, + encoded_id_bytes_len=4, + byzantium=0, + layout=Layout.EVM, + ), + ); + } + + // Base Mainnet + if (chain_id == 8453) { + return ( + info=ChainInfo( + id=8453, + id_bytes_len=2, + encoded_id=0x822105, + encoded_id_bytes_len=3, + byzantium=0, + layout=Layout.EVM, + ), + ); + } + + // Base Sepolia + if (chain_id == 84532) { + return ( + info=ChainInfo( + id=84532, + id_bytes_len=3, + encoded_id=0x83014A34, + encoded_id_bytes_len=4, + byzantium=0, + layout=Layout.EVM, + ), + ); + } + // SN_MAIN if (chain_id == 23448594291968334) { return (