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(&base.address);
238 let amount = U256::from(1e18);
240
241 let res = if is_depositing_eth {
242 self.assert_deposits_enabled()?;
243 self.get_reth_value(amount)?
244 } else {
245 self.get_eth_value(amount)?
246 };
247
248 let res = u256_to_f64(res)? / 1e18;
249
250 Ok(res)
251 }
252
253 #[allow(clippy::collapsible_else_if)]
254 fn get_amount_out(
255 &self,
256 amount_in: BigUint,
257 token_in: &Token,
258 _token_out: &Token,
259 ) -> Result<GetAmountOutResult, SimulationError> {
260 let amount_in = biguint_to_u256(&amount_in);
261 let is_depositing_eth = RocketpoolState::is_depositing_eth(&token_in.address);
262
263 let amount_out = if is_depositing_eth {
264 self.assert_deposits_enabled()?;
265
266 if amount_in < self.min_deposit_amount {
267 return Err(SimulationError::InvalidInput(
268 format!(
269 "Deposit amount {} is less than the minimum deposit of {}",
270 amount_in, self.min_deposit_amount
271 ),
272 None,
273 ));
274 }
275
276 let capacity_needed = safe_add_u256(self.deposit_contract_balance, amount_in)?;
277 let max_capacity = self.get_max_deposit_capacity()?;
278 if capacity_needed > max_capacity {
279 return Err(SimulationError::InvalidInput(
280 format!(
281 "Deposit would exceed maximum pool size (capacity needed: {}, max: {})",
282 capacity_needed, max_capacity
283 ),
284 None,
285 ));
286 }
287
288 self.get_reth_value(amount_in)?
289 } else {
290 let eth_out = self.get_eth_value(amount_in)?;
291
292 let total_available = self.get_total_available_for_withdrawal()?;
293 if eth_out > total_available {
294 return Err(SimulationError::RecoverableError(format!(
295 "Withdrawal {} exceeds available liquidity {}",
296 eth_out, total_available
297 )));
298 }
299
300 eth_out
301 };
302
303 let mut new_state = self.clone();
304 if is_depositing_eth {
306 new_state.deposit_contract_balance =
307 safe_add_u256(new_state.deposit_contract_balance, amount_in)?;
308
309 let (assignments, eth_assigned) = new_state.calculate_assign_deposits(amount_in)?;
311 if assignments > U256::ZERO {
312 new_state.deposit_contract_balance =
313 safe_sub_u256(new_state.deposit_contract_balance, eth_assigned)?;
314 new_state.queue_variable_start =
315 safe_add_u256(new_state.queue_variable_start, assignments)?;
316 }
317 } else {
318 if amount_out <= new_state.reth_contract_liquidity {
319 new_state.reth_contract_liquidity =
321 safe_sub_u256(new_state.reth_contract_liquidity, amount_out)?;
322 } else {
323 let needed_from_deposit_pool =
325 safe_sub_u256(amount_out, new_state.reth_contract_liquidity)?;
326 new_state.deposit_contract_balance =
327 safe_sub_u256(new_state.deposit_contract_balance, needed_from_deposit_pool)?;
328 new_state.reth_contract_liquidity = U256::ZERO;
329 }
330 };
331
332 let gas_used = if is_depositing_eth { 209_000u32 } else { 134_000u32 };
336
337 Ok(GetAmountOutResult::new(
338 u256_to_biguint(amount_out),
339 BigUint::from(gas_used),
340 Box::new(new_state),
341 ))
342 }
343
344 fn get_limits(
345 &self,
346 sell_token: Bytes,
347 _buy_token: Bytes,
348 ) -> Result<(BigUint, BigUint), SimulationError> {
349 let is_depositing_eth = Self::is_depositing_eth(&sell_token);
350
351 if is_depositing_eth {
352 let max_capacity = self.get_max_deposit_capacity()?;
355 let max_eth_sell = safe_sub_u256(max_capacity, self.deposit_contract_balance)?;
356 let max_reth_buy = self.get_reth_value(max_eth_sell)?;
357 Ok((u256_to_biguint(max_eth_sell), u256_to_biguint(max_reth_buy)))
358 } else {
359 let max_eth_buy = self.get_total_available_for_withdrawal()?;
361 let max_reth_sell = mul_div(max_eth_buy, self.reth_supply, self.total_eth)?;
362 Ok((u256_to_biguint(max_reth_sell), u256_to_biguint(max_eth_buy)))
363 }
364 }
365
366 fn delta_transition(
367 &mut self,
368 delta: ProtocolStateDelta,
369 _tokens: &HashMap<Bytes, Token>,
370 _balances: &Balances,
371 ) -> Result<(), TransitionError<String>> {
372 self.total_eth = delta
373 .updated_attributes
374 .get("total_eth")
375 .map_or(self.total_eth, U256::from_bytes);
376 self.reth_supply = delta
377 .updated_attributes
378 .get("reth_supply")
379 .map_or(self.reth_supply, U256::from_bytes);
380
381 self.deposit_contract_balance = delta
382 .updated_attributes
383 .get("deposit_contract_balance")
384 .map_or(self.deposit_contract_balance, U256::from_bytes);
385 self.reth_contract_liquidity = delta
386 .updated_attributes
387 .get("reth_contract_liquidity")
388 .map_or(self.reth_contract_liquidity, U256::from_bytes);
389
390 self.deposits_enabled = delta
391 .updated_attributes
392 .get("deposits_enabled")
393 .map_or(self.deposits_enabled, |val| !U256::from_bytes(val).is_zero());
394 self.deposit_assigning_enabled = delta
395 .updated_attributes
396 .get("deposit_assigning_enabled")
397 .map_or(self.deposit_assigning_enabled, |val| !U256::from_bytes(val).is_zero());
398 self.deposit_fee = delta
399 .updated_attributes
400 .get("deposit_fee")
401 .map_or(self.deposit_fee, U256::from_bytes);
402 self.min_deposit_amount = delta
403 .updated_attributes
404 .get("min_deposit_amount")
405 .map_or(self.min_deposit_amount, U256::from_bytes);
406 self.max_deposit_pool_size = delta
407 .updated_attributes
408 .get("max_deposit_pool_size")
409 .map_or(self.max_deposit_pool_size, U256::from_bytes);
410 self.deposit_assign_maximum = delta
411 .updated_attributes
412 .get("deposit_assign_maximum")
413 .map_or(self.deposit_assign_maximum, U256::from_bytes);
414 self.deposit_assign_socialised_maximum = delta
415 .updated_attributes
416 .get("deposit_assign_socialised_maximum")
417 .map_or(self.deposit_assign_socialised_maximum, U256::from_bytes);
418
419 self.queue_variable_start = delta
420 .updated_attributes
421 .get("queue_variable_start")
422 .map_or(self.queue_variable_start, U256::from_bytes);
423 self.queue_variable_end = delta
424 .updated_attributes
425 .get("queue_variable_end")
426 .map_or(self.queue_variable_end, U256::from_bytes);
427
428 Ok(())
429 }
430
431 fn clone_box(&self) -> Box<dyn ProtocolSim> {
433 Box::new(self.clone())
434 }
435
436 fn as_any(&self) -> &dyn Any {
437 self
438 }
439
440 fn as_any_mut(&mut self) -> &mut dyn Any {
441 self
442 }
443
444 fn eq(&self, other: &dyn ProtocolSim) -> bool {
445 if let Some(other_state) = other.as_any().downcast_ref::<Self>() {
446 self == other_state
447 } else {
448 false
449 }
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use std::{
456 collections::{HashMap, HashSet},
457 str::FromStr,
458 };
459
460 use approx::assert_ulps_eq;
461 use num_bigint::BigUint;
462 use num_traits::ToPrimitive;
463 use tycho_common::{
464 dto::ProtocolStateDelta,
465 hex_bytes::Bytes,
466 models::{token::Token, Chain},
467 simulation::{
468 errors::SimulationError,
469 protocol_sim::{Balances, ProtocolSim},
470 },
471 };
472
473 use super::*;
474
475 fn create_state() -> RocketpoolState {
484 RocketpoolState::new(
485 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, )
499 }
500
501 fn eth_token() -> Token {
502 Token::new(&Bytes::from(ETH_ADDRESS), "ETH", 18, 0, &[Some(100_000)], Chain::Ethereum, 100)
503 }
504
505 fn reth_token() -> Token {
506 Token::new(
507 &Bytes::from_str("0xae78736Cd615f374D3085123A210448E74Fc6393").unwrap(),
508 "rETH",
509 18,
510 0,
511 &[Some(100_000)],
512 Chain::Ethereum,
513 100,
514 )
515 }
516
517 #[test]
520 fn test_queue_length_normal() {
521 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(15));
522 assert_eq!(length, U256::from(5));
523 }
524
525 #[test]
526 fn test_queue_length_empty() {
527 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(10));
528 assert_eq!(length, U256::ZERO);
529 }
530
531 #[test]
532 fn test_queue_length_wrap_around() {
533 let capacity = queue_capacity();
534 let start = capacity - U256::from(5);
535 let end = U256::from(3);
536 let length = RocketpoolState::get_queue_length(start, end);
537 assert_eq!(length, U256::from(8));
539 }
540
541 #[test]
544 fn test_effective_capacity_empty() {
545 let state = create_state();
546 assert_eq!(state.get_effective_capacity().unwrap(), U256::ZERO);
547 }
548
549 #[test]
550 fn test_effective_capacity_variable_queue() {
551 let mut state = create_state();
552 state.queue_variable_end = U256::from(2); assert_eq!(state.get_effective_capacity().unwrap(), U256::from(62e18));
554 }
555
556 #[test]
559 fn test_max_capacity_assign_disabled() {
560 let state = create_state();
561 let max = state
562 .get_max_deposit_capacity()
563 .unwrap();
564 assert_eq!(max, U256::from(1000e18));
565 }
566
567 #[test]
568 fn test_max_capacity_assign_enabled_empty_queue() {
569 let mut state = create_state();
570 state.deposit_assigning_enabled = true;
571 let max = state
572 .get_max_deposit_capacity()
573 .unwrap();
574 assert_eq!(max, U256::from(1000e18));
575 }
576
577 #[test]
578 fn test_max_capacity_assign_enabled_with_queue() {
579 let mut state = create_state();
580 state.deposit_assigning_enabled = true;
581 state.queue_variable_end = U256::from(10); let max = state
583 .get_max_deposit_capacity()
584 .unwrap();
585 assert_eq!(max, U256::from(1310e18));
587 }
588
589 #[test]
592 fn test_delta_transition_basic() {
593 let mut state = create_state();
594
595 let attributes: HashMap<String, Bytes> = [
596 ("total_eth", U256::from(300u64)),
597 ("reth_supply", U256::from(150u64)),
598 ("deposit_contract_balance", U256::from(100u64)),
599 ("reth_contract_liquidity", U256::from(20u64)),
600 ]
601 .into_iter()
602 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
603 .collect();
604
605 let delta = ProtocolStateDelta {
606 component_id: "Rocketpool".to_owned(),
607 updated_attributes: attributes,
608 deleted_attributes: HashSet::new(),
609 };
610
611 state
612 .delta_transition(delta, &HashMap::new(), &Balances::default())
613 .unwrap();
614
615 assert_eq!(state.total_eth, U256::from(300u64));
616 assert_eq!(state.reth_supply, U256::from(150u64));
617 assert_eq!(state.deposit_contract_balance, U256::from(100u64));
618 assert_eq!(state.reth_contract_liquidity, U256::from(20u64));
619 }
620
621 #[test]
622 fn test_delta_transition_queue_fields() {
623 let mut state = create_state();
624
625 let attributes: HashMap<String, Bytes> = [
626 ("deposit_assigning_enabled", U256::from(1u64)),
627 ("queue_variable_end", U256::from(5u64)),
628 ]
629 .into_iter()
630 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
631 .collect();
632
633 let delta = ProtocolStateDelta {
634 component_id: "Rocketpool".to_owned(),
635 updated_attributes: attributes,
636 deleted_attributes: HashSet::new(),
637 };
638
639 state
640 .delta_transition(delta, &HashMap::new(), &Balances::default())
641 .unwrap();
642
643 assert!(state.deposit_assigning_enabled);
644 assert_eq!(state.queue_variable_end, U256::from(5u64));
645 }
646
647 #[test]
650 fn test_spot_price_deposit() {
651 let state = create_state();
652 let price = state
654 .spot_price(ð_token(), &reth_token())
655 .unwrap();
656 assert_ulps_eq!(price, 0.3);
657 }
658
659 #[test]
660 fn test_spot_price_withdraw() {
661 let state = create_state();
662 let price = state
664 .spot_price(&reth_token(), ð_token())
665 .unwrap();
666 assert_ulps_eq!(price, 2.0);
667 }
668
669 fn create_state_at_block_23929406() -> RocketpoolState {
671 RocketpoolState::new(
672 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(), )
686 }
687
688 #[test]
691 fn test_live_spot_price_reth_to_eth_23929406() {
692 let state = create_state_at_block_23929406();
693
694 let price = state
695 .spot_price(&reth_token(), ð_token())
696 .unwrap();
697
698 let expected = 1.151835724202335;
700 assert_ulps_eq!(price, expected, max_ulps = 10);
701 }
702
703 #[test]
706 fn test_live_spot_price_eth_to_reth_23929406() {
707 let state = create_state_at_block_23929406();
708
709 let price = state
711 .spot_price(ð_token(), &reth_token())
712 .unwrap();
713
714 let expected_without_fee = 0.868179358382478;
716 let fee = state.deposit_fee.to_f64().unwrap() / DEPOSIT_FEE_BASE as f64;
717 let expected = expected_without_fee * (1.0 - fee);
718
719 assert_ulps_eq!(price, expected, max_ulps = 10);
720 }
721
722 #[test]
723 fn test_fee_panics() {
724 let state = create_state();
725 let result = std::panic::catch_unwind(|| state.fee());
726 assert!(result.is_err());
727 }
728
729 #[test]
732 fn test_limits_deposit() {
733 let state = create_state();
734
735 let (max_sell, max_buy) = state
736 .get_limits(eth_token().address, reth_token().address)
737 .unwrap();
738
739 assert_eq!(max_sell, BigUint::from(950_000_000_000_000_000_000u128));
741 assert_eq!(max_buy, BigUint::from(285_000_000_000_000_000_000u128));
743 }
744
745 #[test]
746 fn test_limits_withdrawal() {
747 let state = create_state();
748
749 let (max_sell, max_buy) = state
750 .get_limits(reth_token().address, eth_token().address)
751 .unwrap();
752
753 assert_eq!(max_buy, BigUint::from(50_000_000_000_000_000_000u128));
755 assert_eq!(max_sell, BigUint::from(25_000_000_000_000_000_000u128));
757 }
758
759 #[test]
760 fn test_limits_with_extended_capacity() {
761 let mut state = create_state();
762 state.max_deposit_pool_size = U256::from(100e18);
763 state.deposit_assigning_enabled = true;
764 state.queue_variable_end = U256::from(2); let (max_sell, _) = state
767 .get_limits(eth_token().address, reth_token().address)
768 .unwrap();
769
770 assert_eq!(max_sell, BigUint::from(112_000_000_000_000_000_000u128));
773 }
774
775 #[test]
778 fn test_deposit_eth() {
779 let state = create_state();
780
781 let res = state
783 .get_amount_out(
784 BigUint::from(10_000_000_000_000_000_000u128),
785 ð_token(),
786 &reth_token(),
787 )
788 .unwrap();
789
790 assert_eq!(res.amount, BigUint::from(3_000_000_000_000_000_000u128));
791
792 let new_state = res
793 .new_state
794 .as_any()
795 .downcast_ref::<RocketpoolState>()
796 .unwrap();
797 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
799 assert_eq!(new_state.total_eth, U256::from(200e18));
801 assert_eq!(new_state.reth_supply, U256::from(100e18));
802 }
803
804 #[test]
805 fn test_deposit_within_extended_capacity() {
806 let mut state = create_state();
807 state.deposit_contract_balance = U256::from(990e18);
808 state.max_deposit_pool_size = U256::from(1000e18);
809 state.deposit_assigning_enabled = true;
810 state.queue_variable_end = U256::from(1); let res = state
815 .get_amount_out(
816 BigUint::from(20_000_000_000_000_000_000u128),
817 ð_token(),
818 &reth_token(),
819 )
820 .unwrap();
821
822 assert_eq!(res.amount, BigUint::from(6_000_000_000_000_000_000u128));
823
824 let new_state = res
825 .new_state
826 .as_any()
827 .downcast_ref::<RocketpoolState>()
828 .unwrap();
829 assert_eq!(new_state.deposit_contract_balance, U256::from(1010e18));
831 assert_eq!(new_state.total_eth, U256::from(200e18));
833 assert_eq!(new_state.reth_supply, U256::from(100e18));
834 }
835
836 #[test]
837 fn test_withdraw_reth() {
838 let state = create_state();
839
840 let res = state
842 .get_amount_out(
843 BigUint::from(10_000_000_000_000_000_000u128),
844 &reth_token(),
845 ð_token(),
846 )
847 .unwrap();
848
849 assert_eq!(res.amount, BigUint::from(20_000_000_000_000_000_000u128));
850
851 let new_state = res
852 .new_state
853 .as_any()
854 .downcast_ref::<RocketpoolState>()
855 .unwrap();
856 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
858 assert_eq!(new_state.total_eth, U256::from(200e18));
860 assert_eq!(new_state.reth_supply, U256::from(100e18));
861 }
862
863 #[test]
866 fn test_deposit_disabled() {
867 let mut state = create_state();
868 state.deposits_enabled = false;
869
870 let res = state.get_amount_out(BigUint::from(10u64), ð_token(), &reth_token());
871 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
872 }
873
874 #[test]
875 fn test_deposit_below_minimum() {
876 let mut state = create_state();
877 state.min_deposit_amount = U256::from(100u64);
878
879 let res = state.get_amount_out(BigUint::from(50u64), ð_token(), &reth_token());
880 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
881 }
882
883 #[test]
884 fn test_deposit_exceeds_max_pool() {
885 let mut state = create_state();
886 state.max_deposit_pool_size = U256::from(60e18); let res = state.get_amount_out(
889 BigUint::from(20_000_000_000_000_000_000u128),
890 ð_token(),
891 &reth_token(),
892 );
893 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
894 }
895
896 #[test]
897 fn test_deposit_queue_ignored_when_disabled() {
898 let mut state = create_state();
899 state.deposit_contract_balance = U256::from(990e18);
900 state.max_deposit_pool_size = U256::from(1000e18);
901 state.deposit_assigning_enabled = false;
902 state.queue_variable_end = U256::from(10); let res = state.get_amount_out(
906 BigUint::from(20_000_000_000_000_000_000u128),
907 ð_token(),
908 &reth_token(),
909 );
910 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
911 }
912
913 #[test]
914 fn test_deposit_exceeds_extended_capacity() {
915 let mut state = create_state();
916 state.deposit_contract_balance = U256::from(990e18);
917 state.max_deposit_pool_size = U256::from(1000e18);
918 state.deposit_assigning_enabled = true;
919 state.queue_variable_end = U256::from(1); let res = state.get_amount_out(
923 BigUint::from(50_000_000_000_000_000_000u128),
924 ð_token(),
925 &reth_token(),
926 );
927 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
928 }
929
930 #[test]
931 fn test_withdrawal_insufficient_liquidity() {
932 let state = create_state(); let res = state.get_amount_out(
936 BigUint::from(30_000_000_000_000_000_000u128),
937 &reth_token(),
938 ð_token(),
939 );
940 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
941 }
942
943 #[test]
944 fn test_withdrawal_limited_by_minipool_queue() {
945 let mut state = create_state();
946 state.deposit_contract_balance = U256::from(100e18);
947 state.queue_variable_end = U256::from(2);
949 let res = state.get_amount_out(
953 BigUint::from(20_000_000_000_000_000_000u128),
954 &reth_token(),
955 ð_token(),
956 );
957 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
958
959 let res = state
961 .get_amount_out(
962 BigUint::from(15_000_000_000_000_000_000u128),
963 &reth_token(),
964 ð_token(),
965 )
966 .unwrap();
967 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
968 }
969
970 #[test]
971 fn test_withdrawal_zero_excess_balance() {
972 let mut state = create_state();
973 state.deposit_contract_balance = U256::from(62e18);
974 state.queue_variable_end = U256::from(2);
976 let res = state.get_amount_out(
980 BigUint::from(1_000_000_000_000_000_000u128),
981 &reth_token(),
982 ð_token(),
983 );
984 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
985 }
986
987 #[test]
988 fn test_withdrawal_uses_both_pools() {
989 let mut state = create_state();
990 state.reth_contract_liquidity = U256::from(10e18);
991 state.deposit_contract_balance = U256::from(50e18);
992 let res = state
997 .get_amount_out(
998 BigUint::from(15_000_000_000_000_000_000u128),
999 &reth_token(),
1000 ð_token(),
1001 )
1002 .unwrap();
1003
1004 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
1005
1006 let new_state = res
1007 .new_state
1008 .as_any()
1009 .downcast_ref::<RocketpoolState>()
1010 .unwrap();
1011
1012 assert_eq!(new_state.reth_contract_liquidity, U256::ZERO);
1014 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
1016 }
1017
1018 #[test]
1019 fn test_limits_withdrawal_with_queue() {
1020 let mut state = create_state();
1021 state.deposit_contract_balance = U256::from(100e18);
1022 state.queue_variable_end = U256::from(2);
1024
1025 let (max_sell, max_buy) = state
1026 .get_limits(reth_token().address, eth_token().address)
1027 .unwrap();
1028
1029 assert_eq!(max_buy, BigUint::from(38_000_000_000_000_000_000u128));
1031 assert_eq!(max_sell, BigUint::from(19_000_000_000_000_000_000u128));
1033 }
1034
1035 #[test]
1038 fn test_assign_deposits_dequeues_minipools() {
1039 let mut state = create_state();
1040 state.deposit_contract_balance = U256::from(100e18);
1041 state.deposit_assigning_enabled = true;
1042 state.deposit_assign_maximum = U256::from(10u64);
1043 state.deposit_assign_socialised_maximum = U256::from(2u64);
1044 state.queue_variable_end = U256::from(5); let res = state
1054 .get_amount_out(
1055 BigUint::from(62_000_000_000_000_000_000u128),
1056 ð_token(),
1057 &reth_token(),
1058 )
1059 .unwrap();
1060
1061 let new_state = res
1062 .new_state
1063 .as_any()
1064 .downcast_ref::<RocketpoolState>()
1065 .unwrap();
1066
1067 assert_eq!(new_state.deposit_contract_balance, U256::from(38e18));
1068 assert_eq!(new_state.queue_variable_start, U256::from(4u64));
1069 }
1070
1071 #[test]
1072 fn test_assign_deposits_capped_by_queue_length() {
1073 let mut state = create_state();
1074 state.deposit_contract_balance = U256::from(100e18);
1075 state.deposit_assigning_enabled = true;
1076 state.deposit_assign_maximum = U256::from(10u64);
1077 state.deposit_assign_socialised_maximum = U256::from(5u64);
1078 state.queue_variable_end = U256::from(2); let res = state
1085 .get_amount_out(
1086 BigUint::from(62_000_000_000_000_000_000u128),
1087 ð_token(),
1088 &reth_token(),
1089 )
1090 .unwrap();
1091
1092 let new_state = res
1093 .new_state
1094 .as_any()
1095 .downcast_ref::<RocketpoolState>()
1096 .unwrap();
1097
1098 assert_eq!(new_state.deposit_contract_balance, U256::from(100e18));
1099 assert_eq!(new_state.queue_variable_start, U256::from(2u64));
1100 }
1101
1102 #[test]
1103 fn test_assign_deposits_capped_by_max_assignments() {
1104 let mut state = create_state();
1105 state.deposit_contract_balance = U256::from(100e18);
1106 state.deposit_assigning_enabled = true;
1107 state.deposit_assign_maximum = U256::from(1u64); state.deposit_assign_socialised_maximum = U256::from(5u64);
1109 state.queue_variable_end = U256::from(10);
1110
1111 let res = state
1116 .get_amount_out(
1117 BigUint::from(62_000_000_000_000_000_000u128),
1118 ð_token(),
1119 &reth_token(),
1120 )
1121 .unwrap();
1122
1123 let new_state = res
1124 .new_state
1125 .as_any()
1126 .downcast_ref::<RocketpoolState>()
1127 .unwrap();
1128
1129 assert_eq!(new_state.deposit_contract_balance, U256::from(131e18));
1130 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1131 }
1132
1133 #[test]
1134 fn test_assign_deposits_capped_by_total_eth() {
1135 let mut state = create_state();
1136 state.deposit_contract_balance = U256::from(10e18); state.deposit_assigning_enabled = true;
1138 state.deposit_assign_maximum = U256::from(10u64);
1139 state.deposit_assign_socialised_maximum = U256::from(5u64);
1140 state.queue_variable_end = U256::from(10);
1141
1142 let res = state
1148 .get_amount_out(
1149 BigUint::from(31_000_000_000_000_000_000u128),
1150 ð_token(),
1151 &reth_token(),
1152 )
1153 .unwrap();
1154
1155 let new_state = res
1156 .new_state
1157 .as_any()
1158 .downcast_ref::<RocketpoolState>()
1159 .unwrap();
1160
1161 assert_eq!(new_state.deposit_contract_balance, U256::from(10e18));
1162 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1163 }
1164
1165 #[test]
1166 fn test_assign_deposits_empty_queue_no_change() {
1167 let mut state = create_state();
1168 state.deposit_assigning_enabled = true;
1169 state.deposit_assign_maximum = U256::from(10u64);
1170 state.deposit_assign_socialised_maximum = U256::from(2u64);
1171 let res = state
1175 .get_amount_out(
1176 BigUint::from(10_000_000_000_000_000_000u128),
1177 ð_token(),
1178 &reth_token(),
1179 )
1180 .unwrap();
1181
1182 let new_state = res
1183 .new_state
1184 .as_any()
1185 .downcast_ref::<RocketpoolState>()
1186 .unwrap();
1187
1188 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
1190 assert_eq!(new_state.queue_variable_start, U256::ZERO);
1191 }
1192
1193 #[test]
1200 fn test_live_deposit_tx_6213b6c2() {
1201 let state = RocketpoolState::new(
1202 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(), );
1216
1217 let deposit_amount = BigUint::from(4_500_000_000_000_000_000u128);
1219
1220 let res = state
1221 .get_amount_out(deposit_amount, ð_token(), &reth_token())
1222 .unwrap();
1223
1224 println!("calculated rETH out: {}", res.amount);
1225 let expected_reth_out = BigUint::from(3_905_847_020_555_141_679u128);
1226 assert_eq!(res.amount, expected_reth_out);
1227
1228 let new_state = res
1229 .new_state
1230 .as_any()
1231 .downcast_ref::<RocketpoolState>()
1232 .unwrap();
1233
1234 let expected_balance = U256::from_str_radix("0aa2289fdd01f892", 16).unwrap();
1236 assert_eq!(new_state.deposit_contract_balance, expected_balance);
1237
1238 let expected_queue_start = U256::from_str_radix("6d44", 16).unwrap();
1240 assert_eq!(new_state.queue_variable_start, expected_queue_start);
1241
1242 assert_eq!(new_state.total_eth, state.total_eth);
1244 assert_eq!(new_state.reth_supply, state.reth_supply);
1245 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1246 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1247 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1248 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1249 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1250 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1251 assert_eq!(
1252 new_state.deposit_assign_socialised_maximum,
1253 state.deposit_assign_socialised_maximum
1254 );
1255 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1256 }
1257
1258 #[test]
1262 fn test_live_withdraw_tx_block_23736567() {
1263 let state = RocketpoolState::new(
1264 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(), );
1278
1279 let burn_amount = BigUint::from(20_873_689_741_238_146_923u128);
1281
1282 let res = state
1283 .get_amount_out(burn_amount, &reth_token(), ð_token())
1284 .unwrap();
1285
1286 let expected_eth_out = BigUint::from(24_000_828_571_949_999_998u128);
1288 assert_eq!(res.amount, expected_eth_out);
1289
1290 let new_state = res
1291 .new_state
1292 .as_any()
1293 .downcast_ref::<RocketpoolState>()
1294 .unwrap();
1295
1296 let expected_liquidity = U256::from_str_radix("74d8b62c5", 16).unwrap();
1298 assert_eq!(new_state.reth_contract_liquidity, expected_liquidity);
1299
1300 assert_eq!(new_state.total_eth, state.total_eth);
1302 assert_eq!(new_state.reth_supply, state.reth_supply);
1303 assert_eq!(new_state.deposit_contract_balance, state.deposit_contract_balance);
1304 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1305 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1306 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1307 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1308 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1309 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1310 assert_eq!(
1311 new_state.deposit_assign_socialised_maximum,
1312 state.deposit_assign_socialised_maximum
1313 );
1314 assert_eq!(new_state.queue_variable_start, state.queue_variable_start);
1315 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1316 }
1317}