penumbra_sdk_dex/
state_key.rs

1use crate::{lp::position, DirectedTradingPair, TradingPair};
2use penumbra_sdk_asset::asset;
3use std::string::String;
4
5pub mod config {
6    pub fn dex_params() -> &'static str {
7        "dex/config/dex_params"
8    }
9}
10
11pub fn value_balance(asset_id: &asset::Id) -> String {
12    format!("dex/value_balance/{asset_id}")
13}
14
15pub fn positions(trading_pair: &TradingPair, position_id: &str) -> String {
16    format!("dex/positions/{trading_pair}/opened/{position_id}")
17}
18
19/// Looks up a `Position` by its ID
20// This should only ever be called by `position_manager::Inner::update_position`.
21pub fn position_by_id(id: &position::Id) -> String {
22    format!("dex/position/{id}")
23}
24
25pub fn all_positions() -> &'static str {
26    "dex/position/"
27}
28
29pub mod candlesticks {
30
31    pub mod object {
32        pub fn block_executions() -> &'static str {
33            "dex/candlesticks/object/block_executions"
34        }
35
36        pub fn block_position_executions() -> &'static str {
37            "dex/candlesticks/object/block_position_executions"
38        }
39
40        pub fn block_swap_executions() -> &'static str {
41            "dex/candlesticks/object/block_swap_executions"
42        }
43    }
44
45    pub mod data {
46        use crate::DirectedTradingPair;
47
48        pub fn prefix() -> &'static str {
49            "dex/candlesticks/data/"
50        }
51
52        pub fn by_pair_and_height(pair: &DirectedTradingPair, height: u64) -> String {
53            format!("{}{}/{}/{height:020}", prefix(), &pair.start, &pair.end)
54        }
55
56        pub fn by_pair(pair: &DirectedTradingPair) -> String {
57            format!("{}{}/{}/", prefix(), &pair.start, &pair.end)
58        }
59    }
60}
61
62pub mod block_scoped {
63    pub mod active {
64        pub fn trading_pairs() -> &'static str {
65            "dex/block_scoped/active/trading_pairs"
66        }
67    }
68}
69
70pub fn output_data(height: u64, trading_pair: TradingPair) -> String {
71    format!(
72        "dex/output/{:020}/{}/{}",
73        height,
74        &trading_pair.asset_1(),
75        &trading_pair.asset_2()
76    )
77}
78
79pub fn swap_execution(height: u64, trading_pair: DirectedTradingPair) -> String {
80    format!(
81        "dex/swap_execution/{:020}/{}/{}",
82        height, &trading_pair.start, &trading_pair.end
83    )
84}
85
86pub fn swap_executions() -> &'static str {
87    "dex/swap_execution/"
88}
89
90pub fn arb_execution(height: u64) -> String {
91    format!("dex/arb_execution/{height:020}")
92}
93
94pub fn arb_executions() -> &'static str {
95    "dex/arb_execution/"
96}
97
98pub fn swap_flows() -> &'static str {
99    "dex/swap_flows"
100}
101
102pub fn pending_position_closures() -> &'static str {
103    "dex/pending_position_closures"
104}
105
106pub fn recently_accessed_assets() -> &'static str {
107    "dex/recently_accessed_assets"
108}
109
110pub fn pending_payloads() -> &'static str {
111    "dex/pending_payloads"
112}
113
114pub fn pending_outputs() -> &'static str {
115    "dex/pending_outputs"
116}
117
118pub fn aggregate_value() -> &'static str {
119    "dex/aggregate_value"
120}
121
122pub mod lqt {
123    pub mod v1 {
124        pub mod pair {
125            pub mod lookup {
126                use penumbra_sdk_asset::asset;
127
128                pub(crate) fn prefix(epoch_index: u64) -> String {
129                    format!("dex/lqt/v1/pair/lookup/{epoch_index:020}/")
130                }
131
132                // A lookup index used to inspect aggregate outflows for a given pair.
133                /// It maps a trading pair (staking token, asset) to the cumulative volume of outbound liquidity.
134                ///
135                /// # Key Encoding
136                /// The lookup key is encoded as `prefix || asset`
137                /// # Value Encoding
138                /// The value is encoded as `BE(Amount)`
139                pub(crate) fn volume_by_pair(epoch_index: u64, asset: asset::Id) -> [u8; 76] {
140                    let prefix_bytes = prefix(epoch_index);
141                    let mut key = [0u8; 76];
142                    key[0..44].copy_from_slice(prefix_bytes.as_bytes());
143                    key[44..44 + 32].copy_from_slice(&asset.to_bytes());
144                    key
145                }
146            }
147        }
148
149        pub mod lp {
150            pub mod lookup {
151                pub(crate) fn prefix(epoch_index: u64) -> String {
152                    format!("dex/lqt/v1/lp/lookup/{epoch_index:020}/")
153                }
154
155                /// A lookup index used to update the `by_volume` index.
156                /// It maps a position id to the latest tally of outbound cumulative volume.
157                ///
158                /// # Key Encoding
159                /// The lookup key is encoded as `prefix || position_id`
160                /// # Value Encoding
161                /// The value is encoded as `BE(Amount)`
162                pub(crate) fn volume_by_position(
163                    epoch_index: u64,
164                    position_id: &crate::lp::position::Id,
165                ) -> [u8; 74] {
166                    let prefix_bytes = prefix(epoch_index);
167                    let mut key = [0u8; 74];
168                    key[0..42].copy_from_slice(prefix_bytes.as_bytes());
169                    key[42..42 + 32].copy_from_slice(&position_id.0);
170                    key
171                }
172            }
173
174            pub mod by_volume {
175                use anyhow::{ensure, Result};
176                use penumbra_sdk_asset::asset;
177                use penumbra_sdk_num::Amount;
178
179                use crate::lp::position;
180
181                pub fn prefix(epoch_index: u64) -> String {
182                    format!("dex/lqt/v1/lp/by_volume/{epoch_index:020}/")
183                }
184
185                pub fn prefix_with_asset(epoch_index: u64, asset: &asset::Id) -> [u8; 77] {
186                    let prefix = prefix(epoch_index);
187                    let mut key = [0u8; 77];
188                    key[0..45].copy_from_slice(prefix.as_bytes());
189                    key[45..45 + 32].copy_from_slice(&asset.to_bytes());
190                    key
191                }
192
193                /// Tracks the cumulative volume of outbound liquidity for a given pair.
194                /// The pair is always connected by the staking token, which is the implicit numeraire.
195                ///
196                /// # Encoding
197                /// The full key is encoded as: `prefix || asset || BE(!volume) || position`
198                pub(crate) fn key(
199                    epoch_index: u64,
200                    asset: &asset::Id,
201                    position: &position::Id,
202                    volume: Amount,
203                ) -> [u8; 125] {
204                    let prefix_bytes = prefix(epoch_index);
205                    let mut key = [0u8; 125];
206                    key[0..45].copy_from_slice(prefix_bytes.as_bytes());
207                    key[45..45 + 32].copy_from_slice(&asset.to_bytes());
208                    key[45 + 32..45 + 32 + 16].copy_from_slice(&(!volume).to_be_bytes());
209                    key[45 + 32 + 16..45 + 32 + 16 + 32].copy_from_slice(&position.0);
210                    key
211                }
212
213                /// Parse a raw key into its constituent parts.
214                ///
215                /// # Errors
216                /// This function will return an error if the key is not 125 bytes. Or, if the
217                /// key contains an invalid asset or position identifier.
218                pub(crate) fn parse_key(key: &[u8]) -> Result<(asset::Id, Amount, position::Id)> {
219                    ensure!(key.len() == 125, "key must be 125 bytes");
220
221                    // Skip the first 45 bytes, which is the prefix.
222                    let raw_asset: [u8; 32] = key[45..45 + 32].try_into()?;
223                    let asset: asset::Id = raw_asset.try_into()?;
224
225                    let raw_amount: [u8; 16] = key[45 + 32..45 + 32 + 16].try_into()?;
226                    let amount_complement = Amount::from_be_bytes(raw_amount);
227                    let amount = !amount_complement;
228
229                    let raw_position_id: [u8; 32] =
230                        key[45 + 32 + 16..45 + 32 + 16 + 32].try_into()?;
231                    let position_id = position::Id(raw_position_id);
232
233                    Ok((asset, amount, position_id))
234                }
235            }
236        }
237    }
238}
239
240pub(crate) mod engine {
241    use super::*;
242    use crate::lp::BareTradingFunction;
243
244    pub(crate) mod counter {
245        pub(crate) mod num_positions {
246            use crate::TradingPair;
247
248            pub(crate) fn prefix() -> &'static str {
249                "dex/internal/counter/num_positions/"
250            }
251
252            pub(crate) fn by_trading_pair(trading_pair: &TradingPair) -> [u8; 99] {
253                let mut key = [0u8; 99];
254                let prefix_bytes = prefix().as_bytes();
255                let canonical_pair_bytes = trading_pair.to_bytes();
256
257                key[0..35].copy_from_slice(prefix_bytes);
258                key[35..99].copy_from_slice(&canonical_pair_bytes);
259                key
260            }
261        }
262    }
263
264    pub(crate) mod routable_assets {
265        use penumbra_sdk_asset::asset;
266        use penumbra_sdk_num::Amount;
267
268        use super::*;
269
270        // An ordered encoding of every asset `B` routable from `A` based on the
271        // aggregate liquidity available to route from `B` to `A` (aka. the base liquidity).
272        //
273        /// # Encoding
274        /// The prefix key is encoded as `domain || A`.
275        pub(crate) fn starting_from(from: &asset::Id) -> [u8; 39] {
276            let mut key = [0u8; 39];
277            key[0..7].copy_from_slice(b"dex/ra/");
278            key[7..39].copy_from_slice(&from.to_bytes());
279            key
280        }
281
282        /// A record that an asset `A` is routable to an asset `B` and contains the
283        /// aggregate liquidity available to route from `B` to `A` (aka. the base liquidity).
284        ///
285        /// # Encoding
286        /// The full key is encoded as: `prefix || BE(aggregate_base_liquidity)`
287        pub(crate) fn key(from: &asset::Id, a_from_b: Amount) -> [u8; 55] {
288            let mut key = [0u8; 55];
289            key[0..7].copy_from_slice(b"dex/ra/");
290            key[7..32 + 7].copy_from_slice(&from.to_bytes());
291            // Use the complement of the amount to ensure that the keys are ordered in descending order.
292            key[32 + 7..32 + 7 + 16].copy_from_slice(&(!a_from_b).to_be_bytes());
293            key
294        }
295
296        /// A lookup index used to reconstruct and update the primary index entries.
297        /// It maps a directed trading pair `A -> B` to the aggregate liquidity available
298        /// to route from `B` to `A` (aka. the base asset liquidity).
299        ///
300        /// # Encoding
301        /// The lookup key is encoded as `prefix_lookup || start_asset|| end_asset`.
302        pub(crate) fn lookup_base_liquidity_by_pair(pair: &DirectedTradingPair) -> [u8; 71] {
303            let mut key = [0u8; 71];
304            key[0..7].copy_from_slice(b"dex/ab/");
305            key[7..39].copy_from_slice(&pair.start.to_bytes());
306            key[39..71].copy_from_slice(&pair.end.to_bytes());
307            key
308        }
309    }
310
311    pub(crate) mod price_index {
312
313        use super::*;
314
315        pub(crate) fn prefix(pair: &DirectedTradingPair) -> [u8; 71] {
316            let mut key = [0u8; 71];
317            key[0..7].copy_from_slice(b"dex/pi/");
318            key[7..39].copy_from_slice(&pair.start.to_bytes());
319            key[39..71].copy_from_slice(&pair.end.to_bytes());
320            key
321        }
322
323        pub(crate) fn key(
324            pair: &DirectedTradingPair,
325            btf: &BareTradingFunction,
326            id: &position::Id,
327        ) -> Vec<u8> {
328            let id_bytes = id.0;
329            let mut key = [0u8; 135];
330            key[0..71].copy_from_slice(&prefix(pair));
331            key[71..103].copy_from_slice(&btf.effective_price_key_bytes());
332            key[103..135].copy_from_slice(&id_bytes);
333            key.to_vec()
334        }
335    }
336}
337
338pub(crate) mod eviction_queue {
339    pub(crate) mod inventory_index {
340        use crate::lp::position;
341        use crate::DirectedTradingPair;
342        use anyhow::ensure;
343        use penumbra_sdk_num::Amount;
344
345        pub(crate) fn by_trading_pair(pair: &DirectedTradingPair) -> [u8; 107] {
346            let mut prefix = [0u8; 107];
347            prefix[0..43].copy_from_slice(b"dex/internal/eviction_queue/inventory_index");
348            prefix[43..75].copy_from_slice(&pair.start.to_bytes());
349            prefix[75..107].copy_from_slice(&pair.end.to_bytes());
350            prefix
351        }
352
353        pub(crate) fn key(
354            pair: &DirectedTradingPair,
355            inventory: Amount,
356            id: &position::Id,
357        ) -> [u8; 155] {
358            let mut full_key = [0u8; 155];
359            let prefix = by_trading_pair(pair);
360            full_key[0..107].copy_from_slice(&prefix);
361            full_key[107..123].copy_from_slice(&inventory.to_be_bytes());
362            full_key[123..155].copy_from_slice(&id.0);
363
364            full_key
365        }
366
367        pub(crate) fn parse_id_from_key(key: Vec<u8>) -> anyhow::Result<[u8; 32]> {
368            ensure!(key.len() == 155, "key must be 155 bytes");
369            let k = &key[123..155];
370            Ok(k.try_into()?)
371        }
372    }
373}