pcli/command/tx/auction/dutch/
gda.rs

1use clap::ArgEnum;
2use penumbra_sdk_asset::Value;
3use penumbra_sdk_auction::auction::dutch::DutchAuctionDescription;
4use rand::Rng;
5use rand::RngCore;
6use rand_core::OsRng;
7use serde::Serialize;
8
9#[derive(ArgEnum, Clone, Debug, Serialize)]
10pub enum GdaRecipe {
11    #[clap(name = "10m")]
12    TenMinutes,
13    #[clap(name = "30m")]
14    ThirtyMinutes,
15    #[clap(name = "1h")]
16    OneHour,
17    #[clap(name = "2h")]
18    TwoHours,
19    #[clap(name = "6h")]
20    SixHours,
21    #[clap(name = "12h")]
22    TwelveHours,
23    #[clap(name = "1d")]
24    OneDay,
25    #[clap(name = "2d")]
26    TwoDays,
27}
28
29impl GdaRecipe {
30    pub fn as_blocks(&self) -> u64 {
31        match self {
32            GdaRecipe::TenMinutes => 10 * 12,
33            GdaRecipe::ThirtyMinutes => 30 * 12,
34            GdaRecipe::OneHour => 60 * 12,
35            GdaRecipe::TwoHours => 2 * 60 * 12,
36            GdaRecipe::SixHours => 6 * 60 * 12,
37            GdaRecipe::TwelveHours => 12 * 60 * 12,
38            GdaRecipe::OneDay => 24 * 60 * 12,
39            GdaRecipe::TwoDays => 2 * 24 * 60 * 12,
40        }
41    }
42
43    pub fn poisson_intensity(&self) -> f64 {
44        match &self {
45            GdaRecipe::TenMinutes => 0.0645833333333,
46            GdaRecipe::ThirtyMinutes => 0.05058333333,
47            GdaRecipe::OneHour => 0.02525,
48            GdaRecipe::TwoHours => 0.02266666666,
49            GdaRecipe::SixHours => 0.01075,
50            GdaRecipe::TwelveHours => 0.0053333333,
51            GdaRecipe::OneDay => 0.035,
52            GdaRecipe::TwoDays => 0.00175,
53        }
54    }
55
56    pub fn num_auctions(&self) -> u64 {
57        match self {
58            GdaRecipe::TenMinutes => 4,
59            GdaRecipe::ThirtyMinutes => 12,
60            GdaRecipe::OneHour => 12,
61            GdaRecipe::TwoHours => 24,
62            GdaRecipe::SixHours => 36,
63            GdaRecipe::TwelveHours => 36,
64            GdaRecipe::OneDay => 48,
65            GdaRecipe::TwoDays => 48,
66        }
67    }
68
69    pub fn sub_auction_length(&self) -> u64 {
70        match &self {
71            GdaRecipe::TenMinutes => 60,
72            GdaRecipe::ThirtyMinutes => 60,
73            GdaRecipe::OneHour => 120,
74            GdaRecipe::TwoHours => 120,
75            GdaRecipe::SixHours => 240,
76            GdaRecipe::TwelveHours => 480,
77            GdaRecipe::OneDay => 720,
78            GdaRecipe::TwoDays => 1440,
79        }
80    }
81
82    pub fn step_count(&self) -> u64 {
83        60
84    }
85}
86
87#[derive(Debug, Serialize)]
88pub struct GradualAuction {
89    pub input: Value,
90    pub max_output: Value,
91    pub min_output: Value,
92    pub recipe: GdaRecipe,
93    pub start_height: u64,
94}
95
96impl GradualAuction {
97    pub fn new(
98        input: Value,
99        max_output: Value,
100        min_output: Value,
101        recipe: GdaRecipe,
102        start_height: u64,
103    ) -> Self {
104        GradualAuction {
105            input,
106            max_output,
107            min_output,
108            recipe,
109            start_height,
110        }
111    }
112
113    pub fn generate_start_heights(&self) -> Vec<u64> {
114        let lambda = self.recipe.poisson_intensity();
115        let num_auctions = self.recipe.num_auctions();
116        let start_height = self.start_height;
117        let sub_auction_length = self.recipe.sub_auction_length();
118        tracing::debug!(
119            lambda,
120            num_auctions,
121            start_height,
122            sub_auction_length,
123            num_blocks = self.recipe.as_blocks(),
124            "generating auction starts"
125        );
126
127        let mut rng = rand::thread_rng();
128        let mut current_height = start_height as f64;
129
130        let mut auction_starts = Vec::with_capacity(num_auctions as usize);
131        for _ in 0..num_auctions {
132            // See https://en.wikipedia.org/wiki/Inverse_transform_sampling
133            // aka. a smirnov transform that is a big workshopped with the abs, but it generates a
134            // nice calendar of auctions.
135            let ff_clock = (rng.gen::<f64>() / lambda).ln().abs();
136            current_height += ff_clock;
137            let height = current_height.ceil() as u64;
138            tracing::debug!(height, arrival_time = ff_clock, "selected auction start");
139            auction_starts.push(height)
140        }
141
142        auction_starts
143    }
144
145    pub fn generate_auctions(&self) -> Vec<DutchAuctionDescription> {
146        let start_heights = self.generate_start_heights();
147        let sub_auction_length = self.recipe.sub_auction_length();
148        let step_count = self.recipe.step_count();
149        let num_auctions = self.recipe.num_auctions();
150        let mut auctions = Vec::with_capacity(num_auctions as usize);
151        for start_height in start_heights {
152            let amount_chunk = self.input.amount.value() / num_auctions as u128;
153            let input_chunk = Value {
154                asset_id: self.input.asset_id,
155                amount: amount_chunk.into(),
156            };
157
158            let scaled_min_output = self.min_output.amount.value() / num_auctions as u128;
159            let scaled_max_output = self.max_output.amount.value() / num_auctions as u128;
160
161            let mut nonce = [0u8; 32];
162            OsRng.fill_bytes(&mut nonce);
163
164            let end_height = start_height + sub_auction_length;
165            tracing::debug!(
166                start_height,
167                end_height,
168                sub_auction_length,
169                step_count,
170                ?input_chunk,
171                "generating auction"
172            );
173
174            let auction = DutchAuctionDescription {
175                input: input_chunk,
176                output_id: self.max_output.asset_id,
177                max_output: scaled_max_output.into(),
178                min_output: scaled_min_output.into(),
179                start_height,
180                end_height,
181                step_count,
182                nonce,
183            };
184            auctions.push(auction);
185        }
186        auctions
187    }
188}