From 3d0094124be2ca0faecdb7ac4944525146978cc7 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:39:04 -0300 Subject: [PATCH] Add `EXCESS_BALANCE` hint & Bump Rust to 1.74 (base 0.9.x) (#1786) * Add `EXCESS_BALANCE` hint (#1777) * WIP * Add MarginParams * Impl imf * Start excess_balance fn * Add TODO comment * refactor + handle errors * Remove unused error * Finish implementing hint * Handle error * Fix var names * Setup hint test * Fix test * Fix test * Fix test * Fix test values * Fix * Update * Fix * Use constants * Remove unwrap * Remove allow * Remove unused feature * Add failure test case * Fix test values * Add no-std imports * Add no-std imports * Add hint code * Add changelog entry * Use checked arithmetic operations * lock flamegraph dep * Lock more dependencies * fix * Lock more dependencies * Update Rust version to 1.74.1 (#1605) * Update rust version to 1.74.1 * cargo fmt -all * Update Makefile * cargo clippy --fix * fix cargo clippy * restore fuzzer/Cargo.lock --------- Co-authored-by: Pedro Fontana * Apply clippy * fmt * Remove cairo1-run * Revert "Remove cairo1-run" This reverts commit ef5d214ea895e40c1818e3b54c71e56c6e0551f0. * Remove changes to cairo1-run crate --------- Co-authored-by: Pedro Fontana Co-authored-by: Pedro Fontana --- .github/workflows/bench.yml | 2 +- .github/workflows/cairo_1_programs.yml | 2 +- .github/workflows/fresh_run.yml | 2 +- .github/workflows/hint_accountant.yml | 2 +- .github/workflows/hyperfine.yml | 2 +- .github/workflows/iai_main.yml | 2 +- .github/workflows/iai_pr.yml | 4 +- .github/workflows/publish.yml | 10 +- .github/workflows/rust.yml | 8 +- CHANGELOG.md | 2 + Cargo.lock | 11 + Makefile | 8 +- README.md | 2 +- felt/src/lib_bigint_felt.rs | 2 +- rust-toolchain | 2 +- vm/Cargo.toml | 1 + .../builtin_hint_processor_definition.rs | 8 + .../builtin_hint_processor/dict_manager.rs | 11 + .../builtin_hint_processor/excess_balance.rs | 1225 +++++++++++++++++ .../find_element_hint.rs | 2 +- .../builtin_hint_processor/hint_code.rs | 9 + .../builtin_hint_processor/mod.rs | 1 + vm/src/serde/deserialize_program.rs | 4 +- vm/src/serde/deserialize_utils.rs | 2 +- vm/src/tests/mod.rs | 16 +- vm/src/vm/errors/hint_errors.rs | 6 + vm/src/vm/runners/cairo_pie.rs | 1 + 27 files changed, 1307 insertions(+), 40 deletions(-) create mode 100644 vm/src/hint_processor/builtin_hint_processor/excess_balance.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f3443d5f68..cf4a54a96d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: rustfmt, clippy - uses: actions/checkout@v3 diff --git a/.github/workflows/cairo_1_programs.yml b/.github/workflows/cairo_1_programs.yml index d8a4c1f3f4..c2872a03b4 100644 --- a/.github/workflows/cairo_1_programs.yml +++ b/.github/workflows/cairo_1_programs.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/fresh_run.yml b/.github/workflows/fresh_run.yml index 4660f1f8a2..de76902a24 100644 --- a/.github/workflows/fresh_run.yml +++ b/.github/workflows/fresh_run.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Install Pyenv uses: "gabrielfalcao/pyenv-action@v13" diff --git a/.github/workflows/hint_accountant.yml b/.github/workflows/hint_accountant.yml index 4a3a1e023f..3bd9ca4ff0 100644 --- a/.github/workflows/hint_accountant.yml +++ b/.github/workflows/hint_accountant.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/hyperfine.yml b/.github/workflows/hyperfine.yml index 87bf26f3e3..6fb098b6a6 100644 --- a/.github/workflows/hyperfine.yml +++ b/.github/workflows/hyperfine.yml @@ -74,7 +74,7 @@ jobs: - name: Install Rust if: ${{ steps.cache.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Checkout if: ${{ steps.cache.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/iai_main.yml b/.github/workflows/iai_main.yml index e1217085ba..f61eef7e3e 100644 --- a/.github/workflows/iai_main.yml +++ b/.github/workflows/iai_main.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Python3 Build diff --git a/.github/workflows/iai_pr.yml b/.github/workflows/iai_pr.yml index db446adeb9..afb9e3a97c 100644 --- a/.github/workflows/iai_pr.yml +++ b/.github/workflows/iai_pr.yml @@ -23,7 +23,7 @@ jobs: - name: Install Rust if: ${{ steps.cache-iai-results.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache if: ${{ steps.cache-iai-results.outputs.cache-hit != 'true' }} uses: Swatinem/rust-cache@v2 @@ -51,7 +51,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Python3 Build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f8f5c9b81a..a06efe8806 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,15 +13,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.70.0 - - name: Publish crate cairo-felt - env: - CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish --token ${CRATES_TOKEN} --all-features --manifest-path ./felt/Cargo.toml - # FIXME: there should be a better way to make sure the index in crates.io is updated before publishing - # cairo-vm but right now the step throws timeout and fails. - - name: wait for index in crates.io - run: sleep 300 + uses: dtolnay/rust-toolchain@1.74.1 - name: Publish crate cairo-vm env: CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 32a20befbd..8b34694243 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: rustfmt, clippy - name: Set up cargo cache @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: wasm32-unknown-unknown @@ -220,7 +220,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: llvm-tools-preview - name: Set up cargo cache @@ -281,7 +281,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/CHANGELOG.md b/CHANGELOG.md index de8cf7b9d0..35b7c23019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Add `EXCESS_BALANCE` hint [#1777](https://github.com/lambdaclass/cairo-vm/pull/1777) + #### [0.9.2] - 2024-01-3 * Change ec_op_impl() to use ProjectivePoint [#1534](https://github.com/lambdaclass/cairo-vm/pull/1534) diff --git a/Cargo.lock b/Cargo.lock index e213786e36..66d942feb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,7 @@ dependencies = [ "proptest", "rand", "rstest", + "rust_decimal", "serde", "serde_json", "sha2", @@ -2208,6 +2209,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "num-traits 0.2.17", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/Makefile b/Makefile index 401f41ffe2..19f1136e8e 100644 --- a/Makefile +++ b/Makefile @@ -177,11 +177,11 @@ build-cairo-2-compiler: cargo-deps: cargo install --version 0.3.1 iai-callgrind-runner cargo install --version 1.1.0 cargo-criterion - cargo install --version 0.6.1 flamegraph + cargo install --version 0.6.1 flamegraph --locked cargo install --version 1.14.0 hyperfine - cargo install --version 0.9.49 cargo-nextest - cargo install --version 0.5.9 cargo-llvm-cov - cargo install --version 0.12.1 wasm-pack + cargo install --version 0.9.49 cargo-nextest --locked + cargo install --version 0.5.9 cargo-llvm-cov --locked + cargo install --version 0.12.1 wasm-pack --locked cairo1-run-deps: cd cairo1-run; make deps diff --git a/README.md b/README.md index 52c2dd4043..db368a35db 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ It's Turing-complete and it was created by [Starkware](https://starkware.co/) as These are needed in order to compile and use the project. -- [Rust 1.70.0 or newer](https://www.rust-lang.org/tools/install) +- [Rust 1.74.1 or newer](https://www.rust-lang.org/tools/install) - Cargo #### Optional diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs index 1c9f3e0479..87472acfd1 100644 --- a/felt/src/lib_bigint_felt.rs +++ b/felt/src/lib_bigint_felt.rs @@ -377,7 +377,7 @@ impl Add<&Felt252> for u64 { }; // A single digit means this is effectively the sum of two `u64` numbers. let Some(h0) = rhs_digits.next() else { - return self.checked_add(low) + return self.checked_add(low); }; // Now we need to compare the 3 most significant digits. // There are two relevant cases from now on, either `rhs` behaves like a diff --git a/rust-toolchain b/rust-toolchain index 2d24a1e077..4da0ec7b8e 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,4 +1,4 @@ [toolchain] -channel = "1.70.0" +channel = "1.74.1" components = ["rustfmt", "clippy"] profile = "minimal" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index a101ab257a..64065503ea 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -58,6 +58,7 @@ keccak = { workspace = true } hashbrown = { workspace = true } anyhow = { workspace = true } thiserror-no-std = { workspace = true } +rust_decimal = { version = "1.35.0", default-features = false } # only for std num-prime = { version = "0.4.3", features = ["big-int"], optional = true } diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index 1c3c5f4b40..f01ce2bd5e 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -4,6 +4,7 @@ use super::{ ec_recover_divmod_n_packed, ec_recover_product_div_m, ec_recover_product_mod, ec_recover_sub_a_b, }, + excess_balance::excess_balance_hint, field_arithmetic::{u256_get_square_root, u384_get_square_root, uint384_div}, secp::{ ec_utils::{ @@ -815,6 +816,13 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::SPLIT_XX => split_xx(vm, &hint_data.ids_data, &hint_data.ap_tracking), #[cfg(feature = "skip_next_instruction_hint")] hint_code::SKIP_NEXT_INSTRUCTION => skip_next_instruction(vm), + hint_code::EXCESS_BALANCE => excess_balance_hint( + vm, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + exec_scopes, + ), code => Err(HintError::UnknownHint(code.to_string().into_boxed_str())), } } diff --git a/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs b/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs index 29d73a4a32..dd4ed198d2 100644 --- a/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs +++ b/vm/src/hint_processor/builtin_hint_processor/dict_manager.rs @@ -191,6 +191,17 @@ impl DictTracker { } } + //Returns a reference to the contained dictionary, losing the dictionary type in the process + pub fn get_dictionary_ref(&self) -> &HashMap { + match &self.data { + Dictionary::SimpleDictionary(dict) => dict, + Dictionary::DefaultDictionary { + dict, + default_value: _, + } => dict, + } + } + pub fn get_value(&mut self, key: &MaybeRelocatable) -> Result<&MaybeRelocatable, HintError> { self.data .get(key) diff --git a/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs b/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs new file mode 100644 index 0000000000..7c75fa137f --- /dev/null +++ b/vm/src/hint_processor/builtin_hint_processor/excess_balance.rs @@ -0,0 +1,1225 @@ +use crate::{ + hint_processor::hint_processor_definition::HintReference, + serde::deserialize_program::ApTracking, + stdlib::collections::HashMap, + types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable}, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, +}; +use core::str::FromStr; + +use crate::felt::Felt252; +use num_bigint::{BigInt, BigUint}; +use rust_decimal::Decimal; + +use crate::{ + math_utils::isqrt, + stdlib::prelude::{String, ToString, Vec}, + types::relocatable::Relocatable, + vm::vm_memory::memory::Memory, +}; +use lazy_static::lazy_static; + +use super::{ + dict_manager::DictManager, + hint_utils::{ + get_constant_from_var_name, get_integer_from_var_name, get_ptr_from_var_name, + insert_value_from_var_name, + }, +}; + +// General helper functions + +lazy_static! { + static ref DECIMAL_ADJUSTMENT_POSITIVE: Decimal = Decimal::from_scientific("1e8").unwrap(); + static ref DECIMAL_ADJUSTMENT: Decimal = Decimal::from_scientific("1e-8").unwrap(); + static ref DECIMAL_ADJUSTMENT_HALVED: Decimal = Decimal::from_scientific("1e-4").unwrap(); +} + +fn felt_to_scaled_decimal(f: &Felt252) -> Option { + Some(Decimal::from_str_radix(&f.to_signed_felt().to_string(), 10).ok()? * *DECIMAL_ADJUSTMENT) +} + +fn felt_to_trimmed_str(f: &Felt252) -> Option { + Some( + core::str::from_utf8(&f.to_bytes_be()) + .ok()? + .trim_start_matches('\0') + .to_string(), + ) +} + +// Internal Data types + +#[derive(Debug, PartialEq, Eq, Hash)] +struct Position { + market: String, + amount: Decimal, + cost: Decimal, + cached_funding: Decimal, +} + +#[derive(Debug, PartialEq)] +struct MarginParams { + market: String, + imf_base: Decimal, + imf_factor: Decimal, + mmf_factor: Decimal, + imf_shift: Decimal, +} + +impl Position { + fn read_from_memory(memory: &Memory, read_ptr: Relocatable) -> Option { + Some(Position { + market: felt_to_trimmed_str(memory.get_integer(read_ptr).ok()?.as_ref())?, + amount: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 1_i32).ok()?).ok()?.as_ref(), + )?, + cost: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 2_i32).ok()?).ok()?.as_ref(), + )?, + cached_funding: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 3_i32).ok()?).ok()?.as_ref(), + )?, + }) + } +} + +impl MarginParams { + fn read_from_memory(memory: &Memory, read_ptr: Relocatable) -> Option { + Some(MarginParams { + market: felt_to_trimmed_str(memory.get_integer(read_ptr).ok()?.as_ref())?, + imf_base: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 4_i32).ok()?).ok()?.as_ref(), + )?, + imf_factor: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 5_i32).ok()?).ok()?.as_ref(), + )?, + mmf_factor: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 6_i32).ok()?).ok()?.as_ref(), + )?, + imf_shift: felt_to_scaled_decimal( + memory.get_integer((read_ptr + 7_i32).ok()?).ok()?.as_ref(), + )?, + }) + } + + fn imf(&self, abs_value: Decimal) -> Option { + let diff = abs_value + .checked_sub(self.imf_shift)? + .checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?; + let max = BigUint::from_str(&Decimal::ZERO.max(diff.trunc()).to_string()).ok()?; + let part_sqrt = isqrt(&max).ok()?; + let part_sqrt = Decimal::from_str(&part_sqrt.to_string()) + .ok()? + .checked_mul(*DECIMAL_ADJUSTMENT_HALVED)?; + Some(self.imf_base.max(self.imf_factor.checked_mul(part_sqrt)?)) + } + + fn mmf(&self, abs_value: Decimal) -> Option { + self.mmf_factor.checked_mul(self.imf(abs_value)?) + } +} + +// Excess Balance helpers + +fn dict_ref_from_var_name<'a>( + var_name: &'a str, + vm: &'a VirtualMachine, + dict_manager: &'a DictManager, + ids_data: &'a HashMap, + ap_tracking: &'a ApTracking, +) -> Option<&'a HashMap> { + let prices_cache_ptr = get_ptr_from_var_name(var_name, vm, ids_data, ap_tracking).ok()?; + Some( + dict_manager + .get_tracker(prices_cache_ptr) + .ok()? + .get_dictionary_ref(), + ) +} + +fn prices_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let prices = + dict_ref_from_var_name("prices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, Decimal)> { + Some(( + felt_to_trimmed_str(k.get_int_ref()?)?, + felt_to_scaled_decimal(v.get_int_ref()?)?, + )) + }; + + prices + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn indices_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let indices = + dict_ref_from_var_name("indices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, Decimal)> { + Some(( + felt_to_trimmed_str(k.get_int_ref()?)?, + felt_to_scaled_decimal(v.get_int_ref()?)?, + )) + }; + + indices + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn perps_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let perps = dict_ref_from_var_name("perps_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, MarginParams)> { + Some(( + felt_to_trimmed_str(k.get_int_ref()?)?, + MarginParams::read_from_memory(&vm.segments.memory, v.get_relocatable()?)?, + )) + }; + + perps + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn fees_dict( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let fees = dict_ref_from_var_name("fees_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?; + + // Apply data type conversions + let apply_conversion = + |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(Felt252, Decimal)> { + Some(( + k.get_int_ref()?.clone(), + felt_to_scaled_decimal(v.get_int_ref()?)?, + )) + }; + + fees.iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +fn balances_list( + vm: &VirtualMachine, + dict_manager: &DictManager, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Option> { + // Fetch dictionary + let balances = dict_ref_from_var_name( + "perps_balances_cache_ptr", + vm, + dict_manager, + ids_data, + ap_tracking, + )?; + + // Apply data type conversions + let apply_conversion = |_, v: &MaybeRelocatable| -> Option { + Position::read_from_memory(&vm.segments.memory, v.get_relocatable()?) + }; + + balances + .iter() + .map(|(k, v)| apply_conversion(k, v)) + .collect::>() +} + +pub fn excess_balance_hint( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + constants: &HashMap, + exec_scopes: &ExecutionScopes, +) -> Result<(), HintError> { + // Fetch constants & variables + let margin_check_type = + get_integer_from_var_name("margin_check_type", vm, ids_data, ap_tracking)?; + let margin_check_initial = get_constant_from_var_name("MARGIN_CHECK_INITIAL", constants)?; + let token_assets_value_d = + get_integer_from_var_name("token_assets_value_d", vm, ids_data, ap_tracking)?; + let account = get_integer_from_var_name("account", vm, ids_data, ap_tracking)?; + // Fetch DictManager + let dict_manager_rc = exec_scopes.get_dict_manager()?; + let dict_manager = dict_manager_rc.borrow(); + // Fetch dictionaries + let prices = prices_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("prices".into()))?; + let indices = indices_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("indices".into()))?; + let perps = perps_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("perps".into()))?; + let fees = fees_dict(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("fees".into()))?; + let balances = balances_list(vm, &dict_manager, ids_data, ap_tracking) + .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("balances".into()))?; + + // Fetch settelement price + let settlement_asset = String::from("USDC-USD"); + let settlement_price = prices + .get(&settlement_asset) + .ok_or_else(|| HintError::ExcessBalanceKeyError("prices".into()))?; + + let mut unrealized_pnl = Decimal::ZERO; + let mut unrealized_funding_pnl = Decimal::ZERO; + let mut abs_balance_value = Decimal::ZERO; + let mut position_margin = Decimal::ZERO; + + for position in balances { + if position.market == settlement_asset { + continue; + } + + let price = prices + .get(&position.market) + .ok_or_else(|| HintError::ExcessBalanceKeyError("prices".into()))?; + let funding_index = indices + .get(&position.market) + .ok_or_else(|| HintError::ExcessBalanceKeyError("indices".into()))?; + let position_value = position + .amount + .checked_mul(*price) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("position_value".into()))?; + let position_value_abs = position_value.abs(); + + abs_balance_value = abs_balance_value + .checked_add(position_value_abs) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("abs_balance_value".into()))?; + + let market_perps = perps + .get(&position.market) + .ok_or_else(|| HintError::ExcessBalanceKeyError("perps".into()))?; + let margin_fraction = if margin_check_type.as_ref() == margin_check_initial { + market_perps.imf(position_value_abs) + } else { + market_perps.mmf(position_value_abs) + } + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_fraction".into()))?; + // position_margin += margin_fraction * position_value_abs + position_margin = margin_fraction + .checked_mul(position_value_abs) + .and_then(|mul| position_margin.checked_add(mul)) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("position_margin".into()))?; + // unrealized_pnl += position_value - position.cost * settlement_price + let calc_unrealized_pnl = |unrealized_pnl: Decimal, + position: &Position, + settlement_price: Decimal| + -> Option { + unrealized_pnl.checked_add( + position_value.checked_sub(position.cost.checked_mul(settlement_price)?)?, + ) + }; + unrealized_pnl = calc_unrealized_pnl(unrealized_pnl, &position, *settlement_price) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl".into()))?; + // unrealized_funding_pnl += (position.cached_funding - funding_index) * position.amount*settlement_price + let calc_unrealized_funding_pnl = |unrealized_funding_pnl: Decimal, + position: &Position, + funding_index: Decimal, + settlement_price: Decimal| + -> Option { + unrealized_funding_pnl.checked_add( + position + .cached_funding + .checked_sub(funding_index)? + .checked_mul(position.amount)? + .checked_mul(settlement_price)?, + ) + }; + unrealized_funding_pnl = calc_unrealized_funding_pnl( + unrealized_funding_pnl, + &position, + *funding_index, + *settlement_price, + ) + .ok_or_else(|| { + HintError::ExcessBalanceCalculationFailed("unrealized_funding_pnl".into()) + })?; + } + + // Calculate final results + let token_assets_value_d = felt_to_scaled_decimal(&token_assets_value_d) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; + let account_value = unrealized_pnl + .checked_add(unrealized_funding_pnl) + .and_then(|sum| sum.checked_add(token_assets_value_d)) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; + let fee_provision = fees + .get(account.as_ref()) + .and_then(|fee| abs_balance_value.checked_mul(*fee)) + .ok_or_else(|| HintError::ExcessBalanceKeyError("fees".into()))?; + let margin_requirement = position_margin + .checked_add(fee_provision) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirements".into()))?; + let excess_balance = account_value + .checked_sub(margin_requirement) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?; + + // Convert final results to Felt + let felt_from_decimal = |d: Decimal| -> Option { + Some(Felt252::from( + BigInt::from_str( + &(d.checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?) + .trunc() + .to_string(), + ) + .ok()?, + )) + }; + + let account_value = felt_from_decimal(account_value) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?; + let excess_balance = felt_from_decimal(excess_balance) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?; + let margin_requirement = felt_from_decimal(margin_requirement) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirement_d".into()))?; + let unrealized_pnl = felt_from_decimal(unrealized_pnl) + .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl_d".into()))?; + + // Write results into memory + insert_value_from_var_name( + "check_account_value", + account_value, + vm, + ids_data, + ap_tracking, + )?; + insert_value_from_var_name( + "check_excess_balance", + excess_balance, + vm, + ids_data, + ap_tracking, + )?; + insert_value_from_var_name( + "check_margin_requirement_d", + margin_requirement, + vm, + ids_data, + ap_tracking, + )?; + insert_value_from_var_name( + "check_unrealized_pnl_d", + unrealized_pnl, + vm, + ids_data, + ap_tracking, + ) +} + +#[cfg(test)] +mod tests { + use num_traits::{One, Zero}; + + use crate::stdlib::{cell::RefCell, rc::Rc}; + use core::str::FromStr; + + use super::*; + use crate::felt::felt_str; + use crate::utils::test_utils::*; + + #[test] + fn test_read_position() { + let memory = memory![ + ((0, 0), ("5176525270854594879110454268496", 10)), + ((0, 1), 1000000000), + ((0, 2), 20000), + ((0, 3), 0) + ]; + let expected_position = Position { + market: String::from("AVAX-USD-PERP"), + amount: Decimal::from_str("10.00000000").unwrap(), + cost: Decimal::from_str("0.00020000").unwrap(), + cached_funding: Decimal::from_scientific("0e-8").unwrap(), + }; + assert_eq!( + expected_position, + Position::read_from_memory(&memory, (0, 0).into()).unwrap() + ) + } + + #[test] + fn test_read_margin_params() { + let memory = memory![ + ((0, 0), ("20527877651862571847371805264", 10)), + ((0, 4), 5000000), + ((0, 5), 20000), + ((0, 6), 50000000), + ((0, 7), 20000000000000) + ]; + let expected_position = MarginParams { + market: String::from("BTC-USD-PERP"), + imf_base: Decimal::from_str("0.05000000").unwrap(), + imf_factor: Decimal::from_str("0.00020000").unwrap(), + mmf_factor: Decimal::from_str("0.50000000").unwrap(), + imf_shift: Decimal::from_str("200000.00000000").unwrap(), + }; + assert_eq!( + expected_position, + MarginParams::read_from_memory(&memory, (0, 0).into()).unwrap() + ) + } + + #[test] + fn test_imf() { + let abs_value = Decimal::from_str("459000.0000000000000000").unwrap(); + let margin_params = MarginParams { + market: String::from("BTC-USD-PERP"), + imf_base: Decimal::from_str("0.05000000").unwrap(), + imf_factor: Decimal::from_str("0.00020000").unwrap(), + mmf_factor: Decimal::from_str("0.50000000").unwrap(), + imf_shift: Decimal::from_str("200000.00000000").unwrap(), + }; + let expected_res = Decimal::from_str("0.101784080000").unwrap(); + assert_eq!(expected_res, margin_params.imf(abs_value).unwrap()); + } + + #[test] + fn run_excess_balance_hint_succesful_trade() { + // TEST DATA + + // INPUT VALUES + // ids.margin_check_type 1 + // ids.MARGIN_CHECK_INITIAL 1 + // ids.token_assets_value_d 1005149999998000 + // ids.account 200 + // DICTIONARIES + // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000} + // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0} + // perps {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=3092), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=3467), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=3842), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=4217), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=4592)} + // fees {100: 10000, 200: 10000} + // balances {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=6406), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=6625), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=6844), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=7063), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=18230)} + // MEMORY VALUES REFERENCED BY DICTIONARIES + // 1:3092 6044027408028715819619898970704 + // 1:3096 5000000 + // 1:3097 20000 + // 1:3098 50000000 + // 1:3099 20000000000000 + // 1:3467 25783120691025710696626475600 + // 1:3471 5000000 + // 1:3472 20000 + // 1:3473 50000000 + // 1:3474 20000000000000 + // 1:3842 5176525270854594879110454268496 + // 1:3846 5000000 + // 1:3847 20000 + // 1:3848 50000000 + // 1:3849 20000000000000 + // 1:4217 21456356293159021401772216912 + // 1:4221 5000000 + // 1:4222 20000 + // 1:4223 50000000 + // 1:4224 20000000000000 + // 1:4592 20527877651862571847371805264 + // 1:4596 5000000 + // 1:4597 20000 + // 1:4598 50000000 + // 1:4599 20000000000000 + // 1:6406 6044027408028715819619898970704 + // 1:6407 1000000000 + // 1:6408 20000 + // 1:6409 0 + // 1:6406 6044027408028715819619898970704 + // 1:6407 1000000000 + // 1:6408 20000 + // 1:6409 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 1000000000 + // 1:6627 20000 + // 1:6628 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 1000000000 + // 1:6627 20000 + // 1:6628 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 1000000000 + // 1:6846 20000 + // 1:6847 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 1000000000 + // 1:6846 20000 + // 1:6847 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 1000000000 + // 1:7065 20000 + // 1:7066 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 1000000000 + // 1:7065 20000 + // 1:7066 0 + // 1:18582 20527877651862571847371805264 + // 1:18583 900000000 + // 1:18584 18000 + // 1:18585 0 + // 1:18582 20527877651862571847371805264 + // 1:18583 900000000 + // 1:18584 18000 + // 1:18585 0 + // EXPECTED RESULTS + // ids.check_account_value 1255049999900000 + // ids.check_excess_balance 1227636643508000 + // ids.check_margin_requirement_d 27413356392000 + // ids.check_unrealized_pnl_d 249899999902000 + + // SETUP + let mut vm = vm!(); + // CONSTANTS + let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::one())]); + // IDS + vm.segments = segments!( + ((1, 0), 1), // ids.margin_check_type + ((1, 1), 1005149999998000), // ids.token_assets_value_d + ((1, 2), 200), // ids.account + ((1, 3), (2, 0)), // ids.prices_cache_ptr + ((1, 4), (3, 0)), // ids.indices_cache_ptr + ((1, 5), (4, 0)), // ids.perps_cache_ptr + ((1, 6), (5, 0)), // ids.fees_cache_ptr + ((1, 7), (6, 0)), // ids.perps_balances_cache_ptr + //((1, 8), ids.check_account_value) + //((1, 9), ids.check_excess_balance) + //((1, 10), ids.check_margin_requirement_d) + //((1, 11), ids.check_unrealized_pnl_d) + // Memory values referenced by hints + ((1, 3092), 6044027408028715819619898970704), + ((1, 3096), 5000000), + ((1, 3097), 20000), + ((1, 3098), 50000000), + ((1, 3099), 20000000000000), + ((1, 3467), 25783120691025710696626475600), + ((1, 3471), 5000000), + ((1, 3472), 20000), + ((1, 3473), 50000000), + ((1, 3474), 20000000000000), + ((1, 3842), 5176525270854594879110454268496), + ((1, 3846), 5000000), + ((1, 3847), 20000), + ((1, 3848), 50000000), + ((1, 3849), 20000000000000), + ((1, 4217), 21456356293159021401772216912), + ((1, 4221), 5000000), + ((1, 4222), 20000), + ((1, 4223), 50000000), + ((1, 4224), 20000000000000), + ((1, 4592), 20527877651862571847371805264), + ((1, 4596), 5000000), + ((1, 4597), 20000), + ((1, 4598), 50000000), + ((1, 4599), 20000000000000), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 1000000000), + ((1, 6408), 20000), + ((1, 6409), 0), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 1000000000), + ((1, 6408), 20000), + ((1, 6409), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 1000000000), + ((1, 6627), 20000), + ((1, 6628), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 1000000000), + ((1, 6627), 20000), + ((1, 6628), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 1000000000), + ((1, 6846), 20000), + ((1, 6847), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 1000000000), + ((1, 6846), 20000), + ((1, 6847), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 1000000000), + ((1, 7065), 20000), + ((1, 7066), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 1000000000), + ((1, 7065), 20000), + ((1, 7066), 0), + ((1, 18582), 20527877651862571847371805264), + ((1, 18583), 900000000), + ((1, 18584), 18000), + ((1, 18585), 0), + ((1, 18582), 20527877651862571847371805264), + ((1, 18583), 900000000), + ((1, 18584), 18000), + ((1, 18585), 0) + ); + vm.run_context.set_fp(12); + let ids = ids_data![ + "margin_check_type", + "token_assets_value_d", + "account", + "prices_cache_ptr", + "indices_cache_ptr", + "perps_cache_ptr", + "fees_cache_ptr", + "perps_balances_cache_ptr", + "check_account_value", + "check_excess_balance", + "check_margin_requirement_d", + "check_unrealized_pnl_d" + ]; + // DICTIONARIES + let mut exec_scopes = ExecutionScopes::new(); + let mut dict_manager = DictManager::new(); + // ids.prices_cache_ptr = (2, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("6148332971604923204").into(), + felt_str!("100000000").into(), + ), + ]), + ) + .unwrap(); + // ids.indices_cache_ptr = (3, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + Felt252::zero().into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + Felt252::zero().into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + Felt252::zero().into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + Felt252::zero().into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + Felt252::zero().into(), + ), + ]), + ) + .unwrap(); + // ids.perps_cache_ptr = (4, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 3092).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 3467).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 3842).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 4217).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 4592).into(), + ), + ]), + ) + .unwrap(); + // ids.fees_cache_ptr = (5, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + (Felt252::from(100).into(), Felt252::from(10000).into()), + (Felt252::from(200).into(), Felt252::from(10000).into()), + ]), + ) + .unwrap(); + // ids.perps_balances_cache_ptr = (6, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 6406).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 6625).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 6844).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 7063).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 18582).into(), + ), + ]), + ) + .unwrap(); + exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager))); + + // EXECUTION + assert!(excess_balance_hint( + &mut vm, + &ids, + &ApTracking::default(), + &constants, + &exec_scopes + ) + .is_ok()); + + // CHECK MEMORY VALUES + check_memory![ + vm.segments.memory, + // ids.check_account_value + ((1, 8), 1255049999900000), + // ids.check_excess_balance + ((1, 9), 1227636643508000), + // ids.check_margin_requirement_d + ((1, 10), 27413356392000), + // ids.check_unrealized_pnl_d + ((1, 11), 249899999902000) + ]; + } + + #[test] + fn run_excess_balance_hint_trade_failure() { + // TEST DATA + + // INPUT VALUES + // ids.margin_check_type 1 + // ids.MARGIN_CHECK_INITIAL 1 + // ids.token_assets_value_d 0 + // ids.account 100 + // DICTIONARIES + // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000} + // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0} + // perps {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=3092), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=3467), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=3842), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=4217), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=4592)} + // fees {100: 10000, 200: 10000} + // balances {6044027408028715819619898970704: RelocatableValue(segment_index=1, offset=6406), 25783120691025710696626475600: RelocatableValue(segment_index=1, offset=6625), 5176525270854594879110454268496: RelocatableValue(segment_index=1, offset=6844), 21456356293159021401772216912: RelocatableValue(segment_index=1, offset=7063), 20527877651862571847371805264: RelocatableValue(segment_index=1, offset=18230)} + // MEMORY VALUES REFERENCED BY DICTIONARIES + // 1:3092 6044027408028715819619898970704 + // 1:3096 5000000 + // 1:3097 20000 + // 1:3098 50000000 + // 1:3099 20000000000000 + // 1:3467 25783120691025710696626475600 + // 1:3471 5000000 + // 1:3472 20000 + // 1:3473 50000000 + // 1:3474 20000000000000 + // 1:3842 5176525270854594879110454268496 + // 1:3846 5000000 + // 1:3847 20000 + // 1:3848 50000000 + // 1:3849 20000000000000 + // 1:4217 21456356293159021401772216912 + // 1:4221 5000000 + // 1:4222 20000 + // 1:4223 50000000 + // 1:4224 20000000000000 + // 1:4592 20527877651862571847371805264 + // 1:4596 5000000 + // 1:4597 20000 + // 1:4598 50000000 + // 1:4599 20000000000000 + // 1:6406 6044027408028715819619898970704 + // 1:6407 0 + // 1:6408 0 + // 1:6409 0 + // 1:6406 6044027408028715819619898970704 + // 1:6407 0 + // 1:6408 0 + // 1:6409 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 0 + // 1:6627 0 + // 1:6628 0 + // 1:6625 25783120691025710696626475600 + // 1:6626 0 + // 1:6627 0 + // 1:6628 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 0 + // 1:6846 0 + // 1:6847 0 + // 1:6844 5176525270854594879110454268496 + // 1:6845 0 + // 1:6846 0 + // 1:6847 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 0 + // 1:7065 0 + // 1:7066 0 + // 1:7063 21456356293159021401772216912 + // 1:7064 0 + // 1:7065 0 + // 1:7066 0 + // 1:18230 20527877651862571847371805264 + // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481 + // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481 + // 1:18233 0 + // 1:18230 20527877651862571847371805264 + // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481 + // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481 + // 1:18233 0 + // EXPECTED RESULTS + // ids.check_account_value 50000000000 + // ids.check_excess_balance 3618502788666131213697322783095070105623107215331596699973092055930362020481 + // ids.check_margin_requirement_d 255510000000 + // ids.check_unrealized_pnl_d 50000000000 + + // SETUP + let mut vm = vm!(); + // CONSTANTS + let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::one())]); + // IDS + vm.segments = segments!( + ((1, 0), 1), // ids.margin_check_type + ((1, 1), 0), // ids.token_assets_value_d + ((1, 2), 100), // ids.account + ((1, 3), (2, 0)), // ids.prices_cache_ptr + ((1, 4), (3, 0)), // ids.indices_cache_ptr + ((1, 5), (4, 0)), // ids.perps_cache_ptr + ((1, 6), (5, 0)), // ids.fees_cache_ptr + ((1, 7), (6, 0)), // ids.perps_balances_cache_ptr + //((1, 8), ids.check_account_value) + //((1, 9), ids.check_excess_balance) + //((1, 10), ids.check_margin_requirement_d) + //((1, 11), ids.check_unrealized_pnl_d) + // Memory values referenced by hints + ((1, 3092), 6044027408028715819619898970704), + ((1, 3096), 5000000), + ((1, 3097), 20000), + ((1, 3098), 50000000), + ((1, 3099), 20000000000000), + ((1, 3467), 25783120691025710696626475600), + ((1, 3471), 5000000), + ((1, 3472), 20000), + ((1, 3473), 50000000), + ((1, 3474), 20000000000000), + ((1, 3842), 5176525270854594879110454268496), + ((1, 3846), 5000000), + ((1, 3847), 20000), + ((1, 3848), 50000000), + ((1, 3849), 20000000000000), + ((1, 4217), 21456356293159021401772216912), + ((1, 4221), 5000000), + ((1, 4222), 20000), + ((1, 4223), 50000000), + ((1, 4224), 20000000000000), + ((1, 4592), 20527877651862571847371805264), + ((1, 4596), 5000000), + ((1, 4597), 20000), + ((1, 4598), 50000000), + ((1, 4599), 20000000000000), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 0), + ((1, 6408), 0), + ((1, 6409), 0), + ((1, 6406), 6044027408028715819619898970704), + ((1, 6407), 0), + ((1, 6408), 0), + ((1, 6409), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 0), + ((1, 6627), 0), + ((1, 6628), 0), + ((1, 6625), 25783120691025710696626475600), + ((1, 6626), 0), + ((1, 6627), 0), + ((1, 6628), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 0), + ((1, 6846), 0), + ((1, 6847), 0), + ((1, 6844), 5176525270854594879110454268496), + ((1, 6845), 0), + ((1, 6846), 0), + ((1, 6847), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 0), + ((1, 7065), 0), + ((1, 7066), 0), + ((1, 7063), 21456356293159021401772216912), + ((1, 7064), 0), + ((1, 7065), 0), + ((1, 7066), 0), + ((1, 18230), 20527877651862571847371805264), + ( + (1, 18231), + ( + "3618502788666131213697322783095070105623107215331596699973092056135772020481", + 10 + ) + ), + ( + (1, 18232), + ( + "3618502788666131213697322783095070105623107215331596699973092050985872020481", + 10 + ) + ), + ((1, 18233), 0), + ((1, 18230), 20527877651862571847371805264), + ( + (1, 18231), + ( + "3618502788666131213697322783095070105623107215331596699973092056135772020481", + 10 + ) + ), + ( + (1, 18232), + ( + "3618502788666131213697322783095070105623107215331596699973092050985872020481", + 10 + ) + ), + ((1, 18233), 0), + ); + vm.run_context.set_fp(12); + let ids = ids_data![ + "margin_check_type", + "token_assets_value_d", + "account", + "prices_cache_ptr", + "indices_cache_ptr", + "perps_cache_ptr", + "fees_cache_ptr", + "perps_balances_cache_ptr", + "check_account_value", + "check_excess_balance", + "check_margin_requirement_d", + "check_unrealized_pnl_d" + ]; + // DICTIONARIES + let mut exec_scopes = ExecutionScopes::new(); + let mut dict_manager = DictManager::new(); + // ids.prices_cache_ptr = (2, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + felt_str!("5100000000000").into(), + ), + ( + felt_str!("6148332971604923204").into(), + felt_str!("100000000").into(), + ), + ]), + ) + .unwrap(); + // ids.indices_cache_ptr = (3, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + Felt252::zero().into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + Felt252::zero().into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + Felt252::zero().into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + Felt252::zero().into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + Felt252::zero().into(), + ), + ]), + ) + .unwrap(); + // ids.perps_cache_ptr = (4, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 3092).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 3467).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 3842).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 4217).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 4592).into(), + ), + ]), + ) + .unwrap(); + // ids.fees_cache_ptr = (5, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + (Felt252::from(100).into(), Felt252::from(10000).into()), + (Felt252::from(200).into(), Felt252::from(10000).into()), + ]), + ) + .unwrap(); + // ids.perps_balances_cache_ptr = (6, 0) + dict_manager + .new_dict( + &mut vm, + HashMap::from([ + ( + felt_str!("6044027408028715819619898970704").into(), + (1, 6406).into(), + ), + ( + felt_str!("25783120691025710696626475600").into(), + (1, 6625).into(), + ), + ( + felt_str!("5176525270854594879110454268496").into(), + (1, 6844).into(), + ), + ( + felt_str!("21456356293159021401772216912").into(), + (1, 7063).into(), + ), + ( + felt_str!("20527877651862571847371805264").into(), + (1, 18230).into(), + ), + ]), + ) + .unwrap(); + exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager))); + + // EXECUTION + assert!(excess_balance_hint( + &mut vm, + &ids, + &ApTracking::default(), + &constants, + &exec_scopes + ) + .is_ok()); + + // CHECK MEMORY VALUES + check_memory![ + vm.segments.memory, + // ids.check_account_value + ((1, 8), 50000000000), + // ids.check_excess_balance + ( + (1, 9), + ( + "3618502788666131213697322783095070105623107215331596699973092055930362020481", + 10 + ) + ), + // ids.check_margin_requirement_d + ((1, 10), 255510000000), + // ids.check_unrealized_pnl_d + ((1, 11), 50000000000) + ]; + } +} diff --git a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs index d9d7a56600..9ea30fb6e3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs +++ b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs @@ -162,7 +162,7 @@ mod tests { vm.segments.add(); } - let addresses = vec![ + let addresses = [ Relocatable::from((1, 0)), Relocatable::from((1, 1)), Relocatable::from((1, 2)), diff --git a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs index 2b55c91f30..9ebe4ac139 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1409,3 +1409,12 @@ ids.x.low = x & ((1<<128)-1) ids.x.high = x >> 128"; #[cfg(feature = "skip_next_instruction_hint")] pub const SKIP_NEXT_INSTRUCTION: &str = "skip_next_instruction()"; + +pub const EXCESS_BALANCE: &str = r#"from excess_balance import excess_balance_func + +res = excess_balance_func(ids, memory, __dict_manager) + +ids.check_account_value = res["account_value"] +ids.check_excess_balance = res["excess_balance"] +ids.check_margin_requirement_d = res["margin_requirement"] +ids.check_unrealized_pnl_d = res["unrealized_pnl"]"#; diff --git a/vm/src/hint_processor/builtin_hint_processor/mod.rs b/vm/src/hint_processor/builtin_hint_processor/mod.rs index 0a3f067b5b..11480dcfc5 100644 --- a/vm/src/hint_processor/builtin_hint_processor/mod.rs +++ b/vm/src/hint_processor/builtin_hint_processor/mod.rs @@ -7,6 +7,7 @@ pub mod dict_hint_utils; pub mod dict_manager; pub mod ec_recover; pub mod ec_utils; +pub mod excess_balance; pub mod field_arithmetic; pub mod find_element_hint; pub mod garaga; diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index 52c5e70a42..072ab32ce7 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -327,7 +327,7 @@ impl<'de> de::Visitor<'de> for Felt252Visitor { let no_prefix_hex = deserialize_utils::maybe_add_padding(no_prefix_hex.to_string()); Ok(Felt252::from_str_radix(&no_prefix_hex, 16).map_err(de::Error::custom)?) } else { - Err(String::from("hex prefix error")).map_err(de::Error::custom) + Err(de::Error::custom(String::from("hex prefix error"))) } } } @@ -355,7 +355,7 @@ impl<'de> de::Visitor<'de> for MaybeRelocatableVisitor { Felt252::from_str_radix(&no_prefix_hex, 16).map_err(de::Error::custom)?, )); } else { - return Err(String::from("hex prefix error")).map_err(de::Error::custom); + return Err(de::Error::custom(String::from("hex prefix error"))); }; } Ok(data) diff --git a/vm/src/serde/deserialize_utils.rs b/vm/src/serde/deserialize_utils.rs index 5a90ec863d..53bcce78ca 100644 --- a/vm/src/serde/deserialize_utils.rs +++ b/vm/src/serde/deserialize_utils.rs @@ -230,7 +230,7 @@ fn take_until_unbalanced( .ok_or_else(|| Err::Error(Error::from_error_kind(i, ErrorKind::TakeUntil)))? .chars(); match it.next().unwrap_or_default() { - c if c == '\\' => { + '\\' => { // Skip the escape char `\`. index += '\\'.len_utf8(); // Skip also the following char. diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index cecacbad8d..ec27c96aab 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -45,30 +45,30 @@ mod skip_instruction_test; //For simple programs that should just succeed and have no special needs. //Checks memory holes == 0 -pub(self) fn run_program_simple(data: &[u8]) { +fn run_program_simple(data: &[u8]) { run_program(data, Some("all_cairo"), None, None, Some(0)) } //For simple programs that should just succeed and have no special needs. //Checks memory holes -pub(self) fn run_program_simple_with_memory_holes(data: &[u8], holes: usize) { +fn run_program_simple_with_memory_holes(data: &[u8], holes: usize) { run_program(data, Some("all_cairo"), None, None, Some(holes)) } //For simple programs that should just succeed but using small layout. -pub(self) fn run_program_small(data: &[u8]) { +fn run_program_small(data: &[u8]) { run_program(data, Some("small"), None, None, None) } -pub(self) fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { +fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { run_program(data, Some("all_cairo"), Some(trace), None, None) } -pub(self) fn run_program_with_error(data: &[u8], error: &str) { +fn run_program_with_error(data: &[u8], error: &str) { run_program(data, Some("all_cairo"), None, Some(error), None) } -pub(self) fn run_program( +fn run_program( data: &[u8], layout: Option<&str>, trace: Option<&[(usize, usize, usize)]>, @@ -109,7 +109,7 @@ pub(self) fn run_program( #[cfg(feature = "cairo-1-hints")] // Runs a contract entrypoint with given arguments and checks its return values // Doesn't use a syscall_handler -pub(self) fn run_cairo_1_entrypoint( +fn run_cairo_1_entrypoint( program_content: &[u8], entrypoint_offset: usize, args: &[MaybeRelocatable], @@ -215,7 +215,7 @@ pub(self) fn run_cairo_1_entrypoint( #[cfg(feature = "cairo-1-hints")] /// Equals to fn run_cairo_1_entrypoint /// But with run_resources as an input -pub(self) fn run_cairo_1_entrypoint_with_run_resources( +fn run_cairo_1_entrypoint_with_run_resources( contract_class: CasmContractClass, entrypoint_offset: usize, hint_processor: &mut Cairo1HintProcessor, diff --git a/vm/src/vm/errors/hint_errors.rs b/vm/src/vm/errors/hint_errors.rs index bcf8935c5d..9113db6013 100644 --- a/vm/src/vm/errors/hint_errors.rs +++ b/vm/src/vm/errors/hint_errors.rs @@ -184,6 +184,12 @@ pub enum HintError { NPairBitsTooLowM, #[error("{0}")] SyscallError(Box), + #[error("excess_balance_func: Failed to fetch {0} dictionary. It is either missing or has unexpected data types")] + ExcessBalanceFailedToFecthDict(Box), + #[error("excess_balance_func: Key not found in {0} dictionary")] + ExcessBalanceKeyError(Box), + #[error("excess_balance_func: Failed to calculate {0}")] + ExcessBalanceCalculationFailed(Box), } #[cfg(test)] diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index 60a0fa147e..2862a0fdbb 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -147,6 +147,7 @@ mod serde_impl { seq_serializer.end() } + #[allow(clippy::format_collect)] pub fn serialize_memory( values: &[((usize, usize), MaybeRelocatable)], serializer: S,