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
459#[cfg(test)]
460mod tests {
461 use std::{
462 collections::{HashMap, HashSet},
463 str::FromStr,
464 };
465
466 use approx::assert_ulps_eq;
467 use num_bigint::BigUint;
468 use num_traits::ToPrimitive;
469 use tycho_common::{
470 dto::ProtocolStateDelta,
471 hex_bytes::Bytes,
472 models::{token::Token, Chain},
473 simulation::{
474 errors::SimulationError,
475 protocol_sim::{Balances, ProtocolSim},
476 },
477 };
478
479 use super::*;
480 use crate::evm::protocol::utils::add_fee_markup;
481
482 fn create_state() -> RocketpoolState {
491 RocketpoolState::new(
492 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, )
506 }
507
508 fn eth_token() -> Token {
509 Token::new(&Bytes::from(ETH_ADDRESS), "ETH", 18, 0, &[Some(100_000)], Chain::Ethereum, 100)
510 }
511
512 fn reth_token() -> Token {
513 Token::new(
514 &Bytes::from_str("0xae78736Cd615f374D3085123A210448E74Fc6393").unwrap(),
515 "rETH",
516 18,
517 0,
518 &[Some(100_000)],
519 Chain::Ethereum,
520 100,
521 )
522 }
523
524 #[test]
527 fn test_queue_length_normal() {
528 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(15));
529 assert_eq!(length, U256::from(5));
530 }
531
532 #[test]
533 fn test_queue_length_empty() {
534 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(10));
535 assert_eq!(length, U256::ZERO);
536 }
537
538 #[test]
539 fn test_queue_length_wrap_around() {
540 let capacity = queue_capacity();
541 let start = capacity - U256::from(5);
542 let end = U256::from(3);
543 let length = RocketpoolState::get_queue_length(start, end);
544 assert_eq!(length, U256::from(8));
546 }
547
548 #[test]
551 fn test_effective_capacity_empty() {
552 let state = create_state();
553 assert_eq!(state.get_effective_capacity().unwrap(), U256::ZERO);
554 }
555
556 #[test]
557 fn test_effective_capacity_variable_queue() {
558 let mut state = create_state();
559 state.queue_variable_end = U256::from(2); assert_eq!(state.get_effective_capacity().unwrap(), U256::from(62e18));
561 }
562
563 #[test]
566 fn test_max_capacity_assign_disabled() {
567 let state = create_state();
568 let max = state
569 .get_max_deposit_capacity()
570 .unwrap();
571 assert_eq!(max, U256::from(1000e18));
572 }
573
574 #[test]
575 fn test_max_capacity_assign_enabled_empty_queue() {
576 let mut state = create_state();
577 state.deposit_assigning_enabled = true;
578 let max = state
579 .get_max_deposit_capacity()
580 .unwrap();
581 assert_eq!(max, U256::from(1000e18));
582 }
583
584 #[test]
585 fn test_max_capacity_assign_enabled_with_queue() {
586 let mut state = create_state();
587 state.deposit_assigning_enabled = true;
588 state.queue_variable_end = U256::from(10); let max = state
590 .get_max_deposit_capacity()
591 .unwrap();
592 assert_eq!(max, U256::from(1310e18));
594 }
595
596 #[test]
599 fn test_delta_transition_basic() {
600 let mut state = create_state();
601
602 let attributes: HashMap<String, Bytes> = [
603 ("total_eth", U256::from(300u64)),
604 ("reth_supply", U256::from(150u64)),
605 ("deposit_contract_balance", U256::from(100u64)),
606 ("reth_contract_liquidity", U256::from(20u64)),
607 ]
608 .into_iter()
609 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
610 .collect();
611
612 let delta = ProtocolStateDelta {
613 component_id: "Rocketpool".to_owned(),
614 updated_attributes: attributes,
615 deleted_attributes: HashSet::new(),
616 };
617
618 state
619 .delta_transition(delta, &HashMap::new(), &Balances::default())
620 .unwrap();
621
622 assert_eq!(state.total_eth, U256::from(300u64));
623 assert_eq!(state.reth_supply, U256::from(150u64));
624 assert_eq!(state.deposit_contract_balance, U256::from(100u64));
625 assert_eq!(state.reth_contract_liquidity, U256::from(20u64));
626 }
627
628 #[test]
629 fn test_delta_transition_queue_fields() {
630 let mut state = create_state();
631
632 let attributes: HashMap<String, Bytes> = [
633 ("deposit_assigning_enabled", U256::from(1u64)),
634 ("queue_variable_end", U256::from(5u64)),
635 ]
636 .into_iter()
637 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
638 .collect();
639
640 let delta = ProtocolStateDelta {
641 component_id: "Rocketpool".to_owned(),
642 updated_attributes: attributes,
643 deleted_attributes: HashSet::new(),
644 };
645
646 state
647 .delta_transition(delta, &HashMap::new(), &Balances::default())
648 .unwrap();
649
650 assert!(state.deposit_assigning_enabled);
651 assert_eq!(state.queue_variable_end, U256::from(5u64));
652 }
653
654 #[test]
657 fn test_spot_price_deposit() {
658 let state = create_state();
659 let price = state
663 .spot_price(ð_token(), &reth_token())
664 .unwrap();
665 assert_ulps_eq!(price, 0.5);
666 }
667
668 #[test]
669 fn test_spot_price_withdraw() {
670 let state = create_state();
671 let price = state
675 .spot_price(&reth_token(), ð_token())
676 .unwrap();
677 assert_ulps_eq!(price, 1.0 / 0.3);
678 }
679
680 fn create_state_at_block_23929406() -> RocketpoolState {
682 RocketpoolState::new(
683 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(), )
697 }
698
699 #[test]
702 fn test_live_spot_price_reth_to_buy_eth_23929406() {
703 let state = create_state_at_block_23929406();
704
705 let price = state
706 .spot_price(ð_token(), &reth_token())
707 .unwrap();
708
709 let expected = 0.868179358382478;
710 assert_ulps_eq!(price, expected, max_ulps = 10);
711 }
712
713 #[test]
716 fn test_live_spot_price_eth_to_buy_reth_23929406() {
717 let state = create_state_at_block_23929406();
718
719 let price = state
720 .spot_price(&reth_token(), ð_token())
721 .unwrap();
722
723 let expected_without_fee = 1.151835724202335;
724 let fee = state.deposit_fee.to_f64().unwrap() / DEPOSIT_FEE_BASE as f64;
725 let expected = add_fee_markup(expected_without_fee, fee);
726
727 assert_ulps_eq!(price, expected, max_ulps = 10);
728 }
729
730 #[test]
731 fn test_fee_panics() {
732 let state = create_state();
733 let result = std::panic::catch_unwind(|| state.fee());
734 assert!(result.is_err());
735 }
736
737 #[test]
740 fn test_limits_deposit() {
741 let state = create_state();
742
743 let (max_sell, max_buy) = state
744 .get_limits(eth_token().address, reth_token().address)
745 .unwrap();
746
747 assert_eq!(max_sell, BigUint::from(950_000_000_000_000_000_000u128));
749 assert_eq!(max_buy, BigUint::from(285_000_000_000_000_000_000u128));
751 }
752
753 #[test]
754 fn test_limits_withdrawal() {
755 let state = create_state();
756
757 let (max_sell, max_buy) = state
758 .get_limits(reth_token().address, eth_token().address)
759 .unwrap();
760
761 assert_eq!(max_buy, BigUint::from(50_000_000_000_000_000_000u128));
763 assert_eq!(max_sell, BigUint::from(25_000_000_000_000_000_000u128));
765 }
766
767 #[test]
768 fn test_limits_with_extended_capacity() {
769 let mut state = create_state();
770 state.max_deposit_pool_size = U256::from(100e18);
771 state.deposit_assigning_enabled = true;
772 state.queue_variable_end = U256::from(2); let (max_sell, _) = state
775 .get_limits(eth_token().address, reth_token().address)
776 .unwrap();
777
778 assert_eq!(max_sell, BigUint::from(112_000_000_000_000_000_000u128));
781 }
782
783 #[test]
786 fn test_deposit_eth() {
787 let state = create_state();
788
789 let res = state
791 .get_amount_out(
792 BigUint::from(10_000_000_000_000_000_000u128),
793 ð_token(),
794 &reth_token(),
795 )
796 .unwrap();
797
798 assert_eq!(res.amount, BigUint::from(3_000_000_000_000_000_000u128));
799
800 let new_state = res
801 .new_state
802 .as_any()
803 .downcast_ref::<RocketpoolState>()
804 .unwrap();
805 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
807 assert_eq!(new_state.total_eth, U256::from(200e18));
809 assert_eq!(new_state.reth_supply, U256::from(100e18));
810 }
811
812 #[test]
813 fn test_deposit_within_extended_capacity() {
814 let mut state = create_state();
815 state.deposit_contract_balance = U256::from(990e18);
816 state.max_deposit_pool_size = U256::from(1000e18);
817 state.deposit_assigning_enabled = true;
818 state.queue_variable_end = U256::from(1); let res = state
823 .get_amount_out(
824 BigUint::from(20_000_000_000_000_000_000u128),
825 ð_token(),
826 &reth_token(),
827 )
828 .unwrap();
829
830 assert_eq!(res.amount, BigUint::from(6_000_000_000_000_000_000u128));
831
832 let new_state = res
833 .new_state
834 .as_any()
835 .downcast_ref::<RocketpoolState>()
836 .unwrap();
837 assert_eq!(new_state.deposit_contract_balance, U256::from(1010e18));
839 assert_eq!(new_state.total_eth, U256::from(200e18));
841 assert_eq!(new_state.reth_supply, U256::from(100e18));
842 }
843
844 #[test]
845 fn test_withdraw_reth() {
846 let state = create_state();
847
848 let res = state
850 .get_amount_out(
851 BigUint::from(10_000_000_000_000_000_000u128),
852 &reth_token(),
853 ð_token(),
854 )
855 .unwrap();
856
857 assert_eq!(res.amount, BigUint::from(20_000_000_000_000_000_000u128));
858
859 let new_state = res
860 .new_state
861 .as_any()
862 .downcast_ref::<RocketpoolState>()
863 .unwrap();
864 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
866 assert_eq!(new_state.total_eth, U256::from(200e18));
868 assert_eq!(new_state.reth_supply, U256::from(100e18));
869 }
870
871 #[test]
874 fn test_deposit_disabled() {
875 let mut state = create_state();
876 state.deposits_enabled = false;
877
878 let res = state.get_amount_out(BigUint::from(10u64), ð_token(), &reth_token());
879 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
880 }
881
882 #[test]
883 fn test_deposit_below_minimum() {
884 let mut state = create_state();
885 state.min_deposit_amount = U256::from(100u64);
886
887 let res = state.get_amount_out(BigUint::from(50u64), ð_token(), &reth_token());
888 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
889 }
890
891 #[test]
892 fn test_deposit_exceeds_max_pool() {
893 let mut state = create_state();
894 state.max_deposit_pool_size = U256::from(60e18); let res = state.get_amount_out(
897 BigUint::from(20_000_000_000_000_000_000u128),
898 ð_token(),
899 &reth_token(),
900 );
901 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
902 }
903
904 #[test]
905 fn test_deposit_queue_ignored_when_disabled() {
906 let mut state = create_state();
907 state.deposit_contract_balance = U256::from(990e18);
908 state.max_deposit_pool_size = U256::from(1000e18);
909 state.deposit_assigning_enabled = false;
910 state.queue_variable_end = U256::from(10); let res = state.get_amount_out(
914 BigUint::from(20_000_000_000_000_000_000u128),
915 ð_token(),
916 &reth_token(),
917 );
918 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
919 }
920
921 #[test]
922 fn test_deposit_exceeds_extended_capacity() {
923 let mut state = create_state();
924 state.deposit_contract_balance = U256::from(990e18);
925 state.max_deposit_pool_size = U256::from(1000e18);
926 state.deposit_assigning_enabled = true;
927 state.queue_variable_end = U256::from(1); let res = state.get_amount_out(
931 BigUint::from(50_000_000_000_000_000_000u128),
932 ð_token(),
933 &reth_token(),
934 );
935 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
936 }
937
938 #[test]
939 fn test_withdrawal_insufficient_liquidity() {
940 let state = create_state(); let res = state.get_amount_out(
944 BigUint::from(30_000_000_000_000_000_000u128),
945 &reth_token(),
946 ð_token(),
947 );
948 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
949 }
950
951 #[test]
952 fn test_withdrawal_limited_by_minipool_queue() {
953 let mut state = create_state();
954 state.deposit_contract_balance = U256::from(100e18);
955 state.queue_variable_end = U256::from(2);
957 let res = state.get_amount_out(
961 BigUint::from(20_000_000_000_000_000_000u128),
962 &reth_token(),
963 ð_token(),
964 );
965 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
966
967 let res = state
969 .get_amount_out(
970 BigUint::from(15_000_000_000_000_000_000u128),
971 &reth_token(),
972 ð_token(),
973 )
974 .unwrap();
975 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
976 }
977
978 #[test]
979 fn test_withdrawal_zero_excess_balance() {
980 let mut state = create_state();
981 state.deposit_contract_balance = U256::from(62e18);
982 state.queue_variable_end = U256::from(2);
984 let res = state.get_amount_out(
988 BigUint::from(1_000_000_000_000_000_000u128),
989 &reth_token(),
990 ð_token(),
991 );
992 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
993 }
994
995 #[test]
996 fn test_withdrawal_uses_both_pools() {
997 let mut state = create_state();
998 state.reth_contract_liquidity = U256::from(10e18);
999 state.deposit_contract_balance = U256::from(50e18);
1000 let res = state
1005 .get_amount_out(
1006 BigUint::from(15_000_000_000_000_000_000u128),
1007 &reth_token(),
1008 ð_token(),
1009 )
1010 .unwrap();
1011
1012 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
1013
1014 let new_state = res
1015 .new_state
1016 .as_any()
1017 .downcast_ref::<RocketpoolState>()
1018 .unwrap();
1019
1020 assert_eq!(new_state.reth_contract_liquidity, U256::ZERO);
1022 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
1024 }
1025
1026 #[test]
1027 fn test_limits_withdrawal_with_queue() {
1028 let mut state = create_state();
1029 state.deposit_contract_balance = U256::from(100e18);
1030 state.queue_variable_end = U256::from(2);
1032
1033 let (max_sell, max_buy) = state
1034 .get_limits(reth_token().address, eth_token().address)
1035 .unwrap();
1036
1037 assert_eq!(max_buy, BigUint::from(38_000_000_000_000_000_000u128));
1039 assert_eq!(max_sell, BigUint::from(19_000_000_000_000_000_000u128));
1041 }
1042
1043 #[test]
1046 fn test_assign_deposits_dequeues_minipools() {
1047 let mut state = create_state();
1048 state.deposit_contract_balance = U256::from(100e18);
1049 state.deposit_assigning_enabled = true;
1050 state.deposit_assign_maximum = U256::from(10u64);
1051 state.deposit_assign_socialised_maximum = U256::from(2u64);
1052 state.queue_variable_end = U256::from(5); let res = state
1062 .get_amount_out(
1063 BigUint::from(62_000_000_000_000_000_000u128),
1064 ð_token(),
1065 &reth_token(),
1066 )
1067 .unwrap();
1068
1069 let new_state = res
1070 .new_state
1071 .as_any()
1072 .downcast_ref::<RocketpoolState>()
1073 .unwrap();
1074
1075 assert_eq!(new_state.deposit_contract_balance, U256::from(38e18));
1076 assert_eq!(new_state.queue_variable_start, U256::from(4u64));
1077 }
1078
1079 #[test]
1080 fn test_assign_deposits_capped_by_queue_length() {
1081 let mut state = create_state();
1082 state.deposit_contract_balance = U256::from(100e18);
1083 state.deposit_assigning_enabled = true;
1084 state.deposit_assign_maximum = U256::from(10u64);
1085 state.deposit_assign_socialised_maximum = U256::from(5u64);
1086 state.queue_variable_end = U256::from(2); let res = state
1093 .get_amount_out(
1094 BigUint::from(62_000_000_000_000_000_000u128),
1095 ð_token(),
1096 &reth_token(),
1097 )
1098 .unwrap();
1099
1100 let new_state = res
1101 .new_state
1102 .as_any()
1103 .downcast_ref::<RocketpoolState>()
1104 .unwrap();
1105
1106 assert_eq!(new_state.deposit_contract_balance, U256::from(100e18));
1107 assert_eq!(new_state.queue_variable_start, U256::from(2u64));
1108 }
1109
1110 #[test]
1111 fn test_assign_deposits_capped_by_max_assignments() {
1112 let mut state = create_state();
1113 state.deposit_contract_balance = U256::from(100e18);
1114 state.deposit_assigning_enabled = true;
1115 state.deposit_assign_maximum = U256::from(1u64); state.deposit_assign_socialised_maximum = U256::from(5u64);
1117 state.queue_variable_end = U256::from(10);
1118
1119 let res = state
1124 .get_amount_out(
1125 BigUint::from(62_000_000_000_000_000_000u128),
1126 ð_token(),
1127 &reth_token(),
1128 )
1129 .unwrap();
1130
1131 let new_state = res
1132 .new_state
1133 .as_any()
1134 .downcast_ref::<RocketpoolState>()
1135 .unwrap();
1136
1137 assert_eq!(new_state.deposit_contract_balance, U256::from(131e18));
1138 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1139 }
1140
1141 #[test]
1142 fn test_assign_deposits_capped_by_total_eth() {
1143 let mut state = create_state();
1144 state.deposit_contract_balance = U256::from(10e18); state.deposit_assigning_enabled = true;
1146 state.deposit_assign_maximum = U256::from(10u64);
1147 state.deposit_assign_socialised_maximum = U256::from(5u64);
1148 state.queue_variable_end = U256::from(10);
1149
1150 let res = state
1156 .get_amount_out(
1157 BigUint::from(31_000_000_000_000_000_000u128),
1158 ð_token(),
1159 &reth_token(),
1160 )
1161 .unwrap();
1162
1163 let new_state = res
1164 .new_state
1165 .as_any()
1166 .downcast_ref::<RocketpoolState>()
1167 .unwrap();
1168
1169 assert_eq!(new_state.deposit_contract_balance, U256::from(10e18));
1170 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1171 }
1172
1173 #[test]
1174 fn test_assign_deposits_empty_queue_no_change() {
1175 let mut state = create_state();
1176 state.deposit_assigning_enabled = true;
1177 state.deposit_assign_maximum = U256::from(10u64);
1178 state.deposit_assign_socialised_maximum = U256::from(2u64);
1179 let res = state
1183 .get_amount_out(
1184 BigUint::from(10_000_000_000_000_000_000u128),
1185 ð_token(),
1186 &reth_token(),
1187 )
1188 .unwrap();
1189
1190 let new_state = res
1191 .new_state
1192 .as_any()
1193 .downcast_ref::<RocketpoolState>()
1194 .unwrap();
1195
1196 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
1198 assert_eq!(new_state.queue_variable_start, U256::ZERO);
1199 }
1200
1201 #[test]
1208 fn test_live_deposit_tx_6213b6c2() {
1209 let state = RocketpoolState::new(
1210 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(), );
1224
1225 let deposit_amount = BigUint::from(4_500_000_000_000_000_000u128);
1227
1228 let res = state
1229 .get_amount_out(deposit_amount, ð_token(), &reth_token())
1230 .unwrap();
1231
1232 println!("calculated rETH out: {}", res.amount);
1233 let expected_reth_out = BigUint::from(3_905_847_020_555_141_679u128);
1234 assert_eq!(res.amount, expected_reth_out);
1235
1236 let new_state = res
1237 .new_state
1238 .as_any()
1239 .downcast_ref::<RocketpoolState>()
1240 .unwrap();
1241
1242 let expected_balance = U256::from_str_radix("0aa2289fdd01f892", 16).unwrap();
1244 assert_eq!(new_state.deposit_contract_balance, expected_balance);
1245
1246 let expected_queue_start = U256::from_str_radix("6d44", 16).unwrap();
1248 assert_eq!(new_state.queue_variable_start, expected_queue_start);
1249
1250 assert_eq!(new_state.total_eth, state.total_eth);
1252 assert_eq!(new_state.reth_supply, state.reth_supply);
1253 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1254 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1255 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1256 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1257 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1258 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1259 assert_eq!(
1260 new_state.deposit_assign_socialised_maximum,
1261 state.deposit_assign_socialised_maximum
1262 );
1263 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1264 }
1265
1266 #[test]
1270 fn test_live_withdraw_tx_block_23736567() {
1271 let state = RocketpoolState::new(
1272 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(), );
1286
1287 let burn_amount = BigUint::from(20_873_689_741_238_146_923u128);
1289
1290 let res = state
1291 .get_amount_out(burn_amount, &reth_token(), ð_token())
1292 .unwrap();
1293
1294 let expected_eth_out = BigUint::from(24_000_828_571_949_999_998u128);
1296 assert_eq!(res.amount, expected_eth_out);
1297
1298 let new_state = res
1299 .new_state
1300 .as_any()
1301 .downcast_ref::<RocketpoolState>()
1302 .unwrap();
1303
1304 let expected_liquidity = U256::from_str_radix("74d8b62c5", 16).unwrap();
1306 assert_eq!(new_state.reth_contract_liquidity, expected_liquidity);
1307
1308 assert_eq!(new_state.total_eth, state.total_eth);
1310 assert_eq!(new_state.reth_supply, state.reth_supply);
1311 assert_eq!(new_state.deposit_contract_balance, state.deposit_contract_balance);
1312 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1313 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1314 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1315 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1316 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1317 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1318 assert_eq!(
1319 new_state.deposit_assign_socialised_maximum,
1320 state.deposit_assign_socialised_maximum
1321 );
1322 assert_eq!(new_state.queue_variable_start, state.queue_variable_start);
1323 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1324 }
1325}