diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abcda79f2a..7fd1ec4836 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,3 +363,18 @@ jobs: working-directory: ./debug_utils/sierra-emu run: cargo test test_corelib -- --nocapture continue-on-error: true # ignore result for now. When sierra-emu is fully implemented this should be removed + + build-sierra2casm-dbg: + name: Build sierra2casm-dbg + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Retreive cached dependecies + uses: Swatinem/rust-cache@v2 + - name: Build + working-directory: ./debug_utils/sierra2casm-dbg + run: cargo build --all-features --verbose + - name: Run tests + working-directory: ./debug_utils/sierra2casm-dbg + run: cargo test diff --git a/Cargo.lock b/Cargo.lock index 4c7f82323c..722d5ebc72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3353,6 +3353,19 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "sierra2casm-dbg" +version = "0.1.0" +dependencies = [ + "bincode 2.0.1", + "cairo-lang-casm", + "cairo-lang-utils", + "cairo-vm 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "serde_json", + "starknet-types-core", +] + [[package]] name = "signature" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index bf41ddcd87..38276730de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,4 +177,4 @@ name = "libfuncs" harness = false [workspace] -members = ["debug_utils/sierra-emu"] +members = ["debug_utils/sierra-emu", "debug_utils/sierra2casm-dbg"] diff --git a/debug_utils/sierra2casm-dbg/.gitignore b/debug_utils/sierra2casm-dbg/.gitignore new file mode 100644 index 0000000000..afde32e009 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/.gitignore @@ -0,0 +1,19 @@ +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + + +corelib/ +run/ diff --git a/debug_utils/sierra2casm-dbg/Cargo.toml b/debug_utils/sierra2casm-dbg/Cargo.toml new file mode 100644 index 0000000000..804a6a6e83 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sierra2casm-dbg" +version = "0.1.0" +edition = "2021" + +[dependencies] +bincode = { version = "2.0.0-rc.3", default-features = false } +clap = { version = "4.5.27", features = ["derive"] } +cairo-lang-casm = { version = "=2.12.0-dev.1", default-features = false, features = [ + "serde", +] } +cairo-lang-utils = { version = "=2.12.0-dev.1", default-features = false } +cairo-vm = { version = "2.0.1", default-features = false } +serde_json = "1.0.138" +starknet-types-core = { version = "0.1.7", default-features = false } diff --git a/debug_utils/sierra2casm-dbg/examples/basic.rs b/debug_utils/sierra2casm-dbg/examples/basic.rs new file mode 100644 index 0000000000..95fb70bb96 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/examples/basic.rs @@ -0,0 +1,37 @@ +use bincode::de::read::SliceReader; +use sierra2casm_dbg::{decode_instruction, GraphMappings, Memory, Trace, ValueId}; +use std::{collections::HashMap, fs}; + +fn main() { + let memory = Memory::decode(SliceReader::new(&fs::read("memory-2.bin").unwrap())); + let trace = Trace::decode(SliceReader::new(&fs::read("trace-2.bin").unwrap())); + + let mappings = GraphMappings::new(&memory, &trace, &HashMap::new()); + + // let value_idx = memory + // .iter() + // .copied() + // .enumerate() + // .filter_map(|(idx, val)| { + // (val == Some(Felt::from_str("9980669810").unwrap())).then_some(idx) + // }) + // .collect::>(); + + let value_idx = ValueId(93139); + + println!("Memory offset: {value_idx:?}"); + for id in &mappings[value_idx] { + println!( + "{id:?} => {} [{:?}]", + decode_instruction(&memory, trace[id.0].pc), + trace[id.0] + ); + } + + println!("[93139] = {}", memory[93139].unwrap()); + println!("[93140] = {}", memory[93140].unwrap()); + println!("[93142] = {}", memory[93142].unwrap()); + + // dbg!(memory[25386].unwrap().to_string()); + // dbg!(value_idx); +} diff --git a/debug_utils/sierra2casm-dbg/src/lib.rs b/debug_utils/sierra2casm-dbg/src/lib.rs new file mode 100644 index 0000000000..e65f8882ad --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/lib.rs @@ -0,0 +1,18 @@ +pub use self::{ + mappings::GraphMappings, memory::Memory, program::decode_instruction, + search::run_search_algorithm, trace::Trace, +}; + +mod mappings; +mod memory; +mod program; +pub mod search; +mod trace; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct StepId(pub usize); + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct ValueId(pub usize); diff --git a/debug_utils/sierra2casm-dbg/src/main.rs b/debug_utils/sierra2casm-dbg/src/main.rs new file mode 100644 index 0000000000..36e0361ae2 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/main.rs @@ -0,0 +1,162 @@ +use bincode::de::read::SliceReader; +use cairo_vm::serde::deserialize_program::HintParams; +use clap::Parser; +use serde_json::Value; +use sierra2casm_dbg::{ + run_search_algorithm, + search::{DfsQueue, NodeId}, + GraphMappings, Memory, Trace, +}; +use starknet_types_core::felt::Felt; +use std::{collections::HashMap, fs, path::PathBuf, str::FromStr}; + +#[derive(Debug, Parser)] +struct CmdArgs { + #[clap(long)] + memory_path: PathBuf, + #[clap(long)] + trace_path: PathBuf, + #[clap(long)] + program_path: Option, + + #[clap(short, long, value_parser = parse_felt252)] + source_value: Felt, + #[clap(short, long, value_parser = parse_felt252)] + target_value: Felt, +} + +fn parse_felt252(input: &str) -> Result { + Felt::from_str(input).map_err(|e| e.to_string()) +} + +fn main() { + let args = CmdArgs::parse(); + + // + // Load data from disk. + // + println!("Loading memory and trace."); + let memory = Memory::decode(SliceReader::new(&fs::read(args.memory_path).unwrap())); + let trace = Trace::decode(SliceReader::new(&fs::read(args.trace_path).unwrap())); + println!(" {:?}", trace.first().unwrap()); + println!(" {:?}", trace.last().unwrap()); + + let hints = match args.program_path { + None => HashMap::default(), + Some(program_path) => { + println!("Loading hints from provided program JSON."); + let mut program_json: Value = + serde_json::from_slice(&fs::read(program_path).unwrap()).unwrap(); + let hints = program_json + .as_object_mut() + .unwrap() + .remove("hints") + .unwrap(); + + let hints: HashMap> = serde_json::from_value(hints).unwrap(); + hints + .into_iter() + .map(|(key, value)| { + // + ( + key, + value + .into_iter() + .map(|hint_params| serde_json::from_str(&hint_params.code).unwrap()) + .collect(), + ) + }) + .collect() + } + }; + + // + // Generate graph mappings. + // + println!("Generating graph mappings."); + let mappings = GraphMappings::new(&memory, &trace, &hints); + + // + // Find initial and final values. + // + println!("Finding initial and final values within the data."); + let source_value = mappings + .value2step() + .keys() + .copied() + .filter(|x| memory[x.0] == Some(args.source_value)) + .min() + .expect("Source value not found within accessed memory."); + let target_value = mappings + .value2step() + .keys() + .copied() + .filter(|x| memory[x.0] == Some(args.target_value)) + .max() + .expect("Target value not found within accessed memory."); + println!(" Source value found at {}.", source_value.0); + println!(" Target value found at {}.", target_value.0); + + println!(); + + // + // Find a path between the source and target nodes. + // + // Queue containers: + // - BfsQueue: Will find the shortest path using the BFS algorithm. + // - DfsQueue: Will find the left-most path using the DFS algorithm. + // + println!("Starting search algorithm."); + let mut iter = + run_search_algorithm::>(&memory, &mappings, source_value, target_value); + println!(); + println!(); + + let mut num_solutions = 0; + while let Some(path) = iter.next() { + num_solutions += 1; + + println!("Found solution at step {}.", iter.queue().current_step()); + // println!("Connecting path (spans {} steps):", path.len() >> 1); + // for id in path { + // match id { + // NodeId::Step(offset) => { + // if let Some(hints) = hints.get(&offset.0) { + // for hint in hints { + // println!("; {}", hint.representing_string()); + // } + // } + // println!("{}", decode_instruction(&memory, trace[offset.0].pc)); + // println!(" {:?}", trace[offset.0]); + // } + // NodeId::Value(offset) => { + // println!(" [{}] = {}", offset.0, memory[offset.0].unwrap()); + // println!(); + // } + // } + // } + // println!(); + + let mut prev_value: Option = None; + for id in path.into_iter().filter_map(|x| match x { + NodeId::Step(_) => None, + NodeId::Value(id) => Some(id), + }) { + let curr_value = memory[id.0].unwrap(); + match prev_value { + Some(prev_value) if curr_value != prev_value => println!( + " [{}] = {curr_value} (Δ{})", + id.0, + curr_value.to_bigint() - prev_value.to_bigint() + ), + None => println!(" [{}] = {curr_value}", id.0), + _ => {} + } + prev_value = Some(curr_value); + } + println!(); + println!(); + } + + println!("Done! Found {num_solutions} solutions."); +} diff --git a/debug_utils/sierra2casm-dbg/src/mappings.rs b/debug_utils/sierra2casm-dbg/src/mappings.rs new file mode 100644 index 0000000000..703ce312f3 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/mappings.rs @@ -0,0 +1,397 @@ +use crate::{decode_instruction, Memory, StepId, Trace, ValueId}; +use cairo_lang_casm::{ + hints::{CoreHint, CoreHintBase, DeprecatedHint, ExternalHint, Hint, StarknetHint}, + instructions::InstructionBody, + operand::{CellRef, DerefOrImmediate, Register, ResOperand}, +}; +use cairo_vm::vm::trace::trace_entry::RelocatedTraceEntry; +use std::{ + collections::{HashMap, HashSet}, + ops::Index, +}; + +#[derive(Debug)] +pub struct GraphMappings { + step2value: HashMap>, + value2step: HashMap>, +} + +impl GraphMappings { + pub fn new(memory: &Memory, trace: &Trace, hints: &HashMap>) -> Self { + let mut step2value = HashMap::>::new(); + let mut value2step = HashMap::>::new(); + + for (step, trace) in trace.iter().enumerate() { + let mut add_mapping = |value| { + step2value + .entry(StepId(step)) + .or_default() + .insert(ValueId(value)); + value2step + .entry(ValueId(value)) + .or_default() + .insert(StepId(step)); + }; + + Self::iter_memory_references(memory, trace, &mut add_mapping); + if let Some(hints) = hints.get(&step) { + for hint in hints { + Self::iter_hint_references(memory, trace, hint, &mut add_mapping); + } + } + } + + Self { + step2value, + value2step, + } + } + + pub fn step2value(&self) -> &HashMap> { + &self.step2value + } + + pub fn value2step(&self) -> &HashMap> { + &self.value2step + } + + fn iter_memory_references( + memory: &Memory, + trace: &RelocatedTraceEntry, + mut callback: impl FnMut(usize), + ) { + let instr = decode_instruction(memory, trace.pc); + + let mut process_cell_ref = |x: CellRef| { + let offset = match x.register { + Register::AP => trace.ap.wrapping_add_signed(x.offset as isize), + Register::FP => trace.fp.wrapping_add_signed(x.offset as isize), + }; + callback(offset); + offset + }; + + match instr.body { + InstructionBody::AddAp(add_ap_instruction) => match add_ap_instruction.operand { + ResOperand::Deref(_) => todo!(), + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + }, + InstructionBody::AssertEq(assert_eq_instruction) => { + process_cell_ref(assert_eq_instruction.a); + match assert_eq_instruction.b { + ResOperand::Deref(cell_ref) => { + process_cell_ref(cell_ref); + } + ResOperand::DoubleDeref(cell_ref, _) => { + let offset = process_cell_ref(cell_ref); + callback(memory[offset].unwrap().try_into().unwrap()); + } + ResOperand::Immediate(_) => {} + ResOperand::BinOp(bin_op_operand) => { + process_cell_ref(bin_op_operand.a); + match bin_op_operand.b { + DerefOrImmediate::Deref(cell_ref) => { + process_cell_ref(cell_ref); + } + DerefOrImmediate::Immediate(_) => {} + } + } + } + } + InstructionBody::Call(call_instruction) => match call_instruction.target { + DerefOrImmediate::Deref(_) => todo!(), + DerefOrImmediate::Immediate(_) => {} + }, + InstructionBody::Jnz(jnz_instruction) => { + process_cell_ref(jnz_instruction.condition); + match jnz_instruction.jump_offset { + DerefOrImmediate::Deref(_) => todo!(), + DerefOrImmediate::Immediate(_) => {} + } + } + InstructionBody::Jump(jump_instruction) => match jump_instruction.target { + DerefOrImmediate::Deref(cell_ref) => { + process_cell_ref(cell_ref); + } + DerefOrImmediate::Immediate(_) => {} + }, + InstructionBody::Ret(_) => {} + InstructionBody::QM31AssertEq(_) => todo!(), + InstructionBody::Blake2sCompress(_) => todo!(), + } + } + + fn iter_hint_references( + _memory: &Memory, + trace: &RelocatedTraceEntry, + hint: &Hint, + mut callback: impl FnMut(usize), + ) { + let mut process_cell_ref = |x: CellRef| { + let offset = match x.register { + Register::AP => trace.ap.wrapping_add_signed(x.offset as isize), + Register::FP => trace.fp.wrapping_add_signed(x.offset as isize), + }; + callback(offset); + offset + }; + + match hint { + Hint::Core(core_hint_base) => match core_hint_base { + CoreHintBase::Core(core_hint) => match core_hint { + CoreHint::AllocSegment { dst } => { + process_cell_ref(*dst); + } + CoreHint::TestLessThan { lhs, rhs, dst } => { + match lhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(bin_op_operand) => { + process_cell_ref(bin_op_operand.a); + match bin_op_operand.b { + DerefOrImmediate::Deref(cell_ref) => { + process_cell_ref(cell_ref); + } + DerefOrImmediate::Immediate(_) => {} + } + } + } + match rhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + process_cell_ref(*dst); + } + CoreHint::TestLessThanOrEqual { lhs, rhs, dst } => { + match lhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match rhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + process_cell_ref(*dst); + } + CoreHint::TestLessThanOrEqualAddress { .. } => todo!(), + CoreHint::WideMul128 { + lhs, + rhs, + high, + low, + } => { + match lhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match rhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + process_cell_ref(*high); + process_cell_ref(*low); + } + CoreHint::DivMod { + lhs, + rhs, + quotient, + remainder, + } => { + match lhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match rhs { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + process_cell_ref(*quotient); + process_cell_ref(*remainder); + } + CoreHint::Uint256DivMod { + dividend0, + dividend1, + divisor0, + divisor1, + quotient0, + quotient1, + remainder0, + remainder1, + } => { + match dividend0 { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match dividend1 { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match divisor0 { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match divisor1 { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + process_cell_ref(*quotient0); + process_cell_ref(*quotient1); + process_cell_ref(*remainder0); + process_cell_ref(*remainder1); + } + CoreHint::Uint512DivModByUint256 { .. } => todo!(), + CoreHint::SquareRoot { .. } => todo!(), + CoreHint::Uint256SquareRoot { .. } => todo!(), + CoreHint::LinearSplit { + value, + scalar, + max_x, + x, + y, + } => { + match value { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match scalar { + ResOperand::Deref(_) => todo!(), + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + match max_x { + ResOperand::Deref(_) => todo!(), + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(_) => todo!(), + } + process_cell_ref(*x); + process_cell_ref(*y); + } + CoreHint::AllocFelt252Dict { .. } => todo!(), + CoreHint::Felt252DictEntryInit { .. } => todo!(), + CoreHint::Felt252DictEntryUpdate { .. } => todo!(), + CoreHint::GetSegmentArenaIndex { .. } => todo!(), + CoreHint::InitSquashData { .. } => todo!(), + CoreHint::GetCurrentAccessIndex { .. } => todo!(), + CoreHint::ShouldSkipSquashLoop { .. } => todo!(), + CoreHint::GetCurrentAccessDelta { .. } => todo!(), + CoreHint::ShouldContinueSquashLoop { .. } => todo!(), + CoreHint::GetNextDictKey { .. } => todo!(), + CoreHint::AssertLeFindSmallArcs { .. } => todo!(), + CoreHint::AssertLeIsFirstArcExcluded { .. } => todo!(), + CoreHint::AssertLeIsSecondArcExcluded { .. } => todo!(), + CoreHint::RandomEcPoint { .. } => todo!(), + CoreHint::FieldSqrt { .. } => todo!(), + CoreHint::DebugPrint { .. } => todo!(), + CoreHint::AllocConstantSize { .. } => todo!(), + CoreHint::U256InvModN { .. } => todo!(), + CoreHint::EvalCircuit { .. } => todo!(), + }, + CoreHintBase::Deprecated(deprecated_hint) => match deprecated_hint { + DeprecatedHint::AssertCurrentAccessIndicesIsEmpty => todo!(), + DeprecatedHint::AssertAllAccessesUsed { .. } => { + todo!() + } + DeprecatedHint::AssertAllKeysUsed => todo!(), + DeprecatedHint::AssertLeAssertThirdArcExcluded => todo!(), + DeprecatedHint::AssertLtAssertValidInput { .. } => todo!(), + DeprecatedHint::Felt252DictRead { .. } => todo!(), + DeprecatedHint::Felt252DictWrite { .. } => todo!(), + }, + }, + Hint::Starknet(starknet_hint) => match starknet_hint { + StarknetHint::SystemCall { system } => match system { + ResOperand::Deref(cell_ref) => { + process_cell_ref(*cell_ref); + } + ResOperand::DoubleDeref(_, _) => todo!(), + ResOperand::Immediate(_) => {} + ResOperand::BinOp(bin_op_operand) => { + process_cell_ref(bin_op_operand.a); + match bin_op_operand.b { + DerefOrImmediate::Deref(_) => todo!(), + DerefOrImmediate::Immediate(_) => {} + } + } + }, + StarknetHint::Cheatcode { .. } => todo!(), + }, + Hint::External(external_hint) => match external_hint { + ExternalHint::AddRelocationRule { .. } => todo!(), + ExternalHint::WriteRunParam { .. } => todo!(), + ExternalHint::AddMarker { .. } => todo!(), + ExternalHint::AddTrace { .. } => todo!(), + }, + } + } +} + +impl Index for GraphMappings { + type Output = HashSet; + + fn index(&self, index: StepId) -> &Self::Output { + self.step2value.get(&index).unwrap() + } +} + +impl Index for GraphMappings { + type Output = HashSet; + + fn index(&self, index: ValueId) -> &Self::Output { + self.value2step.get(&index).unwrap() + } +} diff --git a/debug_utils/sierra2casm-dbg/src/memory.rs b/debug_utils/sierra2casm-dbg/src/memory.rs new file mode 100644 index 0000000000..3e02e2c449 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/memory.rs @@ -0,0 +1,45 @@ +use bincode::{de::read::Reader, error::DecodeError}; +use starknet_types_core::felt::Felt; +use std::ops::Deref; + +#[derive(Debug)] +pub struct Memory(Vec>); + +impl Memory { + pub fn decode(mut data: impl Reader) -> Self { + let mut memory = Vec::new(); + + let mut addr_data = [0u8; 8]; + let mut value_data = [0u8; 32]; + loop { + match data.read(&mut addr_data) { + Ok(_) => {} + Err(DecodeError::UnexpectedEnd { additional: 8 }) => break, + e @ Err(_) => e.unwrap(), + } + data.read(&mut value_data).unwrap(); + + let addr = u64::from_le_bytes(addr_data); + let value = Felt::from_bytes_le(&value_data); + + if addr >= memory.len() as u64 { + memory.resize(addr as usize + 1, None); + } + + match &mut memory[addr as usize] { + Some(_) => panic!("duplicated memory cell"), + x @ None => *x = Some(value), + } + } + + Self(memory) + } +} + +impl Deref for Memory { + type Target = [Option]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/debug_utils/sierra2casm-dbg/src/program.rs b/debug_utils/sierra2casm-dbg/src/program.rs new file mode 100644 index 0000000000..bdbd2f9b4b --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/program.rs @@ -0,0 +1,338 @@ +use crate::Memory; +use cairo_lang_casm::{ + instructions::{ + AddApInstruction, AssertEqInstruction, CallInstruction, Instruction, InstructionBody, + JnzInstruction, JumpInstruction, RetInstruction, + }, + operand::{BinOpOperand, CellRef, DerefOrImmediate, Operation, ResOperand}, +}; +use cairo_lang_utils::bigint::BigIntAsHex; +use cairo_vm::{ + types::instruction::{ + ApUpdate, FpUpdate, Instruction as InstructionRepr, Op1Addr, Opcode, PcUpdate, Register, + Res, + }, + vm::decoding::decoder, +}; +use starknet_types_core::felt::Felt; + +/// Source: https://github.com/starkware-libs/cairo/blob/main/crates/cairo-lang-casm/src/assembler.rs +pub fn decode_instruction(memory: &Memory, offset: usize) -> Instruction { + let instr_repr = + decoder::decode_instruction(memory[offset].unwrap().try_into().unwrap()).unwrap(); + + match instr_repr { + InstructionRepr { + off0: -1, + off1, + off2, + dst_register: Register::FP, + op0_register, + op1_addr, + res, + pc_update: PcUpdate::Regular, + ap_update: ApUpdate::Add, + fp_update: FpUpdate::Regular, + opcode: Opcode::NOp, + opcode_extension: _, + } => Instruction { + body: InstructionBody::AddAp(AddApInstruction { + operand: decode_res_operand(ResDescription { + off1, + off2, + imm: memory.get(offset + 1).copied().flatten(), + op0_register, + op1_addr, + res, + }), + }), + inc_ap: false, + hints: Vec::new(), + }, + InstructionRepr { + off0, + off1, + off2, + dst_register, + op0_register, + op1_addr, + res, + pc_update: PcUpdate::Regular, + ap_update: ap_update @ (ApUpdate::Add1 | ApUpdate::Regular), + fp_update: FpUpdate::Regular, + opcode: Opcode::AssertEq, + opcode_extension: _, + } => Instruction { + body: InstructionBody::AssertEq(AssertEqInstruction { + a: CellRef { + register: match dst_register { + Register::AP => cairo_lang_casm::operand::Register::AP, + Register::FP => cairo_lang_casm::operand::Register::FP, + }, + offset: off0 as i16, + }, + b: decode_res_operand(ResDescription { + off1, + off2, + imm: memory.get(offset + 1).copied().flatten(), + op0_register, + op1_addr, + res, + }), + }), + inc_ap: match ap_update { + ApUpdate::Regular => false, + ApUpdate::Add1 => true, + _ => unreachable!(), + }, + hints: Vec::new(), + }, + InstructionRepr { + off0: 0, + off1: 1, + off2, + dst_register: Register::AP, + op0_register: Register::AP, + op1_addr: op1_addr @ (Op1Addr::AP | Op1Addr::FP | Op1Addr::Imm), + res: Res::Op1, + pc_update: pc_update @ (PcUpdate::JumpRel | PcUpdate::Jump), + ap_update: ApUpdate::Add2, + fp_update: FpUpdate::APPlus2, + opcode: Opcode::Call, + opcode_extension: _, + } => Instruction { + body: InstructionBody::Call(CallInstruction { + target: match op1_addr { + Op1Addr::Imm => { + assert_eq!(off2, 1); + DerefOrImmediate::Immediate(BigIntAsHex { + value: memory[offset + 1].unwrap().to_bigint(), + }) + } + Op1Addr::AP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::AP, + offset: off2 as i16, + }), + Op1Addr::FP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::FP, + offset: off2 as i16, + }), + _ => unreachable!(), + }, + relative: match pc_update { + PcUpdate::Jump => false, + PcUpdate::JumpRel => true, + _ => unreachable!(), + }, + }), + inc_ap: false, + hints: Vec::new(), + }, + InstructionRepr { + off0: -1, + off1: -1, + off2, + dst_register: Register::FP, + op0_register: Register::FP, + op1_addr: op1_addr @ (Op1Addr::AP | Op1Addr::FP | Op1Addr::Imm), + res: Res::Op1, + pc_update: pc_update @ (PcUpdate::JumpRel | PcUpdate::Jump), + ap_update: ap_update @ (ApUpdate::Add1 | ApUpdate::Regular), + fp_update: FpUpdate::Regular, + opcode: Opcode::NOp, + opcode_extension: _, + } => Instruction { + body: InstructionBody::Jump(JumpInstruction { + target: match op1_addr { + Op1Addr::Imm => { + assert_eq!(off2, 1); + DerefOrImmediate::Immediate(BigIntAsHex { + value: memory[offset + 1].unwrap().to_bigint(), + }) + } + Op1Addr::AP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::AP, + offset: off2 as i16, + }), + Op1Addr::FP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::FP, + offset: off2 as i16, + }), + _ => unreachable!(), + }, + relative: match pc_update { + PcUpdate::Jump => false, + PcUpdate::JumpRel => true, + _ => unreachable!(), + }, + }), + inc_ap: match ap_update { + ApUpdate::Regular => false, + ApUpdate::Add1 => true, + _ => unreachable!(), + }, + hints: Vec::new(), + }, + InstructionRepr { + off0, + off1: -1, + off2, + dst_register, + op0_register: Register::FP, + op1_addr: op1_addr @ (Op1Addr::AP | Op1Addr::FP | Op1Addr::Imm), + res: Res::Unconstrained, + pc_update: PcUpdate::Jnz, + ap_update: ap_update @ (ApUpdate::Add1 | ApUpdate::Regular), + fp_update: FpUpdate::Regular, + opcode: Opcode::NOp, + opcode_extension: _, + } => Instruction { + body: InstructionBody::Jnz(JnzInstruction { + jump_offset: match op1_addr { + Op1Addr::Imm => { + assert_eq!(off2, 1); + DerefOrImmediate::Immediate(BigIntAsHex { + value: memory[offset + 1].unwrap().to_bigint(), + }) + } + Op1Addr::AP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::AP, + offset: off2 as i16, + }), + Op1Addr::FP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::FP, + offset: off2 as i16, + }), + _ => unreachable!(), + }, + condition: CellRef { + register: match dst_register { + Register::AP => cairo_lang_casm::operand::Register::AP, + Register::FP => cairo_lang_casm::operand::Register::FP, + }, + offset: off0 as i16, + }, + }), + inc_ap: match ap_update { + ApUpdate::Regular => false, + ApUpdate::Add1 => true, + _ => unreachable!(), + }, + hints: Vec::new(), + }, + InstructionRepr { + off0: -2, + off1: -1, + off2: -1, + dst_register: Register::FP, + op0_register: Register::FP, + op1_addr: Op1Addr::FP, + res: Res::Op1, + pc_update: PcUpdate::Jump, + ap_update: ApUpdate::Regular, + fp_update: FpUpdate::Dst, + opcode: Opcode::Ret, + opcode_extension: _, + } => Instruction { + body: InstructionBody::Ret(RetInstruction {}), + inc_ap: false, + hints: Vec::new(), + }, + _ => panic!(), + } +} + +struct ResDescription { + off1: isize, + off2: isize, + imm: Option, + op0_register: Register, + op1_addr: Op1Addr, + res: Res, +} + +fn decode_res_operand(desc: ResDescription) -> ResOperand { + match desc { + ResDescription { + off1: -1, + off2, + imm: _, + op0_register: Register::FP, + op1_addr: op1_addr @ (Op1Addr::AP | Op1Addr::FP), + res: Res::Op1, + } => ResOperand::Deref(CellRef { + register: match op1_addr { + Op1Addr::AP => cairo_lang_casm::operand::Register::AP, + Op1Addr::FP => cairo_lang_casm::operand::Register::FP, + _ => unreachable!(), + }, + offset: off2 as i16, + }), + ResDescription { + off1, + off2, + imm: _, + op0_register, + op1_addr: Op1Addr::Op0, + res: Res::Op1, + } => ResOperand::DoubleDeref( + CellRef { + register: match op0_register { + Register::AP => cairo_lang_casm::operand::Register::AP, + Register::FP => cairo_lang_casm::operand::Register::FP, + }, + offset: off1 as i16, + }, + off2 as i16, + ), + ResDescription { + off1: -1, + off2: 1, + imm: Some(imm), + op0_register: Register::FP, + op1_addr: Op1Addr::Imm, + res: Res::Op1, + } => ResOperand::Immediate(BigIntAsHex { + value: imm.to_bigint(), + }), + ResDescription { + off1, + off2, + imm, + op0_register, + op1_addr: op1_addr @ (Op1Addr::AP | Op1Addr::FP | Op1Addr::Imm), + res: res @ (Res::Add | Res::Mul), + } => ResOperand::BinOp(BinOpOperand { + op: match res { + Res::Add => Operation::Add, + Res::Mul => Operation::Mul, + _ => unreachable!(), + }, + a: CellRef { + register: match op0_register { + Register::AP => cairo_lang_casm::operand::Register::AP, + Register::FP => cairo_lang_casm::operand::Register::FP, + }, + offset: off1 as i16, + }, + b: match op1_addr { + Op1Addr::Imm => { + assert_eq!(off2, 1); + DerefOrImmediate::Immediate(BigIntAsHex { + value: imm.unwrap().to_bigint(), + }) + } + Op1Addr::AP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::AP, + offset: off2 as i16, + }), + Op1Addr::FP => DerefOrImmediate::Deref(CellRef { + register: cairo_lang_casm::operand::Register::FP, + offset: off2 as i16, + }), + _ => unreachable!(), + }, + }), + _ => panic!(), + } +} diff --git a/debug_utils/sierra2casm-dbg/src/search.rs b/debug_utils/sierra2casm-dbg/src/search.rs new file mode 100644 index 0000000000..8afe29fdd0 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/search.rs @@ -0,0 +1,213 @@ +use crate::{GraphMappings, Memory, StepId, ValueId}; +use std::collections::VecDeque; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum NodeId { + Step(StepId), + Value(ValueId), +} + +pub trait QueueContainer { + fn new(init: T) -> Self; + + fn extend(&mut self, iter: I) + where + I: IntoIterator; + fn pop(&mut self) -> Option; +} + +pub struct BfsQueue { + queue: VecDeque, + step: usize, +} + +impl BfsQueue { + pub fn current_step(&self) -> usize { + self.step + } +} + +impl QueueContainer for BfsQueue { + fn new(init: T) -> Self { + Self { + queue: VecDeque::from([init]), + step: 0, + } + } + + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.queue.extend(iter); + } + + fn pop(&mut self) -> Option { + self.queue.pop_front().inspect(|_| self.step += 1) + } +} + +pub struct DfsQueue { + queue: Vec, + step: usize, +} + +impl DfsQueue { + pub fn current_step(&self) -> usize { + self.step + } +} + +impl QueueContainer for DfsQueue { + fn new(init: T) -> Self { + Self { + queue: Vec::from([init]), + step: 0, + } + } + + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.queue.extend(iter); + } + + fn pop(&mut self) -> Option { + self.queue.pop().inspect(|_| self.step += 1) + } +} + +pub struct SearchAlgorithmIter<'a, Q> +where + Q: QueueContainer>, +{ + memory: &'a Memory, + mappings: &'a GraphMappings, + + // visited: HashSet, + queue: Q, + + target: ValueId, +} + +impl SearchAlgorithmIter<'_, Q> +where + Q: QueueContainer>, +{ + pub fn queue(&self) -> &Q { + &self.queue + } + + pub fn into_queue(self) -> Q { + self.queue + } +} + +impl Iterator for SearchAlgorithmIter<'_, Q> +where + Q: QueueContainer>, +{ + type Item = Vec; + + fn next(&mut self) -> Option { + while let Some(path) = self.queue.pop() { + if *path.last().unwrap() == NodeId::Value(self.target) { + return Some(path); + } + + match *path.last().unwrap() { + NodeId::Step(id) => { + self.queue.extend( + self.mappings[id] + .iter() + .copied() + .filter(|x| { + // self.visited.insert(NodeId::Value(*x)) + !path.contains(&NodeId::Value(*x)) + }) + .filter(|curr_id| { + // // When searching for gas paths, the gas should not increase. + // let prev_id = path + // .split_last() + // .unwrap() + // .1 + // .iter() + // .rev() + // .find_map(|x| match x { + // NodeId::Step(_) => None, + // NodeId::Value(id) => Some(id), + // }) + // .unwrap(); + // self.memory[curr_id.0] + // .is_some_and(|value| self.memory[prev_id.0].unwrap() >= value) + + self.memory[curr_id.0].is_some_and(|value| { + let min_value = 9919468708u64; + let _max_value = 9997035710u64; + + // Use half the min value to give some leeway for redeposited + // gas. + value >= (min_value >> 1).into() + // && (value <= (max_value << 1).into()) + }) + }) + .map(|x| { + let mut new_path = path.clone(); + new_path.push(NodeId::Value(x)); + new_path + }), + ); + } + NodeId::Value(id) => { + self.queue.extend( + self.mappings[id] + .iter() + .copied() + .filter(|x| { + // self.visited.insert(NodeId::Step(*x)) + !path.contains(&NodeId::Step(*x)) + }) + .filter(|curr_id| { + // StepId should only increase. + let prev_id = path.iter().rev().find_map(|x| match x { + NodeId::Step(id) => Some(id), + NodeId::Value(_) => None, + }); + + match prev_id { + Some(prev_id) => curr_id.0 > prev_id.0, + None => true, + } + }) + .map(|x| { + let mut new_path = path.clone(); + new_path.push(NodeId::Step(x)); + new_path + }), + ); + } + } + } + + None + } +} + +pub fn run_search_algorithm<'a, Q>( + memory: &'a Memory, + mappings: &'a GraphMappings, + source: ValueId, + target: ValueId, +) -> SearchAlgorithmIter<'a, Q> +where + Q: QueueContainer>, +{ + SearchAlgorithmIter { + memory, + mappings, + // visited: HashSet::from([NodeId::Value(source)]), + queue: Q::new(vec![NodeId::Value(source)]), + target, + } +} diff --git a/debug_utils/sierra2casm-dbg/src/trace.rs b/debug_utils/sierra2casm-dbg/src/trace.rs new file mode 100644 index 0000000000..72f3af9da6 --- /dev/null +++ b/debug_utils/sierra2casm-dbg/src/trace.rs @@ -0,0 +1,40 @@ +use bincode::{de::read::Reader, error::DecodeError}; +use cairo_vm::vm::trace::trace_entry::RelocatedTraceEntry; +use std::ops::Deref; + +#[derive(Debug)] +pub struct Trace(Vec); + +impl Trace { + pub fn decode(mut data: impl Reader) -> Self { + let mut trace = Vec::new(); + + let mut buf = [0u8; 8]; + loop { + match data.read(&mut buf) { + Ok(_) => {} + Err(DecodeError::UnexpectedEnd { additional: 8 }) => break, + e @ Err(_) => e.unwrap(), + } + let ap = u64::from_le_bytes(buf) as usize; + + data.read(&mut buf).unwrap(); + let fp = u64::from_le_bytes(buf) as usize; + + data.read(&mut buf).unwrap(); + let pc = u64::from_le_bytes(buf) as usize; + + trace.push(RelocatedTraceEntry { pc, ap, fp }); + } + + Self(trace) + } +} + +impl Deref for Trace { + type Target = [RelocatedTraceEntry]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +}