penumbra_sdk_dex/lp/
plan.rs

1use ark_ff::Zero;
2use decaf377::Fr;
3use penumbra_sdk_asset::{balance, Balance, Value};
4use penumbra_sdk_proto::{penumbra::core::component::dex::v1 as pb, DomainType};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    lp::{position, LpNft, Reserves},
9    TradingPair,
10};
11
12use super::action::PositionWithdraw;
13
14/// A planned [`PositionWithdraw`](PositionWithdraw).
15#[derive(Clone, Debug, Deserialize, Serialize)]
16#[serde(
17    try_from = "pb::PositionWithdrawPlan",
18    into = "pb::PositionWithdrawPlan"
19)]
20pub struct PositionWithdrawPlan {
21    pub reserves: Reserves,
22    pub position_id: position::Id,
23    pub pair: TradingPair,
24    pub sequence: u64,
25    pub rewards: Vec<Value>,
26}
27
28impl PositionWithdrawPlan {
29    /// Convenience method to construct the [`PositionWithdraw`] described by this [`PositionWithdrawPlan`].
30    pub fn position_withdraw(&self) -> PositionWithdraw {
31        PositionWithdraw {
32            position_id: self.position_id,
33            reserves_commitment: self.reserves_commitment(),
34            sequence: self.sequence,
35        }
36    }
37
38    pub fn reserves_commitment(&self) -> balance::Commitment {
39        let mut reserves_balance = self.reserves.balance(&self.pair);
40        for reward in &self.rewards {
41            reserves_balance += *reward;
42        }
43        reserves_balance.commit(Fr::zero())
44    }
45
46    pub fn balance(&self) -> Balance {
47        // PositionWithdraw outputs will correspond to the final reserves
48        // and a PositionWithdraw token.
49        // Spends will be the PositionClose token.
50        let mut balance = self.reserves.balance(&self.pair);
51
52        // We consume a token of self.sequence-1 and produce one of self.sequence.
53        // We treat -1 as "closed", the previous state.
54        balance -= if self.sequence == 0 {
55            Value {
56                amount: 1u64.into(),
57                asset_id: LpNft::new(self.position_id, position::State::Closed).asset_id(),
58            }
59        } else {
60            Value {
61                amount: 1u64.into(),
62                asset_id: LpNft::new(
63                    self.position_id,
64                    position::State::Withdrawn {
65                        sequence: self.sequence - 1,
66                    },
67                )
68                .asset_id(),
69            }
70        };
71        balance += Value {
72            amount: 1u64.into(),
73            asset_id: LpNft::new(
74                self.position_id,
75                position::State::Withdrawn {
76                    sequence: self.sequence,
77                },
78            )
79            .asset_id(),
80        };
81
82        balance
83    }
84}
85
86impl DomainType for PositionWithdrawPlan {
87    type Proto = pb::PositionWithdrawPlan;
88}
89
90impl From<PositionWithdrawPlan> for pb::PositionWithdrawPlan {
91    fn from(msg: PositionWithdrawPlan) -> Self {
92        Self {
93            reserves: Some(msg.reserves.into()),
94            position_id: Some(msg.position_id.into()),
95            pair: Some(msg.pair.into()),
96            sequence: msg.sequence,
97            rewards: msg.rewards.into_iter().map(Into::into).collect(),
98        }
99    }
100}
101
102impl TryFrom<pb::PositionWithdrawPlan> for PositionWithdrawPlan {
103    type Error = anyhow::Error;
104    fn try_from(msg: pb::PositionWithdrawPlan) -> Result<Self, Self::Error> {
105        Ok(Self {
106            reserves: msg
107                .reserves
108                .ok_or_else(|| anyhow::anyhow!("missing reserves"))?
109                .try_into()?,
110            position_id: msg
111                .position_id
112                .ok_or_else(|| anyhow::anyhow!("missing position_id"))?
113                .try_into()?,
114            pair: msg
115                .pair
116                .ok_or_else(|| anyhow::anyhow!("missing pair"))?
117                .try_into()?,
118            sequence: msg.sequence,
119            rewards: msg
120                .rewards
121                .into_iter()
122                .map(TryInto::try_into)
123                .collect::<Result<_, _>>()?,
124        })
125    }
126}