diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f070cbe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Retreive cached dependecies + uses: Swatinem/rust-cache@v2 + - name: Deps + run: make deps + - name: Build + run: cargo build --all-features --verbose + - name: Run tests + run: make test diff --git a/.gitignore b/.gitignore index 389159a..041d014 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,11 @@ Cargo.lock *.pdb # End of https://www.toptal.com/developers/gitignore/api/rust + +cairo2/ + +corelib + +*.tar + +*.json diff --git a/Cargo.toml b/Cargo.toml index 118869d..97a85fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,18 +4,22 @@ version = "0.1.0" edition = "2021" [dependencies] -cairo-lang-sierra = "2.7.0" -cairo-lang-utils = "2.7.0" -clap = { version = "4.5.11", features = ["derive"] } +cairo-lang-sierra = "2.7.1" +cairo-lang-utils = "2.7.1" +clap = { version = "4.5.16", features = ["derive"] } k256 = "0.13.3" keccak = "0.1.5" num-bigint = "0.4.6" +num-traits = "0.2.19" p256 = "0.13.2" +rand = "0.8.5" sec1 = { version = "0.7.3", features = ["std"] } -serde = { version = "1.0.204", features = ["derive"] } -serde_json = "1.0.121" +serde = { version = "1.0.208", features = ["derive"] } +serde_json = "1.0.125" sha2 = { version = "0.10.8", features = ["compress"] } smallvec = "1.13.2" +starknet-crypto = "0.7.1" +starknet-curve = "0.5.0" starknet-types-core = "0.1.2" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } @@ -23,3 +27,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [dev-dependencies] cairo-lang-compiler = "2.7.0" cairo-lang-starknet = "2.7.0" + +# On dev optimize dependencies a bit so it's not as slow. +[profile.dev.package."*"] +opt-level = 1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8ffeb61 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +.PHONY: usage deps build check needs-cairo2 deps-macos build-cairo-2-compiler-macos decompress-cairo install-scarb clean + +UNAME := $(shell uname) + +CAIRO_2_VERSION=2.7.0 +SCARB_VERSION = 2.7.0 + +needs-cairo2: +ifeq ($(wildcard ./cairo2/.),) + $(error You are missing the Starknet Cairo 1 compiler, please run 'make deps' to install the necessary dependencies.) +endif + ./scripts/check-corelib-version.sh $(CAIRO_2_VERSION) + +usage: + @echo "Usage:" + @echo " deps: Installs the necesarry dependencies." + @echo " build: Builds the cairo-native library and binaries." + @echo " check: Checks format and lints." + @echo " test: Runs all tests." + @echo " clean: Cleans the built artifacts." + +build: + cargo build --release --all-features + +check: + cargo fmt --all -- --check + cargo clippy --all-targets --all-features -- -D warnings + +test: needs-cairo2 + cargo test --all-features + +clean: + cargo clean + +deps: +ifeq ($(UNAME), Linux) +deps: build-cairo-2-compiler install-scarb +endif +ifeq ($(UNAME), Darwin) +deps: deps-macos +endif + -rm -rf corelib + -ln -s cairo2/corelib corelib + +deps-macos: build-cairo-2-compiler-macos install-scarb-macos + +cairo-repo-2-dir = cairo2 +cairo-repo-2-dir-macos = cairo2-macos + +build-cairo-2-compiler-macos: | $(cairo-repo-2-dir-macos) + +$(cairo-repo-2-dir-macos): cairo-${CAIRO_2_VERSION}-macos.tar + $(MAKE) decompress-cairo SOURCE=$< TARGET=cairo2/ + +build-cairo-2-compiler: | $(cairo-repo-2-dir) + +$(cairo-repo-2-dir): cairo-${CAIRO_2_VERSION}.tar + $(MAKE) decompress-cairo SOURCE=$< TARGET=cairo2/ + +decompress-cairo: + rm -rf $(TARGET) \ + && tar -xzvf $(SOURCE) \ + && mv cairo/ $(TARGET) + +cairo-%-macos.tar: + curl -L -o "$@" "https://github.com/starkware-libs/cairo/releases/download/v$*/release-aarch64-apple-darwin.tar" + +cairo-%.tar: + curl -L -o "$@" "https://github.com/starkware-libs/cairo/releases/download/v$*/release-x86_64-unknown-linux-musl.tar.gz" + +install-scarb: + curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh| sh -s -- --no-modify-path --version $(SCARB_VERSION) + +install-scarb-macos: + curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh| sh -s -- --version $(SCARB_VERSION) diff --git a/programs/syscalls.cairo b/programs/syscalls.cairo new file mode 100644 index 0000000..3328400 --- /dev/null +++ b/programs/syscalls.cairo @@ -0,0 +1,114 @@ +use core::starknet::{ + call_contract_syscall, class_hash_const, contract_address_const, ContractAddress, + deploy_syscall, emit_event_syscall, ExecutionInfo, get_block_hash_syscall, + keccak_syscall, + library_call_syscall, replace_class_syscall, send_message_to_l1_syscall, + storage_address_try_from_felt252, storage_read_syscall, storage_write_syscall, SyscallResult, + testing::cheatcode, +}; +use core::starknet::syscalls::get_execution_info_syscall; +use core::starknet::syscalls::get_execution_info_v2_syscall; + +fn get_block_hash() -> SyscallResult { + get_block_hash_syscall(0) +} + +fn get_execution_info() -> SyscallResult> { + get_execution_info_syscall() +} + +fn get_execution_info_v2() -> SyscallResult> { + get_execution_info_v2_syscall() +} + +fn deploy() -> SyscallResult<(ContractAddress, Span)> { + deploy_syscall(class_hash_const::<0>(), 0, array![].span(), false) +} + +fn replace_class() -> SyscallResult<()> { + replace_class_syscall(class_hash_const::<0>()) +} + +fn library_call() -> SyscallResult> { + library_call_syscall(class_hash_const::<0>(), 0, array![].span()) +} + +fn call_contract() -> SyscallResult> { + call_contract_syscall(contract_address_const::<0>(), 0, array![].span()) +} + +fn storage_read() -> felt252 { + storage_read_syscall(0, storage_address_try_from_felt252(0).unwrap()).unwrap() +} + +fn storage_write() { + storage_write_syscall(0, storage_address_try_from_felt252(0).unwrap(), 0).unwrap() +} + +fn emit_event() -> SyscallResult<()> { + emit_event_syscall(array![].span(), array![].span()) +} + +fn send_message_to_l1() -> SyscallResult<()> { + send_message_to_l1_syscall(3, array![2].span()) +} + +fn keccak() -> SyscallResult { + keccak_syscall(array![].span()) +} + +fn set_sequencer_address(address: felt252) -> Span { + return cheatcode::<'set_sequencer_address'>(array![address].span()); +} + +fn set_account_contract_address(address: felt252) -> Span { + return cheatcode::<'set_account_contract_address'>(array![address].span()); +} + +fn set_block_number(number: felt252) -> Span { + return cheatcode::<'set_block_number'>(array![number].span()); +} + +fn set_block_timestamp(timestamp: felt252) -> Span { + return cheatcode::<'set_block_timestamp'>(array![timestamp].span()); +} + +fn set_caller_address(address: felt252) -> Span { + return cheatcode::<'set_caller_address'>(array![address].span()); +} + +fn set_chain_id(id: felt252) -> Span { + return cheatcode::<'set_chain_id'>(array![id].span()); +} + +fn set_contract_address(address: felt252) -> Span { + return cheatcode::<'set_contract_address'>(array![address].span()); +} + +fn set_max_fee(fee: felt252) -> Span { + return cheatcode::<'set_max_fee'>(array![fee].span()); +} + +fn set_nonce(nonce: felt252) -> Span { + return cheatcode::<'set_nonce'>(array![nonce].span()); +} + +fn set_signature(signature: Array) -> Span { + return cheatcode::<'set_signature'>(signature.span()); +} + +fn set_transaction_hash(hash: felt252) -> Span { + return cheatcode::<'set_transaction_hash'>(array![hash].span()); +} + +fn set_version(version: felt252) -> Span { + return cheatcode::<'set_version'>(array![version].span()); +} + +fn pop_log(log: felt252) -> Span { + return cheatcode::<'pop_log'>(array![log].span()); +} + +fn pop_l2_to_l1_message(message: felt252) -> Span { + return cheatcode::<'pop_l2_to_l1_message'>(array![message].span()); +} diff --git a/scripts/check-corelib-version.sh b/scripts/check-corelib-version.sh new file mode 100755 index 0000000..2458d67 --- /dev/null +++ b/scripts/check-corelib-version.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# Script to check the corelib version matches. + +_result=$(grep "version = \"$1\"" corelib/Scarb.toml) + +if [ $? -ne 0 ]; then + echo "corelib version mismatch, please re-run 'make deps'" + exit 1 +fi diff --git a/src/dump.rs b/src/dump.rs index e65fe3a..ab625c1 100644 --- a/src/dump.rs +++ b/src/dump.rs @@ -2,11 +2,12 @@ use crate::value::Value; use cairo_lang_sierra::{ids::VarId, program::StatementIdx}; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use serde::{ser::SerializeMap, Serialize}; +use starknet_crypto::Felt; use std::collections::BTreeMap; #[derive(Clone, Debug, Default, Serialize)] pub struct ProgramTrace { - states: Vec, + pub states: Vec, // TODO: Syscall data. } @@ -22,8 +23,8 @@ impl ProgramTrace { #[derive(Clone, Debug)] pub struct StateDump { - statement_idx: StatementIdx, - items: BTreeMap, + pub statement_idx: StatementIdx, + pub items: BTreeMap, } impl StateDump { @@ -51,3 +52,73 @@ impl Serialize for StateDump { s.end() } } + +#[derive(Debug, Clone)] +pub struct ContractExecutionResult { + pub remaining_gas: u128, + pub failure_flag: bool, + pub return_values: Vec, + pub error_msg: Option, +} + +impl ContractExecutionResult { + pub fn from_trace(trace: &ProgramTrace) -> Option { + let last = trace.states.last()?; + + let mut remaining_gas = None; + let mut error_msg = None; + let mut failure_flag = false; + let mut return_values = Vec::new(); + + for value in last.items.values() { + match value { + Value::U128(gas) => remaining_gas = Some(*gas), + Value::Enum { + self_ty: _, + index, + payload, + } => { + failure_flag = (*index) != 0; + + if let Value::Struct(inner) = &**payload { + if !failure_flag { + if let Value::Struct(inner) = &inner[0] { + if let Value::Array { ty: _, data } = &inner[0] { + for value in data.iter() { + if let Value::Felt(x) = value { + return_values.push(*x); + } + } + } + } + } else if let Value::Array { ty: _, data } = &inner[1] { + let mut error_felt_vec = Vec::new(); + for value in data.iter() { + if let Value::Felt(x) = value { + error_felt_vec.push(*x); + } + } + let bytes_err: Vec<_> = error_felt_vec + .iter() + .flat_map(|felt| felt.to_bytes_be().to_vec()) + // remove null chars + .filter(|b| *b != 0) + .collect(); + let str_error = String::from_utf8(bytes_err).unwrap().to_owned(); + error_msg = Some(str_error); + } + } + } + Value::Unit => {} + _ => None?, + } + } + + Some(Self { + remaining_gas: remaining_gas.unwrap_or(0), + return_values, + error_msg, + failure_flag, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5004c05..fd94664 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,28 @@ +use cairo_lang_sierra::program::{GenFunction, Program, StatementIdx}; + pub use self::{dump::*, value::*, vm::VirtualMachine}; mod dump; pub mod starknet; mod value; mod vm; + +pub fn find_entry_point_by_idx( + program: &Program, + entry_point_idx: usize, +) -> Option<&GenFunction> { + program + .funcs + .iter() + .find(|x| x.id.id == entry_point_idx as u64) +} + +pub fn find_entry_point_by_name<'a>( + program: &'a Program, + name: &str, +) -> Option<&'a GenFunction> { + program + .funcs + .iter() + .find(|x| x.id.debug_name.as_ref().map(|x| x.as_str()) == Some(name)) +} diff --git a/src/main.rs b/src/main.rs index 1b751c8..37bbec5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,9 +99,10 @@ mod test { use std::path::Path; use cairo_lang_compiler::CompilerConfig; - use cairo_lang_sierra::program::{GenFunction, Program, StatementIdx}; use cairo_lang_starknet::compile::compile_path; - use sierra_emu::{ProgramTrace, StateDump, VirtualMachine}; + use sierra_emu::{ + find_entry_point_by_idx, ContractExecutionResult, ProgramTrace, StateDump, VirtualMachine, + }; #[test] fn test_contract() { @@ -171,17 +172,14 @@ mod test { trace.push(StateDump::new(statement_idx, state)); } + assert!(!vm.syscall_handler.storage.is_empty()); + + let result = ContractExecutionResult::from_trace(&trace).unwrap(); + assert!(!result.failure_flag); + assert_eq!(result.return_values.len(), 0); + assert_eq!(result.error_msg, None); + // let trace_str = serde_json::to_string_pretty(&trace).unwrap(); // std::fs::write("contract_trace.json", trace_str).unwrap(); } - - pub fn find_entry_point_by_idx( - program: &Program, - entry_point_idx: usize, - ) -> Option<&GenFunction> { - program - .funcs - .iter() - .find(|x| x.id.id == entry_point_idx as u64) - } } diff --git a/src/starknet/secp256k1_point.rs b/src/starknet/secp256k1_point.rs index ad0183e..767b5f1 100644 --- a/src/starknet/secp256k1_point.rs +++ b/src/starknet/secp256k1_point.rs @@ -8,10 +8,11 @@ pub struct Secp256k1Point { } impl Secp256k1Point { + #[allow(unused)] pub(crate) fn into_value(self) -> Value { Value::Struct(vec![ - Value::U256(self.x.lo, self.x.hi), - Value::U256(self.y.lo, self.y.hi), + Value::Struct(vec![Value::U128(self.x.lo), Value::U128(self.x.hi)]), + Value::Struct(vec![Value::U128(self.y.lo), Value::U128(self.y.hi)]), ]) } } diff --git a/src/starknet/secp256r1_point.rs b/src/starknet/secp256r1_point.rs index d5a5761..b3137ac 100644 --- a/src/starknet/secp256r1_point.rs +++ b/src/starknet/secp256r1_point.rs @@ -8,10 +8,11 @@ pub struct Secp256r1Point { } impl Secp256r1Point { + #[allow(unused)] pub(crate) fn into_value(self) -> Value { Value::Struct(vec![ - Value::U256(self.x.lo, self.x.hi), - Value::U256(self.y.lo, self.y.hi), + Value::Struct(vec![Value::U128(self.x.lo), Value::U128(self.x.hi)]), + Value::Struct(vec![Value::U128(self.y.lo), Value::U128(self.y.hi)]), ]) } } diff --git a/src/starknet/tx_info.rs b/src/starknet/tx_info.rs index f44cb4e..ac9206d 100644 --- a/src/starknet/tx_info.rs +++ b/src/starknet/tx_info.rs @@ -19,10 +19,10 @@ impl TxInfo { Value::Felt(self.version), Value::Felt(self.account_contract_address), Value::U128(self.max_fee), - Value::Array { + Value::Struct(vec![Value::Array { ty: felt252_ty, data: self.signature.into_iter().map(Value::Felt).collect(), - }, + }]), Value::Felt(self.transaction_hash), Value::Felt(self.chain_id), Value::Felt(self.nonce), diff --git a/src/starknet/tx_v2_info.rs b/src/starknet/tx_v2_info.rs index 0495a30..77083de 100644 --- a/src/starknet/tx_v2_info.rs +++ b/src/starknet/tx_v2_info.rs @@ -31,36 +31,36 @@ impl TxV2Info { Value::Felt(self.version), Value::Felt(self.account_contract_address), Value::U128(self.max_fee), - Value::Array { + Value::Struct(vec![Value::Array { ty: felt252_ty.clone(), data: self.signature.into_iter().map(Value::Felt).collect(), - }, + }]), Value::Felt(self.transaction_hash), Value::Felt(self.chain_id), Value::Felt(self.nonce), - Value::Array { + Value::Struct(vec![Value::Array { ty: resource_bounds_ty, data: self .resource_bounds .into_iter() .map(ResourceBounds::into_value) .collect(), - }, + }]), Value::U128(self.tip), - Value::Array { + Value::Struct(vec![Value::Array { ty: felt252_ty.clone(), data: self.paymaster_data.into_iter().map(Value::Felt).collect(), - }, + }]), Value::U32(self.nonce_data_availability_mode), Value::U32(self.fee_data_availability_mode), - Value::Array { + Value::Struct(vec![Value::Array { ty: felt252_ty, data: self .account_deployment_data .into_iter() .map(Value::Felt) .collect(), - }, + }]), ]) } } diff --git a/src/starknet/u256.rs b/src/starknet/u256.rs index 5758ee7..da9c75e 100644 --- a/src/starknet/u256.rs +++ b/src/starknet/u256.rs @@ -7,6 +7,7 @@ pub struct U256 { } impl U256 { + #[allow(unused)] pub(crate) fn into_value(self) -> Value { Value::Struct(vec![Value::U128(self.lo), Value::U128(self.hi)]) } diff --git a/src/value.rs b/src/value.rs index 244caca..f72ac05 100644 --- a/src/value.rs +++ b/src/value.rs @@ -2,6 +2,7 @@ use cairo_lang_sierra::{ extensions::{ core::{CoreLibfunc, CoreType, CoreTypeConcrete}, starknet::StarkNetTypeConcrete, + ConcreteType, }, ids::ConcreteTypeId, program_registry::ProgramRegistry, @@ -27,6 +28,7 @@ pub enum Value { payload: Box, }, Felt(Felt), + Bytes31(Felt), FeltDict { ty: ConcreteTypeId, data: HashMap, @@ -36,10 +38,20 @@ pub enum Value { data: HashMap, key: Felt, }, + EcPoint { + x: Felt, + y: Felt, + }, + EcState { + x0: Felt, + y0: Felt, + x1: Felt, + y1: Felt, + }, I8(i8), Struct(Vec), U128(u128), - U256(u128, u128), + U16(u16), U32(u32), U64(u64), U8(u8), @@ -65,7 +77,8 @@ impl Value { registry: &ProgramRegistry, type_id: &ConcreteTypeId, ) -> bool { - match registry.get_type(type_id).unwrap() { + let ty = registry.get_type(type_id).unwrap(); + let res = match ty { CoreTypeConcrete::Array(info) => { matches!(self, Self::Array { ty, .. } if *ty == info.ty) } @@ -73,6 +86,7 @@ impl Value { matches!(self, Self::Enum { self_ty, .. } if self_ty == type_id) } CoreTypeConcrete::Felt252(_) => matches!(self, Self::Felt(_)), + CoreTypeConcrete::Bytes31(_) => matches!(self, Self::Bytes31(_)), CoreTypeConcrete::Felt252Dict(info) => { matches!(self, Self::FeltDict { ty, .. } if *ty == info.ty) } @@ -99,29 +113,29 @@ impl Value { // To do: CoreTypeConcrete::Coupon(_) => todo!(), - CoreTypeConcrete::Bitwise(_) => todo!(), - CoreTypeConcrete::Box(_) => todo!(), + CoreTypeConcrete::Bitwise(_) => matches!(self, Self::Unit), + CoreTypeConcrete::Box(info) => self.is(registry, &info.ty), CoreTypeConcrete::Circuit(_) => todo!(), CoreTypeConcrete::Const(_) => todo!(), - CoreTypeConcrete::EcOp(_) => todo!(), - CoreTypeConcrete::EcPoint(_) => todo!(), - CoreTypeConcrete::EcState(_) => todo!(), - CoreTypeConcrete::BuiltinCosts(_) => todo!(), - CoreTypeConcrete::Uint16(_) => todo!(), - CoreTypeConcrete::Uint64(_) => todo!(), - CoreTypeConcrete::Uint128(_) => todo!(), - CoreTypeConcrete::Uint128MulGuarantee(_) => todo!(), + CoreTypeConcrete::EcOp(_) => matches!(self, Self::Unit), + CoreTypeConcrete::EcPoint(_) => matches!(self, Self::EcPoint { .. }), + CoreTypeConcrete::EcState(_) => matches!(self, Self::EcState { .. }), + CoreTypeConcrete::BuiltinCosts(_) => matches!(self, Self::Unit), + CoreTypeConcrete::Uint16(_) => matches!(self, Self::U16(_)), + CoreTypeConcrete::Uint64(_) => matches!(self, Self::U64(_)), + CoreTypeConcrete::Uint128(_) => matches!(self, Self::U128(_)), + CoreTypeConcrete::Uint128MulGuarantee(_) => matches!(self, Self::Unit), CoreTypeConcrete::Sint16(_) => todo!(), CoreTypeConcrete::Sint32(_) => todo!(), CoreTypeConcrete::Sint64(_) => todo!(), CoreTypeConcrete::Sint128(_) => todo!(), - CoreTypeConcrete::Nullable(_) => todo!(), - CoreTypeConcrete::RangeCheck96(_) => todo!(), + CoreTypeConcrete::Nullable(info) => self.is(registry, &info.ty), + CoreTypeConcrete::RangeCheck96(_) => matches!(self, Self::Unit), CoreTypeConcrete::Uninitialized(_) => todo!(), CoreTypeConcrete::Felt252DictEntry(_) => todo!(), CoreTypeConcrete::SquashedFelt252Dict(_) => todo!(), - CoreTypeConcrete::Pedersen(_) => todo!(), - CoreTypeConcrete::Poseidon(_) => todo!(), + CoreTypeConcrete::Pedersen(_) => matches!(self, Self::Unit), + CoreTypeConcrete::Poseidon(_) => matches!(self, Self::Unit), CoreTypeConcrete::Span(_) => todo!(), CoreTypeConcrete::StarkNet(inner) => match inner { StarkNetTypeConcrete::ClassHash(_) @@ -132,9 +146,14 @@ impl Value { StarkNetTypeConcrete::Secp256Point(_) => todo!(), StarkNetTypeConcrete::Sha256StateHandle(_) => todo!(), }, - CoreTypeConcrete::Bytes31(_) => todo!(), - CoreTypeConcrete::BoundedInt(_) => todo!(), + CoreTypeConcrete::BoundedInt(_) => matches!(self, Self::BoundedInt { .. }), + }; + + if !res { + dbg!("value is mismatch", ty.info(), self); } + + res } #[doc(hidden)] diff --git a/src/vm.rs b/src/vm.rs index b8b655c..3f51a0d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -7,26 +7,30 @@ use cairo_lang_sierra::{ extensions::{ core::{CoreConcreteLibfunc, CoreLibfunc, CoreType, CoreTypeConcrete}, starknet::StarkNetTypeConcrete, + ConcreteType, }, ids::{ConcreteLibfuncId, FunctionId, VarId}, program::{GenFunction, GenStatement, Invocation, Program, StatementIdx}, program_registry::ProgramRegistry, }; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use starknet_types_core::felt::Felt; use std::{cell::Cell, sync::Arc}; use tracing::debug; mod ap_tracking; mod array; +mod bool; mod bounded_int; mod r#box; mod branch_align; +mod bytes31; mod cast; mod r#const; mod drop; mod dup; +mod ec; mod r#enum; mod felt252; mod felt252_dict; @@ -35,16 +39,22 @@ mod function_call; mod gas; mod jump; mod mem; +mod pedersen; +mod poseidon; mod snapshot_take; mod starknet; mod r#struct; +mod uint128; +mod uint16; +mod uint252; mod uint32; +mod uint64; mod uint8; pub struct VirtualMachine { program: Arc, registry: ProgramRegistry, - syscall_handler: S, + pub syscall_handler: S, frames: Vec, } @@ -123,8 +133,11 @@ impl VirtualMachine { | CoreTypeConcrete::Poseidon(_) | CoreTypeConcrete::Bitwise(_) | CoreTypeConcrete::BuiltinCosts(_) + | CoreTypeConcrete::EcOp(_) | CoreTypeConcrete::SegmentArena(_) => Value::Unit, - _ => unreachable!(), + x => { + todo!("{:?}", x.info()) + } } }) .collect::>(), @@ -166,8 +179,8 @@ impl VirtualMachine { let state_snapshot = frame.state.get_mut().clone(); debug!( - "Evaluating statement {} ({})", - frame.pc.0, &self.program.statements[frame.pc.0] + "Evaluating statement {} ({}) (values: \n{:#?}\n)", + frame.pc.0, &self.program.statements[frame.pc.0], state_snapshot ); match &self.program.statements[frame.pc.0] { GenStatement::Invocation(invocation) => { @@ -269,13 +282,13 @@ fn eval<'a>( self::ap_tracking::eval(registry, selector, args) } CoreConcreteLibfunc::Array(selector) => self::array::eval(registry, selector, args), - CoreConcreteLibfunc::Bool(_) => todo!(), + CoreConcreteLibfunc::Bool(selector) => self::bool::eval(registry, selector, args), CoreConcreteLibfunc::BoundedInt(selector) => { self::bounded_int::eval(registry, selector, args) } CoreConcreteLibfunc::Box(selector) => self::r#box::eval(registry, selector, args), CoreConcreteLibfunc::BranchAlign(info) => self::branch_align::eval(registry, info, args), - CoreConcreteLibfunc::Bytes31(_) => todo!(), + CoreConcreteLibfunc::Bytes31(selector) => self::bytes31::eval(registry, selector, args), CoreConcreteLibfunc::Cast(selector) => self::cast::eval(registry, selector, args), CoreConcreteLibfunc::Circuit(_) => todo!(), CoreConcreteLibfunc::Const(selector) => self::r#const::eval(registry, selector, args), @@ -284,7 +297,7 @@ fn eval<'a>( CoreConcreteLibfunc::Debug(_) => todo!(), CoreConcreteLibfunc::Drop(info) => self::drop::eval(registry, info, args), CoreConcreteLibfunc::Dup(info) => self::dup::eval(registry, info, args), - CoreConcreteLibfunc::Ec(_) => todo!(), + CoreConcreteLibfunc::Ec(selector) => self::ec::eval(registry, selector, args), CoreConcreteLibfunc::Enum(selector) => self::r#enum::eval(registry, selector, args), CoreConcreteLibfunc::Felt252(selector) => self::felt252::eval(registry, selector, args), CoreConcreteLibfunc::Felt252Dict(selector) => { @@ -297,8 +310,8 @@ fn eval<'a>( CoreConcreteLibfunc::Gas(selector) => self::gas::eval(registry, selector, args), CoreConcreteLibfunc::Mem(selector) => self::mem::eval(registry, selector, args), CoreConcreteLibfunc::Nullable(_) => todo!(), - CoreConcreteLibfunc::Pedersen(_) => todo!(), - CoreConcreteLibfunc::Poseidon(_) => todo!(), + CoreConcreteLibfunc::Pedersen(selector) => self::pedersen::eval(registry, selector, args), + CoreConcreteLibfunc::Poseidon(selector) => self::poseidon::eval(registry, selector, args), CoreConcreteLibfunc::Sint128(_) => todo!(), CoreConcreteLibfunc::Sint16(_) => todo!(), CoreConcreteLibfunc::Sint32(_) => todo!(), @@ -309,14 +322,18 @@ fn eval<'a>( self::starknet::eval(registry, selector, args, syscall_handler) } CoreConcreteLibfunc::Struct(selector) => self::r#struct::eval(registry, selector, args), - CoreConcreteLibfunc::Uint128(_) => todo!(), - CoreConcreteLibfunc::Uint16(_) => todo!(), - CoreConcreteLibfunc::Uint256(_) => todo!(), + CoreConcreteLibfunc::Uint128(selector) => self::uint128::eval(registry, selector, args), + CoreConcreteLibfunc::Uint16(selector) => self::uint16::eval(registry, selector, args), + CoreConcreteLibfunc::Uint256(selector) => self::uint252::eval(registry, selector, args), CoreConcreteLibfunc::Uint32(selector) => self::uint32::eval(registry, selector, args), CoreConcreteLibfunc::Uint512(_) => todo!(), - CoreConcreteLibfunc::Uint64(_) => todo!(), + CoreConcreteLibfunc::Uint64(selector) => self::uint64::eval(registry, selector, args), CoreConcreteLibfunc::Uint8(selector) => self::uint8::eval(registry, selector, args), CoreConcreteLibfunc::UnconditionalJump(info) => self::jump::eval(registry, info, args), - CoreConcreteLibfunc::UnwrapNonZero(_) => todo!(), + CoreConcreteLibfunc::UnwrapNonZero(_info) => { + let [value] = args.try_into().unwrap(); + + EvalAction::NormalBranch(0, smallvec![value]) + } } } diff --git a/src/vm/bool.rs b/src/vm/bool.rs new file mode 100644 index 0000000..8d63295 --- /dev/null +++ b/src/vm/bool.rs @@ -0,0 +1,159 @@ +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + boolean::BoolConcreteLibfunc, + core::{CoreLibfunc, CoreType}, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use smallvec::smallvec; + +pub fn eval( + registry: &ProgramRegistry, + selector: &BoolConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + BoolConcreteLibfunc::And(info) => eval_and(registry, info, args), + BoolConcreteLibfunc::Not(info) => eval_not(registry, info, args), + BoolConcreteLibfunc::Xor(info) => eval_xor(registry, info, args), + BoolConcreteLibfunc::Or(info) => eval_or(registry, info, args), + BoolConcreteLibfunc::ToFelt252(info) => eval_to_felt252(registry, info, args), + } +} + +pub fn eval_and( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Enum { + self_ty, + index: lhs, + payload, + }, Value::Enum { + self_ty: _, + index: rhs, + payload: _, + }]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let lhs = lhs != 0; + let rhs = rhs != 0; + + EvalAction::NormalBranch( + 0, + smallvec![Value::Enum { + self_ty, + index: (lhs && rhs) as usize, + payload + }], + ) +} + +pub fn eval_not( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Enum { + self_ty, + index: lhs, + payload, + }]: [Value; 1] = args.try_into().unwrap() + else { + panic!() + }; + + EvalAction::NormalBranch( + 0, + smallvec![Value::Enum { + self_ty, + index: (lhs == 0) as usize, + payload + }], + ) +} + +pub fn eval_xor( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Enum { + self_ty, + index: lhs, + payload, + }, Value::Enum { + self_ty: _, + index: rhs, + payload: _, + }]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let lhs = lhs != 0; + let rhs = rhs != 0; + + EvalAction::NormalBranch( + 0, + smallvec![Value::Enum { + self_ty, + index: (lhs ^ rhs) as usize, + payload + }], + ) +} + +pub fn eval_or( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Enum { + self_ty, + index: lhs, + payload, + }, Value::Enum { + self_ty: _, + index: rhs, + payload: _, + }]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let lhs = lhs != 0; + let rhs = rhs != 0; + + EvalAction::NormalBranch( + 0, + smallvec![Value::Enum { + self_ty, + index: (lhs || rhs) as usize, + payload + }], + ) +} + +pub fn eval_to_felt252( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Enum { + self_ty: _, + index: lhs, + payload: _, + }]: [Value; 1] = args.try_into().unwrap() + else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(lhs.into())]) +} diff --git a/src/vm/bytes31.rs b/src/vm/bytes31.rs new file mode 100644 index 0000000..5d37259 --- /dev/null +++ b/src/vm/bytes31.rs @@ -0,0 +1,65 @@ +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + bytes31::Bytes31ConcreteLibfunc, + consts::SignatureAndConstConcreteLibfunc, + core::{CoreLibfunc, CoreType}, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::BigInt; +use smallvec::smallvec; +use starknet_crypto::Felt; + +pub fn eval( + registry: &ProgramRegistry, + selector: &Bytes31ConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + Bytes31ConcreteLibfunc::Const(info) => eval_const(registry, info, args), + Bytes31ConcreteLibfunc::ToFelt252(info) => eval_to_felt252(registry, info, args), + Bytes31ConcreteLibfunc::TryFromFelt252(info) => eval_from_felt(registry, info, args), + } +} + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &SignatureAndConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::Bytes31(info.c.clone().into())]) +} + +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let max = Felt::from(BigInt::from(2).pow(248) - 1); + + if value <= max { + EvalAction::NormalBranch(0, smallvec![range_check, Value::Bytes31(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +pub fn eval_to_felt252( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Bytes31(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value)]) +} diff --git a/src/vm/cast.rs b/src/vm/cast.rs index 112d168..5fff9ed 100644 --- a/src/vm/cast.rs +++ b/src/vm/cast.rs @@ -8,6 +8,7 @@ use cairo_lang_sierra::{ }, program_registry::ProgramRegistry, }; +use num_bigint::BigInt; use smallvec::smallvec; pub fn eval( @@ -32,6 +33,11 @@ pub fn eval_downcast( let value = match value { Value::BoundedInt { value, .. } => value, + Value::U128(value) => BigInt::from(value), + Value::U64(value) => BigInt::from(value), + Value::U32(value) => BigInt::from(value), + Value::U16(value) => BigInt::from(value), + Value::U8(value) => BigInt::from(value), _ => todo!(), }; @@ -43,6 +49,11 @@ pub fn eval_downcast( range_check, match registry.get_type(&info.to_ty).unwrap() { CoreTypeConcrete::Sint8(_) => Value::I8(value.try_into().unwrap()), + CoreTypeConcrete::Uint8(_) => Value::U8(value.try_into().unwrap()), + CoreTypeConcrete::Uint16(_) => Value::U16(value.try_into().unwrap()), + CoreTypeConcrete::Uint32(_) => Value::U32(value.try_into().unwrap()), + CoreTypeConcrete::Uint64(_) => Value::U64(value.try_into().unwrap()), + CoreTypeConcrete::Uint128(_) => Value::U128(value.try_into().unwrap()), _ => todo!(), } ], @@ -57,24 +68,15 @@ pub fn eval_upcast( info: &SignatureOnlyConcreteLibfunc, args: Vec, ) -> EvalAction { - dbg!(info - .signature - .param_signatures - .iter() - .map(|x| x.ty.to_string()) - .collect::>()); - dbg!(info - .signature - .branch_signatures - .iter() - .map(|x| x.vars.iter().map(|x| x.ty.to_string()).collect::>()) - .collect::>()); - dbg!(&args); - let [value] = args.try_into().unwrap(); let value = match value { Value::BoundedInt { value, .. } => value, + Value::U128(value) => BigInt::from(value), + Value::U64(value) => BigInt::from(value), + Value::U32(value) => BigInt::from(value), + Value::U16(value) => BigInt::from(value), + Value::U8(value) => BigInt::from(value), _ => todo!(), }; @@ -85,6 +87,11 @@ pub fn eval_upcast( .unwrap() { CoreTypeConcrete::Sint8(_) => Value::I8(value.try_into().unwrap()), + CoreTypeConcrete::Uint8(_) => Value::U8(value.try_into().unwrap()), + CoreTypeConcrete::Uint16(_) => Value::U16(value.try_into().unwrap()), + CoreTypeConcrete::Uint32(_) => Value::U32(value.try_into().unwrap()), + CoreTypeConcrete::Uint64(_) => Value::U64(value.try_into().unwrap()), + CoreTypeConcrete::Uint128(_) => Value::U128(value.try_into().unwrap()), _ => todo!(), }], ) diff --git a/src/vm/const.rs b/src/vm/const.rs index 54f3a03..77e3f20 100644 --- a/src/vm/const.rs +++ b/src/vm/const.rs @@ -68,6 +68,33 @@ pub fn eval_as_immediate( [GenericArg::Value(value)] => Value::U8(value.try_into().unwrap()), _ => unreachable!(), }, + CoreTypeConcrete::Uint128(_) => match inner_data { + [GenericArg::Value(value)] => Value::U128(value.try_into().unwrap()), + _ => unreachable!(), + }, + CoreTypeConcrete::Struct(_) => { + let mut fields = Vec::new(); + + for field in inner_data { + match field { + GenericArg::Type(const_field_ty) => { + let field_type = registry.get_type(const_field_ty).unwrap(); + + match &field_type { + CoreTypeConcrete::Const(const_ty) => { + let field_value = + inner(registry, &const_ty.inner_ty, &const_ty.inner_data); + fields.push(field_value); + } + _ => unreachable!(), + }; + } + _ => unreachable!(), + } + } + + Value::Struct(fields) + } _ => todo!("{:?}", type_id), } } diff --git a/src/vm/ec.rs b/src/vm/ec.rs new file mode 100644 index 0000000..64ab982 --- /dev/null +++ b/src/vm/ec.rs @@ -0,0 +1,260 @@ +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + ec::EcConcreteLibfunc, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_traits::identities::Zero; +use rand::Rng; +use smallvec::smallvec; +use starknet_crypto::Felt; +use starknet_curve::curve_params::BETA; +use starknet_types_core::curve::{AffinePoint, ProjectivePoint}; +use std::ops::Mul; +use std::ops::Neg; + +// todo: verify these are correct. + +pub fn eval( + registry: &ProgramRegistry, + selector: &EcConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + EcConcreteLibfunc::IsZero(info) => eval_is_zero(registry, info, args), + EcConcreteLibfunc::Neg(info) => eval_neg(registry, info, args), + EcConcreteLibfunc::StateAdd(info) => eval_state_add(registry, info, args), + EcConcreteLibfunc::TryNew(info) => eval_new(registry, info, args), + EcConcreteLibfunc::StateFinalize(info) => eval_state_finalize(registry, info, args), + EcConcreteLibfunc::StateInit(info) => eval_state_init(registry, info, args), + EcConcreteLibfunc::StateAddMul(info) => eval_state_add_mul(registry, info, args), + EcConcreteLibfunc::PointFromX(info) => eval_point_from_x(registry, info, args), + EcConcreteLibfunc::UnwrapPoint(info) => eval_unwrap_point(registry, info, args), + EcConcreteLibfunc::Zero(_) => todo!(), + } +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value @ Value::EcPoint { x: _, y }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + // To check whether `(x, y) = (0, 0)` (the zero point), it is enough to check + // whether `y = 0`, since there is no point on the curve with y = 0. + if y.is_zero() { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![value]) + } +} + +pub fn eval_unwrap_point( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::EcPoint { x, y }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + EvalAction::NormalBranch(0, smallvec![Value::Felt(x), Value::Felt(y)]) +} + +pub fn eval_neg( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::EcPoint { x, y }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let point = AffinePoint::new(x, y).unwrap().neg(); + + EvalAction::NormalBranch( + 0, + smallvec![Value::EcPoint { + x: point.x(), + y: point.y(), + }], + ) +} + +pub fn eval_new( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Felt(x), Value::Felt(y)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + match AffinePoint::new(x, y) { + Ok(point) => EvalAction::NormalBranch( + 0, + smallvec![Value::EcPoint { + x: point.x(), + y: point.y(), + }], + ), + Err(_) => EvalAction::NormalBranch(1, smallvec![]), + } +} + +pub fn eval_state_init( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + _args: Vec, +) -> EvalAction { + let state = random_ec_point(); + + EvalAction::NormalBranch( + 0, + smallvec![Value::EcState { + x0: state.x(), + y0: state.y(), + x1: state.x(), + y1: state.y(), + }], + ) +} + +pub fn eval_state_add( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::EcState { x0, y0, x1, y1 }, Value::EcPoint { x, y }]: [Value; 2] = + args.try_into().unwrap() + else { + panic!() + }; + + let mut state = ProjectivePoint::from_affine(x0, y0).unwrap(); + let point = AffinePoint::new(x, y).unwrap(); + + state += &point; + let state = state.to_affine().unwrap(); + + EvalAction::NormalBranch( + 0, + smallvec![Value::EcState { + x0: state.x(), + y0: state.y(), + x1, + y1 + }], + ) +} + +pub fn eval_state_add_mul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [ec @ Value::Unit, Value::EcState { x0, y0, x1, y1 }, Value::Felt(scalar), Value::EcPoint { x, y }]: [Value; 4] = + args.try_into().unwrap() + else { + panic!() + }; + + let mut state = ProjectivePoint::from_affine(x0, y0).unwrap(); + let point = ProjectivePoint::from_affine(x, y).unwrap(); + + state += &point.mul(scalar); + let state = state.to_affine().unwrap(); + + EvalAction::NormalBranch( + 0, + smallvec![ + ec, + Value::EcState { + x0: state.x(), + y0: state.y(), + x1, + y1 + } + ], + ) +} + +pub fn eval_state_finalize( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::EcState { x0, y0, x1, y1 }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let state = ProjectivePoint::from_affine(x0, y0).unwrap(); + let random_point = ProjectivePoint::from_affine(x1, y1).unwrap(); + + if state.x() == random_point.x() && state.y() == random_point.y() { + EvalAction::NormalBranch(1, smallvec![]) + } else { + let point = &state - &random_point; + let point = point.to_affine().unwrap(); + EvalAction::NormalBranch( + 0, + smallvec![Value::EcPoint { + x: point.x(), + y: point.y(), + }], + ) + } +} + +pub fn eval_point_from_x( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(x)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + // https://github.com/starkware-libs/cairo/blob/aaad921bba52e729dc24ece07fab2edf09ccfa15/crates/cairo-lang-sierra-to-casm/src/invocations/ec.rs#L63 + + let x2 = x * x; + let x3 = x2 * x; + let alpha_x_plus_beta = x + BETA; + let rhs = x3 + alpha_x_plus_beta; + let y = rhs.sqrt().unwrap_or_else(|| Felt::from(3) * rhs); + + match AffinePoint::new(x, y) { + Ok(point) => EvalAction::NormalBranch( + 0, + smallvec![ + range_check, + Value::EcPoint { + x: point.x(), + y: point.y(), + } + ], + ), + Err(_) => EvalAction::NormalBranch(1, smallvec![range_check]), + } +} + +fn random_ec_point() -> AffinePoint { + // https://github.com/starkware-libs/cairo/blob/aaad921bba52e729dc24ece07fab2edf09ccfa15/crates/cairo-lang-runner/src/casm_run/mod.rs#L1802 + let mut rng = rand::thread_rng(); + let (random_x, random_y) = loop { + // Randominzing 31 bytes to make sure is in range. + let x_bytes: [u8; 31] = rng.gen(); + let random_x = Felt::from_bytes_be_slice(&x_bytes); + let random_y_squared = random_x * random_x * random_x + random_x + BETA; + if let Some(random_y) = random_y_squared.sqrt() { + break (random_x, random_y); + } + }; + + AffinePoint::new(random_x, random_y).unwrap() +} diff --git a/src/vm/felt252.rs b/src/vm/felt252.rs index 8e8c163..00c0c84 100644 --- a/src/vm/felt252.rs +++ b/src/vm/felt252.rs @@ -3,11 +3,16 @@ use crate::Value; use cairo_lang_sierra::{ extensions::{ core::{CoreLibfunc, CoreType}, - felt252::{Felt252BinaryOperationConcrete, Felt252BinaryOperator, Felt252Concrete}, + felt252::{ + Felt252BinaryOperationConcrete, Felt252BinaryOperator, Felt252Concrete, + Felt252ConstConcreteLibfunc, + }, + lib_func::SignatureOnlyConcreteLibfunc, }, program_registry::ProgramRegistry, }; use smallvec::smallvec; +use starknet_crypto::Felt; pub fn eval( registry: &ProgramRegistry, @@ -15,13 +20,13 @@ pub fn eval( args: Vec, ) -> EvalAction { match selector { - Felt252Concrete::BinaryOperation(info) => eval_squash(registry, info, args), - Felt252Concrete::Const(_) => todo!(), - Felt252Concrete::IsZero(_) => todo!(), + Felt252Concrete::BinaryOperation(info) => eval_operation(registry, info, args), + Felt252Concrete::Const(info) => eval_const(registry, info, args), + Felt252Concrete::IsZero(info) => eval_felt_is_zero(registry, info, args), } } -pub fn eval_squash( +pub fn eval_operation( _registry: &ProgramRegistry, info: &Felt252BinaryOperationConcrete, args: Vec, @@ -44,3 +49,27 @@ pub fn eval_squash( EvalAction::NormalBranch(0, smallvec![Value::Felt(res)]) } + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &Felt252ConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::Felt(info.c.clone().into())]) +} + +pub fn eval_felt_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Felt(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if value == Felt::ZERO { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![Value::Felt(value)]) + } +} diff --git a/src/vm/pedersen.rs b/src/vm/pedersen.rs new file mode 100644 index 0000000..04da05a --- /dev/null +++ b/src/vm/pedersen.rs @@ -0,0 +1,39 @@ +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + lib_func::SignatureOnlyConcreteLibfunc, + pedersen::PedersenConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use smallvec::smallvec; + +use crate::Value; + +use super::EvalAction; + +pub fn eval( + registry: &ProgramRegistry, + selector: &PedersenConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + PedersenConcreteLibfunc::PedersenHash(info) => eval_pedersen_hash(registry, info, args), + } +} + +fn eval_pedersen_hash( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [pedersen @ Value::Unit, Value::Felt(lhs), Value::Felt(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let res = starknet_crypto::pedersen_hash(&lhs, &rhs); + + EvalAction::NormalBranch(0, smallvec![pedersen, Value::Felt(res),]) +} diff --git a/src/vm/poseidon.rs b/src/vm/poseidon.rs new file mode 100644 index 0000000..d1a2048 --- /dev/null +++ b/src/vm/poseidon.rs @@ -0,0 +1,51 @@ +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + lib_func::SignatureOnlyConcreteLibfunc, + poseidon::PoseidonConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use smallvec::smallvec; + +use crate::Value; + +use super::EvalAction; + +pub fn eval( + registry: &ProgramRegistry, + selector: &PoseidonConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + PoseidonConcreteLibfunc::HadesPermutation(info) => { + eval_hades_permutation(registry, info, args) + } + } +} + +fn eval_hades_permutation( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [poseidon @ Value::Unit, Value::Felt(p1), Value::Felt(p2), Value::Felt(p3)]: [Value; 4] = + args.try_into().unwrap() + else { + panic!() + }; + + let mut state = [p1, p2, p3]; + + starknet_crypto::poseidon_permute_comp(&mut state); + + EvalAction::NormalBranch( + 0, + smallvec![ + poseidon, + Value::Felt(state[0]), + Value::Felt(state[1]), + Value::Felt(state[2]) + ], + ) +} diff --git a/src/vm/starknet.rs b/src/vm/starknet.rs index 3e62691..75fee73 100644 --- a/src/vm/starknet.rs +++ b/src/vm/starknet.rs @@ -11,6 +11,7 @@ use cairo_lang_sierra::{ program_registry::ProgramRegistry, }; use smallvec::smallvec; +use starknet_types_core::felt::Felt; pub fn eval( registry: &ProgramRegistry, @@ -20,16 +21,26 @@ pub fn eval( ) -> EvalAction { match selector { StarkNetConcreteLibfunc::CallContract(info) => { - self::eval_call_contract(registry, info, args) + self::eval_call_contract(registry, info, args, syscall_handler) } StarkNetConcreteLibfunc::ClassHashConst(info) => { eval_class_hash_const(registry, info, args) } - StarkNetConcreteLibfunc::ClassHashTryFromFelt252(_) => todo!(), - StarkNetConcreteLibfunc::ClassHashToFelt252(_) => todo!(), - StarkNetConcreteLibfunc::ContractAddressConst(_) => todo!(), - StarkNetConcreteLibfunc::ContractAddressTryFromFelt252(_) => todo!(), - StarkNetConcreteLibfunc::ContractAddressToFelt252(_) => todo!(), + StarkNetConcreteLibfunc::ClassHashTryFromFelt252(info) => { + eval_class_hash_try_from_felt(registry, info, args) + } + StarkNetConcreteLibfunc::ClassHashToFelt252(info) => { + eval_class_hash_to_felt(registry, info, args) + } + StarkNetConcreteLibfunc::ContractAddressConst(info) => { + eval_contract_address_const(registry, info, args) + } + StarkNetConcreteLibfunc::ContractAddressTryFromFelt252(info) => { + eval_contract_address_try_from_felt(registry, info, args) + } + StarkNetConcreteLibfunc::ContractAddressToFelt252(info) => { + eval_contract_address_to_felt(registry, info, args) + } StarkNetConcreteLibfunc::StorageRead(info) => { eval_storage_read(registry, info, args, syscall_handler) } @@ -39,33 +50,49 @@ pub fn eval( StarkNetConcreteLibfunc::StorageBaseAddressConst(info) => { eval_storage_base_address_const(registry, info, args) } - StarkNetConcreteLibfunc::StorageBaseAddressFromFelt252(_) => todo!(), + StarkNetConcreteLibfunc::StorageBaseAddressFromFelt252(info) => { + eval_storage_base_address_from_felt(registry, info, args) + } StarkNetConcreteLibfunc::StorageAddressFromBase(info) => { eval_storage_address_from_base(registry, info, args) } - StarkNetConcreteLibfunc::StorageAddressFromBaseAndOffset(_) => todo!(), - StarkNetConcreteLibfunc::StorageAddressToFelt252(_) => todo!(), - StarkNetConcreteLibfunc::StorageAddressTryFromFelt252(_) => todo!(), - StarkNetConcreteLibfunc::EmitEvent(info) => eval_emit_event(registry, info, args), - StarkNetConcreteLibfunc::GetBlockHash(info) => eval_get_block_hash(registry, info, args), + StarkNetConcreteLibfunc::StorageAddressFromBaseAndOffset(info) => { + eval_storage_address_from_base_and_offset(registry, info, args) + } + StarkNetConcreteLibfunc::StorageAddressToFelt252(info) => { + eval_storage_address_to_felt(registry, info, args) + } + StarkNetConcreteLibfunc::StorageAddressTryFromFelt252(info) => { + eval_storage_address_try_from_felt(registry, info, args) + } + StarkNetConcreteLibfunc::EmitEvent(info) => { + eval_emit_event(registry, info, args, syscall_handler) + } + StarkNetConcreteLibfunc::GetBlockHash(info) => { + eval_get_block_hash(registry, info, args, syscall_handler) + } StarkNetConcreteLibfunc::GetExecutionInfo(info) => { - eval_get_execution_info(registry, info, args) + eval_get_execution_info(registry, info, args, syscall_handler) } StarkNetConcreteLibfunc::GetExecutionInfoV2(info) => { - eval_get_execution_info_v2(registry, info, args) + eval_get_execution_info_v2(registry, info, args, syscall_handler) } - StarkNetConcreteLibfunc::Deploy(info) => eval_deploy(registry, info, args), - StarkNetConcreteLibfunc::Keccak(info) => eval_keccak(registry, info, args), + StarkNetConcreteLibfunc::Deploy(info) => eval_deploy(registry, info, args, syscall_handler), + StarkNetConcreteLibfunc::Keccak(info) => eval_keccak(registry, info, args, syscall_handler), StarkNetConcreteLibfunc::Sha256ProcessBlock(_) => todo!(), StarkNetConcreteLibfunc::Sha256StateHandleInit(_) => todo!(), StarkNetConcreteLibfunc::Sha256StateHandleDigest(_) => todo!(), - StarkNetConcreteLibfunc::LibraryCall(info) => eval_library_call(registry, info, args), - StarkNetConcreteLibfunc::ReplaceClass(info) => eval_replace_class(registry, info, args), + StarkNetConcreteLibfunc::LibraryCall(info) => { + eval_library_call(registry, info, args, syscall_handler) + } + StarkNetConcreteLibfunc::ReplaceClass(info) => { + eval_replace_class(registry, info, args, syscall_handler) + } StarkNetConcreteLibfunc::SendMessageToL1(info) => { - eval_send_message_to_l1(registry, info, args) + eval_send_message_to_l1(registry, info, args, syscall_handler) } - StarkNetConcreteLibfunc::Testing(info) => todo!(), - StarkNetConcreteLibfunc::Secp256(info) => todo!(), + StarkNetConcreteLibfunc::Testing(_info) => todo!(), + StarkNetConcreteLibfunc::Secp256(_info) => todo!(), } } @@ -85,6 +112,122 @@ fn eval_storage_base_address_const( EvalAction::NormalBranch(0, smallvec![Value::Felt(info.c.clone().into())]) } +fn eval_contract_address_const( + _registry: &ProgramRegistry, + info: &SignatureAndConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::Felt(info.c.clone().into())]) +} + +fn eval_class_hash_try_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + // 2 ** 251 = 3618502788666131106986593281521497120414687020801267626233049500247285301248 + + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + if value + < Felt::from_dec_str( + "3618502788666131106986593281521497120414687020801267626233049500247285301248", + ) + .unwrap() + { + EvalAction::NormalBranch(0, smallvec![range_check, Value::Felt(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +fn eval_contract_address_try_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + // 2 ** 251 = 3618502788666131106986593281521497120414687020801267626233049500247285301248 + + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + if value + < Felt::from_dec_str( + "3618502788666131106986593281521497120414687020801267626233049500247285301248", + ) + .unwrap() + { + EvalAction::NormalBranch(0, smallvec![range_check, Value::Felt(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +fn eval_storage_address_try_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + // 2 ** 251 = 3618502788666131106986593281521497120414687020801267626233049500247285301248 + + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + if value + < Felt::from_dec_str( + "3618502788666131106986593281521497120414687020801267626233049500247285301248", + ) + .unwrap() + { + EvalAction::NormalBranch(0, smallvec![range_check, Value::Felt(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +fn eval_storage_base_address_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check, value] = args.try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, value]) +} + +fn eval_storage_address_to_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value] = args.try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![value]) +} + +fn eval_contract_address_to_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value] = args.try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![value]) +} + +fn eval_class_hash_to_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value] = args.try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![value]) +} + fn eval_storage_address_from_base( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, @@ -94,12 +237,86 @@ fn eval_storage_address_from_base( EvalAction::NormalBranch(0, smallvec![value]) } +fn eval_storage_address_from_base_and_offset( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Felt(value), Value::U8(offset)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value + Felt::from(offset))]) +} + fn eval_call_contract( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Felt(address), Value::Felt(entry_point_selector), Value::Struct(calldata)]: [Value; 5] = + args.try_into().unwrap() + else { + panic!() + }; + + let [Value::Array { + ty: _, + data: calldata, + }]: [Value; 1] = calldata.try_into().unwrap() + else { + panic!() + }; + + let calldata = calldata + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.call_contract(address, entry_point_selector, calldata, &mut gas); + + match result { + Ok(return_values) => EvalAction::NormalBranch( + 0, + smallvec![ + Value::U128(gas), + system, + Value::Struct(vec![Value::Array { + ty: felt_ty, + data: return_values + .into_iter() + .map(Value::Felt) + .collect::>(), + }]) + ], + ), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_storage_read( @@ -108,7 +325,9 @@ fn eval_storage_read( args: Vec, syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - let args: [Value; 4] = args.try_into().unwrap(); + let [Value::U128(mut gas), system, Value::U32(address_domain), Value::Felt(storage_key)]: [Value; 4] = args.try_into().unwrap() else { + panic!() + }; let error_felt_ty = { match registry .get_type(&info.branch_signatures()[1].vars[2].ty) @@ -119,29 +338,23 @@ fn eval_storage_read( } }; - match args { - [Value::U128(mut gas), system, Value::U32(address_domain), Value::Felt(storage_key)] => { - let result = syscall_handler.storage_read(address_domain, storage_key, &mut gas); - - match result { - Ok(value) => EvalAction::NormalBranch( - 0, - smallvec![Value::U128(gas), system, Value::Felt(value)], - ), - Err(e) => EvalAction::NormalBranch( - 1, - smallvec![ - Value::U128(gas), - system, - Value::Array { - ty: error_felt_ty, - data: e.into_iter().map(Value::Felt).collect::>(), - } - ], - ), - } - } - _ => panic!(), + let result = syscall_handler.storage_read(address_domain, storage_key, &mut gas); + + match result { + Ok(value) => { + EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system, Value::Felt(value)]) + } + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: error_felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), } } @@ -151,7 +364,9 @@ fn eval_storage_write( args: Vec, syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - let args: [Value; 5] = args.try_into().unwrap(); + let [Value::U128(mut gas), system, Value::U32(address_domain), Value::Felt(storage_key), Value::Felt(value)]: [Value; 5] = args.try_into().unwrap() else { + panic!() + }; let error_felt_ty = { match registry .get_type(&info.branch_signatures()[1].vars[2].ty) @@ -162,28 +377,21 @@ fn eval_storage_write( } }; - match args { - [Value::U128(mut gas), system, Value::U32(address_domain), Value::Felt(storage_key), Value::Felt(value)] => - { - let result = - syscall_handler.storage_write(address_domain, storage_key, value, &mut gas); - - match result { - Ok(_) => EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system]), - Err(e) => EvalAction::NormalBranch( - 1, - smallvec![ - Value::U128(gas), - system, - Value::Array { - ty: error_felt_ty, - data: e.into_iter().map(Value::Felt).collect::>(), - } - ], - ), - } - } - _ => panic!(), + let result = syscall_handler.storage_write(address_domain, storage_key, value, &mut gas); + + match result { + Ok(_) => EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system]), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: error_felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), } } @@ -191,70 +399,528 @@ fn eval_emit_event( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Struct(key_arr), Value::Struct(data_arr)]: [Value; + 4] = args.try_into().unwrap() + else { + panic!() + }; + + let [Value::Array { ty: _, data: keys }]: [Value; 1] = key_arr.try_into().unwrap() else { + panic!() + }; + + let [Value::Array { ty: _, data }]: [Value; 1] = data_arr.try_into().unwrap() else { + panic!() + }; + + let error_felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let keys = keys + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + let data = data + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + let result = syscall_handler.emit_event(keys, data, &mut gas); + + match result { + Ok(_) => EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system]), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: error_felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_get_block_hash( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::U64(block_number)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + let error_felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.get_block_hash(block_number, &mut gas); + + match result { + Ok(res) => { + EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system, Value::Felt(res)]) + } + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: error_felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_get_execution_info( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.get_execution_info(&mut gas); + + match result { + Ok(res) => EvalAction::NormalBranch( + 0, + smallvec![Value::U128(gas), system, res.into_value(felt_ty)], + ), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_get_execution_info_v2( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.get_execution_info_v2(&mut gas); + + let mut out_ty = registry + .get_type(&info.branch_signatures()[0].vars[2].ty) + .unwrap(); + let mut out_ty_id = &info.branch_signatures()[0].vars[2].ty; + + if let CoreTypeConcrete::Box(inner) = out_ty { + out_ty_id = &inner.ty; + out_ty = registry.get_type(&inner.ty).unwrap(); + }; + + if let CoreTypeConcrete::Struct(inner) = out_ty { + out_ty_id = &inner.members[1]; + out_ty = registry.get_type(&inner.members[1]).unwrap(); + }; + + if let CoreTypeConcrete::Box(inner) = out_ty { + out_ty_id = &inner.ty; + out_ty = registry.get_type(&inner.ty).unwrap(); + }; + + if let CoreTypeConcrete::Struct(inner) = out_ty { + out_ty_id = &inner.members[7]; + out_ty = registry.get_type(&inner.members[7]).unwrap(); + }; + + if let CoreTypeConcrete::Struct(inner) = out_ty { + out_ty_id = &inner.members[0]; + out_ty = registry.get_type(&inner.members[0]).unwrap(); + }; + if let CoreTypeConcrete::Snapshot(inner) = out_ty { + out_ty_id = &inner.ty; + out_ty = registry.get_type(&inner.ty).unwrap(); + }; + if let CoreTypeConcrete::Array(inner) = out_ty { + out_ty_id = &inner.ty; + }; + + match result { + Ok(res) => EvalAction::NormalBranch( + 0, + smallvec![ + Value::U128(gas), + system, + res.into_value(felt_ty, out_ty_id.clone()) + ], + ), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_deploy( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Felt(class_hash), Value::Felt(contract_address_salt), Value::Struct(calldata), Value::Enum { + self_ty: _, + index: deploy_from_zero, + payload: _, + }]: [Value; 6] = args.try_into().unwrap() + else { + panic!() + }; + + let deploy_from_zero = deploy_from_zero != 0; + + let [Value::Array { + ty: _, + data: calldata, + }]: [Value; 1] = calldata.try_into().unwrap() + else { + panic!() + }; + + let calldata = calldata + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.deploy( + class_hash, + contract_address_salt, + calldata, + deploy_from_zero, + &mut gas, + ); + + match result { + Ok((contract_address, return_values)) => EvalAction::NormalBranch( + 0, + smallvec![ + Value::U128(gas), + system, + Value::Felt(contract_address), + Value::Struct(vec![Value::Array { + ty: felt_ty, + data: return_values + .into_iter() + .map(Value::Felt) + .collect::>(), + }]) + ], + ), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_keccak( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Struct(input)]: [Value; 3] = args.try_into().unwrap() + else { + panic!() + }; + + let [Value::Array { ty: _, data: input }]: [Value; 1] = input.try_into().unwrap() else { + panic!() + }; + + let input = input + .into_iter() + .map(|x| match x { + Value::U64(x) => x, + _ => unreachable!(), + }) + .collect(); + + let result = syscall_handler.keccak(input, &mut gas); + + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + match result { + Ok(res) => { + EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system, res.into_value()]) + } + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_library_call( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Felt(class_hash), Value::Felt(function_selector), Value::Struct(calldata)]: [Value; 5] = + args.try_into().unwrap() + else { + panic!() + }; + + let [Value::Array { + ty: _, + data: calldata, + }]: [Value; 1] = calldata.try_into().unwrap() + else { + panic!() + }; + + let calldata = calldata + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.library_call(class_hash, function_selector, calldata, &mut gas); + + match result { + Ok(return_values) => EvalAction::NormalBranch( + 0, + smallvec![ + Value::U128(gas), + system, + Value::Struct(vec![Value::Array { + ty: felt_ty, + data: return_values + .into_iter() + .map(Value::Felt) + .collect::>(), + }]) + ], + ), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_replace_class( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Felt(class_hash)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.replace_class(class_hash, &mut gas); + + match result { + Ok(()) => EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system]), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } fn eval_send_message_to_l1( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, ) -> EvalAction { - todo!() + let [Value::U128(mut gas), system, Value::Felt(address), Value::Struct(payload)]: [Value; 4] = + args.try_into().unwrap() + else { + panic!() + }; + + let [Value::Array { + ty: _, + data: payload, + }]: [Value; 1] = payload.try_into().unwrap() + else { + panic!() + }; + + let payload = payload + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + let result = syscall_handler.send_message_to_l1(address, payload, &mut gas); + + match result { + Ok(()) => EvalAction::NormalBranch(0, smallvec![Value::U128(gas), system]), + Err(e) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U128(gas), + system, + Value::Array { + ty: felt_ty, + data: e.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } } diff --git a/src/vm/struct.rs b/src/vm/struct.rs index 609944e..1e84436 100644 --- a/src/vm/struct.rs +++ b/src/vm/struct.rs @@ -18,7 +18,9 @@ pub fn eval( match selector { StructConcreteLibfunc::Construct(info) => eval_construct(registry, info, args), StructConcreteLibfunc::Deconstruct(info) => eval_deconstruct(registry, info, args), - StructConcreteLibfunc::SnapshotDeconstruct(_) => todo!(), + StructConcreteLibfunc::SnapshotDeconstruct(info) => { + eval_snapshot_deconstruct(registry, info, args) + } } } @@ -65,3 +67,33 @@ pub fn eval_deconstruct( EvalAction::NormalBranch(0, values.into()) } + +pub fn eval_snapshot_deconstruct( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Struct(values)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let CoreTypeConcrete::Snapshot(snapshot_ty) = registry + .get_type(&info.signature.param_signatures[0].ty) + .unwrap() + else { + panic!() + }; + + let CoreTypeConcrete::Struct(StructConcreteType { members, .. }) = + registry.get_type(&snapshot_ty.ty).unwrap() + else { + panic!() + }; + assert_eq!(values.len(), members.len()); + assert!(values + .iter() + .zip(members) + .all(|(value, ty)| value.is(registry, ty))); + + EvalAction::NormalBranch(0, values.into()) +} diff --git a/src/vm/uint128.rs b/src/vm/uint128.rs new file mode 100644 index 0000000..6ffc2cd --- /dev/null +++ b/src/vm/uint128.rs @@ -0,0 +1,205 @@ +use std::u128; + +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + int::{ + unsigned128::{Uint128Concrete, Uint128Traits}, + IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, + }, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::BigUint; +use smallvec::smallvec; +use starknet_crypto::Felt; +use starknet_types_core::felt::NonZeroFelt; + +pub fn eval( + registry: &ProgramRegistry, + selector: &Uint128Concrete, + args: Vec, +) -> EvalAction { + match selector { + Uint128Concrete::Const(info) => eval_const(registry, info, args), + Uint128Concrete::Operation(info) => eval_operation(registry, info, args), + Uint128Concrete::SquareRoot(_) => todo!(), + Uint128Concrete::Equal(info) => eval_equal(registry, info, args), + Uint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), + Uint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint128Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint128Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint128Concrete::Bitwise(info) => eval_bitwise(registry, info, args), + Uint128Concrete::GuaranteeMul(info) => eval_guarantee_mul(registry, info, args), + Uint128Concrete::MulGuaranteeVerify(info) => eval_guarantee_verify(registry, info, args), + Uint128Concrete::ByteReverse(_) => todo!(), + } +} + +pub fn eval_guarantee_mul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U128(lhs), Value::U128(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let mask128 = BigUint::from(u128::MAX); + let result = BigUint::from(lhs) * BigUint::from(rhs); + let high = Value::U128((&result >> 128u32).try_into().unwrap()); + let low = Value::U128((result & mask128).try_into().unwrap()); + + EvalAction::NormalBranch(0, smallvec![high, low, Value::Unit]) +} + +pub fn eval_guarantee_verify( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, _verify @ Value::Unit]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![range_check]) +} + +pub fn eval_divmod( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U128(x), Value::U128(y)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let val = Value::U128(x / y); + let rem = Value::U128(x % y); + + EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) +} + +pub fn eval_operation( + _registry: &ProgramRegistry, + info: &IntOperationConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U128(lhs), Value::U128(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let (result, has_overflow) = match info.operator { + IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), + IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), + }; + + EvalAction::NormalBranch( + has_overflow as usize, + smallvec![range_check, Value::U128(result)], + ) +} + +pub fn eval_equal( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U128(lhs), Value::U128(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [vm_value @ Value::U128(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if value == 0 { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![vm_value]) + } +} + +pub fn eval_bitwise( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [bitwise @ Value::Unit, Value::U128(lhs), Value::U128(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let and = lhs & rhs; + let or = lhs | rhs; + let xor = lhs ^ rhs; + + EvalAction::NormalBranch( + 0, + smallvec![bitwise, Value::U128(and), Value::U128(or), Value::U128(xor)], + ) +} + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &IntConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::U128(info.c)]) +} + +pub fn eval_to_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U128(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) +} + +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let bound = Felt::from(u128::MAX) + 1; + + if value < bound { + let value: u128 = value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::U128(value)]) + } else { + let (new_value, overflow) = value.div_rem(&NonZeroFelt::try_from(bound).unwrap()); + + let overflow: u128 = overflow.to_biguint().try_into().unwrap(); + let new_value: u128 = new_value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch( + 1, + smallvec![range_check, Value::U128(new_value), Value::U128(overflow)], + ) + } +} diff --git a/src/vm/uint16.rs b/src/vm/uint16.rs new file mode 100644 index 0000000..b394c96 --- /dev/null +++ b/src/vm/uint16.rs @@ -0,0 +1,176 @@ +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + int::{ + unsigned::{Uint16Concrete, Uint16Traits}, + IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, + }, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use smallvec::smallvec; +use starknet_crypto::Felt; + +pub fn eval( + registry: &ProgramRegistry, + selector: &Uint16Concrete, + args: Vec, +) -> EvalAction { + match selector { + Uint16Concrete::Const(info) => eval_const(registry, info, args), + Uint16Concrete::Operation(info) => eval_operation(registry, info, args), + Uint16Concrete::SquareRoot(_) => todo!(), + Uint16Concrete::Equal(info) => eval_equal(registry, info, args), + Uint16Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), + Uint16Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint16Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint16Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint16Concrete::WideMul(info) => eval_widemul(registry, info, args), + Uint16Concrete::Bitwise(info) => eval_bitwise(registry, info, args), + } +} + +pub fn eval_divmod( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U16(x), Value::U16(y)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let val = Value::U16(x / y); + let rem = Value::U16(x % y); + + EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) +} + +pub fn eval_operation( + _registry: &ProgramRegistry, + info: &IntOperationConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U16(lhs), Value::U16(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let (result, has_overflow) = match info.operator { + IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), + IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), + }; + + EvalAction::NormalBranch( + has_overflow as usize, + smallvec![range_check, Value::U16(result)], + ) +} + +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let max = Felt::from(u16::MAX); + + if value <= max { + let value: u16 = value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::U16(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +pub fn eval_equal( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U16(lhs), Value::U16(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +pub fn eval_bitwise( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [bitwise @ Value::Unit, Value::U16(lhs), Value::U16(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let and = lhs & rhs; + let or = lhs | rhs; + let xor = lhs ^ rhs; + + EvalAction::NormalBranch( + 0, + smallvec![bitwise, Value::U16(and), Value::U16(or), Value::U16(xor)], + ) +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [vm_value @ Value::U16(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if value == 0 { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![vm_value]) + } +} + +pub fn eval_to_felt252( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U16(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) +} + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &IntConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::U16(info.c)]) +} + +pub fn eval_widemul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U16(lhs), Value::U16(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let result = (lhs as u32) * (rhs as u32); + + EvalAction::NormalBranch(0, smallvec![Value::U32(result)]) +} diff --git a/src/vm/uint252.rs b/src/vm/uint252.rs new file mode 100644 index 0000000..98e8aab --- /dev/null +++ b/src/vm/uint252.rs @@ -0,0 +1,96 @@ +use std::u128; + +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + int::unsigned256::Uint256Concrete, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::BigUint; +use smallvec::smallvec; + +pub fn eval( + registry: &ProgramRegistry, + selector: &Uint256Concrete, + args: Vec, +) -> EvalAction { + match selector { + Uint256Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint256Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint256Concrete::SquareRoot(_) => todo!(), + Uint256Concrete::InvModN(_) => todo!(), + } +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Struct(fields)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let [Value::U128(lo), Value::U128(hi)]: [Value; 2] = fields.clone().try_into().unwrap() else { + panic!() + }; + + if lo == 0 && hi == 0 { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![Value::Struct(fields)]) + } +} + +#[inline] +pub fn u256_to_biguint(lo: u128, hi: u128) -> BigUint { + BigUint::from(lo) + (BigUint::from(hi) << 128) +} + +#[inline] +pub fn u256_to_value(value: BigUint) -> Value { + let hi: u128 = (&value >> 128u32).try_into().unwrap(); + let lo: u128 = (value & BigUint::from(u128::MAX)).try_into().unwrap(); + Value::Struct(vec![Value::U128(lo), Value::U128(hi)]) +} + +pub fn eval_divmod( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Struct(lhs), Value::Struct(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let [Value::U128(lhs_lo), Value::U128(lhs_hi)]: [Value; 2] = lhs.try_into().unwrap() else { + panic!() + }; + + let lhs = u256_to_biguint(lhs_lo, lhs_hi); + + let [Value::U128(rhs_lo), Value::U128(rhs_hi)]: [Value; 2] = rhs.try_into().unwrap() else { + panic!() + }; + + let rhs = u256_to_biguint(rhs_lo, rhs_hi); + + let div = &lhs / &rhs; + let modulo = lhs % rhs; + + EvalAction::NormalBranch( + 0, + smallvec![ + range_check, + u256_to_value(div), + u256_to_value(modulo), + Value::Unit + ], + ) +} diff --git a/src/vm/uint32.rs b/src/vm/uint32.rs index 82ff35e..b1daf1d 100644 --- a/src/vm/uint32.rs +++ b/src/vm/uint32.rs @@ -3,12 +3,16 @@ use crate::Value; use cairo_lang_sierra::{ extensions::{ core::{CoreLibfunc, CoreType}, - int::{unsigned::Uint32Concrete, IntOperationConcreteLibfunc, IntOperator}, + int::{ + unsigned::{Uint32Concrete, Uint32Traits}, + IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, + }, lib_func::SignatureOnlyConcreteLibfunc, }, program_registry::ProgramRegistry, }; use smallvec::smallvec; +use starknet_crypto::Felt; pub fn eval( registry: &ProgramRegistry, @@ -16,19 +20,36 @@ pub fn eval( args: Vec, ) -> EvalAction { match selector { - Uint32Concrete::Const(_) => todo!(), + Uint32Concrete::Const(info) => eval_const(registry, info, args), Uint32Concrete::Operation(info) => eval_operation(registry, info, args), Uint32Concrete::SquareRoot(_) => todo!(), - Uint32Concrete::Equal(_) => todo!(), + Uint32Concrete::Equal(info) => eval_equal(registry, info, args), Uint32Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), - Uint32Concrete::FromFelt252(_) => todo!(), - Uint32Concrete::IsZero(_) => todo!(), - Uint32Concrete::Divmod(_) => todo!(), - Uint32Concrete::WideMul(_) => todo!(), - Uint32Concrete::Bitwise(_) => todo!(), + Uint32Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint32Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint32Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint32Concrete::WideMul(info) => eval_widemul(registry, info, args), + Uint32Concrete::Bitwise(info) => eval_bitwise(registry, info, args), } } +pub fn eval_divmod( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U32(x), Value::U32(y)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let val = Value::U32(x / y); + let rem = Value::U32(x % y); + + EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) +} + pub fn eval_operation( _registry: &ProgramRegistry, info: &IntOperationConcreteLibfunc, @@ -51,6 +72,75 @@ pub fn eval_operation( ) } +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let max = Felt::from(u32::MAX); + + if value <= max { + let value: u32 = value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::U32(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +pub fn eval_equal( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U32(lhs), Value::U32(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +pub fn eval_bitwise( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [bitwise @ Value::Unit, Value::U32(lhs), Value::U32(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let and = lhs & rhs; + let or = lhs | rhs; + let xor = lhs ^ rhs; + + EvalAction::NormalBranch( + 0, + smallvec![bitwise, Value::U32(and), Value::U32(or), Value::U32(xor)], + ) +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [vm_value @ Value::U32(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if value == 0 { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![vm_value]) + } +} + pub fn eval_to_felt252( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, @@ -62,3 +152,25 @@ pub fn eval_to_felt252( EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) } + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &IntConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::U32(info.c)]) +} + +pub fn eval_widemul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U32(lhs), Value::U32(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let result = (lhs as u64) * (rhs as u64); + + EvalAction::NormalBranch(0, smallvec![Value::U64(result)]) +} diff --git a/src/vm/uint64.rs b/src/vm/uint64.rs new file mode 100644 index 0000000..224dcba --- /dev/null +++ b/src/vm/uint64.rs @@ -0,0 +1,176 @@ +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + int::{ + unsigned::{Uint64Concrete, Uint64Traits}, + IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, + }, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use smallvec::smallvec; +use starknet_crypto::Felt; + +pub fn eval( + registry: &ProgramRegistry, + selector: &Uint64Concrete, + args: Vec, +) -> EvalAction { + match selector { + Uint64Concrete::Const(info) => eval_const(registry, info, args), + Uint64Concrete::Operation(info) => eval_operation(registry, info, args), + Uint64Concrete::SquareRoot(_) => todo!(), + Uint64Concrete::Equal(info) => eval_equal(registry, info, args), + Uint64Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), + Uint64Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint64Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint64Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint64Concrete::WideMul(info) => eval_widemul(registry, info, args), + Uint64Concrete::Bitwise(info) => eval_bitwise(registry, info, args), + } +} + +pub fn eval_divmod( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U64(x), Value::U64(y)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let val = Value::U64(x / y); + let rem = Value::U64(x % y); + + EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) +} + +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let max = Felt::from(u64::MAX); + + if value <= max { + let value: u64 = value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::U64(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +pub fn eval_operation( + _registry: &ProgramRegistry, + info: &IntOperationConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U64(lhs), Value::U64(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let (result, has_overflow) = match info.operator { + IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), + IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), + }; + + EvalAction::NormalBranch( + has_overflow as usize, + smallvec![range_check, Value::U64(result)], + ) +} + +pub fn eval_equal( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U64(lhs), Value::U64(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +pub fn eval_bitwise( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [bitwise @ Value::Unit, Value::U64(lhs), Value::U64(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let and = lhs & rhs; + let or = lhs | rhs; + let xor = lhs ^ rhs; + + EvalAction::NormalBranch( + 0, + smallvec![bitwise, Value::U64(and), Value::U64(or), Value::U64(xor)], + ) +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [vm_value @ Value::U64(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if value == 0 { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![vm_value]) + } +} + +pub fn eval_to_felt252( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U64(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) +} + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &IntConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::U64(info.c)]) +} + +pub fn eval_widemul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U64(lhs), Value::U64(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let result = (lhs as u128) * (rhs as u128); + + EvalAction::NormalBranch(0, smallvec![Value::U128(result)]) +} diff --git a/src/vm/uint8.rs b/src/vm/uint8.rs index 5e29e49..24d18ee 100644 --- a/src/vm/uint8.rs +++ b/src/vm/uint8.rs @@ -3,12 +3,16 @@ use crate::Value; use cairo_lang_sierra::{ extensions::{ core::{CoreLibfunc, CoreType}, - int::unsigned::Uint8Concrete, + int::{ + unsigned::{Uint8Concrete, Uint8Traits}, + IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, + }, lib_func::SignatureOnlyConcreteLibfunc, }, program_registry::ProgramRegistry, }; use smallvec::smallvec; +use starknet_crypto::Felt; pub fn eval( registry: &ProgramRegistry, @@ -16,16 +20,87 @@ pub fn eval( args: Vec, ) -> EvalAction { match selector { - Uint8Concrete::Const(_) => todo!(), - Uint8Concrete::Operation(_) => todo!(), + Uint8Concrete::Const(info) => eval_const(registry, info, args), + Uint8Concrete::Operation(info) => eval_operation(registry, info, args), Uint8Concrete::SquareRoot(_) => todo!(), Uint8Concrete::Equal(info) => eval_equal(registry, info, args), - Uint8Concrete::ToFelt252(_) => todo!(), - Uint8Concrete::FromFelt252(_) => todo!(), - Uint8Concrete::IsZero(_) => todo!(), - Uint8Concrete::Divmod(_) => todo!(), - Uint8Concrete::WideMul(_) => todo!(), - Uint8Concrete::Bitwise(_) => todo!(), + Uint8Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), + Uint8Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint8Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint8Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint8Concrete::WideMul(info) => eval_widemul(registry, info, args), + Uint8Concrete::Bitwise(info) => eval_bitwise(registry, info, args), + } +} + +pub fn eval_divmod( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U8(x), Value::U8(y)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let val = Value::U8(x / y); + let rem = Value::U8(x % y); + + EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) +} + +pub fn eval_to_felt252( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U8(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) +} + +pub fn eval_operation( + _registry: &ProgramRegistry, + info: &IntOperationConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::U8(lhs), Value::U8(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let (result, has_overflow) = match info.operator { + IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), + IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), + }; + + EvalAction::NormalBranch( + has_overflow as usize, + smallvec![range_check, Value::U8(result)], + ) +} + +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let max = Felt::from(u8::MAX); + + if value <= max { + let value: u8 = value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::U8(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) } } @@ -38,5 +113,64 @@ pub fn eval_equal( panic!() }; - EvalAction::NormalBranch((lhs != rhs) as usize, smallvec![]) + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +pub fn eval_bitwise( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [bitwise @ Value::Unit, Value::U8(lhs), Value::U8(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let and = lhs & rhs; + let or = lhs | rhs; + let xor = lhs ^ rhs; + + EvalAction::NormalBranch( + 0, + smallvec![bitwise, Value::U8(and), Value::U8(or), Value::U8(xor)], + ) +} + +pub fn eval_is_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [vm_value @ Value::U8(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if value == 0 { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![vm_value]) + } +} + +pub fn eval_const( + _registry: &ProgramRegistry, + info: &IntConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::U8(info.c)]) +} + +pub fn eval_widemul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U8(lhs), Value::U8(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let result = (lhs as u16) * (rhs as u16); + + EvalAction::NormalBranch(0, smallvec![Value::U16(result)]) } diff --git a/tests/libfuncs.rs b/tests/libfuncs.rs new file mode 100644 index 0000000..5fcf124 --- /dev/null +++ b/tests/libfuncs.rs @@ -0,0 +1,133 @@ +use std::{path::Path, sync::Arc}; + +use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; +use cairo_lang_sierra::{ + extensions::{core::CoreTypeConcrete, starknet::StarkNetTypeConcrete}, + program::{GenFunction, Program, StatementIdx}, +}; +use sierra_emu::{ProgramTrace, StateDump, Value, VirtualMachine}; + +fn run_program(path: &str, func_name: &str, args: &[Value]) -> Vec { + let path = Path::new(path); + + let sierra_program = Arc::new( + compile_cairo_project_at_path( + path, + CompilerConfig { + replace_ids: true, + ..Default::default() + }, + ) + .unwrap(), + ); + + let function = find_entry_point_by_name(&sierra_program, func_name).unwrap(); + + let mut vm = VirtualMachine::new(sierra_program.clone()); + + let mut args = args.iter().cloned(); + let initial_gas = 1000000; + + vm.push_frame( + function.id.clone(), + function + .signature + .param_types + .iter() + .map(|type_id| { + let type_info = vm.registry().get_type(type_id).unwrap(); + match type_info { + CoreTypeConcrete::GasBuiltin(_) => Value::U128(initial_gas), + CoreTypeConcrete::StarkNet(StarkNetTypeConcrete::System(_)) => Value::Unit, + CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::Pedersen(_) + | CoreTypeConcrete::Poseidon(_) + | CoreTypeConcrete::Bitwise(_) + | CoreTypeConcrete::BuiltinCosts(_) + | CoreTypeConcrete::SegmentArena(_) => Value::Unit, + _ => args.next().unwrap(), + } + }) + .collect::>(), + ); + + let mut trace = ProgramTrace::new(); + + while let Some((statement_idx, state)) = vm.step() { + trace.push(StateDump::new(statement_idx, state)); + } + + trace + .states + .last() + .unwrap() + .items + .values() + .cloned() + .collect() +} + +#[test] +fn test_u32_overflow() { + let r = run_program( + "tests/tests/test_u32.cairo", + "test_u32::test_u32::run_test", + &[Value::U32(2), Value::U32(2)], + ); + assert!(matches!( + r[1], + Value::Enum { + self_ty: _, + index: 0, + payload: _ + } + )); + + let r = run_program( + "tests/tests/test_u32.cairo", + "test_u32::test_u32::run_test", + &[Value::U32(2), Value::U32(3)], + ); + assert!(matches!( + r[1], + Value::Enum { + self_ty: _, + index: 1, + payload: _ + } + )); + + let r = run_program( + "tests/tests/test_u32.cairo", + "test_u32::test_u32::run_test", + &[Value::U32(0), Value::U32(0)], + ); + assert!(matches!( + r[1], + Value::Enum { + self_ty: _, + index: 0, + payload: _ + } + )); +} + +pub fn find_entry_point_by_idx( + program: &Program, + entry_point_idx: usize, +) -> Option<&GenFunction> { + program + .funcs + .iter() + .find(|x| x.id.id == entry_point_idx as u64) +} + +pub fn find_entry_point_by_name<'a>( + program: &'a Program, + name: &str, +) -> Option<&'a GenFunction> { + program + .funcs + .iter() + .find(|x| x.id.debug_name.as_ref().map(|x| x.as_str()) == Some(name)) +} diff --git a/tests/syscalls.rs b/tests/syscalls.rs new file mode 100644 index 0000000..9121091 --- /dev/null +++ b/tests/syscalls.rs @@ -0,0 +1,62 @@ +use std::{path::Path, sync::Arc}; + +use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; +use cairo_lang_sierra::program::{GenFunction, Program, StatementIdx}; +use sierra_emu::{ProgramTrace, StateDump, VirtualMachine}; + +fn run_syscall(func_name: &str) -> ProgramTrace { + let path = Path::new("programs/syscalls.cairo"); + + let sierra_program = Arc::new( + compile_cairo_project_at_path( + path, + CompilerConfig { + replace_ids: true, + ..Default::default() + }, + ) + .unwrap(), + ); + + let function = find_entry_point_by_name(&sierra_program, func_name).unwrap(); + + let mut vm = VirtualMachine::new(sierra_program.clone()); + + let calldata = []; + let initial_gas = 1000000; + + vm.call_contract(function, initial_gas, calldata); + + let mut trace = ProgramTrace::new(); + + while let Some((statement_idx, state)) = vm.step() { + trace.push(StateDump::new(statement_idx, state)); + } + + trace +} + +#[test] +fn test_contract_constructor() { + run_syscall("syscalls::syscalls::get_execution_info_v2"); +} + +pub fn find_entry_point_by_idx( + program: &Program, + entry_point_idx: usize, +) -> Option<&GenFunction> { + program + .funcs + .iter() + .find(|x| x.id.id == entry_point_idx as u64) +} + +pub fn find_entry_point_by_name<'a>( + program: &'a Program, + name: &str, +) -> Option<&'a GenFunction> { + program + .funcs + .iter() + .find(|x| x.id.debug_name.as_ref().map(|x| x.as_str()) == Some(name)) +} diff --git a/tests/tests/test_u32.cairo b/tests/tests/test_u32.cairo new file mode 100644 index 0000000..1aa78c3 --- /dev/null +++ b/tests/tests/test_u32.cairo @@ -0,0 +1,3 @@ +fn run_test(lhs: u32, rhs: u32) -> u32 { + lhs - rhs +}