1use std::{any::Any, collections::HashMap};
2
3use alloy::primitives::U256;
4use num_bigint::BigUint;
5use serde::{Deserialize, Serialize};
6use tycho_common::{
7 dto::ProtocolStateDelta,
8 models::token::Token,
9 simulation::{
10 errors::{SimulationError, TransitionError},
11 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
12 },
13 Bytes,
14};
15use tycho_ethereum::BytesCodec;
16
17use crate::evm::protocol::{
18 rocketpool::ETH_ADDRESS,
19 safe_math::{safe_add_u256, safe_mul_u256, safe_sub_u256},
20 u256_num::{biguint_to_u256, u256_to_biguint, u256_to_f64},
21 utils::solidity_math::mul_div,
22};
23
24const 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 {
30 U256::from(1) << 255
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
34pub struct RocketpoolState {
35 pub reth_supply: U256,
36 pub total_eth: U256,
37 pub deposit_contract_balance: U256,
39 pub reth_contract_liquidity: U256,
41 pub deposit_fee: U256,
43 pub deposits_enabled: bool,
44 pub min_deposit_amount: U256,
45 pub max_deposit_pool_size: U256,
46 pub deposit_assigning_enabled: bool,
48 pub deposit_assign_maximum: U256,
50 pub deposit_assign_socialised_maximum: U256,
52 pub queue_variable_start: U256,
54 pub queue_variable_end: U256,
55}
56
57impl RocketpoolState {
58 #[allow(clippy::too_many_arguments)]
59 pub fn new(
60 reth_supply: U256,
61 total_eth: U256,
62 deposit_contract_balance: U256,
63 reth_contract_liquidity: U256,
64 deposit_fee: U256,
65 deposits_enabled: bool,
66 min_deposit_amount: U256,
67 max_deposit_pool_size: U256,
68 deposit_assigning_enabled: bool,
69 deposit_assign_maximum: U256,
70 deposit_assign_socialised_maximum: U256,
71 queue_variable_start: U256,
72 queue_variable_end: U256,
73 ) -> Self {
74 Self {
75 reth_supply,
76 total_eth,
77 deposit_contract_balance,
78 reth_contract_liquidity,
79 deposit_fee,
80 deposits_enabled,
81 min_deposit_amount,
82 max_deposit_pool_size,
83 deposit_assigning_enabled,
84 deposit_assign_maximum,
85 deposit_assign_socialised_maximum,
86 queue_variable_start,
87 queue_variable_end,
88 }
89 }
90
91 fn get_reth_value(&self, eth_amount: U256) -> Result<U256, SimulationError> {
93 let fee = mul_div(eth_amount, self.deposit_fee, U256::from(DEPOSIT_FEE_BASE))?;
95 let net_eth = safe_sub_u256(eth_amount, fee)?;
96
97 mul_div(net_eth, self.reth_supply, self.total_eth)
99 }
100
101 fn get_eth_value(&self, reth_amount: U256) -> Result<U256, SimulationError> {
103 mul_div(reth_amount, self.total_eth, self.reth_supply)
105 }
106
107 fn is_depositing_eth(token_in: &Bytes) -> bool {
108 token_in.as_ref() == ETH_ADDRESS
109 }
110
111 fn assert_deposits_enabled(&self) -> Result<(), SimulationError> {
112 if !self.deposits_enabled {
113 Err(SimulationError::RecoverableError(
114 "Deposits are currently disabled in Rocketpool".to_string(),
115 ))
116 } else {
117 Ok(())
118 }
119 }
120
121 fn get_queue_length(start: U256, end: U256) -> U256 {
124 if end < start {
125 end + queue_capacity() - start
127 } else {
128 end - start
129 }
130 }
131
132 fn get_effective_capacity(&self) -> Result<U256, SimulationError> {
143 let variable_length =
144 Self::get_queue_length(self.queue_variable_start, self.queue_variable_end);
145
146 let variable_capacity =
147 safe_mul_u256(variable_length, U256::from(VARIABLE_DEPOSIT_AMOUNT))?;
148
149 Ok(variable_capacity)
150 }
151
152 fn get_max_deposit_capacity(&self) -> Result<U256, SimulationError> {
155 if self.deposit_assigning_enabled {
156 let effective_capacity = self.get_effective_capacity()?;
157 safe_add_u256(self.max_deposit_pool_size, effective_capacity)
158 } else {
159 Ok(self.max_deposit_pool_size)
160 }
161 }
162
163 fn get_deposit_pool_excess_balance(&self) -> Result<U256, SimulationError> {
169 let minipool_capacity = self.get_effective_capacity()?;
170 if minipool_capacity >= self.deposit_contract_balance {
171 Ok(U256::ZERO)
172 } else {
173 safe_sub_u256(self.deposit_contract_balance, minipool_capacity)
174 }
175 }
176
177 fn get_total_available_for_withdrawal(&self) -> Result<U256, SimulationError> {
180 let deposit_pool_excess = self.get_deposit_pool_excess_balance()?;
181 safe_add_u256(self.reth_contract_liquidity, deposit_pool_excess)
182 }
183
184 fn calculate_assign_deposits(
196 &self,
197 deposit_amount: U256,
198 ) -> Result<(U256, U256), SimulationError> {
199 if !self.deposit_assigning_enabled {
200 return Ok((U256::ZERO, U256::ZERO));
201 }
202
203 let variable_deposit = U256::from(VARIABLE_DEPOSIT_AMOUNT);
210
211 let scaling_count = deposit_amount / variable_deposit;
213 let desired_assignments = self.deposit_assign_socialised_maximum + scaling_count;
214
215 let eth_cap_assignments = self.deposit_contract_balance / variable_deposit;
216 let settings_cap_assignments = self.deposit_assign_maximum;
217 let queue_cap_assignments =
218 Self::get_queue_length(self.queue_variable_start, self.queue_variable_end);
219
220 let assignments = desired_assignments
222 .min(eth_cap_assignments)
223 .min(settings_cap_assignments)
224 .min(queue_cap_assignments);
225
226 let eth_assigned = safe_mul_u256(assignments, variable_deposit)?;
227
228 Ok((assignments, eth_assigned))
229 }
230}
231
232#[typetag::serde]
233impl ProtocolSim for RocketpoolState {
234 fn fee(&self) -> f64 {
235 unimplemented!("Rocketpool has asymmetric fees; use spot_price or get_amount_out instead")
236 }
237
238 fn spot_price(&self, _base: &Token, quote: &Token) -> Result<f64, SimulationError> {
239 let is_depositing_eth = RocketpoolState::is_depositing_eth("e.address);
241
242 let amount = U256::from(1e18);
245
246 let base_per_quote = if is_depositing_eth {
247 self.assert_deposits_enabled()?;
249 self.get_reth_value(amount)?
250 } else {
251 self.get_eth_value(amount)?
253 };
254
255 let base_per_quote = u256_to_f64(base_per_quote)? / 1e18;
256
257 Ok(1.0 / base_per_quote)
259 }
260
261 #[allow(clippy::collapsible_else_if)]
262 fn get_amount_out(
263 &self,
264 amount_in: BigUint,
265 token_in: &Token,
266 _token_out: &Token,
267 ) -> Result<GetAmountOutResult, SimulationError> {
268 let amount_in = biguint_to_u256(&amount_in);
269 let is_depositing_eth = RocketpoolState::is_depositing_eth(&token_in.address);
270
271 let amount_out = if is_depositing_eth {
272 self.assert_deposits_enabled()?;
273
274 if amount_in < self.min_deposit_amount {
275 return Err(SimulationError::InvalidInput(
276 format!(
277 "Deposit amount {} is less than the minimum deposit of {}",
278 amount_in, self.min_deposit_amount
279 ),
280 None,
281 ));
282 }
283
284 let capacity_needed = safe_add_u256(self.deposit_contract_balance, amount_in)?;
285 let max_capacity = self.get_max_deposit_capacity()?;
286 if capacity_needed > max_capacity {
287 return Err(SimulationError::InvalidInput(
288 format!(
289 "Deposit would exceed maximum pool size (capacity needed: {}, max: {})",
290 capacity_needed, max_capacity
291 ),
292 None,
293 ));
294 }
295
296 self.get_reth_value(amount_in)?
297 } else {
298 let eth_out = self.get_eth_value(amount_in)?;
299
300 let total_available = self.get_total_available_for_withdrawal()?;
301 if eth_out > total_available {
302 return Err(SimulationError::RecoverableError(format!(
303 "Withdrawal {} exceeds available liquidity {}",
304 eth_out, total_available
305 )));
306 }
307
308 eth_out
309 };
310
311 let mut new_state = self.clone();
312 if is_depositing_eth {
314 new_state.deposit_contract_balance =
315 safe_add_u256(new_state.deposit_contract_balance, amount_in)?;
316
317 let (assignments, eth_assigned) = new_state.calculate_assign_deposits(amount_in)?;
319 if assignments > U256::ZERO {
320 new_state.deposit_contract_balance =
321 safe_sub_u256(new_state.deposit_contract_balance, eth_assigned)?;
322 new_state.queue_variable_start =
323 safe_add_u256(new_state.queue_variable_start, assignments)?;
324 }
325 } else {
326 if amount_out <= new_state.reth_contract_liquidity {
327 new_state.reth_contract_liquidity =
329 safe_sub_u256(new_state.reth_contract_liquidity, amount_out)?;
330 } else {
331 let needed_from_deposit_pool =
333 safe_sub_u256(amount_out, new_state.reth_contract_liquidity)?;
334 new_state.deposit_contract_balance =
335 safe_sub_u256(new_state.deposit_contract_balance, needed_from_deposit_pool)?;
336 new_state.reth_contract_liquidity = U256::ZERO;
337 }
338 };
339
340 let gas_used = if is_depositing_eth { 209_000u32 } else { 134_000u32 };
344
345 Ok(GetAmountOutResult::new(
346 u256_to_biguint(amount_out),
347 BigUint::from(gas_used),
348 Box::new(new_state),
349 ))
350 }
351
352 fn get_limits(
353 &self,
354 sell_token: Bytes,
355 _buy_token: Bytes,
356 ) -> Result<(BigUint, BigUint), SimulationError> {
357 let is_depositing_eth = Self::is_depositing_eth(&sell_token);
358
359 if is_depositing_eth {
360 let max_capacity = self.get_max_deposit_capacity()?;
363 let max_eth_sell = safe_sub_u256(max_capacity, self.deposit_contract_balance)?;
364 let max_reth_buy = self.get_reth_value(max_eth_sell)?;
365 Ok((u256_to_biguint(max_eth_sell), u256_to_biguint(max_reth_buy)))
366 } else {
367 let max_eth_buy = self.get_total_available_for_withdrawal()?;
369 let max_reth_sell = mul_div(max_eth_buy, self.reth_supply, self.total_eth)?;
370 Ok((u256_to_biguint(max_reth_sell), u256_to_biguint(max_eth_buy)))
371 }
372 }
373
374 fn delta_transition(
375 &mut self,
376 delta: ProtocolStateDelta,
377 _tokens: &HashMap<Bytes, Token>,
378 _balances: &Balances,
379 ) -> Result<(), TransitionError<String>> {
380 self.total_eth = delta
381 .updated_attributes
382 .get("total_eth")
383 .map_or(self.total_eth, U256::from_bytes);
384 self.reth_supply = delta
385 .updated_attributes
386 .get("reth_supply")
387 .map_or(self.reth_supply, U256::from_bytes);
388
389 self.deposit_contract_balance = delta
390 .updated_attributes
391 .get("deposit_contract_balance")
392 .map_or(self.deposit_contract_balance, U256::from_bytes);
393 self.reth_contract_liquidity = delta
394 .updated_attributes
395 .get("reth_contract_liquidity")
396 .map_or(self.reth_contract_liquidity, U256::from_bytes);
397
398 self.deposits_enabled = delta
399 .updated_attributes
400 .get("deposits_enabled")
401 .map_or(self.deposits_enabled, |val| !U256::from_bytes(val).is_zero());
402 self.deposit_assigning_enabled = delta
403 .updated_attributes
404 .get("deposit_assigning_enabled")
405 .map_or(self.deposit_assigning_enabled, |val| !U256::from_bytes(val).is_zero());
406 self.deposit_fee = delta
407 .updated_attributes
408 .get("deposit_fee")
409 .map_or(self.deposit_fee, U256::from_bytes);
410 self.min_deposit_amount = delta
411 .updated_attributes
412 .get("min_deposit_amount")
413 .map_or(self.min_deposit_amount, U256::from_bytes);
414 self.max_deposit_pool_size = delta
415 .updated_attributes
416 .get("max_deposit_pool_size")
417 .map_or(self.max_deposit_pool_size, U256::from_bytes);
418 self.deposit_assign_maximum = delta
419 .updated_attributes
420 .get("deposit_assign_maximum")
421 .map_or(self.deposit_assign_maximum, U256::from_bytes);
422 self.deposit_assign_socialised_maximum = delta
423 .updated_attributes
424 .get("deposit_assign_socialised_maximum")
425 .map_or(self.deposit_assign_socialised_maximum, U256::from_bytes);
426
427 self.queue_variable_start = delta
428 .updated_attributes
429 .get("queue_variable_start")
430 .map_or(self.queue_variable_start, U256::from_bytes);
431 self.queue_variable_end = delta
432 .updated_attributes
433 .get("queue_variable_end")
434 .map_or(self.queue_variable_end, U256::from_bytes);
435
436 Ok(())
437 }
438
439 fn clone_box(&self) -> Box<dyn ProtocolSim> {
441 Box::new(self.clone())
442 }
443
444 fn as_any(&self) -> &dyn Any {
445 self
446 }
447
448 fn as_any_mut(&mut self) -> &mut dyn Any {
449 self
450 }
451
452 fn eq(&self, other: &dyn ProtocolSim) -> bool {
453 if let Some(other_state) = other.as_any().downcast_ref::<Self>() {
454 self == other_state
455 } else {
456 false
457 }
458 }
459
460 fn query_pool_swap(
461 &self,
462 params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
463 ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
464 crate::evm::query_pool_swap::query_pool_swap(self, params)
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use std::{
471 collections::{HashMap, HashSet},
472 str::FromStr,
473 };
474
475 use approx::assert_ulps_eq;
476 use num_bigint::BigUint;
477 use num_traits::ToPrimitive;
478 use tycho_common::{
479 dto::ProtocolStateDelta,
480 hex_bytes::Bytes,
481 models::{token::Token, Chain},
482 simulation::{
483 errors::SimulationError,
484 protocol_sim::{Balances, ProtocolSim},
485 },
486 };
487
488 use super::*;
489 use crate::evm::protocol::utils::add_fee_markup;
490
491 fn create_state() -> RocketpoolState {
500 RocketpoolState::new(
501 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, )
515 }
516
517 fn eth_token() -> Token {
518 Token::new(&Bytes::from(ETH_ADDRESS), "ETH", 18, 0, &[Some(100_000)], Chain::Ethereum, 100)
519 }
520
521 fn reth_token() -> Token {
522 Token::new(
523 &Bytes::from_str("0xae78736Cd615f374D3085123A210448E74Fc6393").unwrap(),
524 "rETH",
525 18,
526 0,
527 &[Some(100_000)],
528 Chain::Ethereum,
529 100,
530 )
531 }
532
533 #[test]
536 fn test_queue_length_normal() {
537 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(15));
538 assert_eq!(length, U256::from(5));
539 }
540
541 #[test]
542 fn test_queue_length_empty() {
543 let length = RocketpoolState::get_queue_length(U256::from(10), U256::from(10));
544 assert_eq!(length, U256::ZERO);
545 }
546
547 #[test]
548 fn test_queue_length_wrap_around() {
549 let capacity = queue_capacity();
550 let start = capacity - U256::from(5);
551 let end = U256::from(3);
552 let length = RocketpoolState::get_queue_length(start, end);
553 assert_eq!(length, U256::from(8));
555 }
556
557 #[test]
560 fn test_effective_capacity_empty() {
561 let state = create_state();
562 assert_eq!(state.get_effective_capacity().unwrap(), U256::ZERO);
563 }
564
565 #[test]
566 fn test_effective_capacity_variable_queue() {
567 let mut state = create_state();
568 state.queue_variable_end = U256::from(2); assert_eq!(state.get_effective_capacity().unwrap(), U256::from(62e18));
570 }
571
572 #[test]
575 fn test_max_capacity_assign_disabled() {
576 let state = create_state();
577 let max = state
578 .get_max_deposit_capacity()
579 .unwrap();
580 assert_eq!(max, U256::from(1000e18));
581 }
582
583 #[test]
584 fn test_max_capacity_assign_enabled_empty_queue() {
585 let mut state = create_state();
586 state.deposit_assigning_enabled = true;
587 let max = state
588 .get_max_deposit_capacity()
589 .unwrap();
590 assert_eq!(max, U256::from(1000e18));
591 }
592
593 #[test]
594 fn test_max_capacity_assign_enabled_with_queue() {
595 let mut state = create_state();
596 state.deposit_assigning_enabled = true;
597 state.queue_variable_end = U256::from(10); let max = state
599 .get_max_deposit_capacity()
600 .unwrap();
601 assert_eq!(max, U256::from(1310e18));
603 }
604
605 #[test]
608 fn test_delta_transition_basic() {
609 let mut state = create_state();
610
611 let attributes: HashMap<String, Bytes> = [
612 ("total_eth", U256::from(300u64)),
613 ("reth_supply", U256::from(150u64)),
614 ("deposit_contract_balance", U256::from(100u64)),
615 ("reth_contract_liquidity", U256::from(20u64)),
616 ]
617 .into_iter()
618 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
619 .collect();
620
621 let delta = ProtocolStateDelta {
622 component_id: "Rocketpool".to_owned(),
623 updated_attributes: attributes,
624 deleted_attributes: HashSet::new(),
625 };
626
627 state
628 .delta_transition(delta, &HashMap::new(), &Balances::default())
629 .unwrap();
630
631 assert_eq!(state.total_eth, U256::from(300u64));
632 assert_eq!(state.reth_supply, U256::from(150u64));
633 assert_eq!(state.deposit_contract_balance, U256::from(100u64));
634 assert_eq!(state.reth_contract_liquidity, U256::from(20u64));
635 }
636
637 #[test]
638 fn test_delta_transition_queue_fields() {
639 let mut state = create_state();
640
641 let attributes: HashMap<String, Bytes> = [
642 ("deposit_assigning_enabled", U256::from(1u64)),
643 ("queue_variable_end", U256::from(5u64)),
644 ]
645 .into_iter()
646 .map(|(k, v)| (k.to_string(), Bytes::from(v.to_be_bytes_vec())))
647 .collect();
648
649 let delta = ProtocolStateDelta {
650 component_id: "Rocketpool".to_owned(),
651 updated_attributes: attributes,
652 deleted_attributes: HashSet::new(),
653 };
654
655 state
656 .delta_transition(delta, &HashMap::new(), &Balances::default())
657 .unwrap();
658
659 assert!(state.deposit_assigning_enabled);
660 assert_eq!(state.queue_variable_end, U256::from(5u64));
661 }
662
663 #[test]
666 fn test_spot_price_deposit() {
667 let state = create_state();
668 let price = state
672 .spot_price(ð_token(), &reth_token())
673 .unwrap();
674 assert_ulps_eq!(price, 0.5);
675 }
676
677 #[test]
678 fn test_spot_price_withdraw() {
679 let state = create_state();
680 let price = state
684 .spot_price(&reth_token(), ð_token())
685 .unwrap();
686 assert_ulps_eq!(price, 1.0 / 0.3);
687 }
688
689 fn create_state_at_block_23929406() -> RocketpoolState {
691 RocketpoolState::new(
692 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(), )
706 }
707
708 #[test]
711 fn test_live_spot_price_reth_to_buy_eth_23929406() {
712 let state = create_state_at_block_23929406();
713
714 let price = state
715 .spot_price(ð_token(), &reth_token())
716 .unwrap();
717
718 let expected = 0.868179358382478;
719 assert_ulps_eq!(price, expected, max_ulps = 10);
720 }
721
722 #[test]
725 fn test_live_spot_price_eth_to_buy_reth_23929406() {
726 let state = create_state_at_block_23929406();
727
728 let price = state
729 .spot_price(&reth_token(), ð_token())
730 .unwrap();
731
732 let expected_without_fee = 1.151835724202335;
733 let fee = state.deposit_fee.to_f64().unwrap() / DEPOSIT_FEE_BASE as f64;
734 let expected = add_fee_markup(expected_without_fee, fee);
735
736 assert_ulps_eq!(price, expected, max_ulps = 10);
737 }
738
739 #[test]
740 fn test_fee_panics() {
741 let state = create_state();
742 let result = std::panic::catch_unwind(|| state.fee());
743 assert!(result.is_err());
744 }
745
746 #[test]
749 fn test_limits_deposit() {
750 let state = create_state();
751
752 let (max_sell, max_buy) = state
753 .get_limits(eth_token().address, reth_token().address)
754 .unwrap();
755
756 assert_eq!(max_sell, BigUint::from(950_000_000_000_000_000_000u128));
758 assert_eq!(max_buy, BigUint::from(285_000_000_000_000_000_000u128));
760 }
761
762 #[test]
763 fn test_limits_withdrawal() {
764 let state = create_state();
765
766 let (max_sell, max_buy) = state
767 .get_limits(reth_token().address, eth_token().address)
768 .unwrap();
769
770 assert_eq!(max_buy, BigUint::from(50_000_000_000_000_000_000u128));
772 assert_eq!(max_sell, BigUint::from(25_000_000_000_000_000_000u128));
774 }
775
776 #[test]
777 fn test_limits_with_extended_capacity() {
778 let mut state = create_state();
779 state.max_deposit_pool_size = U256::from(100e18);
780 state.deposit_assigning_enabled = true;
781 state.queue_variable_end = U256::from(2); let (max_sell, _) = state
784 .get_limits(eth_token().address, reth_token().address)
785 .unwrap();
786
787 assert_eq!(max_sell, BigUint::from(112_000_000_000_000_000_000u128));
790 }
791
792 #[test]
795 fn test_deposit_eth() {
796 let state = create_state();
797
798 let res = state
800 .get_amount_out(
801 BigUint::from(10_000_000_000_000_000_000u128),
802 ð_token(),
803 &reth_token(),
804 )
805 .unwrap();
806
807 assert_eq!(res.amount, BigUint::from(3_000_000_000_000_000_000u128));
808
809 let new_state = res
810 .new_state
811 .as_any()
812 .downcast_ref::<RocketpoolState>()
813 .unwrap();
814 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
816 assert_eq!(new_state.total_eth, U256::from(200e18));
818 assert_eq!(new_state.reth_supply, U256::from(100e18));
819 }
820
821 #[test]
822 fn test_deposit_within_extended_capacity() {
823 let mut state = create_state();
824 state.deposit_contract_balance = U256::from(990e18);
825 state.max_deposit_pool_size = U256::from(1000e18);
826 state.deposit_assigning_enabled = true;
827 state.queue_variable_end = U256::from(1); let res = state
832 .get_amount_out(
833 BigUint::from(20_000_000_000_000_000_000u128),
834 ð_token(),
835 &reth_token(),
836 )
837 .unwrap();
838
839 assert_eq!(res.amount, BigUint::from(6_000_000_000_000_000_000u128));
840
841 let new_state = res
842 .new_state
843 .as_any()
844 .downcast_ref::<RocketpoolState>()
845 .unwrap();
846 assert_eq!(new_state.deposit_contract_balance, U256::from(1010e18));
848 assert_eq!(new_state.total_eth, U256::from(200e18));
850 assert_eq!(new_state.reth_supply, U256::from(100e18));
851 }
852
853 #[test]
854 fn test_withdraw_reth() {
855 let state = create_state();
856
857 let res = state
859 .get_amount_out(
860 BigUint::from(10_000_000_000_000_000_000u128),
861 &reth_token(),
862 ð_token(),
863 )
864 .unwrap();
865
866 assert_eq!(res.amount, BigUint::from(20_000_000_000_000_000_000u128));
867
868 let new_state = res
869 .new_state
870 .as_any()
871 .downcast_ref::<RocketpoolState>()
872 .unwrap();
873 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
875 assert_eq!(new_state.total_eth, U256::from(200e18));
877 assert_eq!(new_state.reth_supply, U256::from(100e18));
878 }
879
880 #[test]
883 fn test_deposit_disabled() {
884 let mut state = create_state();
885 state.deposits_enabled = false;
886
887 let res = state.get_amount_out(BigUint::from(10u64), ð_token(), &reth_token());
888 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
889 }
890
891 #[test]
892 fn test_deposit_below_minimum() {
893 let mut state = create_state();
894 state.min_deposit_amount = U256::from(100u64);
895
896 let res = state.get_amount_out(BigUint::from(50u64), ð_token(), &reth_token());
897 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
898 }
899
900 #[test]
901 fn test_deposit_exceeds_max_pool() {
902 let mut state = create_state();
903 state.max_deposit_pool_size = U256::from(60e18); 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_queue_ignored_when_disabled() {
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 = false;
919 state.queue_variable_end = U256::from(10); let res = state.get_amount_out(
923 BigUint::from(20_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_deposit_exceeds_extended_capacity() {
932 let mut state = create_state();
933 state.deposit_contract_balance = U256::from(990e18);
934 state.max_deposit_pool_size = U256::from(1000e18);
935 state.deposit_assigning_enabled = true;
936 state.queue_variable_end = U256::from(1); let res = state.get_amount_out(
940 BigUint::from(50_000_000_000_000_000_000u128),
941 ð_token(),
942 &reth_token(),
943 );
944 assert!(matches!(res, Err(SimulationError::InvalidInput(_, _))));
945 }
946
947 #[test]
948 fn test_withdrawal_insufficient_liquidity() {
949 let state = create_state(); let res = state.get_amount_out(
953 BigUint::from(30_000_000_000_000_000_000u128),
954 &reth_token(),
955 ð_token(),
956 );
957 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
958 }
959
960 #[test]
961 fn test_withdrawal_limited_by_minipool_queue() {
962 let mut state = create_state();
963 state.deposit_contract_balance = U256::from(100e18);
964 state.queue_variable_end = U256::from(2);
966 let res = state.get_amount_out(
970 BigUint::from(20_000_000_000_000_000_000u128),
971 &reth_token(),
972 ð_token(),
973 );
974 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
975
976 let res = state
978 .get_amount_out(
979 BigUint::from(15_000_000_000_000_000_000u128),
980 &reth_token(),
981 ð_token(),
982 )
983 .unwrap();
984 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
985 }
986
987 #[test]
988 fn test_withdrawal_zero_excess_balance() {
989 let mut state = create_state();
990 state.deposit_contract_balance = U256::from(62e18);
991 state.queue_variable_end = U256::from(2);
993 let res = state.get_amount_out(
997 BigUint::from(1_000_000_000_000_000_000u128),
998 &reth_token(),
999 ð_token(),
1000 );
1001 assert!(matches!(res, Err(SimulationError::RecoverableError(_))));
1002 }
1003
1004 #[test]
1005 fn test_withdrawal_uses_both_pools() {
1006 let mut state = create_state();
1007 state.reth_contract_liquidity = U256::from(10e18);
1008 state.deposit_contract_balance = U256::from(50e18);
1009 let res = state
1014 .get_amount_out(
1015 BigUint::from(15_000_000_000_000_000_000u128),
1016 &reth_token(),
1017 ð_token(),
1018 )
1019 .unwrap();
1020
1021 assert_eq!(res.amount, BigUint::from(30_000_000_000_000_000_000u128));
1022
1023 let new_state = res
1024 .new_state
1025 .as_any()
1026 .downcast_ref::<RocketpoolState>()
1027 .unwrap();
1028
1029 assert_eq!(new_state.reth_contract_liquidity, U256::ZERO);
1031 assert_eq!(new_state.deposit_contract_balance, U256::from(30e18));
1033 }
1034
1035 #[test]
1036 fn test_limits_withdrawal_with_queue() {
1037 let mut state = create_state();
1038 state.deposit_contract_balance = U256::from(100e18);
1039 state.queue_variable_end = U256::from(2);
1041
1042 let (max_sell, max_buy) = state
1043 .get_limits(reth_token().address, eth_token().address)
1044 .unwrap();
1045
1046 assert_eq!(max_buy, BigUint::from(38_000_000_000_000_000_000u128));
1048 assert_eq!(max_sell, BigUint::from(19_000_000_000_000_000_000u128));
1050 }
1051
1052 #[test]
1055 fn test_assign_deposits_dequeues_minipools() {
1056 let mut state = create_state();
1057 state.deposit_contract_balance = U256::from(100e18);
1058 state.deposit_assigning_enabled = true;
1059 state.deposit_assign_maximum = U256::from(10u64);
1060 state.deposit_assign_socialised_maximum = U256::from(2u64);
1061 state.queue_variable_end = U256::from(5); let res = state
1071 .get_amount_out(
1072 BigUint::from(62_000_000_000_000_000_000u128),
1073 ð_token(),
1074 &reth_token(),
1075 )
1076 .unwrap();
1077
1078 let new_state = res
1079 .new_state
1080 .as_any()
1081 .downcast_ref::<RocketpoolState>()
1082 .unwrap();
1083
1084 assert_eq!(new_state.deposit_contract_balance, U256::from(38e18));
1085 assert_eq!(new_state.queue_variable_start, U256::from(4u64));
1086 }
1087
1088 #[test]
1089 fn test_assign_deposits_capped_by_queue_length() {
1090 let mut state = create_state();
1091 state.deposit_contract_balance = U256::from(100e18);
1092 state.deposit_assigning_enabled = true;
1093 state.deposit_assign_maximum = U256::from(10u64);
1094 state.deposit_assign_socialised_maximum = U256::from(5u64);
1095 state.queue_variable_end = U256::from(2); let res = state
1102 .get_amount_out(
1103 BigUint::from(62_000_000_000_000_000_000u128),
1104 ð_token(),
1105 &reth_token(),
1106 )
1107 .unwrap();
1108
1109 let new_state = res
1110 .new_state
1111 .as_any()
1112 .downcast_ref::<RocketpoolState>()
1113 .unwrap();
1114
1115 assert_eq!(new_state.deposit_contract_balance, U256::from(100e18));
1116 assert_eq!(new_state.queue_variable_start, U256::from(2u64));
1117 }
1118
1119 #[test]
1120 fn test_assign_deposits_capped_by_max_assignments() {
1121 let mut state = create_state();
1122 state.deposit_contract_balance = U256::from(100e18);
1123 state.deposit_assigning_enabled = true;
1124 state.deposit_assign_maximum = U256::from(1u64); state.deposit_assign_socialised_maximum = U256::from(5u64);
1126 state.queue_variable_end = U256::from(10);
1127
1128 let res = state
1133 .get_amount_out(
1134 BigUint::from(62_000_000_000_000_000_000u128),
1135 ð_token(),
1136 &reth_token(),
1137 )
1138 .unwrap();
1139
1140 let new_state = res
1141 .new_state
1142 .as_any()
1143 .downcast_ref::<RocketpoolState>()
1144 .unwrap();
1145
1146 assert_eq!(new_state.deposit_contract_balance, U256::from(131e18));
1147 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1148 }
1149
1150 #[test]
1151 fn test_assign_deposits_capped_by_total_eth() {
1152 let mut state = create_state();
1153 state.deposit_contract_balance = U256::from(10e18); state.deposit_assigning_enabled = true;
1155 state.deposit_assign_maximum = U256::from(10u64);
1156 state.deposit_assign_socialised_maximum = U256::from(5u64);
1157 state.queue_variable_end = U256::from(10);
1158
1159 let res = state
1165 .get_amount_out(
1166 BigUint::from(31_000_000_000_000_000_000u128),
1167 ð_token(),
1168 &reth_token(),
1169 )
1170 .unwrap();
1171
1172 let new_state = res
1173 .new_state
1174 .as_any()
1175 .downcast_ref::<RocketpoolState>()
1176 .unwrap();
1177
1178 assert_eq!(new_state.deposit_contract_balance, U256::from(10e18));
1179 assert_eq!(new_state.queue_variable_start, U256::from(1u64));
1180 }
1181
1182 #[test]
1183 fn test_assign_deposits_empty_queue_no_change() {
1184 let mut state = create_state();
1185 state.deposit_assigning_enabled = true;
1186 state.deposit_assign_maximum = U256::from(10u64);
1187 state.deposit_assign_socialised_maximum = U256::from(2u64);
1188 let res = state
1192 .get_amount_out(
1193 BigUint::from(10_000_000_000_000_000_000u128),
1194 ð_token(),
1195 &reth_token(),
1196 )
1197 .unwrap();
1198
1199 let new_state = res
1200 .new_state
1201 .as_any()
1202 .downcast_ref::<RocketpoolState>()
1203 .unwrap();
1204
1205 assert_eq!(new_state.deposit_contract_balance, U256::from(60e18));
1207 assert_eq!(new_state.queue_variable_start, U256::ZERO);
1208 }
1209
1210 #[test]
1217 fn test_live_deposit_tx_6213b6c2() {
1218 let state = RocketpoolState::new(
1219 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(), );
1233
1234 let deposit_amount = BigUint::from(4_500_000_000_000_000_000u128);
1236
1237 let res = state
1238 .get_amount_out(deposit_amount, ð_token(), &reth_token())
1239 .unwrap();
1240
1241 println!("calculated rETH out: {}", res.amount);
1242 let expected_reth_out = BigUint::from(3_905_847_020_555_141_679u128);
1243 assert_eq!(res.amount, expected_reth_out);
1244
1245 let new_state = res
1246 .new_state
1247 .as_any()
1248 .downcast_ref::<RocketpoolState>()
1249 .unwrap();
1250
1251 let expected_balance = U256::from_str_radix("0aa2289fdd01f892", 16).unwrap();
1253 assert_eq!(new_state.deposit_contract_balance, expected_balance);
1254
1255 let expected_queue_start = U256::from_str_radix("6d44", 16).unwrap();
1257 assert_eq!(new_state.queue_variable_start, expected_queue_start);
1258
1259 assert_eq!(new_state.total_eth, state.total_eth);
1261 assert_eq!(new_state.reth_supply, state.reth_supply);
1262 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1263 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1264 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1265 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1266 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1267 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1268 assert_eq!(
1269 new_state.deposit_assign_socialised_maximum,
1270 state.deposit_assign_socialised_maximum
1271 );
1272 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1273 }
1274
1275 #[test]
1279 fn test_live_withdraw_tx_block_23736567() {
1280 let state = RocketpoolState::new(
1281 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(), );
1295
1296 let burn_amount = BigUint::from(20_873_689_741_238_146_923u128);
1298
1299 let res = state
1300 .get_amount_out(burn_amount, &reth_token(), ð_token())
1301 .unwrap();
1302
1303 let expected_eth_out = BigUint::from(24_000_828_571_949_999_998u128);
1305 assert_eq!(res.amount, expected_eth_out);
1306
1307 let new_state = res
1308 .new_state
1309 .as_any()
1310 .downcast_ref::<RocketpoolState>()
1311 .unwrap();
1312
1313 let expected_liquidity = U256::from_str_radix("74d8b62c5", 16).unwrap();
1315 assert_eq!(new_state.reth_contract_liquidity, expected_liquidity);
1316
1317 assert_eq!(new_state.total_eth, state.total_eth);
1319 assert_eq!(new_state.reth_supply, state.reth_supply);
1320 assert_eq!(new_state.deposit_contract_balance, state.deposit_contract_balance);
1321 assert_eq!(new_state.deposit_fee, state.deposit_fee);
1322 assert_eq!(new_state.deposits_enabled, state.deposits_enabled);
1323 assert_eq!(new_state.min_deposit_amount, state.min_deposit_amount);
1324 assert_eq!(new_state.max_deposit_pool_size, state.max_deposit_pool_size);
1325 assert_eq!(new_state.deposit_assigning_enabled, state.deposit_assigning_enabled);
1326 assert_eq!(new_state.deposit_assign_maximum, state.deposit_assign_maximum);
1327 assert_eq!(
1328 new_state.deposit_assign_socialised_maximum,
1329 state.deposit_assign_socialised_maximum
1330 );
1331 assert_eq!(new_state.queue_variable_start, state.queue_variable_start);
1332 assert_eq!(new_state.queue_variable_end, state.queue_variable_end);
1333 }
1334}