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 = fees
380        .get(&account)
381        .ok_or_else(|| HintError::ExcessBalanceKeyError("fees".into()))?;
382    let fee_provision = abs_balance_value
383        .checked_mul(*fee)
384        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("fee_provision".into()))?;
385    let margin_requirement = position_margin
386        .checked_add(fee_provision)
387        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirements".into()))?;
388    let excess_balance = account_value
389        .checked_sub(margin_requirement)
390        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?;
391
392    // Convert final results to Felt
393    let felt_from_decimal = |d: Decimal| -> Option<Felt252> {
394        Some(Felt252::from(
395            BigInt::from_str(
396                &(d.checked_mul(*DECIMAL_ADJUSTMENT_POSITIVE)?)
397                    .trunc()
398                    .to_string(),
399            )
400            .ok()?,
401        ))
402    };
403
404    let account_value = felt_from_decimal(account_value)
405        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("account_value".into()))?;
406    let excess_balance = felt_from_decimal(excess_balance)
407        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("excess_balance".into()))?;
408    let margin_requirement = felt_from_decimal(margin_requirement)
409        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("margin_requirement_d".into()))?;
410    let unrealized_pnl = felt_from_decimal(unrealized_pnl)
411        .ok_or_else(|| HintError::ExcessBalanceCalculationFailed("unrealized_pnl_d".into()))?;
412
413    // Write results into memory
414    insert_value_from_var_name(
415        "check_account_value",
416        account_value,
417        vm,
418        ids_data,
419        ap_tracking,
420    )?;
421    insert_value_from_var_name(
422        "check_excess_balance",
423        excess_balance,
424        vm,
425        ids_data,
426        ap_tracking,
427    )?;
428    insert_value_from_var_name(
429        "check_margin_requirement_d",
430        margin_requirement,
431        vm,
432        ids_data,
433        ap_tracking,
434    )?;
435    insert_value_from_var_name(
436        "check_unrealized_pnl_d",
437        unrealized_pnl,
438        vm,
439        ids_data,
440        ap_tracking,
441    )
442}
443
444#[cfg(test)]
445mod tests {
446    use crate::stdlib::{cell::RefCell, rc::Rc};
447    use core::str::FromStr;
448
449    use super::*;
450    use crate::{felt_str, utils::test_utils::*};
451
452    #[test]
453    fn test_read_position() {
454        let memory = memory![
455            ((0, 0), ("5176525270854594879110454268496", 10)),
456            ((0, 1), 1000000000),
457            ((0, 2), 20000),
458            ((0, 3), 0)
459        ];
460        let expected_position = Position {
461            market: String::from("AVAX-USD-PERP"),
462            amount: Decimal::from_str("10.00000000").unwrap(),
463            cost: Decimal::from_str("0.00020000").unwrap(),
464            cached_funding: Decimal::from_scientific("0e-8").unwrap(),
465        };
466        assert_eq!(
467            expected_position,
468            Position::read_from_memory(&memory, (0, 0).into()).unwrap()
469        )
470    }
471
472    #[test]
473    fn test_read_margin_params() {
474        let memory = memory![
475            ((0, 0), ("20527877651862571847371805264", 10)),
476            ((0, 4), 5000000),
477            ((0, 5), 20000),
478            ((0, 6), 50000000),
479            ((0, 7), 20000000000000)
480        ];
481        let expected_position = MarginParams {
482            market: String::from("BTC-USD-PERP"),
483            imf_base: Decimal::from_str("0.05000000").unwrap(),
484            imf_factor: Decimal::from_str("0.00020000").unwrap(),
485            mmf_factor: Decimal::from_str("0.50000000").unwrap(),
486            imf_shift: Decimal::from_str("200000.00000000").unwrap(),
487        };
488        assert_eq!(
489            expected_position,
490            MarginParams::read_from_memory(&memory, (0, 0).into()).unwrap()
491        )
492    }
493
494    #[test]
495    fn test_imf() {
496        let abs_value = Decimal::from_str("459000.0000000000000000").unwrap();
497        let margin_params = MarginParams {
498            market: String::from("BTC-USD-PERP"),
499            imf_base: Decimal::from_str("0.05000000").unwrap(),
500            imf_factor: Decimal::from_str("0.00020000").unwrap(),
501            mmf_factor: Decimal::from_str("0.50000000").unwrap(),
502            imf_shift: Decimal::from_str("200000.00000000").unwrap(),
503        };
504        let expected_res = Decimal::from_str("0.101784080000").unwrap();
505        assert_eq!(expected_res, margin_params.imf(abs_value).unwrap());
506    }
507
508    #[test]
509    fn run_excess_balance_hint_succesful_trade() {
510        // TEST DATA
511
512        // INPUT VALUES
513        // ids.margin_check_type 1
514        // ids.MARGIN_CHECK_INITIAL 1
515        // ids.token_assets_value_d 1005149999998000
516        // ids.account 200
517        // DICTIONARIES
518        // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000}
519        // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0}
520        // 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)}
521        // fees {100: 10000, 200: 10000}
522        // 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)}
523        // MEMORY VALUES REFERENCED BY DICTIONARIES
524        // 1:3092 6044027408028715819619898970704
525        // 1:3096 5000000
526        // 1:3097 20000
527        // 1:3098 50000000
528        // 1:3099 20000000000000
529        // 1:3467 25783120691025710696626475600
530        // 1:3471 5000000
531        // 1:3472 20000
532        // 1:3473 50000000
533        // 1:3474 20000000000000
534        // 1:3842 5176525270854594879110454268496
535        // 1:3846 5000000
536        // 1:3847 20000
537        // 1:3848 50000000
538        // 1:3849 20000000000000
539        // 1:4217 21456356293159021401772216912
540        // 1:4221 5000000
541        // 1:4222 20000
542        // 1:4223 50000000
543        // 1:4224 20000000000000
544        // 1:4592 20527877651862571847371805264
545        // 1:4596 5000000
546        // 1:4597 20000
547        // 1:4598 50000000
548        // 1:4599 20000000000000
549        // 1:6406 6044027408028715819619898970704
550        // 1:6407 1000000000
551        // 1:6408 20000
552        // 1:6409 0
553        // 1:6406 6044027408028715819619898970704
554        // 1:6407 1000000000
555        // 1:6408 20000
556        // 1:6409 0
557        // 1:6625 25783120691025710696626475600
558        // 1:6626 1000000000
559        // 1:6627 20000
560        // 1:6628 0
561        // 1:6625 25783120691025710696626475600
562        // 1:6626 1000000000
563        // 1:6627 20000
564        // 1:6628 0
565        // 1:6844 5176525270854594879110454268496
566        // 1:6845 1000000000
567        // 1:6846 20000
568        // 1:6847 0
569        // 1:6844 5176525270854594879110454268496
570        // 1:6845 1000000000
571        // 1:6846 20000
572        // 1:6847 0
573        // 1:7063 21456356293159021401772216912
574        // 1:7064 1000000000
575        // 1:7065 20000
576        // 1:7066 0
577        // 1:7063 21456356293159021401772216912
578        // 1:7064 1000000000
579        // 1:7065 20000
580        // 1:7066 0
581        // 1:18582 20527877651862571847371805264
582        // 1:18583 900000000
583        // 1:18584 18000
584        // 1:18585 0
585        // 1:18582 20527877651862571847371805264
586        // 1:18583 900000000
587        // 1:18584 18000
588        // 1:18585 0
589        // EXPECTED RESULTS
590        // ids.check_account_value 1255049999900000
591        // ids.check_excess_balance 1227636643508000
592        // ids.check_margin_requirement_d 27413356392000
593        // ids.check_unrealized_pnl_d 249899999902000
594
595        // SETUP
596        let mut vm = vm!();
597        // CONSTANTS
598        let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
599        // IDS
600        vm.segments = segments!(
601            ((1, 0), 1),                // ids.margin_check_type
602            ((1, 1), 1005149999998000), // ids.token_assets_value_d
603            ((1, 2), 200),              // ids.account
604            ((1, 3), (2, 0)),           // ids.prices_cache_ptr
605            ((1, 4), (3, 0)),           // ids.indices_cache_ptr
606            ((1, 5), (4, 0)),           // ids.perps_cache_ptr
607            ((1, 6), (5, 0)),           // ids.fees_cache_ptr
608            ((1, 7), (6, 0)),           // ids.perps_balances_cache_ptr
609            //((1, 8), ids.check_account_value)
610            //((1, 9), ids.check_excess_balance)
611            //((1, 10), ids.check_margin_requirement_d)
612            //((1, 11), ids.check_unrealized_pnl_d)
613            // Memory values referenced by hints
614            ((1, 3092), 6044027408028715819619898970704),
615            ((1, 3096), 5000000),
616            ((1, 3097), 20000),
617            ((1, 3098), 50000000),
618            ((1, 3099), 20000000000000),
619            ((1, 3467), 25783120691025710696626475600),
620            ((1, 3471), 5000000),
621            ((1, 3472), 20000),
622            ((1, 3473), 50000000),
623            ((1, 3474), 20000000000000),
624            ((1, 3842), 5176525270854594879110454268496),
625            ((1, 3846), 5000000),
626            ((1, 3847), 20000),
627            ((1, 3848), 50000000),
628            ((1, 3849), 20000000000000),
629            ((1, 4217), 21456356293159021401772216912),
630            ((1, 4221), 5000000),
631            ((1, 4222), 20000),
632            ((1, 4223), 50000000),
633            ((1, 4224), 20000000000000),
634            ((1, 4592), 20527877651862571847371805264),
635            ((1, 4596), 5000000),
636            ((1, 4597), 20000),
637            ((1, 4598), 50000000),
638            ((1, 4599), 20000000000000),
639            ((1, 6406), 6044027408028715819619898970704),
640            ((1, 6407), 1000000000),
641            ((1, 6408), 20000),
642            ((1, 6409), 0),
643            ((1, 6406), 6044027408028715819619898970704),
644            ((1, 6407), 1000000000),
645            ((1, 6408), 20000),
646            ((1, 6409), 0),
647            ((1, 6625), 25783120691025710696626475600),
648            ((1, 6626), 1000000000),
649            ((1, 6627), 20000),
650            ((1, 6628), 0),
651            ((1, 6625), 25783120691025710696626475600),
652            ((1, 6626), 1000000000),
653            ((1, 6627), 20000),
654            ((1, 6628), 0),
655            ((1, 6844), 5176525270854594879110454268496),
656            ((1, 6845), 1000000000),
657            ((1, 6846), 20000),
658            ((1, 6847), 0),
659            ((1, 6844), 5176525270854594879110454268496),
660            ((1, 6845), 1000000000),
661            ((1, 6846), 20000),
662            ((1, 6847), 0),
663            ((1, 7063), 21456356293159021401772216912),
664            ((1, 7064), 1000000000),
665            ((1, 7065), 20000),
666            ((1, 7066), 0),
667            ((1, 7063), 21456356293159021401772216912),
668            ((1, 7064), 1000000000),
669            ((1, 7065), 20000),
670            ((1, 7066), 0),
671            ((1, 18582), 20527877651862571847371805264),
672            ((1, 18583), 900000000),
673            ((1, 18584), 18000),
674            ((1, 18585), 0),
675            ((1, 18582), 20527877651862571847371805264),
676            ((1, 18583), 900000000),
677            ((1, 18584), 18000),
678            ((1, 18585), 0)
679        );
680        vm.run_context.set_fp(12);
681        let ids = ids_data![
682            "margin_check_type",
683            "token_assets_value_d",
684            "account",
685            "prices_cache_ptr",
686            "indices_cache_ptr",
687            "perps_cache_ptr",
688            "fees_cache_ptr",
689            "perps_balances_cache_ptr",
690            "check_account_value",
691            "check_excess_balance",
692            "check_margin_requirement_d",
693            "check_unrealized_pnl_d"
694        ];
695        // DICTIONARIES
696        let mut exec_scopes = ExecutionScopes::new();
697        let mut dict_manager = DictManager::new();
698        // ids.prices_cache_ptr = (2, 0)
699        dict_manager
700            .new_dict(
701                &mut vm,
702                HashMap::from([
703                    (
704                        felt_str!("6044027408028715819619898970704").into(),
705                        felt_str!("5100000000000").into(),
706                    ),
707                    (
708                        felt_str!("25783120691025710696626475600").into(),
709                        felt_str!("5100000000000").into(),
710                    ),
711                    (
712                        felt_str!("5176525270854594879110454268496").into(),
713                        felt_str!("5100000000000").into(),
714                    ),
715                    (
716                        felt_str!("21456356293159021401772216912").into(),
717                        felt_str!("5100000000000").into(),
718                    ),
719                    (
720                        felt_str!("20527877651862571847371805264").into(),
721                        felt_str!("5100000000000").into(),
722                    ),
723                    (
724                        felt_str!("6148332971604923204").into(),
725                        felt_str!("100000000").into(),
726                    ),
727                ]),
728            )
729            .unwrap();
730        // ids.indices_cache_ptr = (3, 0)
731        dict_manager
732            .new_dict(
733                &mut vm,
734                HashMap::from([
735                    (
736                        felt_str!("6044027408028715819619898970704").into(),
737                        Felt252::ZERO.into(),
738                    ),
739                    (
740                        felt_str!("25783120691025710696626475600").into(),
741                        Felt252::ZERO.into(),
742                    ),
743                    (
744                        felt_str!("5176525270854594879110454268496").into(),
745                        Felt252::ZERO.into(),
746                    ),
747                    (
748                        felt_str!("21456356293159021401772216912").into(),
749                        Felt252::ZERO.into(),
750                    ),
751                    (
752                        felt_str!("20527877651862571847371805264").into(),
753                        Felt252::ZERO.into(),
754                    ),
755                ]),
756            )
757            .unwrap();
758        // ids.perps_cache_ptr = (4, 0)
759        dict_manager
760            .new_dict(
761                &mut vm,
762                HashMap::from([
763                    (
764                        felt_str!("6044027408028715819619898970704").into(),
765                        (1, 3092).into(),
766                    ),
767                    (
768                        felt_str!("25783120691025710696626475600").into(),
769                        (1, 3467).into(),
770                    ),
771                    (
772                        felt_str!("5176525270854594879110454268496").into(),
773                        (1, 3842).into(),
774                    ),
775                    (
776                        felt_str!("21456356293159021401772216912").into(),
777                        (1, 4217).into(),
778                    ),
779                    (
780                        felt_str!("20527877651862571847371805264").into(),
781                        (1, 4592).into(),
782                    ),
783                ]),
784            )
785            .unwrap();
786        // ids.fees_cache_ptr = (5, 0)
787        dict_manager
788            .new_dict(
789                &mut vm,
790                HashMap::from([
791                    (Felt252::from(100).into(), Felt252::from(10000).into()),
792                    (Felt252::from(200).into(), Felt252::from(10000).into()),
793                ]),
794            )
795            .unwrap();
796        // ids.perps_balances_cache_ptr = (6, 0)
797        dict_manager
798            .new_dict(
799                &mut vm,
800                HashMap::from([
801                    (
802                        felt_str!("6044027408028715819619898970704").into(),
803                        (1, 6406).into(),
804                    ),
805                    (
806                        felt_str!("25783120691025710696626475600").into(),
807                        (1, 6625).into(),
808                    ),
809                    (
810                        felt_str!("5176525270854594879110454268496").into(),
811                        (1, 6844).into(),
812                    ),
813                    (
814                        felt_str!("21456356293159021401772216912").into(),
815                        (1, 7063).into(),
816                    ),
817                    (
818                        felt_str!("20527877651862571847371805264").into(),
819                        (1, 18582).into(),
820                    ),
821                ]),
822            )
823            .unwrap();
824        exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager)));
825
826        // EXECUTION
827        assert!(excess_balance_hint(
828            &mut vm,
829            &ids,
830            &ApTracking::default(),
831            &constants,
832            &exec_scopes
833        )
834        .is_ok());
835
836        // CHECK MEMORY VALUES
837        check_memory![
838            vm.segments.memory,
839            // ids.check_account_value
840            ((1, 8), 1255049999900000),
841            // ids.check_excess_balance
842            ((1, 9), 1227636643508000),
843            // ids.check_margin_requirement_d
844            ((1, 10), 27413356392000),
845            // ids.check_unrealized_pnl_d
846            ((1, 11), 249899999902000)
847        ];
848    }
849
850    #[test]
851    fn run_excess_balance_hint_trade_failure() {
852        // TEST DATA
853
854        // INPUT VALUES
855        // ids.margin_check_type 1
856        // ids.MARGIN_CHECK_INITIAL 1
857        // ids.token_assets_value_d 0
858        // ids.account 100
859        // DICTIONARIES
860        // prices {6044027408028715819619898970704: 5100000000000, 25783120691025710696626475600: 5100000000000, 5176525270854594879110454268496: 5100000000000, 21456356293159021401772216912: 5100000000000, 20527877651862571847371805264: 5100000000000, 6148332971604923204: 100000000}
861        // indices {6044027408028715819619898970704: 0, 25783120691025710696626475600: 0, 5176525270854594879110454268496: 0, 21456356293159021401772216912: 0, 20527877651862571847371805264: 0}
862        // 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)}
863        // fees {100: 10000, 200: 10000}
864        // 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)}
865        // MEMORY VALUES REFERENCED BY DICTIONARIES
866        // 1:3092 6044027408028715819619898970704
867        // 1:3096 5000000
868        // 1:3097 20000
869        // 1:3098 50000000
870        // 1:3099 20000000000000
871        // 1:3467 25783120691025710696626475600
872        // 1:3471 5000000
873        // 1:3472 20000
874        // 1:3473 50000000
875        // 1:3474 20000000000000
876        // 1:3842 5176525270854594879110454268496
877        // 1:3846 5000000
878        // 1:3847 20000
879        // 1:3848 50000000
880        // 1:3849 20000000000000
881        // 1:4217 21456356293159021401772216912
882        // 1:4221 5000000
883        // 1:4222 20000
884        // 1:4223 50000000
885        // 1:4224 20000000000000
886        // 1:4592 20527877651862571847371805264
887        // 1:4596 5000000
888        // 1:4597 20000
889        // 1:4598 50000000
890        // 1:4599 20000000000000
891        // 1:6406 6044027408028715819619898970704
892        // 1:6407 0
893        // 1:6408 0
894        // 1:6409 0
895        // 1:6406 6044027408028715819619898970704
896        // 1:6407 0
897        // 1:6408 0
898        // 1:6409 0
899        // 1:6625 25783120691025710696626475600
900        // 1:6626 0
901        // 1:6627 0
902        // 1:6628 0
903        // 1:6625 25783120691025710696626475600
904        // 1:6626 0
905        // 1:6627 0
906        // 1:6628 0
907        // 1:6844 5176525270854594879110454268496
908        // 1:6845 0
909        // 1:6846 0
910        // 1:6847 0
911        // 1:6844 5176525270854594879110454268496
912        // 1:6845 0
913        // 1:6846 0
914        // 1:6847 0
915        // 1:7063 21456356293159021401772216912
916        // 1:7064 0
917        // 1:7065 0
918        // 1:7066 0
919        // 1:7063 21456356293159021401772216912
920        // 1:7064 0
921        // 1:7065 0
922        // 1:7066 0
923        // 1:18230 20527877651862571847371805264
924        // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481
925        // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481
926        // 1:18233 0
927        // 1:18230 20527877651862571847371805264
928        // 1:18231 3618502788666131213697322783095070105623107215331596699973092056135772020481
929        // 1:18232 3618502788666131213697322783095070105623107215331596699973092050985872020481
930        // 1:18233 0
931        // EXPECTED RESULTS
932        // ids.check_account_value 50000000000
933        // ids.check_excess_balance 3618502788666131213697322783095070105623107215331596699973092055930362020481
934        // ids.check_margin_requirement_d 255510000000
935        // ids.check_unrealized_pnl_d 50000000000
936
937        // SETUP
938        let mut vm = vm!();
939        // CONSTANTS
940        let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
941        // IDS
942        vm.segments = segments!(
943            ((1, 0), 1),      // ids.margin_check_type
944            ((1, 1), 0),      // ids.token_assets_value_d
945            ((1, 2), 100),    // ids.account
946            ((1, 3), (2, 0)), // ids.prices_cache_ptr
947            ((1, 4), (3, 0)), // ids.indices_cache_ptr
948            ((1, 5), (4, 0)), // ids.perps_cache_ptr
949            ((1, 6), (5, 0)), // ids.fees_cache_ptr
950            ((1, 7), (6, 0)), // ids.perps_balances_cache_ptr
951            //((1, 8), ids.check_account_value)
952            //((1, 9), ids.check_excess_balance)
953            //((1, 10), ids.check_margin_requirement_d)
954            //((1, 11), ids.check_unrealized_pnl_d)
955            // Memory values referenced by hints
956            ((1, 3092), 6044027408028715819619898970704),
957            ((1, 3096), 5000000),
958            ((1, 3097), 20000),
959            ((1, 3098), 50000000),
960            ((1, 3099), 20000000000000),
961            ((1, 3467), 25783120691025710696626475600),
962            ((1, 3471), 5000000),
963            ((1, 3472), 20000),
964            ((1, 3473), 50000000),
965            ((1, 3474), 20000000000000),
966            ((1, 3842), 5176525270854594879110454268496),
967            ((1, 3846), 5000000),
968            ((1, 3847), 20000),
969            ((1, 3848), 50000000),
970            ((1, 3849), 20000000000000),
971            ((1, 4217), 21456356293159021401772216912),
972            ((1, 4221), 5000000),
973            ((1, 4222), 20000),
974            ((1, 4223), 50000000),
975            ((1, 4224), 20000000000000),
976            ((1, 4592), 20527877651862571847371805264),
977            ((1, 4596), 5000000),
978            ((1, 4597), 20000),
979            ((1, 4598), 50000000),
980            ((1, 4599), 20000000000000),
981            ((1, 6406), 6044027408028715819619898970704),
982            ((1, 6407), 0),
983            ((1, 6408), 0),
984            ((1, 6409), 0),
985            ((1, 6406), 6044027408028715819619898970704),
986            ((1, 6407), 0),
987            ((1, 6408), 0),
988            ((1, 6409), 0),
989            ((1, 6625), 25783120691025710696626475600),
990            ((1, 6626), 0),
991            ((1, 6627), 0),
992            ((1, 6628), 0),
993            ((1, 6625), 25783120691025710696626475600),
994            ((1, 6626), 0),
995            ((1, 6627), 0),
996            ((1, 6628), 0),
997            ((1, 6844), 5176525270854594879110454268496),
998            ((1, 6845), 0),
999            ((1, 6846), 0),
1000            ((1, 6847), 0),
1001            ((1, 6844), 5176525270854594879110454268496),
1002            ((1, 6845), 0),
1003            ((1, 6846), 0),
1004            ((1, 6847), 0),
1005            ((1, 7063), 21456356293159021401772216912),
1006            ((1, 7064), 0),
1007            ((1, 7065), 0),
1008            ((1, 7066), 0),
1009            ((1, 7063), 21456356293159021401772216912),
1010            ((1, 7064), 0),
1011            ((1, 7065), 0),
1012            ((1, 7066), 0),
1013            ((1, 18230), 20527877651862571847371805264),
1014            (
1015                (1, 18231),
1016                (
1017                    "3618502788666131213697322783095070105623107215331596699973092056135772020481",
1018                    10
1019                )
1020            ),
1021            (
1022                (1, 18232),
1023                (
1024                    "3618502788666131213697322783095070105623107215331596699973092050985872020481",
1025                    10
1026                )
1027            ),
1028            ((1, 18233), 0),
1029            ((1, 18230), 20527877651862571847371805264),
1030            (
1031                (1, 18231),
1032                (
1033                    "3618502788666131213697322783095070105623107215331596699973092056135772020481",
1034                    10
1035                )
1036            ),
1037            (
1038                (1, 18232),
1039                (
1040                    "3618502788666131213697322783095070105623107215331596699973092050985872020481",
1041                    10
1042                )
1043            ),
1044            ((1, 18233), 0),
1045        );
1046        vm.run_context.set_fp(12);
1047        let ids = ids_data![
1048            "margin_check_type",
1049            "token_assets_value_d",
1050            "account",
1051            "prices_cache_ptr",
1052            "indices_cache_ptr",
1053            "perps_cache_ptr",
1054            "fees_cache_ptr",
1055            "perps_balances_cache_ptr",
1056            "check_account_value",
1057            "check_excess_balance",
1058            "check_margin_requirement_d",
1059            "check_unrealized_pnl_d"
1060        ];
1061        // DICTIONARIES
1062        let mut exec_scopes = ExecutionScopes::new();
1063        let mut dict_manager = DictManager::new();
1064        // ids.prices_cache_ptr = (2, 0)
1065        dict_manager
1066            .new_dict(
1067                &mut vm,
1068                HashMap::from([
1069                    (
1070                        felt_str!("6044027408028715819619898970704").into(),
1071                        felt_str!("5100000000000").into(),
1072                    ),
1073                    (
1074                        felt_str!("25783120691025710696626475600").into(),
1075                        felt_str!("5100000000000").into(),
1076                    ),
1077                    (
1078                        felt_str!("5176525270854594879110454268496").into(),
1079                        felt_str!("5100000000000").into(),
1080                    ),
1081                    (
1082                        felt_str!("21456356293159021401772216912").into(),
1083                        felt_str!("5100000000000").into(),
1084                    ),
1085                    (
1086                        felt_str!("20527877651862571847371805264").into(),
1087                        felt_str!("5100000000000").into(),
1088                    ),
1089                    (
1090                        felt_str!("6148332971604923204").into(),
1091                        felt_str!("100000000").into(),
1092                    ),
1093                ]),
1094            )
1095            .unwrap();
1096        // ids.indices_cache_ptr = (3, 0)
1097        dict_manager
1098            .new_dict(
1099                &mut vm,
1100                HashMap::from([
1101                    (
1102                        felt_str!("6044027408028715819619898970704").into(),
1103                        Felt252::ZERO.into(),
1104                    ),
1105                    (
1106                        felt_str!("25783120691025710696626475600").into(),
1107                        Felt252::ZERO.into(),
1108                    ),
1109                    (
1110                        felt_str!("5176525270854594879110454268496").into(),
1111                        Felt252::ZERO.into(),
1112                    ),
1113                    (
1114                        felt_str!("21456356293159021401772216912").into(),
1115                        Felt252::ZERO.into(),
1116                    ),
1117                    (
1118                        felt_str!("20527877651862571847371805264").into(),
1119                        Felt252::ZERO.into(),
1120                    ),
1121                ]),
1122            )
1123            .unwrap();
1124        // ids.perps_cache_ptr = (4, 0)
1125        dict_manager
1126            .new_dict(
1127                &mut vm,
1128                HashMap::from([
1129                    (
1130                        felt_str!("6044027408028715819619898970704").into(),
1131                        (1, 3092).into(),
1132                    ),
1133                    (
1134                        felt_str!("25783120691025710696626475600").into(),
1135                        (1, 3467).into(),
1136                    ),
1137                    (
1138                        felt_str!("5176525270854594879110454268496").into(),
1139                        (1, 3842).into(),
1140                    ),
1141                    (
1142                        felt_str!("21456356293159021401772216912").into(),
1143                        (1, 4217).into(),
1144                    ),
1145                    (
1146                        felt_str!("20527877651862571847371805264").into(),
1147                        (1, 4592).into(),
1148                    ),
1149                ]),
1150            )
1151            .unwrap();
1152        // ids.fees_cache_ptr = (5, 0)
1153        dict_manager
1154            .new_dict(
1155                &mut vm,
1156                HashMap::from([
1157                    (Felt252::from(100).into(), Felt252::from(10000).into()),
1158                    (Felt252::from(200).into(), Felt252::from(10000).into()),
1159                ]),
1160            )
1161            .unwrap();
1162        // ids.perps_balances_cache_ptr = (6, 0)
1163        dict_manager
1164            .new_dict(
1165                &mut vm,
1166                HashMap::from([
1167                    (
1168                        felt_str!("6044027408028715819619898970704").into(),
1169                        (1, 6406).into(),
1170                    ),
1171                    (
1172                        felt_str!("25783120691025710696626475600").into(),
1173                        (1, 6625).into(),
1174                    ),
1175                    (
1176                        felt_str!("5176525270854594879110454268496").into(),
1177                        (1, 6844).into(),
1178                    ),
1179                    (
1180                        felt_str!("21456356293159021401772216912").into(),
1181                        (1, 7063).into(),
1182                    ),
1183                    (
1184                        felt_str!("20527877651862571847371805264").into(),
1185                        (1, 18230).into(),
1186                    ),
1187                ]),
1188            )
1189            .unwrap();
1190        exec_scopes.insert_value("dict_manager", Rc::new(RefCell::new(dict_manager)));
1191
1192        // EXECUTION
1193        assert!(excess_balance_hint(
1194            &mut vm,
1195            &ids,
1196            &ApTracking::default(),
1197            &constants,
1198            &exec_scopes
1199        )
1200        .is_ok());
1201
1202        // CHECK MEMORY VALUES
1203        check_memory![
1204            vm.segments.memory,
1205            // ids.check_account_value
1206            ((1, 8), 50000000000),
1207            // ids.check_excess_balance
1208            (
1209                (1, 9),
1210                (
1211                    "3618502788666131213697322783095070105623107215331596699973092055930362020481",
1212                    10
1213                )
1214            ),
1215            // ids.check_margin_requirement_d
1216            ((1, 10), 255510000000),
1217            // ids.check_unrealized_pnl_d
1218            ((1, 11), 50000000000)
1219        ];
1220    }
1221}