pcli/command/query/
auction.rs1use crate::command::utils::render_positions;
2use crate::App;
3use clap::Subcommand;
4use comfy_table::{presets, Table};
5use comfy_table::{Cell, ContentArrangement};
6use penumbra_sdk_asset::asset::Cache;
7use penumbra_sdk_asset::Value;
8use penumbra_sdk_auction::auction::dutch::DutchAuction;
9use penumbra_sdk_auction::auction::AuctionId;
10use penumbra_sdk_dex::lp::position::{self, Position};
11use penumbra_sdk_num::fixpoint::U128x128;
12use penumbra_sdk_num::Amount;
13use penumbra_sdk_proto::core::component::auction::v1 as pb_auction;
14use penumbra_sdk_proto::core::component::auction::v1::query_service_client::QueryServiceClient as AuctionQueryServiceClient;
15use penumbra_sdk_proto::core::component::auction::v1::AuctionStateByIdRequest;
16use penumbra_sdk_proto::core::component::dex::v1::query_service_client::QueryServiceClient as DexQueryServiceClient;
17use penumbra_sdk_proto::core::component::dex::v1::LiquidityPositionByIdRequest;
18use penumbra_sdk_proto::DomainType;
19use penumbra_sdk_proto::Name;
20use penumbra_sdk_view::ViewClient;
21
22#[derive(Debug, Subcommand)]
23pub enum AuctionCmd {
24 Dutch {
26 #[clap(index = 1)]
27 auction_id: AuctionId,
28 },
29}
30
31impl AuctionCmd {
32 pub async fn exec(&self, app: &mut App) -> anyhow::Result<()> {
33 match self {
34 AuctionCmd::Dutch { auction_id } => {
35 let auction_id = auction_id.clone();
36 let mut auction_client = AuctionQueryServiceClient::new(app.pd_channel().await?);
37 let rsp = auction_client
38 .auction_state_by_id(AuctionStateByIdRequest {
39 id: Some(auction_id.into()),
40 })
41 .await?
42 .into_inner();
43
44 let pb_auction_state = rsp
45 .auction
46 .ok_or_else(|| anyhow::anyhow!("auction state is missing!"))?;
47
48 if pb_auction_state.type_url == pb_auction::DutchAuction::type_url() {
49 let dutch_auction = DutchAuction::decode(pb_auction_state.value)?;
50 let position = if let Some(position_id) = dutch_auction.state.current_position {
51 let mut dex_client = DexQueryServiceClient::new(app.pd_channel().await?);
52 let position: Position = dex_client
53 .liquidity_position_by_id(LiquidityPositionByIdRequest {
54 position_id: Some(position_id.into()),
55 })
56 .await?
57 .into_inner()
58 .data
59 .expect("a position should exist")
60 .try_into()
61 .expect("no decoding error");
62 Some(position)
63 } else {
64 None
65 };
66
67 let asset_cache = app.view().assets().await?;
68
69 render_dutch_auction(&asset_cache, &dutch_auction, None, position).await?;
70 } else {
71 unimplemented!("only supporting dutch auctions at the moment, come back later");
72 }
73 }
74 }
75 Ok(())
76 }
77}
78
79pub async fn render_dutch_auction(
80 asset_cache: &Cache,
81 dutch_auction: &DutchAuction,
82 local_view: Option<u64>,
83 position: Option<Position>,
84) -> anyhow::Result<()> {
85 let auction_id = dutch_auction.description.id();
86 println!("dutch auction with id {auction_id:?}:");
87
88 let initial_input = dutch_auction.description.input;
89 let input_id = initial_input.asset_id;
90 let output_id = dutch_auction.description.output_id;
91
92 let initial_input_amount = U128x128::from(initial_input.amount);
93 let min_output = U128x128::from(dutch_auction.description.min_output);
94 let max_output = U128x128::from(dutch_auction.description.max_output);
95 let start_price = (max_output / initial_input_amount).expect("the input is always nonzero");
96 let end_price = (min_output / initial_input_amount).expect("the input is always nonzero");
97
98 let maybe_id = dutch_auction.state.current_position;
99
100 let (position_input_reserve, position_output_reserve) = position.as_ref().map_or_else(
101 || (Amount::zero(), Amount::zero()),
102 |lp| {
103 (
104 lp.reserves_for(input_id)
105 .expect("lp doesn't have reserves for input asset"),
106 lp.reserves_for(output_id)
107 .expect("lp doesn't have reserves for output asset"),
108 )
109 },
110 );
111
112 let auction_input_reserves = Value {
113 amount: position_input_reserve + dutch_auction.state.input_reserves,
114 asset_id: input_id,
115 };
116 let auction_output_reserves = Value {
117 amount: position_output_reserve + dutch_auction.state.output_reserves,
118 asset_id: output_id,
119 };
120
121 let start_height = dutch_auction.description.start_height;
122 let end_height = dutch_auction.description.end_height;
123
124 let mut auction_table = Table::new();
125 auction_table.load_preset(presets::UTF8_FULL);
126 auction_table
127 .set_header(vec![
128 "Auction id",
129 "State",
130 "Height range",
131 "# steps",
132 "Start price",
133 "End price",
134 "Input",
135 "Balance",
136 "Has lp?",
137 ])
138 .set_content_arrangement(ContentArrangement::DynamicFullWidth)
139 .add_row(vec![
140 Cell::new(truncate_auction_id(&auction_id)).set_delimiter('.'),
141 Cell::new(render_sequence(dutch_auction.state.sequence, local_view)),
142 Cell::new(format!("{start_height} -> {end_height}")),
143 Cell::new(dutch_auction.description.step_count.to_string()),
144 Cell::new(format!("{}", start_price)),
145 Cell::new(format!("{}", end_price)),
146 Cell::new(initial_input.format(asset_cache)),
147 Cell::new(format!(
148 "({}, {})",
149 &auction_input_reserves.format(asset_cache),
150 &auction_output_reserves.format(asset_cache)
151 )),
152 Cell::new(format!("{}", render_position_id(&maybe_id)))
153 .set_alignment(comfy_table::CellAlignment::Center),
154 ]);
155
156 if let Some(lp) = position {
157 auction_table.add_row(vec![Cell::new(format!(
158 "{}",
159 render_positions(asset_cache, &[lp])
160 ))]);
161 }
162
163 println!("{auction_table}");
164 Ok(())
165}
166
167fn render_sequence(state: u64, local_seq: Option<u64>) -> String {
168 let main = if state == 0 {
169 format!("Opened")
170 } else if state == 1 {
171 format!("Closed")
172 } else {
173 format!("Withdrawn (seq={state})")
174 };
175
176 if let Some(local_seq) = local_seq {
177 format!("{main} (local_seq={local_seq})")
178 } else {
179 main
180 }
181}
182
183fn truncate_auction_id(asset_id: &AuctionId) -> String {
184 let input = format!("{asset_id:?}");
185 let prefix_len = 16;
186 if input.len() > prefix_len {
187 format!("{}...", &input[..prefix_len])
188 } else {
189 input
190 }
191}
192
193fn render_position_id(maybe_id: &Option<position::Id>) -> String {
194 let input = maybe_id.map_or_else(|| format!("x"), |_| format!("✓"));
195 let prefix_len = 6;
196 if input.len() > prefix_len {
197 format!("{}..", &input[..prefix_len])
198 } else {
199 input
200 }
201}