penumbra_sdk_auction/auction/
dutch.rs1use std::num::NonZeroU64;
2
3use anyhow::anyhow;
4use penumbra_sdk_asset::{asset, Value};
5use penumbra_sdk_dex::lp::position::{self};
6use penumbra_sdk_num::Amount;
7use penumbra_sdk_proto::{core::component::auction::v1 as pb, DomainType};
8use serde::{Deserialize, Serialize};
9
10use crate::auction::AuctionId;
11
12pub mod actions;
13pub use actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule, ActionDutchAuctionWithdraw};
14
15pub const DUTCH_AUCTION_DOMAIN_SEP: &[u8] = b"penumbra_DA_nft";
16
17#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
20#[serde(try_from = "pb::DutchAuction", into = "pb::DutchAuction")]
21pub struct DutchAuction {
22 pub description: DutchAuctionDescription,
23 pub state: DutchAuctionState,
24}
25
26impl DomainType for DutchAuction {
28 type Proto = pb::DutchAuction;
29}
30
31impl From<DutchAuction> for pb::DutchAuction {
32 fn from(domain: DutchAuction) -> Self {
33 pb::DutchAuction {
34 description: Some(domain.description.into()),
35 state: Some(domain.state.into()),
36 }
37 }
38}
39
40impl TryFrom<pb::DutchAuction> for DutchAuction {
41 type Error = anyhow::Error;
42
43 fn try_from(msg: pb::DutchAuction) -> Result<Self, Self::Error> {
44 Ok(DutchAuction {
45 description: msg
46 .description
47 .ok_or_else(|| anyhow!("DutchAuction is missing description"))?
48 .try_into()?,
49 state: msg
50 .state
51 .ok_or_else(|| anyhow!("DutchAuction is missing a state field"))?
52 .try_into()?,
53 })
54 }
55}
56#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
60#[serde(
61 try_from = "pb::DutchAuctionDescription",
62 into = "pb::DutchAuctionDescription"
63)]
64pub struct DutchAuctionDescription {
65 pub input: Value,
66 pub output_id: asset::Id,
67 pub max_output: Amount,
68 pub min_output: Amount,
69 pub start_height: u64,
70 pub end_height: u64,
71 pub step_count: u64,
72 pub nonce: [u8; 32],
73}
74
75impl DutchAuctionDescription {
76 pub fn id(&self) -> AuctionId {
78 let mut state = blake2b_simd::Params::default()
79 .personal(DUTCH_AUCTION_DOMAIN_SEP)
80 .to_state();
81
82 state.update(&self.nonce);
83 state.update(&self.input.asset_id.to_bytes());
84 state.update(&self.input.amount.to_le_bytes());
85 state.update(&self.max_output.to_le_bytes());
86 state.update(&self.start_height.to_le_bytes());
87 state.update(&self.end_height.to_le_bytes());
88 state.update(&self.step_count.to_le_bytes());
89
90 let hash = state.finalize();
91 let mut bytes = [0; 32];
92 bytes[0..32].copy_from_slice(&hash.as_bytes()[0..32]);
93 AuctionId(bytes)
94 }
95}
96
97impl DomainType for DutchAuctionDescription {
99 type Proto = pb::DutchAuctionDescription;
100}
101
102impl From<DutchAuctionDescription> for pb::DutchAuctionDescription {
103 fn from(domain: DutchAuctionDescription) -> Self {
104 Self {
105 input: Some(domain.input.into()),
106 output_id: Some(domain.output_id.into()),
107 max_output: Some(domain.max_output.into()),
108 min_output: Some(domain.min_output.into()),
109 start_height: domain.start_height,
110 end_height: domain.end_height,
111 step_count: domain.step_count,
112 nonce: domain.nonce.as_slice().to_vec(),
113 }
114 }
115}
116
117impl TryFrom<pb::DutchAuctionDescription> for DutchAuctionDescription {
118 type Error = anyhow::Error;
119
120 fn try_from(msg: pb::DutchAuctionDescription) -> Result<Self, Self::Error> {
121 let d = DutchAuctionDescription {
122 input: msg
123 .input
124 .ok_or_else(|| anyhow!("DutchAuctionDescription message is missing input"))?
125 .try_into()?,
126 output_id: msg
127 .output_id
128 .ok_or_else(|| {
129 anyhow!("DutchAuctionDescription message is missing an output identifier")
130 })?
131 .try_into()?,
132 max_output: msg
133 .max_output
134 .ok_or_else(|| anyhow!("DutchAuctionDescription message is missing max output"))?
135 .try_into()?,
136 min_output: msg
137 .min_output
138 .ok_or_else(|| anyhow!("DutchAuctionDescription message is missing min output"))?
139 .try_into()?,
140 start_height: msg.start_height,
141 end_height: msg.end_height,
142 step_count: msg.step_count,
143 nonce: msg.nonce.as_slice().try_into()?,
144 };
145 Ok(d)
146 }
147}
148#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
169#[serde(try_from = "pb::DutchAuctionState", into = "pb::DutchAuctionState")]
170pub struct DutchAuctionState {
171 pub sequence: u64,
172 pub current_position: Option<position::Id>,
173 pub next_trigger: Option<NonZeroU64>,
174 pub input_reserves: Amount,
175 pub output_reserves: Amount,
176}
177
178impl DomainType for DutchAuctionState {
180 type Proto = pb::DutchAuctionState;
181}
182
183impl From<DutchAuctionState> for pb::DutchAuctionState {
184 fn from(domain: DutchAuctionState) -> Self {
185 Self {
186 seq: domain.sequence,
187 current_position: domain.current_position.map(Into::into),
188 next_trigger: domain.next_trigger.map_or(0u64, Into::into),
189 input_reserves: Some(domain.input_reserves.into()),
190 output_reserves: Some(domain.output_reserves.into()),
191 }
192 }
193}
194
195impl TryFrom<pb::DutchAuctionState> for DutchAuctionState {
196 type Error = anyhow::Error;
197
198 fn try_from(msg: pb::DutchAuctionState) -> Result<Self, Self::Error> {
199 Ok(DutchAuctionState {
200 sequence: msg.seq,
201 current_position: msg.current_position.map(TryInto::try_into).transpose()?,
202 next_trigger: NonZeroU64::new(msg.next_trigger),
203 input_reserves: msg
204 .input_reserves
205 .ok_or_else(|| anyhow!("DutchAuctionState message is missing input reserves"))?
206 .try_into()?,
207 output_reserves: msg
208 .output_reserves
209 .ok_or_else(|| anyhow!("DutchAuctionState message is missing output reserves"))?
210 .try_into()?,
211 })
212 }
213}
214