From 982f089406003bb0aa2436bfb13e066203face7d Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Wed, 1 May 2024 12:20:25 -0400 Subject: [PATCH] pcli: improve auction rendering (#4297) ## Describe your changes This improve rendering of query/view subcommands for DAs. Helpful for testing and integration work. Closes #4242, #4243, #4244 ## Checklist before requesting a review - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: > Client changes --- crates/bin/pcli/src/command/query.rs | 2 +- crates/bin/pcli/src/command/query/auction.rs | 184 ++++++++++++++++--- crates/bin/pcli/src/command/view/auction.rs | 67 ++++++- 3 files changed, 223 insertions(+), 30 deletions(-) diff --git a/crates/bin/pcli/src/command/query.rs b/crates/bin/pcli/src/command/query.rs index 778bb8e6f5..b79fe5a933 100644 --- a/crates/bin/pcli/src/command/query.rs +++ b/crates/bin/pcli/src/command/query.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context, Result}; -mod auction; +pub(crate) mod auction; mod chain; mod community_pool; mod dex; diff --git a/crates/bin/pcli/src/command/query/auction.rs b/crates/bin/pcli/src/command/query/auction.rs index 5661f93d8b..36d4ac6fb4 100644 --- a/crates/bin/pcli/src/command/query/auction.rs +++ b/crates/bin/pcli/src/command/query/auction.rs @@ -1,11 +1,19 @@ use crate::App; use clap::Subcommand; use comfy_table::{presets, Table}; +use comfy_table::{Cell, ContentArrangement}; +use penumbra_asset::asset::Cache; +use penumbra_asset::{asset, Value}; use penumbra_auction::auction::dutch::DutchAuction; use penumbra_auction::auction::AuctionId; +use penumbra_dex::lp::position::{self, Position}; +use penumbra_num::fixpoint::U128x128; +use penumbra_num::Amount; use penumbra_proto::core::component::auction::v1alpha1 as pb_auction; -use penumbra_proto::core::component::auction::v1alpha1::query_service_client::QueryServiceClient; +use penumbra_proto::core::component::auction::v1alpha1::query_service_client::QueryServiceClient as AuctionQueryServiceClient; use penumbra_proto::core::component::auction::v1alpha1::AuctionStateByIdRequest; +use penumbra_proto::core::component::dex::v1::query_service_client::QueryServiceClient as DexQueryServiceClient; +use penumbra_proto::core::component::dex::v1::LiquidityPositionByIdRequest; use penumbra_proto::DomainType; use penumbra_proto::Name; @@ -23,8 +31,8 @@ impl AuctionCmd { match self { AuctionCmd::Dutch { auction_id } => { let auction_id = auction_id.clone(); - let mut client = QueryServiceClient::new(app.pd_channel().await?); - let rsp = client + let mut auction_client = AuctionQueryServiceClient::new(app.pd_channel().await?); + let rsp = auction_client .auction_state_by_id(AuctionStateByIdRequest { id: Some(auction_id.into()), }) @@ -37,26 +45,24 @@ impl AuctionCmd { if pb_auction_state.type_url == pb_auction::DutchAuction::type_url() { let dutch_auction = DutchAuction::decode(pb_auction_state.value)?; - println!("dutch auction with id {auction_id:?}"); - - let mut table = Table::new(); - table.load_preset(presets::NOTHING); - table - .set_header(vec![ - "Auction Id", - "State", - "Start height", - "End height", - "Step count", - ]) // TODO: make this more useful - .add_row(vec![ - &auction_id.to_string(), - &render_state(dutch_auction.state.sequence), - &dutch_auction.description.start_height.to_string(), - &dutch_auction.description.end_height.to_string(), - &dutch_auction.description.step_count.to_string(), - ]); - println!("{table}"); + let position = if let Some(position_id) = dutch_auction.state.current_position { + let mut dex_client = DexQueryServiceClient::new(app.pd_channel().await?); + let position: Position = dex_client + .liquidity_position_by_id(LiquidityPositionByIdRequest { + position_id: Some(position_id.into()), + }) + .await? + .into_inner() + .data + .expect("a position should exist") + .try_into() + .expect("no decoding error"); + Some(position) + } else { + None + }; + + render_dutch_auction(&dutch_auction, position).await?; } else { unimplemented!("only supporting dutch auctions at the moment, come back later"); } @@ -66,7 +72,107 @@ impl AuctionCmd { } } -fn render_state(state: u64) -> String { +pub async fn render_dutch_auction( + dutch_auction: &DutchAuction, + position: Option, +) -> anyhow::Result<()> { + let auction_id = dutch_auction.description.id(); + + println!("dutch auction with id {auction_id:?}"); + if let Some(id) = position.as_ref() { + let position_id = id.id(); + println!("auction has a deployed liquidity position with id: {position_id:?}"); + } + let initial_input = dutch_auction.description.input; + let input_id = initial_input.asset_id; + let output_id = dutch_auction.description.output_id; + + let _input_id_str = truncate_asset_id(&input_id); + let _output_id_str = truncate_asset_id(&output_id); + + let initial_input_amount = U128x128::from(initial_input.amount); + let min_output = U128x128::from(dutch_auction.description.min_output); + let max_output = U128x128::from(dutch_auction.description.max_output); + let start_price = (max_output / initial_input_amount).expect("the input is always nonzero"); + let end_price = (min_output / initial_input_amount).expect("the input is always nonzero"); + + let maybe_id = dutch_auction.state.current_position; + + let (position_input_reserve, position_output_reserve) = position.as_ref().map_or_else( + || (Amount::zero(), Amount::zero()), + |lp| { + ( + lp.reserves_for(input_id).unwrap(), + lp.reserves_for(output_id).unwrap(), + ) + }, + ); + + let auction_input_reserves = Value { + amount: position_input_reserve + dutch_auction.state.input_reserves, + asset_id: input_id, + }; + let auction_output_reserves = Value { + amount: position_output_reserve + dutch_auction.state.output_reserves, + asset_id: output_id, + }; + + let start_height = dutch_auction.description.start_height; + let end_height = dutch_auction.description.end_height; + + let basic_cache = Cache::with_known_assets(); + + let mut auction_table = Table::new(); + auction_table.load_preset(presets::ASCII_FULL); + auction_table + .set_header(vec![ + "Auction id", + "State", + "Height range", + "# steps", + "Start price", + "End price", + "Input", + "Balance", + "Has lp?", + ]) + .set_content_arrangement(ContentArrangement::DynamicFullWidth) + .add_row(vec![ + Cell::new(&truncate_auction_id(&auction_id)).set_delimiter('.'), + Cell::new(&render_sequence(dutch_auction.state.sequence)), + Cell::new(&format!("{start_height} -> {end_height}")), + Cell::new(&dutch_auction.description.step_count.to_string()), + Cell::new(&format!("{}", start_price)), + Cell::new(&format!("{}", end_price)), + Cell::new(&initial_input.format(&basic_cache)), + Cell::new(format!( + "({}, {})", + &auction_input_reserves.format(&basic_cache), + &auction_output_reserves.format(&basic_cache) + )), + Cell::new(format!("{}", render_position_id(&maybe_id))) + .set_alignment(comfy_table::CellAlignment::Center), + ]); + + let mut position_table = Table::new(); + position_table + .load_preset(presets::NOTHING) + .set_content_arrangement(ContentArrangement::Dynamic) + .set_table_width(80) + .set_header(vec![ + "position id", + "state", + "input reserves", + "output reserves", + "quoting price", + ]) + .add_row(vec![Cell::new("nothing for now")]); + + println!("{auction_table}"); + Ok(()) +} + +fn render_sequence(state: u64) -> String { if state == 0 { format!("Opened") } else if state == 1 { @@ -75,3 +181,33 @@ fn render_state(state: u64) -> String { format!("Withdrawn (seq={state})") } } + +fn truncate_asset_id(asset_id: &asset::Id) -> String { + let input = format!("{asset_id:?}"); + let prefix_len = 15; + if input.len() > prefix_len { + format!("{}...", &input[..prefix_len]) + } else { + input + } +} + +fn truncate_auction_id(asset_id: &AuctionId) -> String { + let input = format!("{asset_id:?}"); + let prefix_len = 16; + if input.len() > prefix_len { + format!("{}...", &input[..prefix_len]) + } else { + input + } +} + +fn render_position_id(maybe_id: &Option) -> String { + let input = maybe_id.map_or_else(|| format!("x"), |_| format!("✓")); + let prefix_len = 6; + if input.len() > prefix_len { + format!("{}..", &input[..prefix_len]) + } else { + input + } +} diff --git a/crates/bin/pcli/src/command/view/auction.rs b/crates/bin/pcli/src/command/view/auction.rs index 6e82f089eb..e3cf5e5f71 100644 --- a/crates/bin/pcli/src/command/view/auction.rs +++ b/crates/bin/pcli/src/command/view/auction.rs @@ -1,12 +1,20 @@ use anyhow::Result; +use comfy_table::{presets, Cell, ContentArrangement, Table}; +use penumbra_auction::auction::dutch::DutchAuction; use penumbra_keys::FullViewingKey; +use penumbra_proto::{core::component::auction::v1alpha1 as pb_auction, DomainType, Name}; use penumbra_view::ViewClient; +use crate::command::query::auction::render_dutch_auction; + #[derive(Debug, clap::Args)] pub struct AuctionCmd { #[clap(long)] /// If set, includes the inactive auctions as well. pub include_inactive: bool, + /// If set, make the view server query an RPC and pcli render the full auction state + #[clap(long, default_value_t = true)] + pub query_latest_state: bool, } impl AuctionCmd { @@ -19,13 +27,62 @@ impl AuctionCmd { view_client: &mut impl ViewClient, _fvk: &FullViewingKey, ) -> Result<()> { - let auctions = view_client - .auctions(None, self.include_inactive, false) + let auctions: Vec<( + penumbra_auction::auction::AuctionId, + penumbra_view::SpendableNoteRecord, + Option, + Vec, + )> = view_client + .auctions(None, self.include_inactive, self.query_latest_state) .await?; - auctions.iter().for_each(|(id, snr, _, _)| { - println!("{id:?} {}", snr.note.amount()); - }); + for (auction_id, _, maybe_auction_state, positions) in auctions.into_iter() { + if let Some(pb_auction_state) = maybe_auction_state { + if pb_auction_state.type_url == pb_auction::DutchAuction::type_url() { + let dutch_auction = DutchAuction::decode(pb_auction_state.value) + .expect("no deserialization error"); + let position = positions.get(0).cloned(); + render_dutch_auction(&dutch_auction, position) + .await + .expect("no rendering errors"); + } else { + unimplemented!("only supporting dutch auctions at the moment, come back later"); + } + } else { + let position_ids: Vec = positions + .into_iter() + .map(|lp| format!("{}", lp.id())) + .collect(); + + let mut auction_table = Table::new(); + auction_table.load_preset(presets::ASCII_FULL); + auction_table + .set_header(vec!["Auction id", "LPs"]) + .set_content_arrangement(ContentArrangement::DynamicFullWidth) + .add_row(vec![ + Cell::new(&auction_id).set_delimiter('.'), + Cell::new(format!("{:?}", position_ids)) + .set_alignment(comfy_table::CellAlignment::Center), + ]); + + let mut position_table = Table::new(); + position_table + .load_preset(presets::NOTHING) + .set_content_arrangement(ContentArrangement::Dynamic) + .set_table_width(80) + .set_header(vec![ + "position id", + "state", + "input reserves", + "output reserves", + "quoting price", + ]) + .add_row(vec![Cell::new("nothing for now")]); + + println!("{auction_table}"); + println!("detected auction with") + } + } Ok(()) } }