penumbra_sdk_dex/lp/
reserves.rs

1use penumbra_sdk_asset::{Balance, Value};
2use penumbra_sdk_num::Amount;
3use penumbra_sdk_proto::{penumbra::core::component::dex::v1 as pb, DomainType};
4
5use crate::TradingPair;
6
7use super::position::MAX_RESERVE_AMOUNT;
8
9/// The reserves of a position.
10///
11/// Like a position, this implicitly treats the trading function as being
12/// between assets 1 and 2, without specifying what those assets are, to avoid
13/// duplicating data (each asset ID alone is four times the size of the
14/// reserves).
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Reserves {
17    pub r1: Amount,
18    pub r2: Amount,
19}
20
21impl Reserves {
22    pub fn check_bounds(&self) -> anyhow::Result<()> {
23        if self.r1.value() > MAX_RESERVE_AMOUNT || self.r2.value() > MAX_RESERVE_AMOUNT {
24            anyhow::bail!(format!(
25                "Reserve amounts are out-of-bounds (limit: {MAX_RESERVE_AMOUNT})"
26            ))
27        } else {
28            // Both reserves cannot be empty.
29            if self.r1.value() == 0 && self.r2.value() == 0 {
30                anyhow::bail!("initial reserves must provision some amount of either asset",);
31            }
32
33            Ok(())
34        }
35    }
36
37    /// Augment `self` with type information to get a typed `Balance`.
38    pub fn balance(&self, pair: &TradingPair) -> Balance {
39        let r1 = Value {
40            amount: self.r1,
41            asset_id: pair.asset_1(),
42        };
43
44        let r2 = Value {
45            amount: self.r2,
46            asset_id: pair.asset_2(),
47        };
48
49        Balance::from(r1) + r2
50    }
51
52    /// Flip the reserves
53    pub fn flip(&self) -> Reserves {
54        Self {
55            r1: self.r2,
56            r2: self.r1,
57        }
58    }
59
60    /// Return zero reserves.
61    pub fn zero() -> Self {
62        Self {
63            r1: Amount::zero(),
64            r2: Amount::zero(),
65        }
66    }
67}
68
69impl Default for Reserves {
70    fn default() -> Self {
71        Self::zero()
72    }
73}
74
75impl DomainType for Reserves {
76    type Proto = pb::Reserves;
77}
78
79impl TryFrom<pb::Reserves> for Reserves {
80    type Error = anyhow::Error;
81
82    fn try_from(value: pb::Reserves) -> Result<Self, Self::Error> {
83        Ok(Self {
84            r1: value
85                .r1
86                .ok_or_else(|| anyhow::anyhow!("missing r1"))?
87                .try_into()?,
88            r2: value
89                .r2
90                .ok_or_else(|| anyhow::anyhow!("missing r2"))?
91                .try_into()?,
92        })
93    }
94}
95
96impl From<Reserves> for pb::Reserves {
97    fn from(value: Reserves) -> Self {
98        Self {
99            r1: Some(value.r1.into()),
100            r2: Some(value.r2.into()),
101        }
102    }
103}