cairo_vm/hint_processor/builtin_hint_processor/
excess_balance.rs

1use crate::{
2    hint_processor::hint_processor_definition::HintReference,
3    serde::deserialize_program::ApTracking,
4    stdlib::collections::HashMap,
5    types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable},
6    vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
7};
8use core::str::FromStr;
9
10use num_bigint::{BigInt, BigUint};
11use rust_decimal::Decimal;
12use starknet_types_core::felt::Felt as Felt252;
13
14use crate::{
15    math_utils::{isqrt, signed_felt},
16    stdlib::prelude::{String, ToString, Vec},
17    types::relocatable::Relocatable,
18    vm::vm_memory::memory::Memory,
19};
20use lazy_static::lazy_static;
21
22use super::{
23    dict_manager::DictManager,
24    hint_utils::{
25        get_constant_from_var_name, get_integer_from_var_name, get_ptr_from_var_name,
26        insert_value_from_var_name,
27    },
28};
29
30// General helper functions
31
32lazy_static! {
33    static ref DECIMAL_ADJUSTMENT_POSITIVE: Decimal = Decimal::from_scientific("1e8").unwrap();
34    static ref DECIMAL_ADJUSTMENT: Decimal = Decimal::from_scientific("1e-8").unwrap();
35    static ref DECIMAL_ADJUSTMENT_HALVED: Decimal = Decimal::from_scientific("1e-4").unwrap();
36}
37
38fn felt_to_scaled_decimal(f: &Felt252) -> Option<Decimal> {
39    Some(Decimal::from_str_radix(&signed_felt(*f).to_string(), 10).ok()? * *DECIMAL_ADJUSTMENT)
40}
41
42fn felt_to_trimmed_str(f: &Felt252) -> Option<String> {
43    Some(
44        core::str::from_utf8(&f.to_bytes_be())
45            .ok()?
46            .trim_start_matches('\0')
47            .to_string(),
48    )
49}
50
51// Internal Data types
52
53#[derive(Debug, PartialEq, Eq, Hash)]
54struct Position {
55    market: String,
56    amount: Decimal,
57    cost: Decimal,
58    cached_funding: Decimal,
59}
60
61#[derive(Debug, PartialEq)]
62struct MarginParams {
63    market: String,
64    imf_base: Decimal,
65    imf_factor: Decimal,
66    mmf_factor: Decimal,
67    imf_shift: Decimal,
68}
69
70impl Position {
71    fn read_from_memory(memory: &Memory, read_ptr: Relocatable) -> Option<Self> {
72        Some(Position {
73            market: felt_to_trimmed_str(memory.get_integer(read_ptr).ok()?.as_ref())?,
74            amount: felt_to_scaled_decimal(
75                memory.get_integer((read_ptr + 1_u32).ok()?).ok()?.as_ref(),
76            )?,
77            cost: felt_to_scaled_decimal(
78                memory.get_integer((read_ptr + 2_u32).ok()?).ok()?.as_ref(),
79            )?,
80            cached_funding: felt_to_scaled_decimal(
81                memory.get_integer((read_ptr + 3_u32).ok()?).ok()?.as_ref(),
82            )?,
83        })
84    }
85}
86
87impl MarginParams {
88    fn read_from_memory(memory: &Memory, read_ptr: Relocatable) -> Option<Self> {
89        Some(MarginParams {
90            market: felt_to_trimmed_str(memory.get_integer(read_ptr).ok()?.as_ref())?,
91            imf_base: felt_to_scaled_decimal(
92                memory.get_integer((read_ptr + 4_u32).ok()?).ok()?.as_ref(),
93            )?,
94            imf_factor: felt_to_scaled_decimal(
95                memory.get_integer((read_ptr + 5_u32).ok()?).ok()?.as_ref(),
96            )?,
97            mmf_factor: felt_to_scaled_decimal(
98                memory.get_integer((read_ptr + 6_u32).ok()?).ok()?.as_ref(),
99            )?,
100            imf_shift: felt_to_scaled_decimal(
101                memory.get_integer((read_ptr + 7_u32).ok()?).ok()?.as_ref(),
102            )?,
103        })
104    }
105
106    fn imf(&self, abs_value: Decimal) -> Option<Decimal> {
107        let diff = abs_value
108            .checked_sub(self.imf_shift)?
109            .checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?;
110        let max = BigUint::from_str(&Decimal::ZERO.max(diff.trunc()).to_string()).ok()?;
111        let part_sqrt = isqrt(&max).ok()?;
112        let part_sqrt = Decimal::from_str(&part_sqrt.to_string())
113            .ok()?
114            .checked_mul(*DECIMAL_ADJUSTMENT_HALVED)?;
115        Some(self.imf_base.max(self.imf_factor.checked_mul(part_sqrt)?))
116    }
117
118    fn mmf(&self, abs_value: Decimal) -> Option<Decimal> {
119        self.mmf_factor.checked_mul(self.imf(abs_value)?)
120    }
121}
122
123// Excess Balance helpers
124
125fn dict_ref_from_var_name<'a>(
126    var_name: &'a str,
127    vm: &'a VirtualMachine,
128    dict_manager: &'a DictManager,
129    ids_data: &'a HashMap<String, HintReference>,
130    ap_tracking: &'a ApTracking,
131) -> Option<&'a HashMap<MaybeRelocatable, MaybeRelocatable>> {
132    let prices_cache_ptr = get_ptr_from_var_name(var_name, vm, ids_data, ap_tracking).ok()?;
133    Some(
134        dict_manager
135            .get_tracker(prices_cache_ptr)
136            .ok()?
137            .get_dictionary_ref(),
138    )
139}
140
141fn prices_dict(
142    vm: &VirtualMachine,
143    dict_manager: &DictManager,
144    ids_data: &HashMap<String, HintReference>,
145    ap_tracking: &ApTracking,
146) -> Option<HashMap<String, Decimal>> {
147    // Fetch dictionary
148    let prices =
149        dict_ref_from_var_name("prices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
150
151    // Apply data type conversions
152    let apply_conversion =
153        |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, Decimal)> {
154            Some((
155                felt_to_trimmed_str(k.get_int_ref()?)?,
156                felt_to_scaled_decimal(v.get_int_ref()?)?,
157            ))
158        };
159
160    prices
161        .iter()
162        .map(|(k, v)| apply_conversion(k, v))
163        .collect::<Option<_>>()
164}
165
166fn indices_dict(
167    vm: &VirtualMachine,
168    dict_manager: &DictManager,
169    ids_data: &HashMap<String, HintReference>,
170    ap_tracking: &ApTracking,
171) -> Option<HashMap<String, Decimal>> {
172    // Fetch dictionary
173    let indices =
174        dict_ref_from_var_name("indices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
175
176    // Apply data type conversions
177    let apply_conversion =
178        |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, Decimal)> {
179            Some((
180                felt_to_trimmed_str(k.get_int_ref()?)?,
181                felt_to_scaled_decimal(v.get_int_ref()?)?,
182            ))
183        };
184
185    indices
186        .iter()
187        .map(|(k, v)| apply_conversion(k, v))
188        .collect::<Option<_>>()
189}
190
191fn perps_dict(
192    vm: &VirtualMachine,
193    dict_manager: &DictManager,
194    ids_data: &HashMap<String, HintReference>,
195    ap_tracking: &ApTracking,
196) -> Option<HashMap<String, MarginParams>> {
197    // Fetch dictionary
198    let perps = dict_ref_from_var_name("perps_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
199
200    // Apply data type conversions
201    let apply_conversion =
202        |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(String, MarginParams)> {
203            Some((
204                felt_to_trimmed_str(k.get_int_ref()?)?,
205                MarginParams::read_from_memory(&vm.segments.memory, v.get_relocatable()?)?,
206            ))
207        };
208
209    perps
210        .iter()
211        .map(|(k, v)| apply_conversion(k, v))
212        .collect::<Option<_>>()
213}
214
215fn fees_dict(
216    vm: &VirtualMachine,
217    dict_manager: &DictManager,
218    ids_data: &HashMap<String, HintReference>,
219    ap_tracking: &ApTracking,
220) -> Option<HashMap<Felt252, Decimal>> {
221    // Fetch dictionary
222    let fees = dict_ref_from_var_name("fees_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
223
224    // Apply data type conversions
225    let apply_conversion =
226        |k: &MaybeRelocatable, v: &MaybeRelocatable| -> Option<(Felt252, Decimal)> {
227            Some((k.get_int()?, felt_to_scaled_decimal(v.get_int_ref()?)?))
228        };
229
230    fees.iter()
231        .map(|(k, v)| apply_conversion(k, v))
232        .collect::<Option<_>>()
233}
234
235fn balances_list(
236    vm: &VirtualMachine,
237    dict_manager: &DictManager,
238    ids_data: &HashMap<String, HintReference>,
239    ap_tracking: &ApTracking,
240) -> Option<Vec<Position>> {
241    // Fetch dictionary
242    let balances = dict_ref_from_var_name(
243        "perps_balances_cache_ptr",
244        vm,
245        dict_manager,
246        ids_data,
247        ap_tracking,
248    )?;
249
250    // Apply data type conversions
251    let apply_conversion = |_, v: &MaybeRelocatable| -> Option<Position> {
252        Position::read_from_memory(&vm.segments.memory, v.get_relocatable()?)
253    };
254
255    balances
256        .iter()
257        .map(|(k, v)| apply_conversion(k, v))
258        .collect::<Option<_>>()
259}
260
261pub fn excess_balance_hint(
262    vm: &mut VirtualMachine,
263    ids_data: &HashMap<String, HintReference>,
264    ap_tracking: &ApTracking,
265    constants: &HashMap<String, Felt252>,
266    exec_scopes: &ExecutionScopes,
267) -> Result<(), HintError> {
268    // Fetch constants & variables
269    let margin_check_type =
270        get_integer_from_var_name("margin_check_type", vm, ids_data, ap_tracking)?;
271    let margin_check_initial = get_constant_from_var_name("MARGIN_CHECK_INITIAL", constants)?;
272    let token_assets_value_d =
273        get_integer_from_var_name("token_assets_value_d", vm, ids_data, ap_tracking)?;
274    let account = get_integer_from_var_name("account", vm, ids_data, ap_tracking)?;
275    // Fetch DictManager
276    let dict_manager_rc = exec_scopes.get_dict_manager()?;
277    let dict_manager = dict_manager_rc.borrow();
278    // Fetch dictionaries
279    let prices = prices_dict(vm, &dict_manager, ids_data, ap_tracking)
280        .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("prices".into()))?;
281    let indices = indices_dict(vm, &dict_manager, ids_data, ap_tracking)
282        .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("indices".into()))?;
283    let perps = perps_dict(vm, &dict_manager, ids_data, ap_tracking)
284        .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("perps".into()))?;
285    let fees = fees_dict(vm, &dict_manager, ids_data, ap_tracking)
286        .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("fees".into()))?;
287    let balances = balances_list(vm, &dict_manager, ids_data, ap_tracking)
288        .ok_or_else(|| HintError::ExcessBalanceFailedToFecthDict("balances".into()))?;
289
290    // Fetch settelement price
291    let settlement_asset = String::from("USDC-USD");
292    let settlement_price = prices
293        .get(&settlement_asset)
294        .ok_or_else(|| HintError::ExcessBalanceKeyError("prices".into()))?;
295
296    let mut unrealized_pnl = Decimal::ZERO;
297    let mut unrealized_funding_pnl = Decimal::ZERO;
298    let mut abs_balance_value = Decimal::ZERO;
299    let mut position_margin = Decimal::ZERO;
300
301    for position in balances {
302        if position.market == settlement_asset {
303            continue;
304        }
305
306        let price = prices
307            .get(&position.market)
308            .ok_or_else(|| HintError::ExcessBalanceKeyError("prices".into()))?;
309        let funding_index = indices
310            .get(&position.market)
311            .ok_or_else(|| HintError::ExcessBalanceKeyError("indices".into()))?;
312        let position_value = position
313            .amount
314            .checked_mul(*price)
315            .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("position_value".into()))?;
316        let position_value_abs = position_value.abs();
317
318        abs_balance_value = abs_balance_value
319            .checked_add(position_value_abs)
320            .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("abs_balance_value".into()))?;
321
322        let market_perps = perps
323            .get(&position.market)
324            .ok_or_else(|| HintError::ExcessBalanceKeyError("perps".into()))?;
325        let margin_fraction = if &margin_check_type == margin_check_initial {
326            market_perps.imf(position_value_abs)
327        } else {
328            market_perps.mmf(position_value_abs)
329        }
330        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_fraction".into()))?;
331        // position_margin += margin_fraction * position_value_abs
332        position_margin = margin_fraction
333            .checked_mul(position_value_abs)
334            .and_then(|mul| position_margin.checked_add(mul))
335            .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("position_margin".into()))?;
336        // unrealized_pnl += position_value - position.cost * settlement_price
337        let calc_unrealized_pnl = |unrealized_pnl: Decimal,
338                                   position: &Position,
339                                   settlement_price: Decimal|
340         -> Option<Decimal> {
341            unrealized_pnl.checked_add(
342                position_value.checked_sub(position.cost.checked_mul(settlement_price)?)?,
343            )
344        };
345        unrealized_pnl = calc_unrealized_pnl(unrealized_pnl, &position, *settlement_price)
346            .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl".into()))?;
347        // unrealized_funding_pnl += (position.cached_funding - funding_index) * position.amount*settlement_price
348        let calc_unrealized_funding_pnl = |unrealized_funding_pnl: Decimal,
349                                           position: &Position,
350                                           funding_index: Decimal,
351                                           settlement_price: Decimal|
352         -> Option<Decimal> {
353            unrealized_funding_pnl.checked_add(
354                position
355                    .cached_funding
356                    .checked_sub(funding_index)?
357                    .checked_mul(position.amount)?
358                    .checked_mul(settlement_price)?,
359            )
360        };
361        unrealized_funding_pnl = calc_unrealized_funding_pnl(
362            unrealized_funding_pnl,
363            &position,
364            *funding_index,
365            *settlement_price,
366        )
367        .ok_or_else(|| {
368            HintError::ExcessBalanceCalculationFailed("unrealized_funding_pnl".into())
369        })?;
370    }
371
372    // Calculate final results
373    let token_assets_value_d = felt_to_scaled_decimal(&token_assets_value_d)
374        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?;
375    let account_value = unrealized_pnl
376        .checked_add(unrealized_funding_pnl)
377        .and_then(|sum| sum.checked_add(token_assets_value_d))
378        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?;
379    let fee_provision = fees
380        .get(&account)
381        .and_then(|fee| abs_balance_value.checked_mul(*fee))
382        .ok_or_else(|| HintError::ExcessBalanceKeyError("fees".into()))?;
383    let margin_requirement = position_margin
384        .checked_add(fee_provision)
385        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirements".into()))?;
386    let excess_balance = account_value
387        .checked_sub(margin_requirement)
388        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?;
389
390    // Convert final results to Felt
391    let felt_from_decimal = |d: Decimal| -> Option<Felt252> {
392        Some(Felt252::from(
393            BigInt::from_str(
394                &(d.checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?)
395                    .trunc()
396                    .to_string(),
397            )
398            .ok()?,
399        ))
400    };
401
402    let account_value = felt_from_decimal(account_value)
403        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?;
404    let excess_balance = felt_from_decimal(excess_balance)
405        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?;
406    let margin_requirement = felt_from_decimal(margin_requirement)
407        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirement_d".into()))?;
408    let unrealized_pnl = felt_from_decimal(unrealized_pnl)
409        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl_d".into()))?;
410
411    // Write results into memory
412    insert_value_from_var_name(
413        "check_account_value",
414        account_value,
415        vm,
416        ids_data,
417        ap_tracking,
418    )?;
419    insert_value_from_var_name(
420        "check_excess_balance",
421        excess_balance,
422        vm,
423        ids_data,
424        ap_tracking,
425    )?;
426    insert_value_from_var_name(
427        "check_margin_requirement_d",
428        margin_requirement,
429        vm,
430        ids_data,
431        ap_tracking,
432    )?;
433    insert_value_from_var_name(
434        "check_unrealized_pnl_d",
435        unrealized_pnl,
436        vm,
437        ids_data,
438        ap_tracking,
439    )
440}
441
442#[cfg(test)]
443mod tests {
444    use crate::stdlib::{cell::RefCell, rc::Rc};
445    use core::str::FromStr;
446
447    use super::*;
448    use crate::{felt_str, utils::test_utils::*};
449
450    #[test]
451    fn test_read_position() {
452        let memory = memory![
453            ((0, 0), ("5176525270854594879110454268496", 10)),
454            ((0, 1), 1000000000),
455            ((0, 2), 20000),
456            ((0, 3), 0)
457        ];
458        let expected_position = Position {
459            market: String::from("AVAX-USD-PERP"),
460            amount: Decimal::from_str("10.00000000").unwrap(),
461            cost: Decimal::from_str("0.00020000").unwrap(),
462            cached_funding: Decimal::from_scientific("0e-8").unwrap(),
463        };
464        assert_eq!(
465            expected_position,
466            Position::read_from_memory(&memory, (0, 0).into()).unwrap()
467        )
468    }
469
470    #[test]
471    fn test_read_margin_params() {
472        let memory = memory![
473            ((0, 0), ("20527877651862571847371805264", 10)),
474            ((0, 4), 5000000),
475            ((0, 5), 20000),
476            ((0, 6), 50000000),
477            ((0, 7), 20000000000000)
478        ];
479        let expected_position = MarginParams {
480            market: String::from("BTC-USD-PERP"),
481            imf_base: Decimal::from_str("0.05000000").unwrap(),
482            imf_factor: Decimal::from_str("0.00020000").unwrap(),
483            mmf_factor: Decimal::from_str("0.50000000").unwrap(),
484            imf_shift: Decimal::from_str("200000.00000000").unwrap(),
485        };
486        assert_eq!(
487            expected_position,
488            MarginParams::read_from_memory(&memory, (0, 0).into()).unwrap()
489        )
490    }
491
492    #[test]
493    fn test_imf() {
494        let abs_value = Decimal::from_str("459000.0000000000000000").unwrap();
495        let margin_params = MarginParams {
496            market: String::from("BTC-USD-PERP"),
497            imf_base: Decimal::from_str("0.05000000").unwrap(),
498            imf_factor: Decimal::from_str("0.00020000").unwrap(),
499            mmf_factor: Decimal::from_str("0.50000000").unwrap(),
500            imf_shift: Decimal::from_str("200000.00000000").unwrap(),
501        };
502        let expected_res = Decimal::from_str("0.101784080000").unwrap();
503        assert_eq!(expected_res, margin_params.imf(abs_value).unwrap());
504    }
505
506    #[test]
507    fn run_excess_balance_hint_succesful_trade() {
508        // TEST DATA
509
510        // INPUT VALUES
511        // ids.margin_check_type 1
512        // ids.MARGIN_CHECK_INITIAL 1
513        // ids.token_assets_value_d 1005149999998000
514        // ids.account 200
515        // DICTIONARIES
516        // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000}
517        // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0}
518        // 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)}
519        // fees {100: 10000, 200: 10000}
520        // 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)}
521        // MEMORY VALUES REFERENCED BY DICTIONARIES
522        // 1:3092 6044027408028715819619898970704
523        // 1:3096 5000000
524        // 1:3097 20000
525        // 1:3098 50000000
526        // 1:3099 20000000000000
527        // 1:3467 25783120691025710696626475600
528        // 1:3471 5000000
529        // 1:3472 20000
530        // 1:3473 50000000
531        // 1:3474 20000000000000
532        // 1:3842 5176525270854594879110454268496
533        // 1:3846 5000000
534        // 1:3847 20000
535        // 1:3848 50000000
536        // 1:3849 20000000000000
537        // 1:4217 21456356293159021401772216912
538        // 1:4221 5000000
539        // 1:4222 20000
540        // 1:4223 50000000
541        // 1:4224 20000000000000
542        // 1:4592 20527877651862571847371805264
543        // 1:4596 5000000
544        // 1:4597 20000
545        // 1:4598 50000000
546        // 1:4599 20000000000000
547        // 1:6406 6044027408028715819619898970704
548        // 1:6407 1000000000
549        // 1:6408 20000
550        // 1:6409 0
551        // 1:6406 6044027408028715819619898970704
552        // 1:6407 1000000000
553        // 1:6408 20000
554        // 1:6409 0
555        // 1:6625 25783120691025710696626475600
556        // 1:6626 1000000000
557        // 1:6627 20000
558        // 1:6628 0
559        // 1:6625 25783120691025710696626475600
560        // 1:6626 1000000000
561        // 1:6627 20000
562        // 1:6628 0
563        // 1:6844 5176525270854594879110454268496
564        // 1:6845 1000000000
565        // 1:6846 20000
566        // 1:6847 0
567        // 1:6844 5176525270854594879110454268496
568        // 1:6845 1000000000
569        // 1:6846 20000
570        // 1:6847 0
571        // 1:7063 21456356293159021401772216912
572        // 1:7064 1000000000
573        // 1:7065 20000
574        // 1:7066 0
575        // 1:7063 21456356293159021401772216912
576        // 1:7064 1000000000
577        // 1:7065 20000
578        // 1:7066 0
579        // 1:18582 20527877651862571847371805264
580        // 1:18583 900000000
581        // 1:18584 18000
582        // 1:18585 0
583        // 1:18582 20527877651862571847371805264
584        // 1:18583 900000000
585        // 1:18584 18000
586        // 1:18585 0
587        // EXPECTED RESULTS
588        // ids.check_account_value 1255049999900000
589        // ids.check_excess_balance 1227636643508000
590        // ids.check_margin_requirement_d 27413356392000
591        // ids.check_unrealized_pnl_d 249899999902000
592
593        // SETUP
594        let mut vm = vm!();
595        // CONSTANTS
596        let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
597        // IDS
598        vm.segments = segments!(
599            ((1, 0), 1),                // ids.margin_check_type
600            ((1, 1), 1005149999998000), // ids.token_assets_value_d
601            ((1, 2), 200),              // ids.account
602            ((1, 3), (2, 0)),           // ids.prices_cache_ptr
603            ((1, 4), (3, 0)),           // ids.indices_cache_ptr
604            ((1, 5), (4, 0)),           // ids.perps_cache_ptr
605            ((1, 6), (5, 0)),           // ids.fees_cache_ptr
606            ((1, 7), (6, 0)),           // ids.perps_balances_cache_ptr
607            //((1, 8), ids.check_account_value)
608            //((1, 9), ids.check_excess_balance)
609            //((1, 10), ids.check_margin_requirement_d)
610            //((1, 11), ids.check_unrealized_pnl_d)
611            // Memory values referenced by hints
612            ((1, 3092), 6044027408028715819619898970704),
613            ((1, 3096), 5000000),
614            ((1, 3097), 20000),
615            ((1, 3098), 50000000),
616            ((1, 3099), 20000000000000),
617            ((1, 3467), 25783120691025710696626475600),
618            ((1, 3471), 5000000),
619            ((1, 3472), 20000),
620            ((1, 3473), 50000000),
621            ((1, 3474), 20000000000000),
622            ((1, 3842), 5176525270854594879110454268496),
623            ((1, 3846), 5000000),
624            ((1, 3847), 20000),
625            ((1, 3848), 50000000),
626            ((1, 3849), 20000000000000),
627            ((1, 4217), 21456356293159021401772216912),
628            ((1, 4221), 5000000),
629            ((1, 4222), 20000),
630            ((1, 4223), 50000000),
631            ((1, 4224), 20000000000000),
632            ((1, 4592), 20527877651862571847371805264),
633            ((1, 4596), 5000000),
634            ((1, 4597), 20000),
635            ((1, 4598), 50000000),
636            ((1, 4599), 20000000000000),
637            ((1, 6406), 6044027408028715819619898970704),
638            ((1, 6407), 1000000000),
639            ((1, 6408), 20000),
640            ((1, 6409), 0),
641            ((1, 6406), 6044027408028715819619898970704),
642            ((1, 6407), 1000000000),
643            ((1, 6408), 20000),
644            ((1, 6409), 0),
645            ((1, 6625), 25783120691025710696626475600),
646            ((1, 6626), 1000000000),
647            ((1, 6627), 20000),
648            ((1, 6628), 0),
649            ((1, 6625), 25783120691025710696626475600),
650            ((1, 6626), 1000000000),
651            ((1, 6627), 20000),
652            ((1, 6628), 0),
653            ((1, 6844), 5176525270854594879110454268496),
654            ((1, 6845), 1000000000),
655            ((1, 6846), 20000),
656            ((1, 6847), 0),
657            ((1, 6844), 5176525270854594879110454268496),
658            ((1, 6845), 1000000000),
659            ((1, 6846), 20000),
660            ((1, 6847), 0),
661            ((1, 7063), 21456356293159021401772216912),
662            ((1, 7064), 1000000000),
663            ((1, 7065), 20000),
664            ((1, 7066), 0),
665            ((1, 7063), 21456356293159021401772216912),
666            ((1, 7064), 1000000000),
667            ((1, 7065), 20000),
668            ((1, 7066), 0),
669            ((1, 18582), 20527877651862571847371805264),
670            ((1, 18583), 900000000),
671            ((1, 18584), 18000),
672            ((1, 18585), 0),
673            ((1, 18582), 20527877651862571847371805264),
674            ((1, 18583), 900000000),
675            ((1, 18584), 18000),
676            ((1, 18585), 0)
677        );
678        vm.run_context.set_fp(12);
679        let ids = ids_data![
680            "margin_check_type",
681            "token_assets_value_d",
682            "account",
683            "prices_cache_ptr",
684            "indices_cache_ptr",
685            "perps_cache_ptr",
686            "fees_cache_ptr",
687            "perps_balances_cache_ptr",
688            "check_account_value",
689            "check_excess_balance",
690            "check_margin_requirement_d",
691            "check_unrealized_pnl_d"
692        ];
693        // DICTIONARIES
694        let mut exec_scopes = ExecutionScopes::new();
695        let mut dict_manager = DictManager::new();
696        // ids.prices_cache_ptr = (2, 0)
697        dict_manager
698            .new_dict(
699                &mut vm,
700                HashMap::from([
701                    (
702                        felt_str!("6044027408028715819619898970704").into(),
703                        felt_str!("5100000000000").into(),
704                    ),
705                    (
706                        felt_str!("25783120691025710696626475600").into(),
707                        felt_str!("5100000000000").into(),
708                    ),
709                    (
710                        felt_str!("5176525270854594879110454268496").into(),
711                        felt_str!("5100000000000").into(),
712                    ),
713                    (
714                        felt_str!("21456356293159021401772216912").into(),
715                        felt_str!("5100000000000").into(),
716                    ),
717                    (
718                        felt_str!("20527877651862571847371805264").into(),
719                        felt_str!("5100000000000").into(),
720                    ),
721                    (
722                        felt_str!("6148332971604923204").into(),
723                        felt_str!("100000000").into(),
724                    ),
725                ]),
726            )
727            .unwrap();
728        // ids.indices_cache_ptr = (3, 0)
729        dict_manager
730            .new_dict(
731                &mut vm,
732                HashMap::from([
733                    (
734                        felt_str!("6044027408028715819619898970704").into(),
735                        Felt252::ZERO.into(),
736                    ),
737                    (
738                        felt_str!("25783120691025710696626475600").into(),
739                        Felt252::ZERO.into(),
740                    ),
741                    (
742                        felt_str!("5176525270854594879110454268496").into(),
743                        Felt252::ZERO.into(),
744                    ),
745                    (
746                        felt_str!("21456356293159021401772216912").into(),
747                        Felt252::ZERO.into(),
748                    ),
749                    (
750                        felt_str!("20527877651862571847371805264").into(),
751                        Felt252::ZERO.into(),
752                    ),
753                ]),
754            )
755            .unwrap();
756        // ids.perps_cache_ptr = (4, 0)
757        dict_manager
758            .new_dict(
759                &mut vm,
760                HashMap::from([
761                    (
762                        felt_str!("6044027408028715819619898970704").into(),
763                        (1, 3092).into(),
764                    ),
765                    (
766                        felt_str!("25783120691025710696626475600").into(),
767                        (1, 3467).into(),
768                    ),
769                    (
770                        felt_str!("5176525270854594879110454268496").into(),
771                        (1, 3842).into(),
772                    ),
773                    (
774                        felt_str!("21456356293159021401772216912").into(),
775                        (1, 4217).into(),
776                    ),
777                    (
778                        felt_str!("20527877651862571847371805264").into(),
779                        (1, 4592).into(),
780                    ),
781                ]),
782            )
783            .unwrap();
784        // ids.fees_cache_ptr = (5, 0)
785        dict_manager
786            .new_dict(
787                &mut vm,
788                HashMap::from([
789                    (Felt252::from(100).into(), Felt252::from(10000).into()),
790                    (Felt252::from(200).into(), Felt252::from(10000).into()),
791                ]),
792            )
793            .unwrap();
794        // ids.perps_balances_cache_ptr = (6, 0)
795        dict_manager
796            .new_dict(
797                &mut vm,
798                HashMap::from([
799                    (
800                        felt_str!("6044027408028715819619898970704").into(),
801                        (1, 6406).into(),
802                    ),
803                    (
804                        felt_str!("25783120691025710696626475600").into(),
805                        (1, 6625).into(),
806                    ),
807                    (
808                        felt_str!("5176525270854594879110454268496").into(),
809                        (1, 6844).into(),
810                    ),
811                    (
812                        felt_str!("21456356293159021401772216912").into(),
813                        (1, 7063).into(),
814                    ),
815                    (
816                        felt_str!("20527877651862571847371805264").into(),
817                        (1, 18582).into(),
818                    ),
819                ]),
820            )
821            .unwrap();
822        exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager)));
823
824        // EXECUTION
825        assert!(excess_balance_hint(
826            &mut vm,
827            &ids,
828            &ApTracking::default(),
829            &constants,
830            &exec_scopes
831        )
832        .is_ok());
833
834        // CHECK MEMORY VALUES
835        check_memory![
836            vm.segments.memory,
837            // ids.check_account_value
838            ((1, 8), 1255049999900000),
839            // ids.check_excess_balance
840            ((1, 9), 1227636643508000),
841            // ids.check_margin_requirement_d
842            ((1, 10), 27413356392000),
843            // ids.check_unrealized_pnl_d
844            ((1, 11), 249899999902000)
845        ];
846    }
847
848    #[test]
849    fn run_excess_balance_hint_trade_failure() {
850        // TEST DATA
851
852        // INPUT VALUES
853        // ids.margin_check_type 1
854        // ids.MARGIN_CHECK_INITIAL 1
855        // ids.token_assets_value_d 0
856        // ids.account 100
857        // DICTIONARIES
858        // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000}
859        // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0}
860        // 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)}
861        // fees {100: 10000, 200: 10000}
862        // 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)}
863        // MEMORY VALUES REFERENCED BY DICTIONARIES
864        // 1:3092 6044027408028715819619898970704
865        // 1:3096 5000000
866        // 1:3097 20000
867        // 1:3098 50000000
868        // 1:3099 20000000000000
869        // 1:3467 25783120691025710696626475600
870        // 1:3471 5000000
871        // 1:3472 20000
872        // 1:3473 50000000
873        // 1:3474 20000000000000
874        // 1:3842 5176525270854594879110454268496
875        // 1:3846 5000000
876        // 1:3847 20000
877        // 1:3848 50000000
878        // 1:3849 20000000000000
879        // 1:4217 21456356293159021401772216912
880        // 1:4221 5000000
881        // 1:4222 20000
882        // 1:4223 50000000
883        // 1:4224 20000000000000
884        // 1:4592 20527877651862571847371805264
885        // 1:4596 5000000
886        // 1:4597 20000
887        // 1:4598 50000000
888        // 1:4599 20000000000000
889        // 1:6406 6044027408028715819619898970704
890        // 1:6407 0
891        // 1:6408 0
892        // 1:6409 0
893        // 1:6406 6044027408028715819619898970704
894        // 1:6407 0
895        // 1:6408 0
896        // 1:6409 0
897        // 1:6625 25783120691025710696626475600
898        // 1:6626 0
899        // 1:6627 0
900        // 1:6628 0
901        // 1:6625 25783120691025710696626475600
902        // 1:6626 0
903        // 1:6627 0
904        // 1:6628 0
905        // 1:6844 5176525270854594879110454268496
906        // 1:6845 0
907        // 1:6846 0
908        // 1:6847 0
909        // 1:6844 5176525270854594879110454268496
910        // 1:6845 0
911        // 1:6846 0
912        // 1:6847 0
913        // 1:7063 21456356293159021401772216912
914        // 1:7064 0
915        // 1:7065 0
916        // 1:7066 0
917        // 1:7063 21456356293159021401772216912
918        // 1:7064 0
919        // 1:7065 0
920        // 1:7066 0
921        // 1:18230 20527877651862571847371805264
922        // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481
923        // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481
924        // 1:18233 0
925        // 1:18230 20527877651862571847371805264
926        // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481
927        // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481
928        // 1:18233 0
929        // EXPECTED RESULTS
930        // ids.check_account_value 50000000000
931        // ids.check_excess_balance 3618502788666131213697322783095070105623107215331596699973092055930362020481
932        // ids.check_margin_requirement_d 255510000000
933        // ids.check_unrealized_pnl_d 50000000000
934
935        // SETUP
936        let mut vm = vm!();
937        // CONSTANTS
938        let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
939        // IDS
940        vm.segments = segments!(
941            ((1, 0), 1),      // ids.margin_check_type
942            ((1, 1), 0),      // ids.token_assets_value_d
943            ((1, 2), 100),    // ids.account
944            ((1, 3), (2, 0)), // ids.prices_cache_ptr
945            ((1, 4), (3, 0)), // ids.indices_cache_ptr
946            ((1, 5), (4, 0)), // ids.perps_cache_ptr
947            ((1, 6), (5, 0)), // ids.fees_cache_ptr
948            ((1, 7), (6, 0)), // ids.perps_balances_cache_ptr
949            //((1, 8), ids.check_account_value)
950            //((1, 9), ids.check_excess_balance)
951            //((1, 10), ids.check_margin_requirement_d)
952            //((1, 11), ids.check_unrealized_pnl_d)
953            // Memory values referenced by hints
954            ((1, 3092), 6044027408028715819619898970704),
955            ((1, 3096), 5000000),
956            ((1, 3097), 20000),
957            ((1, 3098), 50000000),
958            ((1, 3099), 20000000000000),
959            ((1, 3467), 25783120691025710696626475600),
960            ((1, 3471), 5000000),
961            ((1, 3472), 20000),
962            ((1, 3473), 50000000),
963            ((1, 3474), 20000000000000),
964            ((1, 3842), 5176525270854594879110454268496),
965            ((1, 3846), 5000000),
966            ((1, 3847), 20000),
967            ((1, 3848), 50000000),
968            ((1, 3849), 20000000000000),
969            ((1, 4217), 21456356293159021401772216912),
970            ((1, 4221), 5000000),
971            ((1, 4222), 20000),
972            ((1, 4223), 50000000),
973            ((1, 4224), 20000000000000),
974            ((1, 4592), 20527877651862571847371805264),
975            ((1, 4596), 5000000),
976            ((1, 4597), 20000),
977            ((1, 4598), 50000000),
978            ((1, 4599), 20000000000000),
979            ((1, 6406), 6044027408028715819619898970704),
980            ((1, 6407), 0),
981            ((1, 6408), 0),
982            ((1, 6409), 0),
983            ((1, 6406), 6044027408028715819619898970704),
984            ((1, 6407), 0),
985            ((1, 6408), 0),
986            ((1, 6409), 0),
987            ((1, 6625), 25783120691025710696626475600),
988            ((1, 6626), 0),
989            ((1, 6627), 0),
990            ((1, 6628), 0),
991            ((1, 6625), 25783120691025710696626475600),
992            ((1, 6626), 0),
993            ((1, 6627), 0),
994            ((1, 6628), 0),
995            ((1, 6844), 5176525270854594879110454268496),
996            ((1, 6845), 0),
997            ((1, 6846), 0),
998            ((1, 6847), 0),
999            ((1, 6844), 5176525270854594879110454268496),
1000            ((1, 6845), 0),
1001            ((1, 6846), 0),
1002            ((1, 6847), 0),
1003            ((1, 7063), 21456356293159021401772216912),
1004            ((1, 7064), 0),
1005            ((1, 7065), 0),
1006            ((1, 7066), 0),
1007            ((1, 7063), 21456356293159021401772216912),
1008            ((1, 7064), 0),
1009            ((1, 7065), 0),
1010            ((1, 7066), 0),
1011            ((1, 18230), 20527877651862571847371805264),
1012            (
1013                (1, 18231),
1014                (
1015                    "3618502788666131213697322783095070105623107215331596699973092056135772020481",
1016                    10
1017                )
1018            ),
1019            (
1020                (1, 18232),
1021                (
1022                    "3618502788666131213697322783095070105623107215331596699973092050985872020481",
1023                    10
1024                )
1025            ),
1026            ((1, 18233), 0),
1027            ((1, 18230), 20527877651862571847371805264),
1028            (
1029                (1, 18231),
1030                (
1031                    "3618502788666131213697322783095070105623107215331596699973092056135772020481",
1032                    10
1033                )
1034            ),
1035            (
1036                (1, 18232),
1037                (
1038                    "3618502788666131213697322783095070105623107215331596699973092050985872020481",
1039                    10
1040                )
1041            ),
1042            ((1, 18233), 0),
1043        );
1044        vm.run_context.set_fp(12);
1045        let ids = ids_data![
1046            "margin_check_type",
1047            "token_assets_value_d",
1048            "account",
1049            "prices_cache_ptr",
1050            "indices_cache_ptr",
1051            "perps_cache_ptr",
1052            "fees_cache_ptr",
1053            "perps_balances_cache_ptr",
1054            "check_account_value",
1055            "check_excess_balance",
1056            "check_margin_requirement_d",
1057            "check_unrealized_pnl_d"
1058        ];
1059        // DICTIONARIES
1060        let mut exec_scopes = ExecutionScopes::new();
1061        let mut dict_manager = DictManager::new();
1062        // ids.prices_cache_ptr = (2, 0)
1063        dict_manager
1064            .new_dict(
1065                &mut vm,
1066                HashMap::from([
1067                    (
1068                        felt_str!("6044027408028715819619898970704").into(),
1069                        felt_str!("5100000000000").into(),
1070                    ),
1071                    (
1072                        felt_str!("25783120691025710696626475600").into(),
1073                        felt_str!("5100000000000").into(),
1074                    ),
1075                    (
1076                        felt_str!("5176525270854594879110454268496").into(),
1077                        felt_str!("5100000000000").into(),
1078                    ),
1079                    (
1080                        felt_str!("21456356293159021401772216912").into(),
1081                        felt_str!("5100000000000").into(),
1082                    ),
1083                    (
1084                        felt_str!("20527877651862571847371805264").into(),
1085                        felt_str!("5100000000000").into(),
1086                    ),
1087                    (
1088                        felt_str!("6148332971604923204").into(),
1089                        felt_str!("100000000").into(),
1090                    ),
1091                ]),
1092            )
1093            .unwrap();
1094        // ids.indices_cache_ptr = (3, 0)
1095        dict_manager
1096            .new_dict(
1097                &mut vm,
1098                HashMap::from([
1099                    (
1100                        felt_str!("6044027408028715819619898970704").into(),
1101                        Felt252::ZERO.into(),
1102                    ),
1103                    (
1104                        felt_str!("25783120691025710696626475600").into(),
1105                        Felt252::ZERO.into(),
1106                    ),
1107                    (
1108                        felt_str!("5176525270854594879110454268496").into(),
1109                        Felt252::ZERO.into(),
1110                    ),
1111                    (
1112                        felt_str!("21456356293159021401772216912").into(),
1113                        Felt252::ZERO.into(),
1114                    ),
1115                    (
1116                        felt_str!("20527877651862571847371805264").into(),
1117                        Felt252::ZERO.into(),
1118                    ),
1119                ]),
1120            )
1121            .unwrap();
1122        // ids.perps_cache_ptr = (4, 0)
1123        dict_manager
1124            .new_dict(
1125                &mut vm,
1126                HashMap::from([
1127                    (
1128                        felt_str!("6044027408028715819619898970704").into(),
1129                        (1, 3092).into(),
1130                    ),
1131                    (
1132                        felt_str!("25783120691025710696626475600").into(),
1133                        (1, 3467).into(),
1134                    ),
1135                    (
1136                        felt_str!("5176525270854594879110454268496").into(),
1137                        (1, 3842).into(),
1138                    ),
1139                    (
1140                        felt_str!("21456356293159021401772216912").into(),
1141                        (1, 4217).into(),
1142                    ),
1143                    (
1144                        felt_str!("20527877651862571847371805264").into(),
1145                        (1, 4592).into(),
1146                    ),
1147                ]),
1148            )
1149            .unwrap();
1150        // ids.fees_cache_ptr = (5, 0)
1151        dict_manager
1152            .new_dict(
1153                &mut vm,
1154                HashMap::from([
1155                    (Felt252::from(100).into(), Felt252::from(10000).into()),
1156                    (Felt252::from(200).into(), Felt252::from(10000).into()),
1157                ]),
1158            )
1159            .unwrap();
1160        // ids.perps_balances_cache_ptr = (6, 0)
1161        dict_manager
1162            .new_dict(
1163                &mut vm,
1164                HashMap::from([
1165                    (
1166                        felt_str!("6044027408028715819619898970704").into(),
1167                        (1, 6406).into(),
1168                    ),
1169                    (
1170                        felt_str!("25783120691025710696626475600").into(),
1171                        (1, 6625).into(),
1172                    ),
1173                    (
1174                        felt_str!("5176525270854594879110454268496").into(),
1175                        (1, 6844).into(),
1176                    ),
1177                    (
1178                        felt_str!("21456356293159021401772216912").into(),
1179                        (1, 7063).into(),
1180                    ),
1181                    (
1182                        felt_str!("20527877651862571847371805264").into(),
1183                        (1, 18230).into(),
1184                    ),
1185                ]),
1186            )
1187            .unwrap();
1188        exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager)));
1189
1190        // EXECUTION
1191        assert!(excess_balance_hint(
1192            &mut vm,
1193            &ids,
1194            &ApTracking::default(),
1195            &constants,
1196            &exec_scopes
1197        )
1198        .is_ok());
1199
1200        // CHECK MEMORY VALUES
1201        check_memory![
1202            vm.segments.memory,
1203            // ids.check_account_value
1204            ((1, 8), 50000000000),
1205            // ids.check_excess_balance
1206            (
1207                (1, 9),
1208                (
1209                    "3618502788666131213697322783095070105623107215331596699973092055930362020481",
1210                    10
1211                )
1212            ),
1213            // ids.check_margin_requirement_d
1214            ((1, 10), 255510000000),
1215            // ids.check_unrealized_pnl_d
1216            ((1, 11), 50000000000)
1217        ];
1218    }
1219}