1use std::{any::Any, collections::HashMap};
2
3use alloy::primitives::U256;
4use num_bigint::BigUint;
5use tycho_common::{
6 dto::ProtocolStateDelta,
7 models::token::Token,
8 simulation::{
9 errors::{SimulationError, TransitionError},
10 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
11 },
12 Bytes,
13};
14use tycho_ethereum::BytesCodec;
15
16use crate::evm::protocol::{
17 rocketpool::ETH_ADDRESS,
18 safe_math::{safe_add_u256, safe_mul_u256, safe_sub_u256},
19 u256_num::{biguint_to_u256, u256_to_biguint, u256_to_f64},
20 utils::solidity_math::mul_div,
21};
22
23const DEPOSIT_FEE_BASE: u128 = 1_000_000_000_000_000_000; const VARIABLE_DEPOSIT_AMOUNT: u128 = 31_000_000_000_000_000_000; fn queue_capacity() -> U256 {
29 U256::from(1) << 255
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub struct RocketpoolState {
34 pub reth_supply: U256,
35 pub total_eth: U256,
36 pub deposit_contract_balance: U256,
38 pub reth_contract_liquidity: U256,
40 pub deposit_fee: U256,
42 pub deposits_enabled: bool,
43 pub min_deposit_amount: U256,
44 pub max_deposit_pool_size: U256,
45 pub deposit_assigning_enabled: bool,
47 pub deposit_assign_maximum: U256,
49 pub deposit_assign_socialised_maximum: U256,
51 pub queue_variable_start: U256,
53 pub queue_variable_end: U256,
54}
55
56impl RocketpoolState {
57 #[allow(clippy::too_many_arguments)]
58 pub fn new(
59 reth_supply: U256,
60 total_eth: U256,
61 deposit_contract_balance: U256,
62 reth_contract_liquidity: U256,
63 deposit_fee: U256,
64 deposits_enabled: bool,
65 min_deposit_amount: U256,
66 max_deposit_pool_size: U256,
67 deposit_assigning_enabled: bool,
68 deposit_assign_maximum: U256,
69 deposit_assign_socialised_maximum: U256,
70 queue_variable_start: U256,
71 queue_variable_end: U256,
72 ) -> Self {
73 Self {
74 reth_supply,
75 total_eth,
76 deposit_contract_balance,
77 reth_contract_liquidity,
78 deposit_fee,
79 deposits_enabled,
80 min_deposit_amount,
81 max_deposit_pool_size,
82 deposit_assigning_enabled,
83 deposit_assign_maximum,
84 deposit_assign_socialised_maximum,
85 queue_variable_start,
86 queue_variable_end,
87 }
88 }
89
90 fn get_reth_value(&self, eth_amount: U256) -> Result<U256, SimulationError> {
92 let fee = mul_div(eth_amount, self.deposit_fee, U256::from(DEPOSIT_FEE_BASE))?;
94 let net_eth = safe_sub_u256(eth_amount, fee)?;
95
96 mul_div(net_eth, self.reth_supply, self.total_eth)
98 }
99
100 fn get_eth_value(&self, reth_amount: U256) -> Result<U256, SimulationError> {
102 mul_div(reth_amount, self.total_eth, self.reth_supply)
104 }
105
106 fn is_depositing_eth(token_in: &Bytes) -> bool {
107 token_in.as_ref() == ETH_ADDRESS
108 }
109
110 fn assert_deposits_enabled(&self) -> Result<(), SimulationError> {
111 if !self.deposits_enabled {
112 Err(SimulationError::RecoverableError(
113 "Deposits are currently disabled in Rocketpool".to_string(),
114 ))
115 } else {
116 Ok(())
117 }
118 }
119
120 fn get_queue_length(start: U256, end: U256) -> U256 {
123 if end < start {
124 end + queue_capacity() - start
126 } else {
127 end - start
128 }
129 }
130
131 fn get_effective_capacity(&self) -> Result<U256, SimulationError> {
142 let variable_length =
143 Self::get_queue_length(self.queue_variable_start, self.queue_variable_end);
144
145 let variable_capacity =
146 safe_mul_u256(variable_length, U256::from(VARIABLE_DEPOSIT_AMOUNT))?;
147
148 Ok(variable_capacity)
149 }
150
151 fn get_max_deposit_capacity(&self) -> Result<U256, SimulationError> {
154 if self.deposit_assigning_enabled {
155 let effective_capacity = self.get_effective_capacity()?;
156 safe_add_u256(self.max_deposit_pool_size, effective_capacity)
157 } else {
158 Ok(self.max_deposit_pool_size)
159 }
160 }
161
162 fn get_deposit_pool_excess_balance(&self) -> Result<U256, SimulationError> {
168 let minipool_capacity = self.get_effective_capacity()?;
169 if minipool_capacity >= self.deposit_contract_balance {
170 Ok(U256::ZERO)
171 } else {
172 safe_sub_u256(self.deposit_contract_balance, minipool_capacity)
173 }
174 }
175
176 fn get_total_available_for_withdrawal(&self) -> Result<U256, SimulationError> {
179 let deposit_pool_excess = self.get_deposit_pool_excess_balance()?;
180 safe_add_u256(self.reth_contract_liquidity, deposit_pool_excess)
181 }
182
183 fn calculate_assign_deposits(
195 &self,
196 deposit_amount: U256,
197 ) -> Result<(U256, U256), SimulationError> {
198 if !self.deposit_assigning_enabled {
199 return Ok((U256::ZERO, U256::ZERO));
200 }
201
202 let variable_deposit = U256::from(VARIABLE_DEPOSIT_AMOUNT);
209
210 let scaling_count = deposit_amount / variable_deposit;
212 let desired_assignments = self.deposit_assign_socialised_maximum + scaling_count;
213
214 let eth_cap_assignments = self.deposit_contract_balance / variable_deposit;
215 let settings_cap_assignments = self.deposit_assign_maximum;
216 let queue_cap_assignments =
217 Self::get_queue_length(self.queue_variable_start, self.queue_variable_end);
218
219 let assignments = desired_assignments
221 .min(eth_cap_assignments)
222 .min(settings_cap_assignments)
223 .min(queue_cap_assignments);
224
225 let eth_assigned = safe_mul_u256(assignments, variable_deposit)?;
226
227 Ok((assignments, eth_assigned))
228 }
229}
230
231impl ProtocolSim for RocketpoolState {
232 fn fee(&self) -> f64 {
233 unimplemented!("Rocketpool has asymmetric fees; use spot_price or get_amount_out instead")
234 }
235
236 fn spot_price(&self, _base: &Token, quote: &Token) -> Result<f64, SimulationError> {
237 let is_depositing_eth = RocketpoolState::is_depositing_eth("e.address);
239
240 let amount = U256::from(1e18);
243
244 let base_per_quote = if is_depositing_eth {
245 self.assert_deposits_enabled()?;
247 self.get_reth_value(amount)?
248 } else {
249 self.get_eth_value(amount)?
251 };
252
253 let base_per_quote = u256_to_f64(base_per_quote)? / 1e18;
254
255 Ok(1.0 / base_per_quote)
257 }
258
259 #[allow(clippy::collapsible_else_if)]
260 fn get_amount_out(
261 &self,
262 amount_in: BigUint,
263 token_in: &Token,
264 _token_out: &Token,
265 ) -> Result<GetAmountOutResult, SimulationError> {
266 let amount_in = biguint_to_u256(&amount_in);
267 let is_depositing_eth = RocketpoolState::is_depositing_eth(&token_in.address);
268
269 let amount_out = if is_depositing_eth {
270 self.assert_deposits_enabled()?;
271
272 if amount_in < self.min_deposit_amount {
273 return Err(SimulationError::InvalidInput(
274 format!(
275 "Deposit amount {} is less than the minimum deposit of {}",
276 amount_in, self.min_deposit_amount
277 ),
278 None,
279 ));
280 }
281
282 let capacity_needed = safe_add_u256(self.deposit_contract_balance, amount_in)?;
283 let max_capacity = self.get_max_deposit_capacity()?;
284 if capacity_needed > max_capacity {
285 return Err(SimulationError::InvalidInput(
286 format!(
287 "Deposit would exceed maximum pool size (capacity needed: {}, max: {})",
288 capacity_needed, max_capacity
289 ),
290 None,
291 ));
292 }
293
294 self.get_reth_value(amount_in)?
295 } else {
296 let eth_out = self.get_eth_value(amount_in)?;
297
298 let total_available = self.get_total_available_for_withdrawal()?;
299 if eth_out > total_available {
300 return Err(SimulationError::RecoverableError(format!(
301 "Withdrawal {} exceeds available liquidity {}",
302 eth_out, total_available
303 )));
304 }
305
306 eth_out
307 };
308
309 let mut new_state = self.clone();
310 if is_depositing_eth {
312 new_state.deposit_contract_balance =
313 safe_add_u256(new_state.deposit_contract_balance, amount_in)?;
314
315 let (assignments, eth_assigned) = new_state.calculate_assign_deposits(amount_in)?;
317 if assignments > U256::ZERO {
318 new_state.deposit_contract_balance =
319 safe_sub_u256(new_state.deposit_contract_balance, eth_assigned)?;
320 new_state.queue_variable_start =
321 safe_add_u256(new_state.queue_variable_start, assignments)?;
322 }
323 } else {
324 if amount_out <= new_state.reth_contract_liquidity {
325 new_state.reth_contract_liquidity =
327 safe_sub_u256(new_state.reth_contract_liquidity, amount_out)?;
328 } else {
329 let needed_from_deposit_pool =
331 safe_sub_u256(amount_out, new_state.reth_contract_liquidity)?;
332 new_state.deposit_contract_balance =
333 safe_sub_u256(new_state.deposit_contract_balance, needed_from_deposit_pool)?;
334 new_state.reth_contract_liquidity = U256::ZERO;
335 }
336 };
337
338 let gas_used = if is_depositing_eth { 209_000u32 } else { 134_000u32 };
342
343 Ok(GetAmountOutResult::new(
344 u256_to_biguint(amount_out),
345 BigUint::from(gas_used),
346 Box::new(new_state),
347 ))
348 }
349
350 fn get_limits(
351 &self,
352 sell_token: Bytes,
353 _buy_token: Bytes,
354 ) -> Result<(BigUint, BigUint), SimulationError> {
355 let is_depositing_eth = Self::is_depositing_eth(&sell_token);
356
357 if is_depositing_eth {
358 let max_capacity = self.get_max_deposit_capacity()?;
361 let max_eth_sell = safe_sub_u256(max_capacity, self.deposit_contract_balance)?;
362 let max_reth_buy = self.get_reth_value(max_eth_sell)?;
363 Ok((u256_to_biguint(max_eth_sell), u256_to_biguint(max_reth_buy)))
364 } else {
365 let max_eth_buy = self.get_total_available_for_withdrawal()?;
367 let max_reth_sell = mul_div(max_eth_buy, self.reth_supply, self.total_eth)?;
368 Ok((u256_to_biguint(max_reth_sell), u256_to_biguint(max_eth_buy)))
369 }
370 }
371
372 fn delta_transition(
373 &mut self,
374 delta: ProtocolStateDelta,
375 _tokens: &HashMap<Bytes, Token>,
376 _balances: &Balances,
377 ) -> Result<(), TransitionError<String>> {
378 self.total_eth = delta
379 .updated_attributes
380 .get("total_eth")
381 .map_or(self.total_eth, U256::from_bytes);
382 self.reth_supply = delta
383 .updated_attributes
384 .get("reth_supply")
385 .map_or(self.reth_supply, U256::from_bytes);
386
387 self.deposit_contract_balance = delta
388 .updated_attributes
389 .get("deposit_contract_balance")
390 .map_or(self.deposit_contract_balance, U256::from_bytes);
391 self.reth_contract_liquidity = delta
392 .updated_attributes
393 .get("reth_contract_liquidity")
394 .map_or(self.reth_contract_liquidity, U256::from_bytes);
395
396 self.deposits_enabled = delta
397 .updated_attributes
398 .get("deposits_enabled")
399 .map_or(self.deposits_enabled, |val| !U256::from_bytes(val).is_zero());
400 self.deposit_assigning_enabled = delta
401 .updated_attributes
402 .get("deposit_assigning_enabled")
403 .map_or(self.deposit_assigning_enabled, |val| !U256::from_bytes(val).is_zero());
404 self.deposit_fee = delta
405 .updated_attributes
406 .get("deposit_fee")
407 .map_or(self.deposit_fee, U256::from_bytes);
408 self.min_deposit_amount = delta
409 .updated_attributes
410 .get("min_deposit_amount")
411 .map_or(self.min_deposit_amount, U256::from_bytes);
412 self.max_deposit_pool_size = delta
413 .updated_attributes
414 .get("max_deposit_pool_size")
415 .map_or(self.max_deposit_pool_size, U256::from_bytes);
416 self.deposit_assign_maximum = delta
417 .updated_attributes
418 .get("deposit_assign_maximum")
419 .map_or(self.deposit_assign_maximum, U256::from_bytes);
420 self.deposit_assign_socialised_maximum = delta
421 .updated_attributes
422 .get("deposit_assign_socialised_maximum")
423 .map_or(self.deposit_assign_socialised_maximum, U256::from_bytes);
424
425 self.queue_variable_start = delta
426 .updated_attributes
427 .get("queue_variable_start")
428 .map_or(self.queue_variable_start, U256::from_bytes);
429 self.queue_variable_end = delta
430 .updated_attributes
431 .get("queue_variable_end")
432 .map_or(self.queue_variable_end, U256::from_bytes);
433
434 Ok(())
435 }
436
437 fn clone_box(&self) -> Box<dyn ProtocolSim> {
439 Box::new(self.clone())
440 }
441
442 fn as_any(&self) -> &dyn Any {
443 self
444 }
445
446 fn as_any_mut(&mut self) -> &mut dyn Any {
447 self
448 }
449
450 fn eq(&self, other: &dyn ProtocolSim) -> bool {
451 if let Some(other_state) = other.as_any().downcast_ref::<Self>() {
452 self == other_state
453 } else {
454 false
455 }
456 }
457
458 fn query_pool_swap(
459 &self,
460 params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
461 ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
462 crate::evm::query_pool_swap::query_pool_swap(self, params)
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use std::{
469 collections::{HashMap, HashSet},
470 str::FromStr,
471 };
472
473 use approx::assert_ulps_eq;
474 use num_bigint::BigUint;
475 use num_traits::ToPrimitive;
476 use tycho_common::{
477 dto::ProtocolStateDelta,
478 hex_bytes::Bytes,
479 models::{token::Token, Chain},
480 simulation::{
481 errors::SimulationError,
482 protocol_sim::{Balances, ProtocolSim},
483 },
484 };
485
486 use super::*;
487 use crate::evm::protocol::utils::add_fee_markup;
488
489 fn create_state() -> RocketpoolState {
498 RocketpoolState::new(
499 U256::from(100e18), U256::from(200e18), U256::from(50e18), U256::ZERO, U256::from(400_000_000_000_000_000u64), true, U256::ZERO, U256::from(1000e18), false, U256::ZERO, U256::ZERO, U256::ZERO, U256::ZERO, )
513 }
514
515 fn eth_token() -> Token {
516 Token::new(&Bytes::from(ETH_ADDRESS), "ETH", 18, 0, &[Some(100_000)], Chain::Ethereum, 100)
517 }
518
519 fn reth_token() -> Token {
520 Token::new(
521 &Bytes::from_str("0xae78736Cd615f374D3085123A210448E74Fc6393").unwrap(),
522 "rETH",
523 18,
524 0,
525 &[Some(100_000)],
526 Chain::Ethereum,
527 100,
528 )
529 }
530
531 #[test]
534 fn test_queue_length_normal() {
535 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(15));
536 assert_eq!(length, U256::from(5));
537 }
538
539 #[test]
540 fn test_queue_length_empty() {
541 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(10));
542 assert_eq!(length, U256::ZERO);
543 }
544
545 #[test]
546 fn test_queue_length_wrap_around() {
547 let capacity = queue_capacity();
548 let start = capacity - U256::from(5);
549 let end = U256::from(3);
550 let length = RocketpoolState::get_queue_length(start, end);
551 assert_eq!(length, U256::from(8));
553 }
554
555 #[test]
558 fn test_effective_capacity_empty() {
559 let state = create_state();
560 assert_eq!(state.get_effective_capacity().unwrap(), U256::ZERO);
561 }
562
563 #[test]
564 fn test_effective_capacity_variable_queue() {
565 let mut state = create_state();
566 state.queue_variable_end = U256::from(2); assert_eq!(state.get_effective_capacity().unwrap(), U256::from(62e18));
568 }
569
570 #[test]
573 fn test_max_capacity_assign_disabled() {
574 let state = create_state();
575 let max = state
576 .get_max_deposit_capacity()
577 .unwrap();
578 assert_eq!(max, U256::from(1000e18));
579 }
580
581 #[test]
582 fn test_max_capacity_assign_enabled_empty_queue() {
583 let mut state = create_state();
584 state.deposit_assigning_enabled = true;
585 let max = state
586 .get_max_deposit_capacity()
587 .unwrap();
588 assert_eq!(max, U256::from(1000e18));
589 }
590
591 #[test]
592 fn test_max_capacity_assign_enabled_with_queue() {
593 let mut state = create_state();
594 state.deposit_assigning_enabled = true;
595 state.queue_variable_end = U256::from(10); let max = state
597 .get_max_deposit_capacity()
598 .unwrap();
599 assert_eq!(max, U256::from(1310e18));
601 }
602
603 #[test]
606 fn test_delta_transition_basic() {
607 let mut state = create_state();
608
609 let attributes: HashMap<String, Bytes> = [
610 ("total_eth", U256::from(300u64)),
611 ("reth_supply", U256::from(150u64)),
612 ("deposit_contract_balance", U256::from(100u64)),
613 ("reth_contract_liquidity", U256::from(20u64)),
614 ]
615 .into_iter()
616 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
617 .collect();
618
619 let delta = ProtocolStateDelta {
620 component_id: "Rocketpool".to_owned(),
621 updated_attributes: attributes,
622 deleted_attributes: HashSet::new(),
623 };
624
625 state
626 .delta_transition(delta, &HashMap::new(), &Balances::default())
627 .unwrap();
628
629 assert_eq!(state.total_eth, U256::from(300u64));
630 assert_eq!(state.reth_supply, U256::from(150u64));
631 assert_eq!(state.deposit_contract_balance, U256::from(100u64));
632 assert_eq!(state.reth_contract_liquidity, U256::from(20u64));
633 }
634
635 #[test]
636 fn test_delta_transition_queue_fields() {
637 let mut state = create_state();
638
639 let attributes: HashMap<String, Bytes> = [
640 ("deposit_assigning_enabled", U256::from(1u64)),
641 ("queue_variable_end", U256::from(5u64)),
642 ]
643 .into_iter()
644 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
645 .collect();
646
647 let delta = ProtocolStateDelta {
648 component_id: "Rocketpool".to_owned(),
649 updated_attributes: attributes,
650 deleted_attributes: HashSet::new(),
651 };
652
653 state
654 .delta_transition(delta, &HashMap::new(), &Balances::default())
655 .unwrap();
656
657 assert!(state.deposit_assigning_enabled);
658 assert_eq!(state.queue_variable_end, U256::from(5u64));
659 }
660
661 #[test]
664 fn test_spot_price_deposit() {
665 let state = create_state();
666 let price = state
670 .spot_price(ð_token(), &reth_token())
671 .unwrap();
672 assert_ulps_eq!(price, 0.5);
673 }
674
675 #[test]
676 fn test_spot_price_withdraw() {
677 let state = create_state();
678 let price = state
682 .spot_price(&reth_token(), ð_token())
683 .unwrap();
684 assert_ulps_eq!(price, 1.0 / 0.3);
685 }
686
687 fn create_state_at_block_23929406() -> RocketpoolState {
689 RocketpoolState::new(
690 U256::from_str_radix("4df2cf698437b72b8937", 16).unwrap(), U256::from_str_radix("59c8a9cb90db4a5aa85e", 16).unwrap(), U256::from_str_radix("11e245d1725f73941", 16).unwrap(), U256::from_str_radix("b6e43509", 16).unwrap(), U256::from_str_radix("1c6bf52634000", 16).unwrap(), true, U256::from_str_radix("2386f26fc10000", 16).unwrap(), U256::from_str_radix("3cfc82e37e9a7400000", 16).unwrap(), true, U256::from_str_radix("5a", 16).unwrap(), U256::from_str_radix("2", 16).unwrap(), U256::from_str_radix("6d45", 16).unwrap(), U256::from_str_radix("6de3", 16).unwrap(), )
704 }
705
706 #[test]
709 fn test_live_spot_price_reth_to_buy_eth_23929406() {
710 let state = create_state_at_block_23929406();
711
712 let price = state
713 .spot_price(ð_token(), &reth_token())
714 .unwrap();
715
716 let expected = 0.868179358382478;
717 assert_ulps_eq!(price, expected, max_ulps = 10);
718 }
719
720 #[test]
723 fn test_live_spot_price_eth_to_buy_reth_23929406() {
724 let state = create_state_at_block_23929406();
725
726 let price = state
727 .spot_price(&reth_token(), ð_token())
728 .unwrap();
729
730 let expected_without_fee = 1.151835724202335;
731 let fee = state.deposit_fee.to_f64().unwrap() / DEPOSIT_FEE_BASE as f64;
732 let expected = add_fee_markup(expected_without_fee, fee);
733
734 assert_ulps_eq!(price, expected, max_ulps = 10);
735 }
736
737 #[test]
738 fn test_fee_panics() {
739 let state = create_state();
740 let result = std::panic::catch_unwind(|| state.fee());
741 assert!(result.is_err());
742 }
743
744 #[test]
747 fn test_limits_deposit() {
748 let state = create_state();
749
750 let (max_sell, max_buy) = state
751 .get_limits(eth_token().address, reth_token().address)
752 .unwrap();
753
754 assert_eq!(max_sell, BigUint::from(950_000_000_000_000_000_000u128));
756 assert_eq!(max_buy, BigUint::from(285_000_000_000_000_000_000u128));
758 }
759
760 #[test]
761 fn test_limits_withdrawal() {
762 let state = create_state();
763
764 let (max_sell, max_buy) = state
765 .get_limits(reth_token().address, eth_token().address)
766 .unwrap();
767
768 assert_eq!(max_buy, BigUint::from(50_000_000_000_000_000_000u128));
770 assert_eq!(max_sell, BigUint::from(25_000_000_000_000_000_000u128));
772 }
773
774 #[test]
775 fn test_limits_with_extended_capacity() {
776 let mut state = create_state();
777 state.max_deposit_pool_size = U256::from(100e18);
778 state.deposit_assigning_enabled = true;
779 state.queue_variable_end = U256::from(2); let (max_sell, _) = state
782 .get_limits(eth_token().address, reth_token().address)
783 .unwrap();
784
785 assert_eq!(max_sell, BigUint::from(112_000_000_000_000_000_000u128));
788 }
789
790 #[test]
793 fn test_deposit_eth() {
794 let state = create_state();
795
796 let res = state
798 .get_amount_out(
799 BigUint::from(10_000_000_000_000_000_000u128),
800 ð_token(),
801 &reth_token(),
802 )
803 .unwrap();
804
805 assert_eq!(res.amount, BigUint::from(3_000_000_000_000_000_000u128));
806
807 let new_state = res
808 .new_state
809 .as_any()
810 .downcast_ref::<RocketpoolState>()
811 .unwrap();
812 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
814 assert_eq!(new_state.total_eth, U256::from(200e18));
816 assert_eq!(new_state.reth_supply, U256::from(100e18));
817 }
818
819 #[test]
820 fn test_deposit_within_extended_capacity() {
821 let mut state = create_state();
822 state.deposit_contract_balance = U256::from(990e18);
823 state.max_deposit_pool_size = U256::from(1000e18);
824 state.deposit_assigning_enabled = true;
825 state.queue_variable_end = U256::from(1); let res = state
830 .get_amount_out(
831 BigUint::from(20_000_000_000_000_000_000u128),
832 ð_token(),
833 &reth_token(),
834 )
835 .unwrap();
836
837 assert_eq!(res.amount, BigUint::from(6_000_000_000_000_000_000u128));
838
839 let new_state = res
840 .new_state
841 .as_any()
842 .downcast_ref::<RocketpoolState>()
843 .unwrap();
844 assert_eq!(new_state.deposit_contract_balance, U256::from(1010e18));
846 assert_eq!(new_state.total_eth, U256::from(200e18));
848 assert_eq!(new_state.reth_supply, U256::from(100e18));
849 }
850
851 #[test]
852 fn test_withdraw_reth() {
853 let state = create_state();
854
855 let res = state
857 .get_amount_out(
858 BigUint::from(10_000_000_000_000_000_000u128),
859 &reth_token(),
860 ð_token(),
861 )
862 .unwrap();
863
864 assert_eq!(res.amount, BigUint::from(20_000_000_000_000_000_000u128));
865
866 let new_state = res
867 .new_state
868 .as_any()
869 .downcast_ref::<RocketpoolState>()
870 .unwrap();
871 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
873 assert_eq!(new_state.total_eth, U256::from(200e18));
875 assert_eq!(new_state.reth_supply, U256::from(100e18));
876 }
877
878 #[test]
881 fn test_deposit_disabled() {
882 let mut state = create_state();
883 state.deposits_enabled = false;
884
885 let res = state.get_amount_out(BigUint::from(10u64), ð_token(), &reth_token());
886 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
887 }
888
889 #[test]
890 fn test_deposit_below_minimum() {
891 let mut state = create_state();
892 state.min_deposit_amount = U256::from(100u64);
893
894 let res = state.get_amount_out(BigUint::from(50u64), ð_token(), &reth_token());
895 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
896 }
897
898 #[test]
899 fn test_deposit_exceeds_max_pool() {
900 let mut state = create_state();
901 state.max_deposit_pool_size = U256::from(60e18); let res = state.get_amount_out(
904 BigUint::from(20_000_000_000_000_000_000u128),
905 ð_token(),
906 &reth_token(),
907 );
908 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
909 }
910
911 #[test]
912 fn test_deposit_queue_ignored_when_disabled() {
913 let mut state = create_state();
914 state.deposit_contract_balance = U256::from(990e18);
915 state.max_deposit_pool_size = U256::from(1000e18);
916 state.deposit_assigning_enabled = false;
917 state.queue_variable_end = U256::from(10); let res = state.get_amount_out(
921 BigUint::from(20_000_000_000_000_000_000u128),
922 ð_token(),
923 &reth_token(),
924 );
925 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
926 }
927
928 #[test]
929 fn test_deposit_exceeds_extended_capacity() {
930 let mut state = create_state();
931 state.deposit_contract_balance = U256::from(990e18);
932 state.max_deposit_pool_size = U256::from(1000e18);
933 state.deposit_assigning_enabled = true;
934 state.queue_variable_end = U256::from(1); let res = state.get_amount_out(
938 BigUint::from(50_000_000_000_000_000_000u128),
939 ð_token(),
940 &reth_token(),
941 );
942 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
943 }
944
945 #[test]
946 fn test_withdrawal_insufficient_liquidity() {
947 let state = create_state(); let res = state.get_amount_out(
951 BigUint::from(30_000_000_000_000_000_000u128),
952 &reth_token(),
953 ð_token(),
954 );
955 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
956 }
957
958 #[test]
959 fn test_withdrawal_limited_by_minipool_queue() {
960 let mut state = create_state();
961 state.deposit_contract_balance = U256::from(100e18);
962 state.queue_variable_end = U256::from(2);
964 let res = state.get_amount_out(
968 BigUint::from(20_000_000_000_000_000_000u128),
969 &reth_token(),
970 ð_token(),
971 );
972 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
973
974 let res = state
976 .get_amount_out(
977 BigUint::from(15_000_000_000_000_000_000u128),
978 &reth_token(),
979 ð_token(),
980 )
981 .unwrap();
982 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
983 }
984
985 #[test]
986 fn test_withdrawal_zero_excess_balance() {
987 let mut state = create_state();
988 state.deposit_contract_balance = U256::from(62e18);
989 state.queue_variable_end = U256::from(2);
991 let res = state.get_amount_out(
995 BigUint::from(1_000_000_000_000_000_000u128),
996 &reth_token(),
997 ð_token(),
998 );
999 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
1000 }
1001
1002 #[test]
1003 fn test_withdrawal_uses_both_pools() {
1004 let mut state = create_state();
1005 state.reth_contract_liquidity = U256::from(10e18);
1006 state.deposit_contract_balance = U256::from(50e18);
1007 let res = state
1012 .get_amount_out(
1013 BigUint::from(15_000_000_000_000_000_000u128),
1014 &reth_token(),
1015 ð_token(),
1016 )
1017 .unwrap();
1018
1019 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
1020
1021 let new_state = res
1022 .new_state
1023 .as_any()
1024 .downcast_ref::<RocketpoolState>()
1025 .unwrap();
1026
1027 assert_eq!(new_state.reth_contract_liquidity, U256::ZERO);
1029 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
1031 }
1032
1033 #[test]
1034 fn test_limits_withdrawal_with_queue() {
1035 let mut state = create_state();
1036 state.deposit_contract_balance = U256::from(100e18);
1037 state.queue_variable_end = U256::from(2);
1039
1040 let (max_sell, max_buy) = state
1041 .get_limits(reth_token().address, eth_token().address)
1042 .unwrap();
1043
1044 assert_eq!(max_buy, BigUint::from(38_000_000_000_000_000_000u128));
1046 assert_eq!(max_sell, BigUint::from(19_000_000_000_000_000_000u128));
1048 }
1049
1050 #[test]
1053 fn test_assign_deposits_dequeues_minipools() {
1054 let mut state = create_state();
1055 state.deposit_contract_balance = U256::from(100e18);
1056 state.deposit_assigning_enabled = true;
1057 state.deposit_assign_maximum = U256::from(10u64);
1058 state.deposit_assign_socialised_maximum = U256::from(2u64);
1059 state.queue_variable_end = U256::from(5); let res = state
1069 .get_amount_out(
1070 BigUint::from(62_000_000_000_000_000_000u128),
1071 ð_token(),
1072 &reth_token(),
1073 )
1074 .unwrap();
1075
1076 let new_state = res
1077 .new_state
1078 .as_any()
1079 .downcast_ref::<RocketpoolState>()
1080 .unwrap();
1081
1082 assert_eq!(new_state.deposit_contract_balance, U256::from(38e18));
1083 assert_eq!(new_state.queue_variable_start, U256::from(4u64));
1084 }
1085
1086 #[test]
1087 fn test_assign_deposits_capped_by_queue_length() {
1088 let mut state = create_state();
1089 state.deposit_contract_balance = U256::from(100e18);
1090 state.deposit_assigning_enabled = true;
1091 state.deposit_assign_maximum = U256::from(10u64);
1092 state.deposit_assign_socialised_maximum = U256::from(5u64);
1093 state.queue_variable_end = U256::from(2); let res = state
1100 .get_amount_out(
1101 BigUint::from(62_000_000_000_000_000_000u128),
1102 ð_token(),
1103 &reth_token(),
1104 )
1105 .unwrap();
1106
1107 let new_state = res
1108 .new_state
1109 .as_any()
1110 .downcast_ref::<RocketpoolState>()
1111 .unwrap();
1112
1113 assert_eq!(new_state.deposit_contract_balance, U256::from(100e18));
1114 assert_eq!(new_state.queue_variable_start, U256::from(2u64));
1115 }
1116
1117 #[test]
1118 fn test_assign_deposits_capped_by_max_assignments() {
1119 let mut state = create_state();
1120 state.deposit_contract_balance = U256::from(100e18);
1121 state.deposit_assigning_enabled = true;
1122 state.deposit_assign_maximum = U256::from(1u64); state.deposit_assign_socialised_maximum = U256::from(5u64);
1124 state.queue_variable_end = U256::from(10);
1125
1126 let res = state
1131 .get_amount_out(
1132 BigUint::from(62_000_000_000_000_000_000u128),
1133 ð_token(),
1134 &reth_token(),
1135 )
1136 .unwrap();
1137
1138 let new_state = res
1139 .new_state
1140 .as_any()
1141 .downcast_ref::<RocketpoolState>()
1142 .unwrap();
1143
1144 assert_eq!(new_state.deposit_contract_balance, U256::from(131e18));
1145 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1146 }
1147
1148 #[test]
1149 fn test_assign_deposits_capped_by_total_eth() {
1150 let mut state = create_state();
1151 state.deposit_contract_balance = U256::from(10e18); state.deposit_assigning_enabled = true;
1153 state.deposit_assign_maximum = U256::from(10u64);
1154 state.deposit_assign_socialised_maximum = U256::from(5u64);
1155 state.queue_variable_end = U256::from(10);
1156
1157 let res = state
1163 .get_amount_out(
1164 BigUint::from(31_000_000_000_000_000_000u128),
1165 ð_token(),
1166 &reth_token(),
1167 )
1168 .unwrap();
1169
1170 let new_state = res
1171 .new_state
1172 .as_any()
1173 .downcast_ref::<RocketpoolState>()
1174 .unwrap();
1175
1176 assert_eq!(new_state.deposit_contract_balance, U256::from(10e18));
1177 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1178 }
1179
1180 #[test]
1181 fn test_assign_deposits_empty_queue_no_change() {
1182 let mut state = create_state();
1183 state.deposit_assigning_enabled = true;
1184 state.deposit_assign_maximum = U256::from(10u64);
1185 state.deposit_assign_socialised_maximum = U256::from(2u64);
1186 let res = state
1190 .get_amount_out(
1191 BigUint::from(10_000_000_000_000_000_000u128),
1192 ð_token(),
1193 &reth_token(),
1194 )
1195 .unwrap();
1196
1197 let new_state = res
1198 .new_state
1199 .as_any()
1200 .downcast_ref::<RocketpoolState>()
1201 .unwrap();
1202
1203 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
1205 assert_eq!(new_state.queue_variable_start, U256::ZERO);
1206 }
1207
1208 #[test]
1215 fn test_live_deposit_tx_6213b6c2() {
1216 let state = RocketpoolState::new(
1217 U256::from_str_radix("4ec08ba071647b927594", 16).unwrap(), U256::from_str_radix("5aafbb189fbbc1704662", 16).unwrap(), U256::from_str_radix("17a651238b0dbf892", 16).unwrap(), U256::from(781003199), U256::from_str_radix("1c6bf52634000", 16).unwrap(), true, U256::from_str_radix("2386f26fc10000", 16).unwrap(), U256::from_str_radix("3cfc82e37e9a7400000", 16).unwrap(), true, U256::from_str_radix("5a", 16).unwrap(), U256::from_str_radix("2", 16).unwrap(), U256::from_str_radix("6d43", 16).unwrap(), U256::from_str_radix("6dde", 16).unwrap(), );
1231
1232 let deposit_amount = BigUint::from(4_500_000_000_000_000_000u128);
1234
1235 let res = state
1236 .get_amount_out(deposit_amount, ð_token(), &reth_token())
1237 .unwrap();
1238
1239 println!("calculated rETH out: {}", res.amount);
1240 let expected_reth_out = BigUint::from(3_905_847_020_555_141_679u128);
1241 assert_eq!(res.amount, expected_reth_out);
1242
1243 let new_state = res
1244 .new_state
1245 .as_any()
1246 .downcast_ref::<RocketpoolState>()
1247 .unwrap();
1248
1249 let expected_balance = U256::from_str_radix("0aa2289fdd01f892", 16).unwrap();
1251 assert_eq!(new_state.deposit_contract_balance, expected_balance);
1252
1253 let expected_queue_start = U256::from_str_radix("6d44", 16).unwrap();
1255 assert_eq!(new_state.queue_variable_start, expected_queue_start);
1256
1257 assert_eq!(new_state.total_eth, state.total_eth);
1259 assert_eq!(new_state.reth_supply, state.reth_supply);
1260 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1261 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1262 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1263 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1264 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1265 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1266 assert_eq!(
1267 new_state.deposit_assign_socialised_maximum,
1268 state.deposit_assign_socialised_maximum
1269 );
1270 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1271 }
1272
1273 #[test]
1277 fn test_live_withdraw_tx_block_23736567() {
1278 let state = RocketpoolState::new(
1279 U256::from_str_radix("516052628fbe875ffff0", 16).unwrap(), U256::from_str_radix("5d9143622860d8bdacea", 16).unwrap(), U256::from_str_radix("1686dc9300da8004d", 16).unwrap(), U256::from_str_radix("14d141273efab8a43", 16).unwrap(), U256::from_str_radix("1c6bf52634000", 16).unwrap(), true, U256::from_str_radix("2386f26fc10000", 16).unwrap(), U256::from_str_radix("3cfc82e37e9a7400000", 16).unwrap(), true, U256::from_str_radix("5a", 16).unwrap(), U256::from_str_radix("2", 16).unwrap(), U256::from_str_radix("6d34", 16).unwrap(), U256::from_str_radix("6dd0", 16).unwrap(), );
1293
1294 let burn_amount = BigUint::from(20_873_689_741_238_146_923u128);
1296
1297 let res = state
1298 .get_amount_out(burn_amount, &reth_token(), ð_token())
1299 .unwrap();
1300
1301 let expected_eth_out = BigUint::from(24_000_828_571_949_999_998u128);
1303 assert_eq!(res.amount, expected_eth_out);
1304
1305 let new_state = res
1306 .new_state
1307 .as_any()
1308 .downcast_ref::<RocketpoolState>()
1309 .unwrap();
1310
1311 let expected_liquidity = U256::from_str_radix("74d8b62c5", 16).unwrap();
1313 assert_eq!(new_state.reth_contract_liquidity, expected_liquidity);
1314
1315 assert_eq!(new_state.total_eth, state.total_eth);
1317 assert_eq!(new_state.reth_supply, state.reth_supply);
1318 assert_eq!(new_state.deposit_contract_balance, state.deposit_contract_balance);
1319 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1320 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1321 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1322 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1323 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1324 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1325 assert_eq!(
1326 new_state.deposit_assign_socialised_maximum,
1327 state.deposit_assign_socialised_maximum
1328 );
1329 assert_eq!(new_state.queue_variable_start, state.queue_variable_start);
1330 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1331 }
1332}