pcli/command/tx/
liquidity_position.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use anyhow::Result;

use penumbra_asset::asset;
use penumbra_dex::{
    lp::{
        position::{self, Position},
        BuyOrder, SellOrder,
    },
    TradingPair,
};
use rand_core::CryptoRngCore;

use super::{replicate::ReplicateCmd, FeeTier};

#[derive(Debug, clap::Subcommand)]
pub enum PositionCmd {
    /// Open a new liquidity position based on order details and credits an open position NFT.
    #[clap(display_order = 100, subcommand)]
    Order(OrderCmd),
    /// Debits an all opened position NFTs associated with a specific source and credits closed position NFTs.
    CloseAll {
        /// Only spend fugdnds originally received by the given address index.
        #[clap(long, default_value = "0")]
        source: u32,
        /// Only close positions for the given trading pair.
        #[clap(long)]
        trading_pair: Option<TradingPair>,
        /// The selected fee tier to multiply the fee amount by.
        #[clap(short, long, default_value_t)]
        fee_tier: FeeTier,
    },
    /// Debits opened position NFTs and credits closed position NFTs.
    Close {
        /// Only spend funds originally received by the given address index.
        #[clap(long, default_value = "0")]
        source: u32,
        /// The list of [`position::Id`] of the positions to close.
        position_ids: Vec<position::Id>,
        /// The selected fee tier to multiply the fee amount by.
        #[clap(short, long, default_value_t)]
        fee_tier: FeeTier,
    },
    /// Debits all closed position NFTs associated with a specific account and credits withdrawn position NFTs and the final reserves.
    WithdrawAll {
        /// Only spend funds originally received by the given address index.
        #[clap(long, default_value = "0")]
        source: u32,
        /// Only withdraw positions for the given trading pair.
        #[clap(long)]
        trading_pair: Option<TradingPair>,
        /// The selected fee tier to multiply the fee amount by.
        #[clap(short, long, default_value_t)]
        fee_tier: FeeTier,
    },
    /// Debits closed position NFTs and credits withdrawn position NFTs and the final reserves.
    Withdraw {
        /// Only spend funds originally received by the given address index.
        #[clap(long, default_value = "0")]
        source: u32,
        /// The list of [`position::Id`] of the positions to withdraw.
        position_ids: Vec<position::Id>,
        /// The selected fee tier to multiply the fee amount by.
        #[clap(short, long, default_value_t)]
        fee_tier: FeeTier,
    },

    /// Debits a withdrawn position NFT and credits a claimed position NFT and any liquidity incentives.
    #[clap(hide(true))] // remove when reward claims exist
    RewardClaim {},
    /// Replicate a trading function
    #[clap(subcommand)]
    Replicate(ReplicateCmd),
}

impl PositionCmd {
    pub fn offline(&self) -> bool {
        match self {
            PositionCmd::Order(_) => false,
            PositionCmd::Close { .. } => false,
            PositionCmd::CloseAll { .. } => false,
            PositionCmd::Withdraw { .. } => false,
            PositionCmd::WithdrawAll { .. } => false,
            PositionCmd::RewardClaim { .. } => false,
            PositionCmd::Replicate(replicate) => replicate.offline(),
        }
    }
}

#[derive(Debug, clap::Subcommand)]
pub enum OrderCmd {
    Buy {
        /// The desired purchase, formatted as a string, e.g. `100penumbra@1.2gm` would attempt
        /// to purchase 100 penumbra at a price of 1.2 gm per 1penumbra.
        ///
        /// An optional suffix of the form `/10bps` may be added to specify a fee spread for the
        /// resulting position, though this is less useful for buy/sell orders than passive LPs.
        buy_order: String,
        /// Only spend funds originally received by the given address index.
        #[clap(long, default_value = "0")]
        source: u32,
        /// When set, tags the position as an auto-closing buy.
        #[clap(long)]
        auto_close: bool,
        /// The selected fee tier to multiply the fee amount by.
        #[clap(short, long, default_value_t)]
        fee_tier: FeeTier,
        /// Duplicate the order for the given number of times.
        #[clap(short, long, default_value = "1")]
        num_copies: u32,
    },
    Sell {
        /// The desired sale, formatted as a string, e.g. `100penumbra@1.2gm` would attempt
        /// to sell 100 penumbra at a price of 1.2 gm per 1penumbra.
        ///
        /// An optional suffix of the form `/10bps` may be added to specify a fee spread for the
        /// resulting position, though this is less useful for buy/sell orders than passive LPs.
        sell_order: String,
        /// Only spend funds originally received by the given address index.
        #[clap(long, default_value = "0")]
        source: u32,
        /// When set, tags the position as an auto-closing sell.
        #[clap(long)]
        auto_close: bool,
        /// The selected fee tier to multiply the fee amount by.
        #[clap(short, long, default_value_t)]
        fee_tier: FeeTier,
        /// Duplicate the order for the given number of times.
        #[clap(short, long, default_value = "1")]
        num_copies: u32,
    },
}

impl OrderCmd {
    pub fn source(&self) -> u32 {
        match self {
            OrderCmd::Buy { source, .. } => *source,
            OrderCmd::Sell { source, .. } => *source,
        }
    }

    pub fn fee_tier(&self) -> FeeTier {
        match self {
            OrderCmd::Buy { fee_tier, .. } => *fee_tier,
            OrderCmd::Sell { fee_tier, .. } => *fee_tier,
        }
    }

    pub fn is_auto_closing(&self) -> bool {
        match self {
            OrderCmd::Buy { auto_close, .. } => *auto_close,
            OrderCmd::Sell { auto_close, .. } => *auto_close,
        }
    }

    pub fn num_copies(&self) -> u32 {
        match self {
            OrderCmd::Buy { num_copies, .. } => *num_copies,
            OrderCmd::Sell { num_copies, .. } => *num_copies,
        }
    }

    pub fn as_position(
        &self,
        // Preserved since we'll need it after denom metadata refactor
        _asset_cache: &asset::Cache,
        mut rng: impl CryptoRngCore,
    ) -> Result<Vec<Position>> {
        let positions = match self {
            OrderCmd::Buy { buy_order, .. } => {
                tracing::info!(?buy_order, "parsing buy order");
                let order = BuyOrder::parse_str(buy_order)?;
                let mut positions = Vec::new();
                for _ in 0..self.num_copies() {
                    let mut position = order.into_position(&mut rng);
                    if self.is_auto_closing() {
                        position.close_on_fill = true;
                    }
                    positions.push(position);
                }
                positions
            }
            OrderCmd::Sell { sell_order, .. } => {
                tracing::info!(?sell_order, "parsing sell order");
                let order = SellOrder::parse_str(sell_order)?;
                let mut positions = Vec::new();

                for _ in 0..self.num_copies() {
                    let mut position = order.into_position(&mut rng);
                    if self.is_auto_closing() {
                        position.close_on_fill = true;
                    }
                    positions.push(position);
                }
                positions
            }
        };

        Ok(positions)
    }
}