penumbra_sdk_dex/lp/
plan.rs1use ark_ff::Zero;
2use decaf377::Fr;
3use penumbra_sdk_asset::{balance, Balance, Value};
4use penumbra_sdk_keys::{
5 keys::FullViewingKey, symmetric::POSITION_METADATA_NONCE_SIZE_BYTES, PositionMetadataKey,
6};
7use penumbra_sdk_proto::{penumbra::core::component::dex::v1 as pb, DomainType};
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 lp::{metadata::PositionMetadata, position, position::Position, LpNft, Reserves},
12 TradingPair,
13};
14
15use super::action::{PositionOpen, PositionWithdraw};
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
19#[serde(try_from = "pb::PositionOpenPlan", into = "pb::PositionOpenPlan")]
20pub struct PositionOpenPlan {
21 pub position: Position,
22 pub metadata: Option<PositionMetadata>,
23}
24
25impl PositionOpenPlan {
26 pub fn position_open(
30 &self,
31 fvk: &FullViewingKey,
32 nonce: Option<&[u8; POSITION_METADATA_NONCE_SIZE_BYTES]>,
33 ) -> PositionOpen {
34 let pmk = PositionMetadataKey::derive(fvk.outgoing());
35 let nonce = nonce.copied().unwrap_or_else(|| {
36 let out: [u8; POSITION_METADATA_NONCE_SIZE_BYTES] = self.position.id().0
37 [..POSITION_METADATA_NONCE_SIZE_BYTES]
38 .try_into()
39 .expect("position id is 32 bytes");
40 out
41 });
42 let encrypted_metadata = self.metadata.map(|m| m.encrypt(&pmk, &nonce));
43 PositionOpen {
44 position: self.position.clone(),
45 encrypted_metadata,
46 }
47 }
48
49 pub fn balance(&self) -> Balance {
50 let opened_position_nft = Value {
51 amount: 1u64.into(),
52 asset_id: LpNft::new(self.position.id(), position::State::Opened).asset_id(),
53 };
54
55 let reserves = self.position.reserves.balance(&self.position.phi.pair);
56
57 Balance::from(opened_position_nft) - reserves
59 }
60}
61
62impl DomainType for PositionOpenPlan {
63 type Proto = pb::PositionOpenPlan;
64}
65
66impl From<PositionOpenPlan> for pb::PositionOpenPlan {
67 fn from(msg: PositionOpenPlan) -> Self {
68 Self {
69 position: Some(msg.position.into()),
70 metadata: msg.metadata.map(|x| x.into()),
71 }
72 }
73}
74
75impl TryFrom<pb::PositionOpenPlan> for PositionOpenPlan {
76 type Error = anyhow::Error;
77 fn try_from(msg: pb::PositionOpenPlan) -> Result<Self, Self::Error> {
78 Ok(Self {
79 position: msg
80 .position
81 .ok_or_else(|| anyhow::anyhow!("missing position"))?
82 .try_into()?,
83 metadata: msg.metadata.map(|x| x.try_into()).transpose()?,
84 })
85 }
86}
87
88#[derive(Clone, Debug, Deserialize, Serialize)]
90#[serde(
91 try_from = "pb::PositionWithdrawPlan",
92 into = "pb::PositionWithdrawPlan"
93)]
94pub struct PositionWithdrawPlan {
95 pub reserves: Reserves,
96 pub position_id: position::Id,
97 pub pair: TradingPair,
98 pub sequence: u64,
99 pub rewards: Vec<Value>,
100}
101
102impl PositionWithdrawPlan {
103 pub fn position_withdraw(&self) -> PositionWithdraw {
105 PositionWithdraw {
106 position_id: self.position_id,
107 reserves_commitment: self.reserves_commitment(),
108 sequence: self.sequence,
109 }
110 }
111
112 pub fn reserves_commitment(&self) -> balance::Commitment {
113 let mut reserves_balance = self.reserves.balance(&self.pair);
114 for reward in &self.rewards {
115 reserves_balance += *reward;
116 }
117 reserves_balance.commit(Fr::zero())
118 }
119
120 pub fn balance(&self) -> Balance {
121 let mut balance = self.reserves.balance(&self.pair);
125
126 balance -= if self.sequence == 0 {
129 Value {
130 amount: 1u64.into(),
131 asset_id: LpNft::new(self.position_id, position::State::Closed).asset_id(),
132 }
133 } else {
134 Value {
135 amount: 1u64.into(),
136 asset_id: LpNft::new(
137 self.position_id,
138 position::State::Withdrawn {
139 sequence: self.sequence - 1,
140 },
141 )
142 .asset_id(),
143 }
144 };
145 balance += Value {
146 amount: 1u64.into(),
147 asset_id: LpNft::new(
148 self.position_id,
149 position::State::Withdrawn {
150 sequence: self.sequence,
151 },
152 )
153 .asset_id(),
154 };
155
156 balance
157 }
158}
159
160impl DomainType for PositionWithdrawPlan {
161 type Proto = pb::PositionWithdrawPlan;
162}
163
164impl From<PositionWithdrawPlan> for pb::PositionWithdrawPlan {
165 fn from(msg: PositionWithdrawPlan) -> Self {
166 Self {
167 reserves: Some(msg.reserves.into()),
168 position_id: Some(msg.position_id.into()),
169 pair: Some(msg.pair.into()),
170 sequence: msg.sequence,
171 rewards: msg.rewards.into_iter().map(Into::into).collect(),
172 }
173 }
174}
175
176impl TryFrom<pb::PositionWithdrawPlan> for PositionWithdrawPlan {
177 type Error = anyhow::Error;
178 fn try_from(msg: pb::PositionWithdrawPlan) -> Result<Self, Self::Error> {
179 Ok(Self {
180 reserves: msg
181 .reserves
182 .ok_or_else(|| anyhow::anyhow!("missing reserves"))?
183 .try_into()?,
184 position_id: msg
185 .position_id
186 .ok_or_else(|| anyhow::anyhow!("missing position_id"))?
187 .try_into()?,
188 pair: msg
189 .pair
190 .ok_or_else(|| anyhow::anyhow!("missing pair"))?
191 .try_into()?,
192 sequence: msg.sequence,
193 rewards: msg
194 .rewards
195 .into_iter()
196 .map(TryInto::try_into)
197 .collect::<Result<_, _>>()?,
198 })
199 }
200}