Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pcli: auction end-all, withdraw-all #4385

Merged
merged 4 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions crates/bin/pcli/src/command/query/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl AuctionCmd {

let asset_cache = app.view().assets().await?;

render_dutch_auction(&asset_cache, &dutch_auction, position).await?;
render_dutch_auction(&asset_cache, &dutch_auction, None, position).await?;
} else {
unimplemented!("only supporting dutch auctions at the moment, come back later");
}
Expand All @@ -79,10 +79,11 @@ impl AuctionCmd {
pub async fn render_dutch_auction(
asset_cache: &Cache,
dutch_auction: &DutchAuction,
local_view: Option<u64>,
position: Option<Position>,
) -> anyhow::Result<()> {
let auction_id = dutch_auction.description.id();
println!("dutch auction with id {auction_id:?}");
println!("dutch auction with id {auction_id:?}:");

let initial_input = dutch_auction.description.input;
let input_id = initial_input.asset_id;
Expand Down Expand Up @@ -137,7 +138,7 @@ pub async fn render_dutch_auction(
.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(render_sequence(dutch_auction.state.sequence, local_view)),
Cell::new(format!("{start_height} -> {end_height}")),
Cell::new(dutch_auction.description.step_count.to_string()),
Cell::new(format!("{}", start_price)),
Expand All @@ -163,13 +164,19 @@ pub async fn render_dutch_auction(
Ok(())
}

fn render_sequence(state: u64) -> String {
if state == 0 {
fn render_sequence(state: u64, local_seq: Option<u64>) -> String {
let main = if state == 0 {
format!("Opened")
} else if state == 1 {
format!("Closed")
} else {
format!("Withdrawn (seq={state})")
};

if let Some(local_seq) = local_seq {
format!("{main} (local_seq={local_seq})")
} else {
main
}
}

Expand Down
201 changes: 142 additions & 59 deletions crates/bin/pcli/src/command/tx/auction/dutch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ use std::path::Path;

use crate::command::tx::FeeTier;
use crate::App;
use anyhow::Result;
use anyhow::{anyhow, bail, Context};
use clap::Subcommand;
use comfy_table::presets;
use dialoguer::Confirm;
use penumbra_asset::{asset::Cache, Value};
use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan;
use penumbra_auction::auction::{dutch::DutchAuction, dutch::DutchAuctionDescription, AuctionId};
use penumbra_dex::lp::position::Position;
use penumbra_keys::keys::AddressIndex;
use penumbra_num::Amount;
use penumbra_proto::{view::v1::GasPricesRequest, DomainType, Name};
use penumbra_view::SpendableNoteRecord;
use penumbra_proto::{view::v1::GasPricesRequest, DomainType};
use penumbra_view::ViewClient;
use penumbra_wallet::plan::Planner;
use rand::RngCore;
Expand Down Expand Up @@ -103,9 +101,12 @@ pub enum DutchCmd {
/// Source account terminating the auction.
#[clap(long, display_order = 100, default_value = "0")]
source: u32,
/// Identifier of the auction.
#[clap(long, display_order = 200)]
auction_id: String,
/// If set, ends all auctions owned by the specified account.
#[clap(long, display_order = 150)]
all: bool,
/// Identifier of the auction to end, if `--all` is not set.
#[clap(display_order = 200)]
auction_id: Option<String>,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, value_enum, default_value_t, display_order = 300)]
fee_tier: FeeTier,
Expand All @@ -114,11 +115,14 @@ pub enum DutchCmd {
#[clap(display_order = 200, name = "withdraw")]
DutchAuctionWithdraw {
/// Source account withdrawing from the auction.
#[clap(long, display_order = 100)]
#[clap(long, display_order = 100, default_value = "0")]
source: u32,
/// The auction to withdraw funds from.
#[clap(long, display_order = 200)]
auction_id: String,
/// If set, withdraws all auctions owned by the specified account.
#[clap(long, display_order = 150)]
all: bool,
/// Identifier of the auction to withdraw from, if `--all` is not set.
#[clap(display_order = 200)]
auction_id: Option<String>,
/// The selected fee tier to multiply the fee amount by.
#[clap(short, long, value_enum, default_value_t, display_order = 600)]
fee_tier: FeeTier,
Expand Down Expand Up @@ -178,92 +182,92 @@ impl DutchCmd {
AddressIndex::new(*source),
)
.await
.context("can't build send transaction")?;
.context("can't build auction schedule transaction")?;
app.build_and_submit_transaction(plan).await?;
Ok(())
}
DutchCmd::DutchAuctionEnd {
all,
auction_id,
source,
fee_tier,
} => {
let auction_id = auction_id.parse::<AuctionId>()?;
let auction_ids = match (all, auction_id) {
(true, _) => auctions_to_end(app.view(), *source).await?,
(false, Some(auction_id)) => {
let auction_id = auction_id.parse::<AuctionId>()?;
vec![auction_id]
}
(false, None) => {
bail!("auction_id is required when --all is not set")
}
};

let plan = Planner::new(OsRng)
let mut planner = Planner::new(OsRng);

planner
.set_gas_prices(gas_prices)
.set_fee_tier((*fee_tier).into())
.dutch_auction_end(auction_id)
.set_fee_tier((*fee_tier).into());

for auction_id in auction_ids {
planner.dutch_auction_end(auction_id);
}

let plan = planner
.plan(
app.view
.as_mut()
.context("view service must be initialized")?,
AddressIndex::new(*source),
)
.await
.context("can't build send transaction")?;
.context("can't build auction end transaction")?;
app.build_and_submit_transaction(plan).await?;
Ok(())
}
DutchCmd::DutchAuctionWithdraw {
all,
source,
auction_id,
fee_tier,
} => {
let auction_id = auction_id.parse::<AuctionId>()?;

use pbjson_types::Any;
let view_client = app.view();
let (auction_id, _, auction_raw, _): (
AuctionId,
SpendableNoteRecord,
Option<Any>,
Vec<Position>,
) = view_client
.auctions(None, true, true)
.await?
.into_iter()
.find(|(id, _, _, _)| &auction_id == id)
.ok_or_else(|| anyhow!("the auction id is unknown from the view service!"))?;

let Some(raw_da_state) = auction_raw else {
bail!("auction state is missing from view server response")
};

use penumbra_proto::core::component::auction::v1 as pb_auction;
// We're processing a Dutch auction:
assert_eq!(raw_da_state.type_url, pb_auction::DutchAuction::type_url());
let auctions = match (all, auction_id) {
(true, _) => auctions_to_withdraw(app.view(), *source).await?,
(false, Some(auction_id)) => {
let auction_id = auction_id.parse::<AuctionId>()?;

let dutch_auction = DutchAuction::decode(raw_da_state.value)?;

let reserves_input = Value {
amount: dutch_auction.state.input_reserves,
asset_id: dutch_auction.description.input.asset_id,
};
let reserves_output = Value {
amount: dutch_auction.state.output_reserves,
asset_id: dutch_auction.description.output_id,
let all = auctions_to_withdraw(app.view(), *source).await?;
vec![all
.into_iter()
.find(|a| a.description.id() == auction_id)
.ok_or_else(|| {
anyhow!("the auction id is unknown from the view service!")
})?]
}
(false, None) => {
bail!("auction_id is required when --all is not set")
}
};
let seq = dutch_auction.state.sequence + 1;

let mut planner = Planner::new(OsRng);

let plan = planner
planner
.set_gas_prices(gas_prices)
.set_fee_tier((*fee_tier).into())
.dutch_auction_withdraw(ActionDutchAuctionWithdrawPlan {
auction_id,
seq,
reserves_input,
reserves_output,
})
.set_fee_tier((*fee_tier).into());

for auction in &auctions {
planner.dutch_auction_withdraw(auction);
}

let plan = planner
.plan(
app.view
.as_mut()
.context("view service must be initialized")?,
AddressIndex::new(*source),
)
.await
.context("can't build send transaction")?;
.context("can't build auction withdrawal transaction")?;
app.build_and_submit_transaction(plan).await?;
Ok(())
}
Expand Down Expand Up @@ -366,6 +370,85 @@ impl DutchCmd {
}
}

async fn all_dutch_auction_states(
view_client: &mut impl ViewClient,
source: impl Into<AddressIndex>,
) -> Result<Vec<(AuctionId, DutchAuction, u64)>> {
fetch_dutch_auction_states(view_client, source, true).await
}

async fn active_dutch_auction_states(
view_client: &mut impl ViewClient,
source: impl Into<AddressIndex>,
) -> Result<Vec<(AuctionId, DutchAuction, u64)>> {
fetch_dutch_auction_states(view_client, source, false).await
}

async fn fetch_dutch_auction_states(
view_client: &mut impl ViewClient,
source: impl Into<AddressIndex>,
include_inactive: bool,
) -> Result<Vec<(AuctionId, DutchAuction, u64)>> {
let auctions = view_client
.auctions(Some(source.into()), include_inactive, true)
.await?
.into_iter()
.filter_map(|(id, _, local_seq, state, _)| {
if let Some(state) = state {
if let Ok(da) = DutchAuction::decode(state.value) {
Some((id, da, local_seq))
} else {
None
}
} else {
None
}
})
.collect();
Ok(auctions)
}
/// Return all the auctions that need to be ended, based on our local view of the chain state.
async fn auctions_to_end(view_client: &mut impl ViewClient, source: u32) -> Result<Vec<AuctionId>> {
let auctions = active_dutch_auction_states(view_client, source).await?;

let auction_ids = auctions
cratelyn marked this conversation as resolved.
Show resolved Hide resolved
.into_iter()
.filter_map(|(id, _auction, local_seq)| {
// We want to end auctions that we track as "opened" (local_seq == 0)
// so that we can close them, or catch-up with the chain state if they are already closed.
if local_seq == 0 {
Some(id)
} else {
None
}
})
.collect();

Ok(auction_ids)
}

async fn auctions_to_withdraw(
view_client: &mut impl ViewClient,
source: u32,
) -> Result<Vec<DutchAuction>> {
let auctions = all_dutch_auction_states(view_client, source).await?;

let auction_ids = auctions
.into_iter()
.filter_map(|(_, auction, local_seq)| {
// We want to end auctions that we track as "closed" (local_seq == 1)
// so that we can close them, or catch-up with the chain state if they are already closed.
if local_seq == 1 {
Some(auction)
} else {
None
}
})
.collect();

Ok(auction_ids)
}

fn display_auction_description(asset_cache: &Cache, auctions: Vec<DutchAuctionDescription>) {
let mut tally_max_output = Amount::zero();
let mut tally_min_output = Amount::zero();
Expand Down
14 changes: 10 additions & 4 deletions crates/bin/pcli/src/command/view/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,27 @@ impl AuctionCmd {
let auctions: Vec<(
penumbra_auction::auction::AuctionId,
penumbra_view::SpendableNoteRecord,
u64,
Option<pbjson_types::Any>,
Vec<penumbra_dex::lp::position::Position>,
)> = view_client
.auctions(None, self.include_inactive, self.query_latest_state)
.await?;

for (auction_id, _, maybe_auction_state, positions) in auctions.into_iter() {
for (auction_id, _, local_seq, 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 asset_cache = view_client.assets().await?;
render_dutch_auction(&asset_cache, &dutch_auction, positions.get(0).cloned())
.await
.expect("no rendering errors");
render_dutch_auction(
&asset_cache,
&dutch_auction,
Some(local_seq),
positions.get(0).cloned(),
)
.await
.expect("no rendering errors");
} else {
unimplemented!("only supporting dutch auctions at the moment, come back later");
}
Expand Down
Loading
Loading