pcli/command/tx/
replicate.rs1use crate::App;
2
3pub mod linear;
4pub mod xyk;
5
6use linear::Linear;
7use penumbra_sdk_dex::DirectedUnitPair;
8use penumbra_sdk_proto::core::component::dex::v1::{
9 query_service_client::QueryServiceClient as DexQueryServiceClient, SpreadRequest,
10};
11use xyk::ConstantProduct;
12
13#[derive(Debug, clap::Subcommand)]
15pub enum ReplicateCmd {
16 #[clap(visible_alias = "xyk", hide = true)]
19 ConstantProduct(ConstantProduct),
20 Linear(Linear),
22}
23
24impl ReplicateCmd {
25 pub async fn exec(&self, app: &mut App) -> anyhow::Result<()> {
26 match self {
27 ReplicateCmd::ConstantProduct(xyk_cmd) => xyk_cmd.exec(app).await?,
28 ReplicateCmd::Linear(linear_cmd) => linear_cmd.exec(app).await?,
29 };
30 Ok(())
31 }
32
33 pub fn offline(&self) -> bool {
34 false
35 }
36}
37
38pub async fn process_price_or_fetch_spread(
39 app: &mut App,
40 user_price: Option<f64>,
41 directed_pair: DirectedUnitPair,
42) -> anyhow::Result<f64> {
43 let canonical_pair = directed_pair.into_directed_trading_pair().to_canonical();
44
45 if let Some(user_price) = user_price {
46 let adjusted = adjust_price_by_exponents(user_price, &directed_pair);
47 tracing::debug!(?user_price, ?adjusted, "adjusted price by units");
48 return Ok(adjusted);
49 } else {
50 tracing::debug!("price not provided, fetching spread");
51
52 let mut client = DexQueryServiceClient::new(app.pd_channel().await?);
53 let spread_data = client
54 .spread(SpreadRequest {
55 trading_pair: Some(canonical_pair.into()),
56 })
57 .await?
58 .into_inner();
59
60 tracing::debug!(
61 ?spread_data,
62 pair = canonical_pair.to_string(),
63 "fetched spread for pair"
64 );
65
66 if spread_data.best_1_to_2_position.is_none() || spread_data.best_2_to_1_position.is_none()
67 {
68 anyhow::bail!("couldn't find a market price for the specified assets, you can manually specify a price using --current-price <price>")
69 }
70
71 if directed_pair.start.id() == canonical_pair.asset_1() {
74 Ok(spread_data.approx_effective_price_2_to_1)
75 } else if directed_pair.start.id() == canonical_pair.asset_2() {
76 Ok(spread_data.approx_effective_price_1_to_2)
77 } else {
78 anyhow::bail!("the supplied liquidity must be on the pair")
79 }
80 }
81}
82
83fn adjust_price_by_exponents(price: f64, pair: &DirectedUnitPair) -> f64 {
84 let start_exponent = pair.start.exponent() as i32;
85 let end_exponent = pair.end.exponent() as i32;
86
87 price * 10f64.powi(start_exponent - end_exponent)
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_adjust_price_by_exponents() {
96 let pair1: DirectedUnitPair = "penumbra:gm".parse().unwrap();
97 let pair2: DirectedUnitPair = "upenumbra:gm".parse().unwrap();
98 let pair3: DirectedUnitPair = "penumbra:ugm".parse().unwrap();
99 let pair4: DirectedUnitPair = "upenumbra:ugm".parse().unwrap();
100
101 let base_price = 1.2;
102
103 assert_eq!(adjust_price_by_exponents(base_price, &pair1), 1.2);
104 assert_eq!(adjust_price_by_exponents(base_price, &pair2), 0.0000012);
105 assert_eq!(adjust_price_by_exponents(base_price, &pair3), 1200000.0);
106 assert_eq!(adjust_price_by_exponents(base_price, &pair4), 1.2);
107 }
108}