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 = 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 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 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 let mut vm = vm!();
597 let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
599 vm.segments = segments!(
601 ((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),
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 let mut exec_scopes = ExecutionScopes::new();
697 let mut dict_manager = DictManager::new();
698 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 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 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 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 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 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![
838 vm.segments.memory,
839 ((1, 8), 1255049999900000),
841 ((1, 9), 1227636643508000),
843 ((1, 10), 27413356392000),
845 ((1, 11), 249899999902000)
847 ];
848 }
849
850 #[test]
851 fn run_excess_balance_hint_trade_failure() {
852 let mut vm = vm!();
939 let constants = HashMap::from([("MARGIN_CHECK_INITIAL".to_string(), Felt252::ONE)]);
941 vm.segments = segments!(
943 ((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),
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 let mut exec_scopes = ExecutionScopes::new();
1063 let mut dict_manager = DictManager::new();
1064 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 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 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 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 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 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![
1204 vm.segments.memory,
1205 ((1, 8), 50000000000),
1207 (
1209 (1, 9),
1210 (
1211 "3618502788666131213697322783095070105623107215331596699973092055930362020481",
1212 10
1213 )
1214 ),
1215 ((1, 10), 255510000000),
1217 ((1, 11), 50000000000)
1219 ];
1220 }
1221}