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
30lazy_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#[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
123fn 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 let prices =
149 dict_ref_from_var_name("prices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
150
151 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 let indices =
174 dict_ref_from_var_name("indices_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
175
176 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 let perps = dict_ref_from_var_name("perps_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
199
200 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 let fees = dict_ref_from_var_name("fees_cache_ptr", vm, dict_manager, ids_data, ap_tracking)?;
223
224 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 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 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 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 let dict_manager_rc = exec_scopes.get_dict_manager()?;
277 let dict_manager = dict_manager_rc.borrow();
278 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 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
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 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 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 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 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 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 let mut vm = vm!();
595 let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
597 vm.segments = segments!(
599 ((1, 0), 1), ((1, 1), 1005149999998000), ((1, 2), 200), ((1, 3), (2, 0)), ((1, 4), (3, 0)), ((1, 5), (4, 0)), ((1, 6), (5, 0)), ((1, 7), (6, 0)), ((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 let mut exec_scopes = ExecutionScopes::new();
695 let mut dict_manager = DictManager::new();
696 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 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 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 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 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 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![
836 vm.segments.memory,
837 ((1, 8), 1255049999900000),
839 ((1, 9), 1227636643508000),
841 ((1, 10), 27413356392000),
843 ((1, 11), 249899999902000)
845 ];
846 }
847
848 #[test]
849 fn run_excess_balance_hint_trade_failure() {
850 let mut vm = vm!();
937 let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
939 vm.segments = segments!(
941 ((1, 0), 1), ((1, 1), 0), ((1, 2), 100), ((1, 3), (2, 0)), ((1, 4), (3, 0)), ((1, 5), (4, 0)), ((1, 6), (5, 0)), ((1, 7), (6, 0)), ((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 let mut exec_scopes = ExecutionScopes::new();
1061 let mut dict_manager = DictManager::new();
1062 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 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 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 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 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 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![
1202 vm.segments.memory,
1203 ((1, 8), 50000000000),
1205 (
1207 (1, 9),
1208 (
1209 "3618502788666131213697322783095070105623107215331596699973092055930362020481",
1210 10
1211 )
1212 ),
1213 ((1, 10), 255510000000),
1215 ((1, 11), 50000000000)
1217 ];
1218 }
1219}